Introduction

The APIGW uses Nginx/OpenResty as an HTTP server and a reverse proxy for the application. Using the default configuration, all connections to the APIGW are over HTTPS and require the user to authenticate. There are two supported methods for authentication; API keys and OpenID Connect. The Nginx/OpenResty configuration files can be edited via /etc/config bind mounts for the proxy container to customize the HTTP server and change options such as TLS versions.

API Keys

API keys are the default method to authenticate with the APIGW. This is done by including the key in the HTTP Authorization request header with type apk. A cURL example using an example key of 1.0p9PMkZO4Hgy0ezwjhX0Fi4lEKrD4pflejgqjd0pfKtywlSWR9G0fIaWajuKcBT3 would appear as:

curl --header 'Authorization: apk 1.0p9PMkZO4Hgy0ezwjhX0Fi4lEKrD4pflejgqjd0pfKtywlSWR9G0fIaWajuKcBT3'


Note that cURL (like web browsers and other HTTP clients) will not connect to the APIGW over HTTPS unless a valid TLS certificate has been configured for the Nginx server. If you haven't performed this configuration step yet, and understand the risk, you may disable the check in the HTTP client. For instance this can done with cURL using the --insecure flag.

Bootstrap First API Key

There is a special process to bootstrap the creation of the first API key. This first API key should only be used to create another key and then promptly deleted, since the bootstrap API will appear in the logs. This process can be repeated as many times as needed, for example, in a case where existing API keys are lost or have been deleted. It also means that the Linux users with permissions to edit the docker-compose file implicitly have the ability to get an API key at any time. There is no mechanism to lock this down after the first bootstrap key is created.

Begin by stopping the application with the following command:

sudo docker-compose stop

Once the application is stopped, edit the docker-compose.yaml file and modify the following lines to the gateway section to set the API_CREATE_CREATE to the string value "true":

services:
  gateway:
    environment:
      API_KEY_CREATE: "true"


Start the Gateway again with sudo docker-compose up. You will see the following output in the logs for the app container (the key will be different from this example):

NEWLY GENERATED API KEY: 1.0p9PMkZO4Hgy0ezwjhX0Fi4lEKrD4pflejgqjd0pfKtywlSWR9G0fIaWajuKcBT3


Now that an API key has been created, it can be used to authenticate with the APIGW. Remember that the API Key value must be prefixed with apk. An example cURL command with the above API Key looks like the following:

curl --header 'Authorization: apk 1.0p9PMkZO4Hgy0ezwjhX0Fi4lEKrD4pflejgqjd0pfKtywlSWR9G0fIaWajuKcBT3'


Edit the docker-compose.yaml file to set the API_KEY_CREATE environment variable value back to "false" and restart the APIGW.


Create and manage API Keys

The initial API key created should be used to create a new admin secure key. This is done create by creating a new Api Client entity and setting both is_admin and generate_api_key. The "name" attribute should be the desired name to uniquely identify the user of this key.

curl --location --request POST 'https://<hostname>/v1/management/api-clients' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: apk 1.0p9PMkZO4Hgy0ezwjhX0Fi4lEKrD4pflejgqjd0pfKtywlSWR9G0fIaWajuKcBT3' \
--data-raw '{
   "name": "admin-secure-key",
   "is_admin": true,
   "generate_api_key": true
}'


A response should be received similar to the lines below:

{
   "api_key_id": 5,
   "token": "5.vCfC0MnpySYZLshuxap2aZ7xqBKAnQvV7hFnobe7xuNlHS9AF2NQnV9XXw4UyET6"
}


Now that the new and secure API key is created, the old one must be deleted for security reasons - the key appeared in the logs. To do this make the following request:


curl --location --request DELETE 'https://<hostname>/v1/management/api-clients/<id>' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: apk 5.vCfC0MnpySYZLshuxap2aZ7xqBKAnQvV7hFnobe7xuNlHS9AF2NQnV9XXw4UyET6'


The
id referenced above is the numeric id of the API client. It is the integer before the period in the token. For example, the id of 1.0p9PMkZO4Hgy0ezwjhX0Fi4lEKrD4pflejgqjd0pfKtywlSWR9G0fIaWajuKcBT3 is 1.

Finally, to list all of the current API clients, make the following request:

curl --location --request GET 'https://<hostname>/v1/management/api-clients/' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: apk <your API key>'


OAuth 2.0

Enable OpenID connect

The APIGW supports OAuth2 authentication via the OpenID Connect Discovery specification. To add support for OAuth 2.0, you must first configure Nginx to communicate with your OAuth2 server: a bind mount must be used. The file needs to be named default.conf and appear at config/nginx/conf.d/default.conf related to the root of the bind mounted directory. See the bind mount section about how to use docker cp to begin with the default version of the file.

There are three important sections of the file that need to be updated. First, the open_id_connect_enabled variable must be set to true and API keys need to be disabled like so:

local api_keys_enabled = false
local open_id_connect_enabled = true


The other two important configuration options are the discovery URL of the OAuth2 server and the specific attribute names of the JWT to provide a unique ID and name for the user. 

    -- OpenID Connect implementation
    if open_id_connect_enabled then
      local opts = {
        -- Replace the discovery URL with the discovery endpoint of your own OAuth2 server.
        discovery = "https://delphix.okta.com/oauth2/default/.well-known/oauth-authorization-server",
        ssl_verify = "yes",
        accept_unsupported_alg = false,
        accept_none_alg = false,
        redirect_uri = "",
      }

      local jwt, err, token = require("resty.openidc").bearer_jwt_verify(opts)
      if err then
          ngx.header["X-jwt-error"] = err
          ngx.status = 401
          ngx.log(ngx.ERR, "Invalid token: " .. err)
          ngx.exit(ngx.HTTP_UNAUTHORIZED)
          return
      end

      -- Replace "sub" with the attribute which is meant to be used
      -- as client_id or client_name in the JWT.
      ngx.var.client_id = jwt.sub
      ngx.var.client_name = jwt.sub

    end


Once requests are authenticated, they must be matched with an existing API client in the APIGW. To do so, one of the claims of the JWT (Json Web Token) must correspond to the client_id of an API client. For instance, imagine one is using a JWT with a sub claim with value abc123 and the configuration above, which extracts the sub claim out of the JWT and sets it as client_id. We can create a corresponding admin API client with the following request:

curl -k --location --request POST 'https://<hostname>/v1/management/api-clients' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: apk 1.0p9PMkZO4Hgy0ezwjhX0Fi4lEKrD4pflejgqjd0pfKtywlSWR9G0fIaWajuKcBT3' \
--data-raw '{
   "name": "oauth2-test-api-client",
   "is_admin": true,
   "api_client_id": "abc123",
   "generate_api_key": false
}'

Set the name to a logical name corresponding to the application or person using the JWT. is_admin denotes whether this API client has admin access to the APIGW. api_client_id must be set to the exact string value found in the JWT claim extracted in the Nginx config above. generate_api_key is disabled in this example as the API client will use exclusively OAuth2 (JWT) to authenticate and not API keys.

Replace HTTPS Certificate for APIGW

By default to enable HTTPS, the APIGW creates a unique self-signed certificate when starting up for the first time. To replace this certificate and private key, use a bind mount. The configuration needs to be bind mounted to a /etc/config/nginx/ssl directory inside the container. The default location for the configuration is /etc/config/nginx/ssl/ssl.conf and therefore should be placed in nginx/ssl/ssl.conf - inside the configuration directory on the host. The default configuration can be retrieved by using docker cp to copy /etc/config/nginx/ssl/ssl.conf out of the proxy container and used as a starting point.

To replace the certificate and corresponding private key used by Nginx, place the updated certificate at nginx/ssl/nginx.crt and the private key at nginx/ssl/nginx.key inside the bind mounted directory on the host. Doing this could require the assistance of someone from your security or IT departments. A new key pair (public and private key) will need to be created, in addition to a certificate signing request (CSR) for that key pair. IT should be able to determine the correct certificate authority (CA) to sign this CSR and produce the new certificate. The common name of the certificate should match the fully qualified-domain name (FQDN) of the host as well include the FQDN as a Subject Alternative Name (SAN). The supported versions of TLS can be changed by altering the ssl_protocols line and the list of ciphers by altering the ssl_ciphers line. After this is done, restart the APIGW application with docker-compose.