Been a while since I’ve done a guide. I have passed my RHCSA!! yeya :) Was focused on the exam, and in the meantime am working on RHCE as well. With that in mind I’m trying to learn other bits on the side which brought me to an issue I had with my TLS certs for my Gitlab server.

Let’s dive in! :)

Requirements

1Gb RAM, 1 CPU host should be more than enough for this build. Debian Bookworm being the distro that I’m sticking to in most my builds these days. Full references can be obtained from their documentation webpage.

First update the server:

sudo apt update && sudo apt upgrade

Following section will need to run as root to install the packages and dependencies for our CA server:

apt-get update && apt-get install -y --no-install-recommends curl jq vim gpg ca-certificates
curl -fsSL https://packages.smallstep.com/keys/apt/repo-signing-key.gpg -o /etc/apt/trusted.gpg.d/smallstep.asc && \
    echo 'deb [signed-by=/etc/apt/trusted.gpg.d/smallstep.asc] https://packages.smallstep.com/stable/debian debs main' \
    | tee /etc/apt/sources.list.d/smallstep.list
apt-get update && apt-get -y install step-cli step-ca

Once the binaries are installed it’s time to init the CA:

step-cli ca init
Initialisation is done through a terminal prompt:

We will choose Standalone:

Use the arrow keys to navigate: ↓ ↑ → ←
? What deployment type would you like to configure?:
  ▸ Standalone - step-ca instance you run yourself
    Linked - standalone, plus cloud configuration, reporting & alerting
    Hosted - fully-managed step-ca cloud instance run for you by smallstep

I’ve named my example: MyDomainCA:

What would you like to name your new PKI?
✔ (e.g. Smallstep): MyDomainCA

DNS needs no explanations and the IP address I went with the one on my test server 10.0.50.67. Yours may differ, so be careful on this config bit:

What DNS names or IP addresses will clients use to reach your CA?
✔ (e.g. ca.example.com[,10.1.2.3,etc.]): mydomain.local,10.0.50.67

Port can be bound on any, I’ve chosen to go with 443

What IP and port will your new CA bind to? (:443 will bind to 0.0.0.0:443)
✔ (e.g. :443 or 127.0.0.1:443): :443

Provisioner can be arbitrary:

What would you like to name the CA's first provisioner?
✔ (e.g. [email protected]): [email protected]

Password either choose one or let the prompt generate one. In my case I’ve left it empty and it has generated one like below:

Choose a password for your CA keys and first provisioner.
✔ [leave empty and we'll generate one]:
✔ Password: m26MAjDKHIwEloyyk3FIuoUO4Mq4sArj

Once the password has been set we should see something like:

Generating root certificate... done!
Generating intermediate certificate... done!

✔ Root certificate: /root/.step/certs/root_ca.crt
✔ Root private key: /root/.step/secrets/root_ca_key
✔ Root fingerprint: 974ddadbfbef067b237d5a3ae0c01b381af68a7cc15a79c0a76356203f3327b6
✔ Intermediate certificate: /root/.step/certs/intermediate_ca.crt
✔ Intermediate private key: /root/.step/secrets/intermediate_ca_key
✔ Database folder: /root/.step/db
✔ Default configuration: /root/.step/config/defaults.json
✔ Certificate Authority configuration: /root/.step/config/ca.json

Your PKI is ready to go. To generate certificates for individual services see 'step help ca'.

NOTE: Really important, make sure you save the above into a password manager, keep them safe as we will need fingerprint.

Running step-ca as a daemon

NOTE: In order for step-ca to run as a daemon it will need a Linux OS running systemd version 245 or greater.

  1. Add a service user for the CA. The service user will only be used by systemd to manage the CA:
sudo useradd --user-group --system --home /etc/step-ca --shell /bin/false step

If the CA will bind to port 443, the step-ca binary will need to be given low port-binding capabilities:

sudo setcap CAP_NET_BIND_SERVICE=+eip $(which step-ca)
  1. Move CA configuration into a system-wide location:
sudo mkdir -p /etc/step-ca && sudo mv $(step path)/*  /etc/step-ca

Now with the new directory structure we will need to create password file:

vim /etc/step-ca/password.txt

Add the password from above in the password file:

m26MAjDKHIwEloyyk3FIuoUO4Mq4sArj

Also needed to amend the config with the new location of the config and root_ca.crt from:

{
        "ca-url": "https://mydomain.local",
        "ca-config": "/root/.step/config/ca.json",
        "fingerprint": "974ddadbfbef067b237d5a3ae0c01b381af68a7cc15a79c0a76356203f3327b6",
        "root": "/root/.step/certs/root_ca.crt"
}

to:

{
        "ca-url": "https://mydomain.local",
        "ca-config": "/etc/step-ca/config/ca.json",
        "fingerprint": "974ddadbfbef067b237d5a3ae0c01b381af68a7cc15a79c0a76356203f3327b6",
        "root": "/etc/step-ca/certs/root_ca.crt"
}

There are four entries that need changing in /etc/step-ca/config/ca/ca.json:

"root": "/root/.step/certs/root_ca.crt",
"crt": "/root/.step/certs/intermediate_ca.crt",
"key": "/root/.step/secrets/intermediate_ca_key",
"dataSource": "/root/.step/db",

Simply change the path to the new structure:

"root": "/etc/step-ca/certs/root_ca.crt",
"crt": "/etc/step-ca/certs/intermediate_ca.crt",
"key": "/etc/step-ca/secrets/intermediate_ca_key",
"dataSource": "/etc/step-ca/db",

Now ownership of the new directory:

chown -R step:step /etc/step-ca

Amend the ca.conf file to point to new database location:

cat <<< $(jq '.db.dataSource = "/etc/step-ca/db"' /etc/step-ca/config/ca.json) > /etc/step-ca/config/ca.json
  1. Create a systemd unit file:
vim /etc/systemd/system/step-ca.service

Add the following contents to it:

[Unit]
Description=step-ca service
Documentation=https://smallstep.com/docs/step-ca
Documentation=https://smallstep.com/docs/step-ca/certificate-authority-server-production
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=30
StartLimitBurst=3
ConditionFileNotEmpty=/etc/step-ca/config/ca.json
ConditionFileNotEmpty=/etc/step-ca/password.txt

[Service]
Type=simple
User=step
Group=step
Environment=STEPPATH=/etc/step-ca
WorkingDirectory=/etc/step-ca
ExecStart=/usr/bin/step-ca config/ca.json --password-file password.txt
ExecReload=/bin/kill --signal HUP $MAINPID
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
StartLimitInterval=30
StartLimitBurst=3

; Process capabilities & privileges
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
SecureBits=keep-caps
NoNewPrivileges=yes

; Sandboxing
ProtectSystem=full
ProtectHome=true
RestrictNamespaces=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
PrivateTmp=true
PrivateDevices=true
ProtectClock=true
ProtectControlGroups=true
ProtectKernelTunables=true
ProtectKernelLogs=true
ProtectKernelModules=true
LockPersonality=true
RestrictSUIDSGID=true
RemoveIPC=true
RestrictRealtime=true
SystemCallFilter=@system-service
SystemCallArchitectures=native
MemoryDenyWriteExecute=true
ReadWriteDirectories=/etc/step-ca/db

[Install]
WantedBy=multi-user.target

More detail on the systemd service file can be found in the office guide here.

  1. Enabling the service

To enable the service we will reload daemons and and follow journalctl to see the status of the daemon:

systemctl daemon-reload && systemctl enable --now step-ca && journalctl --follow --unit=step-ca