On this page:

18.0HTTP Server Setup

 

This page provides information on setting up an HTTP server module, such as a FHIR REST endpoint, or the Web Admin Console.

18.0.1Respecting Forward Headers

 

If the server is behind a reverse proxy, load balancer, or other network infrastructure, this may obscure the originating network details of clients making requests. For example, if all requests are routed through a load balancer, by default all requests will appear to come from that load balancer. This means that audit/transaction logs may show incorrect information, and cause other similar issues.

In order to avoid this issue, you can configure your load balancer to provide a Forwarded header to Smile CDR containing details about the original client's location and connection. In order for Smile CDR to respect this header, you will need to enable the Respect Forward Headers setting.

You can learn more about the Forwarded header (and its legacy equivalents, which are also supported) on the MDN Forwarded page.

The following headers are supported:

  • Forwarded
  • X-Forwarded-Host
  • X-Forwarded-Server
  • X-Forwarded-For
  • X-Forwarded-Proto
  • X-Proxied-Https
  • Proxy-auth-cert
  • Proxy-ssl-id

Reverse Proxying with NGINX

A typical configuration for setting up a reverse proxy using NGINX is shown below. In this example, port 443 (the default https port) is being forwarded to a Smile CDR instance listening on port 8000 (the typical FHIR listener port).

server {
    listen 443 ssl default_server;
    location / {
        proxy_set_header    Host                        $host;
        proxy_set_header    X-Real-IP                   $remote_addr;
        proxy_set_header    X-Forwarded-For             $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Host   $host:443;
        proxy_set_header    X-Forwarded-Server $host;
        proxy_set_header    X-Forwarded-Port   443;
        proxy_set_header    X-Forwarded-Proto  https;
        proxy_pass          http://localhost:8000/;
    }
}

18.0.2Specifying a Custom Context Path

 

In some cases, it is desirable to have an HTTP server module not serve its contents from the root URL on the server.

For example, suppose a Smile CDR instance is set up with two modules, each providing an HTTP server. The first is a JSON Admin Endpoint listening on port 9000 and the second is the Web Web Console listening on port 9100.

A reverse proxy such as NGINX can be configured to listen on a single port and then to serve both Smile CDR modules through this same proxy on the same port.

An example of this configuration is shown in the following diagram.

Alternate Context Root

In order to support this configuration, the reverse proxy could be configured to route requests to path /adminjson on port 443 to port 9000 and to route requests to path /adminweb on port 443 to port 9100.

In order for this setup to work correctly however, the two modules need to be configured to understand that a request to (for example) /adminweb/index.html is actually only a request for /index.html, and that any absolute links served to the client need to include the initial part of the path.

This is accomplished by setting the context_path configuration property.

Reverse Proxying with NGINX

A typical configuration for setting up a reverse proxy using NGINX is shown below. In this example, port 443 (the default https port) is being forwarded to a Smile CDR instance listening on ports 9000 and 9100 (the typical JSON Admin API, and Web Admin Console ports respectively).

server {
    listen 443 ssl default_server;
    location /adminjson {
        proxy_set_header    Host                        $host;
        proxy_set_header    X-Real-IP                   $remote_addr;
        proxy_set_header    X-Forwarded-For             $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Host   $host:443;
        proxy_set_header    X-Forwarded-Server $host;
        proxy_set_header    X-Forwarded-Port   443;
        proxy_set_header    X-Forwarded-Proto  https;
        proxy_pass          http://localhost:9000/admin;
    }
    location /adminweb {
        proxy_set_header    Host                        $host;
        proxy_set_header    X-Real-IP                   $remote_addr;
        proxy_set_header    X-Forwarded-For             $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Host   $host:443;
        proxy_set_header    X-Forwarded-Server $host;
        proxy_set_header    X-Forwarded-Port   443;
        proxy_set_header    X-Forwarded-Proto  https;
        proxy_pass          http://localhost:9100/fhir;
    }
}

