(Small) Step CA

If you have read my earlier posts about PKI/CA servers you might know that I have been using cfssl and written a few posts about it.
Cfssl is nice and all, but it’s quite badly documented and kind of require you reading the Go code to figure out what works and doesnt, it also takes a while to set up correctly.

A few months ago I was asked to check out Smallsteps step ca project, which I did, I haven’t really had time to write too much on the blog since then, so I thought it would be a good idea to actually write a short a post about it!
The certificate authority from smallstep is not only for x509 certificates, it also works as a SSH authority, and it handles the ACME (v2) protocol!
I have been using keybase-ssh for my SSH authority, which is nice, but it’s “ChatOps”, something that I’m not that comfortable with yet. So I thought I’d test this approach instead (will write more about the SSH part in a later post)!

Installing step/certificates

Step distribute binaries released for MacOS, Debian and Arch (the CLI package is also available for windows), the binaries are currently only available for amd64. There are also Docker images and helm charts for the ones who wish to run the server in containers. In this post, I will stick to the standard installation, and I will focus on debian-like distros (ubuntu as usual).

.deb

The easiest way to install step ca and cli is through the deb files distributed through GitHub releases:

https://github.com/smallstep/certificates/releases
https://github.com/smallstep/cli/releases

You actually don’t need the cli application to run the CA, but as we will be using it to generate certificates and such later on, we might as well install that too.

Download the step-certificates_<version>_amd64.deb and step-cli_<version>_amd64.deb files and install them with dpkg.
As of right now, the deb files does not seem to be signed with any pgp keys, so you will either have to just trust the system or build from src.

dpkg -i step-certificates_<version>_amd64.deb
dpkg -i step-cli_<version>_amd64.deb

And that’s it, the bin’s are installed!

Go!

If you rather go the go way (pun intended), you could of course build it from source.
Step ca and cli are both built with go. So building the binary with your local go installation should be fine.

I have not yet tried to build the source myself, but I will update this post as soon as I have!

Initialization

Pre start

I personally prefer to run all my services as their own user, not as root. This is seen as quite a best practice and increases the security quite a bit.
I would really recommend doing this if you intend to use the PKI server for real!

On distros using systemd, creating a new service user is done like this:

useradd -r -U step -s /usr/sbin/nologin

You can then (if needed for some reason) switch to the user with su - step.

Quick start

To set up a step-ca server, all you really have to do is to init and start it:

step ca init
step-ca $(step path)/config/ca.json

Using this approach will allow you to set up the values required through stdin, which I personally prefer not to do, so below (slow start) I will describe how to do it with a single command.

Step have what they call “safe, sane defaults”, which is true, the default settings are a good start!
That means that it’s not that much to configure to get a good and stable CA server up and running.

Slow Start

If you are like me, someone who always want to go deeper into software and configure stuff more than is really needed… then you might not feel that the defaults are good enough for you.
Or maybe you felt that your root certificate should not be generated by step, but you rather want to use your own… Well, then this part might give some help!

Steps documentation is quite alright, it’s a lot better than the CFSSL docs which are basically text files hidden in their repository. One could always wish for more, but this one is at the least good enough!
From the docs you can see that the step ca init command have a bunch of options that can be passed, that is… you can basically through the init command change all the default configuration options!

The following is the snippet that I used when setting up my own ca:

step ca init \
    --root=/home/step/intermediate.pem \
    --key=/home/step/intermediate-key.pem \
    --name="Jitesoft PKI CA" \
    --dns=ca.jitesoft.com,127.0.0.1,localhost \
    --address=127.0.0.1:16443 \
    --provisioner=[email protected] \
    --provisioner-password-file=/home/step/provider-password \
    --with-ca-url=ca.jitesoft.com \
    --password-file=/home/step/ca-password \
    --ssh

I will give a brief explanation of the options I used here (well, I will skip the obvious ones…).

The --root and --key options allow you to set your own root certificate, it of course requires both the key and cert. The --dns option sets up the DNS names for the CA.
I’m not really sure that 127.0.0.1 and localhost is useful here.
The --address option is useful if you wish to bind to an ip or another port than the default one, in my case, I chose a none-privileged port to run it on.
The --provisioner option is the initial provisioner for the CA, Step recommends using an email address, which I chose to follow!

When it comes to the *password-file options, I feel that them existing is great when running in kubernetes or swarm, passing the passwords as secrets is a great way to do it, whilst, I do not run in kubernetes as of now…

Storing passwords to something as vital as your PKI server in open text on the server is a bad thing to do. I will update this post as soon as I have figured out a good way to not do this.

