This document provides my solution to the ForgeRock technical writing task.

Introduction

The task can be summarized as:

Document how to setup tools so that you can serve a JSON file over the HTTPS protocol, and prove it works from the command-line.

This is suitably vague that the solutions can range from extremely simple to quite complex. Some possible approaches include:

  1. Serving static JSON files via a Glitch web server (HTTPS using the *.glitch.com certificate).

  2. Using a Flask web app locally to serve static JSON over HTTPS (using a self-signed certificate).

  3. Installing a Nginx (or other web server), and configuring it to serve static JSON over HTTPS. This also uses a self-signed certificate.

Further clues hint at the third approach, so that is what is described here.

The task also states:

You’re free to choose the format, style, and tone of the documentation.

For this task I chose a hands-on tutorial approach, with limited (but hopefully sufficient) technical discussion.

If further time was available I would:

  • Add a troubleshooting section.

  • Do more research and add more conceptual material on Digital Certificates, Certificate Authorities, and key pairs. The file formats and file extensions used need further clarification.

  • Provide a screencast.

  • Illustrate with an overview diagram.

  • Explain why you would want to serve static JSON files!

  • Try a different syntax highlighter.

  • Add authentication. I did however implement this in a Python/Flask version of the server, hosted on Glitch.

Assumptions

The following assumptions are made:

  1. The platform is Mac OS X.

  2. Install for local testing only.

  3. Curl is already installed.

  4. OpenSSL is already installed.

  5. Commands are carried out on the command line unless otherwise stated.

  6. The user is comfortable using the command line and with editing files.

  7. ForgeRock uses American English.

The process

Tools required

The tools required are:

  • Nginx

  • OpenSSL

  • Curl

Optionally, you can install jq to pretty-print and validate JSON responses.

1 - Install Nginx

In this step you install Nginx, which functions as the web server.

Nginx can be installed using brew:

brew update
brew install nginx

You can test for successful installation using:

nginx -v

The Nginx version is displayed as follows:

nginx version: nginx/1.19.3

Nginx is installed to the the location /usr/local/etc/nginx/. This directory contains the configuration files you will be working with.

You can now start Nginx with:

nginx

Navigate your browser to http://localhost:8080 and you are greeted with an Nginx welcome message.

Note

Port 8080 is the default for Nginx using the HTTP protocol.

You have now successfully installed Nginx.

2 - Verify Nginx can serve static JSON files

In this step you verify that Nginx can serve static JSON files.

Create a sample JSON file in the Nginx data directory /usr/local/var/www. This is where the files to be served by Nginx are located.

For example, create the following file patient.json:

{
  "test-id": "FF475013-CDD9-4377-AEAB-A65932C698DE",
  "retest": false,
  "key-worker": true,
  "testing-center-id": "swindon-east-1",
  "tester-id": "6789-dcba-123",
  "test-number": 123,
  "test-date-time": "2020-10-08T13:10:20Z",
  "patient": {
    "id": "1234-abcd",
    "firstname": "Sally",
    "lastname": "Bright",
    "dob": "1974-05-12",
    "postcode": "SN4 7RH",
    "mobile": "07932-155051"
  },
  "covid-19-result": "negative"
}
Note

This information is fictitious.

You can now verify Nginx is capable of serving this file:

curl http://localhost:8080/patient.json | jq

In this case the output of Curl is piped to jq to nicely format and validate the JSON returned. This is optional.

You receive a response identical to the JSON data file you created previously, verifying that the static file has been served.

3 - Configure Nginx to use HTTPS

In this step you configure Nginx to use HTTPS.

Open the Nginx configuration file /usr/local/etc/nginx/nginx.conf.

Find the following section:

    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}

Remove the comments so that the configuration is as follows:

    # HTTPS server
    #
    server {
        listen       443 ssl;
        server_name  localhost;

        ssl_certificate      cert.pem;
        ssl_certificate_key  cert.key;

        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;

        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers  on;

        location / {
            root   html;
            index  index.html index.htm;
        }
    }

Restart Nginx with the following:

nginx -s reload

This ensures the new configuration is processed by Nginx.

However, you will receive an error:

nginx: [emerg] cannot load certificate "/usr/local/etc/nginx/cert.pem": BIO_new_file() failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/usr/local/etc/nginx/cert.pem','r') error:2006D080:BIO routines:BIO_new_file:no such file)

This is because you need to create a certificate in order to have fully functioning HTTPS support. You see how to do this in the next section.

You have now configured Nginx to use HTTPS.

4 - Create required certificate and private key

In this step you create your key and certificate.

A signed certificate is required to verify the identity of the server. As you are testing locally via localhost in this tutorial, the names used are localhost.key and localhost.crt.

Change into the directory /usr/local/etc/nginx and create your certificate and key using the following command:

openssl req -x509 -newkey rsa:4096 -nodes -out localhost.crt -keyout localhost.key -days 365

You will be prompted to enter the following information. Sample responses are provided:

Value Description

Country code

GB

State or Province Name

Wiltshire

Locality Name

Swindon

Organization

ForgeRock

Organizational Section Name

Engineering

Common Name

localhost

Email Address

A suitable email address

Note

Common Name is important as that is the name of the server whose identity is to be verified. The generated certificate is only valid for the server named localhost. Note that the localhost server name is defined in /etc/hosts to have the IP address 127.0.0.1.

The options you used to create the certificate and key are summarized in the following table:

Parameter Description

req

Certificate request

x509

Certificate format

newkey

Generate a new key

rsa:4096

Key encryption method

nodes

No DES algorithm used

out localhost.crt

Certificate file generated

keyout localhost.key

Private key file

days 365

Certificate is valid for 365 days

Your Nginx configuration now needs to be updated to reflect the newly generated certificate and key:

    ssl_certificate      localhost.crt;
    ssl_certificate_key  localhost.key;

To make sure the updated configuration is loaded, restart Nginx with:

nginx -s reload
Note

If you receive permission errors you will need to use sudo, for example sudo nginx -s reload.

You have now created the certificate that is used to verify the identity of the server.

5 - Request JSON file over HTTPS using Curl

In this step you test whether you can receive a static JSON file over HTTPS.

Attempt to receive the JSON file over HTTPS using the following command:

curl https://localhost/patient.json
Note

The protocol specified is https.

You receive the following error:

curl: (60) SSL certificate problem: self signed certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). If the default
 bundle file isn't adequate, you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.
HTTPS-proxy has similar options --proxy-cacert and --proxy-insecure.

The problem is that a self-signed certificate has been used. This is not as secure as a certificate signed by a well-known Certificate Authority, and is therefore a serious potential security issue. Curl (and your web browser) notice this and prevent access to a potentially malicious server. You can obtain a clearer picture on what is happening by activating the Curl debug mode using the following command:

curl -v https://localhost/patient.json

You will see tracing such as:

* Connected to localhost (127.0.0.1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS alert, Server hello (2):
* SSL certificate problem: self signed certificate
* stopped the pause stream!
* Closing connection 0

In particular, the message SSL certificate problem: self signed certificate indicates the exact nature of the problem.

Note

In a real-world scenario the certificate associated with a server would be signed by a Certificate Authority such as Verisign. Your application, a browser, Curl, or another such client, would perform a certificate check using a database of well-known Certificate Authorities. In the situation where you are testing locally you signed your own certificate, and so are in theory the Certificate Authority, although this is an insecure practice and must not be done for production servers.

In this tutorial you are testing locally, and there are three possible ways of working around the self-signed certificate issue:

  1. Use the curl -k or curl --insecure option to ignore certificate testing.

  2. Specify the Certificate Authority on the command line.

  3. Add the self-signed certificate to your system’s database of Certificate Authorities. This can be achieved using the command sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /usr/local/etc/nginx/localhost.crt. The certificate appears in your Mac keychain, and its details can be verified. You will also be able to access Nginx via your web browsers. However, for security reasons, this third option is not recommended.

The second option is preferable for local testing and an example is shown here:

curl -v --cacert /usr/local/etc/nginx/localhost.crt https://localhost/patient.json

The debug output shows clearly what is happening:

...
* Connected to localhost (127.0.0.1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /usr/local/etc/nginx/localhost.crt
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: C=GB; ST=Wiltshire; L=Swindon; O=ForgeRock; OU=Engineering; CN=localhost; emailAddress=sample@domain.com
*  start date: Oct  9 14:44:56 2020 GMT
*  expire date: Oct  9 14:44:56 2021 GMT
*  common name: localhost (matched)
*  issuer: C=GB; ST=Wiltshire; L=Swindon; O=ForgeRock; OU=Engineering; CN=localhost; emailAddress=sample@domain.com
*  SSL certificate verify ok.
> GET /patient.json HTTP/1.1
> Host: localhost
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.19.3
< Date: Fri, 09 Oct 2020 15:19:47 GMT
< Content-Type: application/json
< Content-Length: 395
< Last-Modified: Fri, 09 Oct 2020 13:48:42 GMT
< Connection: keep-alive
< ETag: "5f806a3a-18b"
< Accept-Ranges: bytes
...

Note that the certificate is verified and the certificate information is correctly displayed. This is followed by the JSON data (not shown).

This verifies that the JSON file has been served over HTTPS, and the debug output shows that the Transport Layer Service (TLS) protocol has been used, and that certificate verification has taken place.

You have now completed this tutorial.

Conclusion

In this tutorial you have seen how to set up an Nginx web server to serve static JSON files over HTTPS, in an environment suitable for local development. If setting up a production server, the process is similar, but there are additional security concerns that are beyond the scope of this tutorial.

Further information

For further information please refer to the following resources:

Colophon

The source for this document is written in AsciiDoc, using Emacs and adoc-mode, and converted to HTML5 and PDF using AsciiDoctor. The syntax highlighter used was Rouge.

The file is spell-checked, converted, and uploaded to hosting provider Neocities, using a simple script:

#!/usr/bin/env bash

mdspell --en-us README.adoc
asciidoctor README.adoc
asciidoctor-pdf README.adoc
cp README.html test.html
cp README.pdf test.pdf
neocities upload -d forgerock test.html test.pdf

Spellchecking is carried out in interactive mode, using mdspell, with the US dictionary specified. All source files are stored for future reference in a private GitHub repository.