18.0.3Enabling TLS (HTTPS / SSL)

 

To enable TLS on an HTTP endpoint, a KeyStore containing a private encryption key must be provided.

Creating a Self-Signed TLS Keystore

Use the following command to create a KeyStore containing a key with the alias server:

keytool -genkey -keyalg RSA -alias server -keystore keystore.p12 -storepass changeit -storetype pkcs12 -validity 360 -keysize 4096

This will produce the following output:

What is your first and last name?
[Unknown]: John Servers
What is the name of your organizational unit?
[Unknown]: FHIR Servers Department
What is the name of your organization?
[Unknown]: Exciting Tech Co
What is the name of your City or Locality?
[Unknown]: Toronto
What is the name of your State or Province?
[Unknown]: ON
What is the two-letter country code for this unit?
[Unknown]: CA
Is CN=John Servers, OU=FHIR Servers Department, O=Exciting Tech Co, L=Toronto, ST=ON, C=CA correct?
[no]: yes

Importing a LetsEncrypt Certificate into a Keystore

The following steps show how to create a KeyStore using a private key and chain obtained from LetsEncrypt. Note that you will need to replace "[hostname]" with your actual LetsEncrypt hostname in several places.

# Export the key into a PKCS12 file
openssl pkcs12 -export -in /etc/letsencrypt/live/[hostname]/cert.pem -inkey /etc/letsencrypt/live/[hostname]/privkey.pem -out keystore.p12 -name [hostname] -CAfile /etc/letsencrypt/live/[hostname]/chain.pem -caname root -password pass:changeit

18.0.4Selecting Ciphers and Protocol

 

TLS-enabled servers may optionally be configured with a whitelist or a blacklist for both ciphers and protocol.

This can be used to restrict specific ciphers, or ban old versions of TLS/SSL.

To see a list of available ciphers, consult your JDK documentation. The following links may be useful:

Optimal Security

If possible, consider setting a protocol whitelist of TLSv1.3, as this ensures that all clients are required to connect using the most modern revision of the TLS specification. Note that this may prevent older clients from being able to connect. See CanIUse TLS 1.3 for a rundown of browser support.

Using TLS 1.0

The TLS 1.0 protocol is generally not recommended for use as it has known vulnerabilities that have been addressed in newer versions of the TLS specification. However, the following configuration is known to work in forcing the use of TLS 1.0 (e.g. to interact with a JDK 6 application that can not be upgraded).

  • Protocol Whitelist: TLSv1
  • Cipher Whitelist: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA

18.0.5Enabling TLS Mutual Authentication (Client Auth)

 

Creating a Client Certificate

If the client does not already have a certificate it can use to identify itself, the following steps may be followed by the client.

Step 1: Generate a Certificate Authority Root

Create the server key. Note that this file should be generated by the client, and kept secure.

openssl genrsa -out ca.key 4096

This command will produce output similar to the following:

Generating RSA private key, 4096 bit long modulus
....................................................................++
e is 65537 (0x10001)

Next, generate a certificate. During this step you will be asked for information about your organization.

openssl req -new -x509 -days 365 -key ca.key -out ca.crt

This command will prompt for some details about the organization making the request.

You are about to be asked to enter information that will be incorporated into your certificate request.

Country Name (2 letter code) []: US
State or Province Name (full name) []: OH
Locality Name (eg, city) []: Springfield
Organization Name (eg, company) []: Acme
Organizational Unit Name (eg, section) []: Widgets
Common Name (eg, fully qualified host name) []: example.com
Email Address []: info@example.com

Step 2: Generate a Client Certificate

This step generates a certificate that is specific to the client application making requests. First, we generate a key.

openssl genrsa -out client.key 4096

Next, generate a signing request.

openssl req -new -key client.key -out client.csr

This will produce output similar to the following. Note that you must not use the exact same answers to all questions that you provided when creating the Certificate Authority Root above.

You are about to be asked to enter information that will be incorporated into your certificate request.