The --ssh flag at the end tells init that I really want the SSH-CA certs generated!

Now, when the init have been executed, all we need to do is to start the server!

step-ca --password-file /step/.step/password /step/.step/config/ca.json

Proxy

When it comes to exposing services to the internet, especially services like this one, I usually use a proxy that I feel comfortable with. I’m certain that the server that step provides is good enough, but I like to be able to configure more settings if needed, so in this case I decided to use nginx.

Installing nginx is quite easy, I would think that basically all linux distros have a nginx package somewhere for you to install!

apt install nginx
touch /etc/nginx/sites-available/ca-proxy
ln -s /etc/nginx/sites-available/ca-proxy /etc/nginx/sites-enabled/ca-proxy

When setting up the server, we should of course use a TLS certificate generated from our own new CA.
The first step to take in this case is to fetch the root certificate of the step server, so that we can install it to the trust store.

If you wish to have certificates that are already trusted by most browsers, you could of course use Let’s encrypt or some other service to obtain certs and add instead!

Initially, we need the fingerprint of the certificate, we use the --insecure flag, due to the fact that we don’t have the certificate stored yet.
After that, a bootstrap command should be done, with the --install flag applied, which should update your certificate store with the new certificate.

FINGERPRINT=$(step certificate fingerprint https://ca.jitesoft.com)
step ca bootstrap -ca-url=ca.jitesoft.com -fingerprint "${FINGERPRINT}" --install

If the certificate can not be installed (for example, the user is not a sudoer), you can fetch the root cert and install it yourself either through copying the actual certificate from /home/step/.step/certs/root_ca.crt or by using the following commands:

FINGERPRINT=$(step certificate fingerprint https://ca.jitesoft.com)
step ca root /usr/share/ca-certificates/extra/my_new_ca.crt --fingerprint "${FINGERPRINT}" # if there is issues with using the "real" ca url, change to https://localhost:port
dpkg-reconfigure ca-certificates

With this done, we have trusted our server and can now request a certificate!
The bootstrap command will also add the server URI and other things like that into the users ~/.step/config/defaults.json, so no need

WEB_SERVER_URI="ca.jitesoft.com" # Change to yours
step ca certificate "${WEB_SERVER_URI}" /etc/ssl/web.crt /etc/ssl/web.key -standalone

After the certificate have been generated, we aught to set up our proxy nginx configuration. Open /etc/nginx/sites-available/ca-proxy and make it look something like this:

server {
    listen 80;
    return 301 https://$host$request_uri;
}

server {
  listen 443 ssl;
  ssl on;
  ssl_certificate /etc/ssl/web.crt;
  ssl_certificate_key /etc/ssl/web.key;
  server_name ca.jitesoft.com; # Change to your domain ofcourse!
  ssl_protocols       TLSv1.2 TLSv1.3;
  ssl_ciphers         HIGH:!aNULL:!MD5;

  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-Proto $scheme;
    proxy_pass_request_headers on;
    proxy_pass                 https://localhost:16443; # Change to correct port.
    proxy_read_timeout         90;
  }
}

The above nginx file will proxy every request to the CA server. You might want other settings to make the server more secure.

The initial server clause in the above file will redirect any http requests to the https port (443 in this case).
The second server clause defines what nginx need to know to proxy any requests to the PKI server.

When done, reload the nginx configuration!

service nginx reload
# if not started yet
service nginx start

Startup script

Running the server in a screen or with the window open is not viable when we try to keep the server up for long time. To be sure that it’s on always and restarts on crashes or reboots, I always try to create a service script for the service in question.

The following script is very basic, you may wish to edit it a bit:

[Unit]
Description=Step-CA Service
StartLimitIntervalSec=5

[Service]
Type=simple
Restart=always
RestartSec=3
User=step
ExecStart=/usr/bin/step-ca \
  --password-file /home/step/.step-conf/password \
  /home/step/.step/config/ca.json

[Install]
WantedBy=multi-user.target

Put the script in /etc/systemd/system/step.service reload the daemon and enable + start the service!

systemctl daemon-reload
systemctl enable step
systemctl start step

What does the script do?!

A service script is a script that (when enabled) will start up your service (and restart on crash) automatically. The script above is set to be a simple type with a start interval of 5 seconds, it executes the step-ca binary with the password file and ca configuration in the ExecStart definition and it uses the service user step.

Final words

Step-ca is quite easy to install and use, it’s a great tool and one of few open source self-hosted CA alternatives.
I, myself, have decided to use the server from now on for my company PKI, so you can expect more tutorials and informal posts about step here further on.

As usual, if you have any input on the post, feel free to drop a comment!

Updated: