CFSSL and Tyk api gateway.

14 minute read

I have earlier written a post about CFSSL and how to create a trusted CA chain.
In this post I thought I’d go through how to set it up as a service which you can reach via the net while not having to be (too) scared about security.

This post will not go through the multirootca tool nor will I talk about revocation or OCSP, things I might write more about in a later post.

CFSSL have a command named serve, this command starts CFSSL as a service, and not only that, it starts it as a web API!
Now, an important thing to know about this api is that there is only a few endpoints which are possible to enable any kind of authentication on. So if you run it bare, people would be able to query it for certificates without your explicit consent!
This is where Tyk-gateway comes in. Tyk is a API Gateway and is both Open Source (in its CE version) and available as a hosted service. In this case though, we will want our backend services to run behind the firewall, with Tyk exposed to the public network.

Installing Tyk and CFSSL

In this post I will use Ubuntu 18.04 as the OS of choice, as it is a quite generic distro and I feel comfortable working with it. You could use whatever distro you wish, but you might have to change some commands accordingly.

Redis Key-Value storage

Tyk is backed by Redis. Redis is a key-value storage and queue engine which is quite powerful and fast, it’s very easy to install and configure. So that is where we start.
To make everything as easy as possible, we use the package that ubuntu already have in its apt repositories from start. And once installed, we set up a password and username.

# Install all the base packages.
apt install openssl apt-transport-https tar curl gpg
apt update
# Install Redis.
apt install redis-server

# To make redis slightly more secure, we add a password.
# This is not really needed if you don't really really want it, but always a good idea to be safe than sorry.
openssl rand -base64 32 > ~/redis-pw.txt
cat ~/redis-pw.txt | xargs printf '\nrequirepass %s\n' $1 >> /etc/redis/redis.conf
# The above command could of course be done by manually editing the redis.conf file
# and add a `requirepass <your password>` line.

#restart redis!
redis-server restart

Now we have redis up and running.
Next part to handle is to install Tyk-gateway and start the gateway api.

Tyk gateway installation

Tyk uses its own package repository, so you aught to add their PGP keys to your keychain and then create a deb file for their repository.
If you don’t trust my guide, you can find the PGP key at https://packagecloud.io/tyk/tyk-gateway/gpgkey else the command to add the key is the following:

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv EA312927

After adding the key, you have to create a list file for the repository, this can be done by the following command:

export DISTRO= # your distro here (in case of Bionic as in this toturial, we cheat and set it to `xenial`)
printf deb https://packagecloud.io/tyk/tyk-gateway/ubuntu/ ${DISTRO} main\ndeb-src https://packagecloud.io/tyk/tyk-gateway/ubuntu/ ${DISTRO} main > /etc/apt/sources.list.d/tyk_tyk-gateway.list
apt update # will update the deb list.

After updating the repository list we can simply install the gateway with

apt install tyk-gateway

Install Go and CFSSL

Installing CFSSL is possible through the standard channels (apt install golang-cfssl), but to make sure we have the latest version we do this the go way!
I have in earlier posts described how to install go, but to re-cap, the easiest way to do this is through downloading the latest version from their page: https://golang.org/dl/ (locate the one for your architecture and os). As always, check the shasum!

# Make sure to change version and arch to what you need
curl https://dl.google.com/go/go1.12.9.linux-amd64.tar.gz -o go.tar.gz 
tar -xzf go.tar.gz /usr/local
mkdir -p ~/go

# Set up GOROOT and GOPATH and add the bin dirs to path.
export GOROOT=/usr/local/go
export GOPATH=~/go
export PATH=$PATH:$GOPATH/bin:$GOROOT/bin

# Add it to your .profiles file to make it stick through logins.
printf '\nGOROOT=/usr/local/go\nGOPATH=~/go\nPATH=$PATH:$GOROOT/bin:$GOPATH/bin\n' >> ~/.profiles

# Make sure it is installed!
go version

# And it's done!

With this, we should have go installed with the latest version.
When GO is installed, we use go to install the full cfssl package (you might not need it all, but it’s not that big, so does’nt really matter):

go get -u github.com/cloudflare/cfssl/cmd/...

And when that is done, CFSSL is installed!

Configuration

When all the above is done we got all the applications we need installed. The next part that we have to do is to configure the software. Every config file is a json file, something that you should be fairly used to by now!

Configure CFSSL

To serve our CFSSL API we need two configuration files. We need the base config.json and we need a database.json configuration. We also require the intermediate certificate (and key) that we will use to sign the certificates with (I will leave that part up to you, read my earlier cfssl tutorial if you need help!).

In this tutorial I will stick to using sqlite3, while a larger PKI would rather use PostgreSQL.

The database configuration file is quite simple. All we need to add to it is a driver key and a data_source. In this case:

{
    "driver": "sqlite3",
    "data_source": "ca.sqlite"
}

Where ca.sqlite will be the database file that all the data will be populated.
Now, one issue here is that CFSSL will not create the database tables by itself. So to make it work, we aught to create them manually. The table structure looks like this, and you might want to put it in a initial.sql file:

create table certificates
(
    serial_number            blob not null,
    authority_key_identifier blob not null,
    ca_label                 blob,
    status                   blob not null,
    reason                   int,
    expiry                   timestamp,
    revoked_at               timestamp,
    pem                      blob not null,
    primary key (serial_number, authority_key_identifier)
);

create table ocsp_responses
(
    serial_number            blob not null,
    authority_key_identifier blob not null,
    body                     blob not null,
    expiry                   timestamp,
    primary key (serial_number, authority_key_identifier),
    foreign key (serial_number, authority_key_identifier) references certificates
);

We will not use the ocsp_responses table in this post, but you might as well add it!

You may create this database locally or on the server you are installing the PKI, but basically, you need sqlite3 and you need to create a new database:

apt install sqlite3
sqlite3 /path/to/database.sqlite < /path/to/initial.sql

And with that, we have created a fresh database for CFSSL to use!

Next part is the actual configuration file for CFSSL. The following file is a very basic one, one with a client, server and peer profile, one which have no extra authentication or anything like that. Feel free to customize it as you wish!

{
    "signing": {
        "default": {
            "expiry": "8760h",
            "usages": [
                "signing",
                "key encipherment",
                "client auth"
            ]
        },
        "profiles": {
            "server": {
                "usages": ["signing", "key encipherment", "server auth"],
                "expiry": "8760h"
            },
            "peer": {
                "usages": ["signing", "key encipherment", "server auth", "client auth"],
                "expiry": "8760h"
            },
            "client": {
                "usages": ["signing", "key encipherment", "client auth"],
                "expiry": "8760h"
            }
        }
    }
}

Now when we have all the configuration files, we could start the server or, as I prefer, create a Service script for it!

[Unit]
Description=My CFSSL service!

[Service]
Type=simple
User=cfssl
Restart=on-failure
RestartSec=5
# You should shange the path to cfssl and the certificates so that it fits your 
# own case, this is just an example!
ExecStart=/root/go/bin/cfssl serve \
         -ca /etc/cfssl.d/certificates/intermediate.pem \
         -ca-key /etc/cfssl.d/certificates/intermediate-key.pem \
         -config /etc/cfssl.d/config.json \
         -db-config /etc/cfssl.d/database.json \
         -port 8801

[Install]
WantedBy=multi-user.target

Place the file in /etc/systemd/system/my-ca.service and reload the daemon with systemctl daemon-reload.
You can now start, stop and restart cfssl with systemctl restart my-ca.

You might notice that my cfssl service is running under its own user. This is something that you should probably do with every service that runs in production. Running a service under root is not recommended!

Configure Tyk

The first part of the Tyk setup is to invoke its setup script.

sudo /opt/tyk-gateway/install/setup.sh --listenport=443 --redishost=localhost --redisport=6379 --domain=""

The above command will tell Tyk to listen to the default https port, to use our redis which is installed and running on localhost and to use any domain which is pointing to the server.
The setup writes the default configuration to /opt/tyk-gateway/tyk.conf, and this file we should to edit slightly to make sure that it uses the parameters we want and is not open for everyone to use!

The keys we aught to change are the following:

  • secret
  • storage.password
  • http_server_options

The following is a configuration file which I use (slightly edited to mask some stuff), make sure your file looks something similar and check the documentation for more information and to make sure everything is as it should be!

{
  "control_api_hostname": "127.0.0.1",
  "control_api_port": "8888",
  "listen_address": "",
  "listen_port": 443,
  "secret": "<a secret key you should create yourself!>",
  "template_path": "/opt/tyk-gateway/templates",
  "use_db_app_configs": false,
  "app_path": "/opt/tyk-gateway/apps",
  "middleware_path": "/opt/tyk-gateway/middleware",
  "storage": {
    "type": "redis",
    "host": "localhost",
    "port": 6379,
    "username": "",
    "password": "<the earlier created redis password!>",
    "database": 0,
    "optimisation_max_idle": 2000,
    "optimisation_max_active": 4000
  },
  "enable_analytics": true,
  "analytics_config": {
    "type": "",
    "ignored_ips": []
  },
  "http_server_options": {
    "use_ssl": true,
    "enable_http2": true,
    "min_version": 771,
    "certificates": [
      {
        "domain_name": "my.domain.tld",
        "cert_file": "/etc/letsencrypt/live/my.domain.tld/cert.pem",
        "key_file": "/etc/letsencrypt/live/my.domain.tld/privkey.pem"
      }
    ]
  },
  "proxy_enable_http2": true,
  "optimisations_use_async_session_write": true,
  "allow_master_keys": false,
  "policies": {
    "policy_source": "file"
  },
  "hash_keys": true,
  "min_key_length": 12,
  "suppress_redis_signal_reload": false,
  "force_global_session_lifetime": false,
  "max_idle_connections_per_host": 500
}

As you see, I use Let’s encrypt to give me valid TLS certificates, there is an option in tyk to use Let’s encrypt right out of the box, not having to generate new certificates through applications like certbot. To enable Let’s encrypt directly, go check the docs!