Country Name (2 letter code) []: US
State or Province Name (full name) []: OH
Locality Name (eg, city) []: Springfield
Organization Name (eg, company) []: Acme
Organizational Unit Name (eg, section) []: Widget Client
Common Name (eg, fully qualified host name) []: example.com
Email Address []:info@example.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []: (hit enter with no value)

Next, generate a signed certificate.

openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt

This will produce output similar to the following:

Signature ok subject=/C=US/ST=OH/L=Springfield/O=Acme/OU=Widget Client/CN=example.com/emailAddress=info@example.com Getting CA Private Key

Optionally, you may now verify that the certificate works:

openssl verify -verbose -CAfile ca.crt client.crt

Finally, generate a PKCS#12 keystore that can be imported into Smile CDR. Note that the keystore password of changeit is used below. You may choose to use a different password. When this command has completed, you will have a file called truststore.p12.

keytool -import -alias acme -file client.crt  -keystore truststore.p12 -storepass changeit -storetype pkcs12

This will produce output similar to the following.

Owner: EMAILADDRESS=info@example.com, CN=example.com, OU=Widget Client, O=Acme, L=Springfield, ST=OH, C=US
Issuer: EMAILADDRESS=info@example.com, CN=example.com, OU=Widgets, O=Acme, L=Springfield, ST=OH, C=US
Serial number: 1
Valid from: Thu Dec 06 09:08:08 EST 2018 until: Fri Dec 06 09:08:08 EST 2019
Certificate fingerprints:
   SHA1: C9:4A:A1:D2:F3:F9:54:04:5A:3A:69:45:14:B0:19:BC:26:63:D4:77
   SHA256: 4A:32:72:7B:C2:9D:D4:EE:A3:98:61:9D:8A:9C:9F:18:1B:98:CE:87:2A:90:F3:B7:7D:77:79:E2:35:6B:F5:E3
Signature algorithm name: SHA1withRSA
Subject Public Key Algorithm: 4096-bit RSA key
Version: 1
Trust this certificate? [no]: yes
Certificate was added to keystore

Step 3: Generate a Client Keystore

The following command creates a private PKCS#12 keystore containing both the key and certificate. This file will be needed by your client in order to invoke the service.

openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12

This command will produce output similar to the following:

Enter Export Password: changeit
Verifying - Enter Export Password: changeit

Step 5: Install Truststore in Smile CDR

In your endpoint module, set the following settings:

Client Authentication yes
TLS TrustStore Filename file:///path/to/cacerts.p12
TLS TrustStore Password Password from truststore above

Step 4: Test

The following example shows a cURL command being used to present the generated client key to the server.

curl --cert client/client.crt --key client/client.key https://localhost:8000

18.0.6Access Logs

 

HTTP servers provided by Smile CDR may be configured to create one or more access logs. These logs can log every HTTP request that is processed by the system at a low level.

Access logs are named appenders that are processed through the standard Logback logging facility. See System Logging for information on how to configure Logback.

To create logs, an access log definition should be placed in the access_log.appenders property.

The value for this property represents the complete collection of appenders, where each line is a new appender. Each line in the property value should take the format:

[appender.name]=[access log line format]

For example, a simple access log could be created by using the following property value:

customer.fhir_endpoint.access=Request: $(request_time_iso8601) $(method) $(request_uri_original) $(request_protocol)

As you can see, the property value has a number of variable substitutions taking the form $(name). A list of available substitutions appears below.

Also note that the appender name (e.g. customer.fhir_endpoint.access) can be any identifier you want. In order to avoid conflicts with internal Smile CDR log appenders, it is recommended to prefix the appender name with customer. as shown.

HTTP Troubleshooting Logs

Note that as an alternative to defining individual access logs, the HTTP Troubleshooting Log can also be used. This log can be dynamically added to a running server without needing to restart the module, which can be a benefit in some troubleshooting circumstances.

Redirecting Access Logs to a Dedicated File