The http_server_options sets the server to use ssl, to enable http2, to use tls 1.2 or higher (771 = 1.2, 770 = 1.1, 769 = 1.0) and specifies what certificates to use.
The control_api_hostname and control_api_port keys are set to only allow access to the Tyk api from localhost. This is to make sure that no-one without direct access to the server is able to create new api keys nor new APIs.

There is a lot more options to use if you need, go check out the Tyk configuration options in the docs for more information!

Be sure to change the secret to a secret that is not the default!

With this, Tyk is set up and ready to start!

service tyk-gateway start

Configure the API!

We now have a cfssl server up and running on port 8801, a Tyk api running on 8888 and the tyk gateway listening on 443.
Make sure that your ports are closed on anything but the 443 port and a port to allow ssl access!

There are two ways to set up a new API in tyk, either by calling the <uri>/tyk/apis endpoint or by creating a new file in the apps/ folder in the tyk installation directory.

As of writing this post, the API way of creating a new API did not work as expected for me, as soon as I have figured it out I will update this post. As of now, I will only show the file way of doing it.

Create api through files

Each API that you wish to expose through the gateway requires its own file. In this post, we create a new api which we call cfssl.json and place it in the /apps directory of the tyk installation dir.

The file that we add should look something like this:

{
    "name": "CA-API",
    "slug": "my-ca",
    "api_id": "1",
    "org_id": "1",
    "auth": {
        "auth_header_name": "Authorization"
    },
    "definition": {
        "location": "header",
        "key": "x-api-version"
    },
    "version_data": {
        "not_versioned": true,
        "versions": {
            "Default": {
                "name": "Default",
                "use_extended_paths": true
            }
        }
    },
    "proxy": {
        "listen_path": "ca/",
        "target_url": "http://127.0.0.1:8801",
        "strip_listen_path": true
    },
    "active": true
}

In this post, I won’t write about users nor organizations in the Tyk API. You may go look it up in the docs, as it might be useful for you, so in the API config file I just use org_id: "1".

We aught to remember a few things from this configuration (we can of course read it afterwards…), what we will need later are the api_id and api_name.
As you might guess, the proxy object is probably the most important part. It allow us to define what URI the target have, and in this case, it is localhost on port 8801 (the cfssl service).
The path is set to /ca, which means that when we call the api from the public network we have to append /ca before the CFSSL path, the strip_listen_path tells Tyk that we don’t want that part to be forwarded to the service though.

When it comes to authentication, the auth object is important. In this case, it tells Tyk that the authorization header to use is Authorization, there are multiple strategies to use with Tyk, but in this case, we will just use a standard static API key.

Configure access

The final thing we have to do - before anyone can create certificates - is to create a key for the API.
This is done through the Tyk web API.

The following snippet will create a token which never expires and can be used quite a lot, while there are parameters which can fine-tune the api access quite a bit. You might want to take a look at the Token docs to get some additional information on what you can do with a specific token!

{
    "allowance": 1000,
    "rate": 1000,
    "per": 1,
    "expires": -1,
    "quota_max": -1,
    "org_id": "1",
    "quota_renews": 1449051461,
    "quota_remaining": -1,
    "quota_renewal_rate": 60,
    "access_rights": {
      "1": {
        "api_id": "1",
        "api_name": "CA-API",
        "versions": ["Default"]
      }
    },
    "meta_data": {}
}

The json payload should be sent to the http://127.0.0.1:8888/tyk/keys/create endpoint and should return a key which you can use to access the CFSSL API. When the request is sent, you have to use the API key that was added to the tyk.conf file as a x-tyk-authorization header.

curl -s -d @payload.json http://127.0.0.1:8888/tyk/keys/create -H "x-tyk-authorization=mykey"

The response should look something like this:

{
  "action": "create",
  "key": "abc123abc123",
  "status": "ok"
}

The key field is your new CFSSL API key!

Query CFSSL

Now when all the configuration and setup is done, we might want to use CFSSL to provision a new certificate. This can’t be done without authorization anymore. Try:

curl https://<your uri>/ca/api/v1/cfssl/scan?host=jite.eu

Hopefully the above request will return a 401, if it does not, you have probably missed something in the above post… but if it does, you can still query it by adding a Authorization header with your newly created key!

curl -h "Authorization: <my new key!>"  https://<your uri>/ca/api/v1/cfssl/scan?host=jite.eu

Last words

Maintaining a PKI is not a task I would recommend, if you have the ability to use a managed pki, do it. Seeing that a CA server is one of the most vital parts of an organizations properties, you should really make sure that you know what you do and that you are aware of all the risks.

Be aware that the above snippets and information are not recommendations from me as a professional, it is more of an informal text that documents my research and experiences with CFSSL and Tyk. If you are about to set up a PKI, make sure that you consult a specialist, as it is a very vital and important thing to have as secure as possible.

I would also like to note that Tyk have sooo much more features than the ones I have shown here. It’s a great tool if you need an api gateway and I really recommend that you take a look at it more closely!

If you find any issues with the post, have any questions or just want to say hi, please post a comment below!