By default, this appender will output to the same smile.log log file where all other logging appears. This may be redirected to a dedicated access log file by placing an appender definition and a logger definition in the Smile CDR logback.xml file (found in the classes/ directory inside your Smile CDR installation). An example is shown below:

<appender name="FHIR_ENDPOINT_ACCESS" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${smile.basedir}/log/fhir_endpoint_access.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>${smile.basedir}/log/fhir_endpoint_access.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
        <maxHistory>30</maxHistory>
    </rollingPolicy>
    <encoder>
        <pattern>%msg%n</pattern>
    </encoder>
</appender>
<logger name="customer.fhir_endpoint.access" additivity="false">
    <appender-ref ref="FHIR_ENDPOINT_ACCESS"/>
</logger>

Available Substitutions

Name Description
$(n) A newline character.
$(module_id) The ID of the Smile CDR module this HTTP server is running on.
$(node_id) The ID of the Smile CDR node this HTTP server is running on.
$(request_time_strftime) The request date/time in strftime format (e.g. 10/Oct/2000:13:55:36 -0700).
$(request_time_iso8601) The request date/time in ISO 8601 format (e.g. 2018-07-18T21:08:32+00:00).
$(remote_ip) The IP of the requesting client.
$(method) The HTTP verb.
$(request_uri_original) The requested URI (e.g. /Patient/123).
$(request_protocol) The requested protocol (e.g. HTTP/1.0).
$(response_status_code) The response status code (e.g. 200).
$(response_bytes_written) The number of bytes written to the response.
$(request_header_cache_control) The value of the request Cache-Control header.
$(request_headers_all) The complete set of headers received in the request. Note that these headers are newline-separated, and this variable substitution will therefore likely write multiple lines to your log file.
$(response_headers_all) The complete set of headers returned by Smile CDR in the response. Note that these headers are newline-separated, and this variable substitution will therefore likely write multiple lines to your log file.
$(response_latency_millis)
$(thread_name) The name of the servicing thread (useful to diagnose multithreaded REST issues).

Example Patterns

NCSA Common Log Format

The following pattern generates an access log using the NCSA Common Log Format.

customer.fhir_endpoint.access=$(remote_ip) - - [$(request_time_strftime)] "$(method) $(request_uri_original) $(request_protocol)" $(response_status_code) $(response_bytes_written)

This pattern produces an output similar to the following:

127.0.0.1 - - [18/Jul/2018:17:25:29 -0400] "GET /Patient HTTP/1.1" 200 9959

Enhanced Access Log Format

customer.fhir_endpoint.access=$(request_time_iso8601) [$(remote_ip) $(thread_name)] $(method) $(request_uri_original) $(request_protocol) -- $(response_status_code) $(response_bytes_written) $(response_latency)

Log Request Headers

The following pattern logs all requests, including the request headers (this will be verbose, but can be useful for troubleshooting).

customer.fhir_endpoint.access=$(request_time_iso8601) $(method) $(request_uri_original)$(n)$(request_headers_all)$(n)

This pattern produces an output similar to the following:

2011-01-01T23:59:59.123-04:00 GET /Patient/123
Host: example.com:8000
Accept-Encoding: gzip,deflate
Accept: application/fhir+json

18.0.7Frame Options

 

By default, Smile CDR HTTP servers use the X-Frame-Options to prevent web content from being embedded in HTML IFrame elements. This is a security measure to prevent Clickjacking attacks.

If you are setting up a system where server content should be available within an IFrame, you can adjust the Frame Options (Allow From) property to allow frame-embedded content.

This setting accepts any of the following values:

  • (Blank)(default) If no value is supplied, responses will be served with an X-Frame-Options value of DENY meaning that content will not be allowed within an IFrame.

  • (Url) – If a URL is supplied, content may be embedded in an IFrame originating from this origin URL.

  • SAMEORIGIN – Content may be served within an IFrame originating at the same origin domain / web server.

  • * – The X-Frame-Options header will not be sent, meaning that content may always be served in an IFrame.