After I’d setup the DNS server for my Internal LAN-only Domain, I needed to get my Public Key Infrastructure (PKI) up and running. I discovered Step-CA, which fit my needs perfectly. What I needed was a Certificate Authority, whose CA certificate I could mark as trusted in all of my computers in the LAN, so that when I generate certificates for subdomains, which I’ll use with Ingress Controllers in my Kubernetes cluster, I don’t have to see my browsers complaining that the certificate is self signed, or signed by an unknown CA. I also needed that CA server to support ACME, so that I could generate certificates like I would with Let’s Encrypt, which is what I’d do when I have to take stuff live.
This will come in handy later, when I setup Longhorn and Portainer and my own services.
First things first
You’ll of course need to setup your DNS server for your domain before you begin to setup Step-CA to issue certificates for said domain.
Here’s how I setup my DNS server for my internal domain.
Install Step
Before setting up the CA service, I needed to install this handy dandy utility called step. I’m using Ubuntu 24.04 on my node, and it’s a Raspberry PI so the architecture is arm64, so what I’ll write here will pertain to that. But in case you need to do this in a different operating system or a different architecture (which would likely be the case), you can refer to Step-Cli Installation guide.
wget https://dl.smallstep.com/cli/docs-cli-install/latest/step-cli_arm64.deb
sudo dpkg -i step-cli_arm64.debWarning
I found this out the hard way, but you don’t have to. To ensure that the step path is correct, we’ll be doing the rest of this thing in a root terminal.
sudo suInitialize the CA
export STEPPATH=/etc/step-ca
mkdir -p /etc/step-ca
# Verify that step path is taken correctly as /etc/step-ca
step path
step ca init --stepThe CA init command would ask a bund of questions relating to the CA we’re creating. Notice that we’re passing --acme here. That’s so that it also sets up support for Automated Certificate Management Environment (ACME) protocol for our CA.
First was this
root@k0r0ptnas:/home/xtreme# step ca init
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 needed a Standaloneinstance, so I chose that.
Second was the name of the new PKI, for which I chose k0r0pt-pki.
root@k0r0ptnas:/home/xtreme# step ca init
✔ Deployment Type: Standalone
What would you like to name your new PKI?
✔ (e.g. Smallstep): k0r0pt-pki█
Third was the DNS names and IP addresses, that clients would use to reach the CA, which from my DNS setup, is ca.k0r0pt.int and 192.168.1.230.
root@k0r0ptnas:/home/xtreme# step ca init
✔ Deployment Type: Standalone
What would you like to name your new PKI?
✔ (e.g. Smallstep): k0r0pt-pki█
What DNS names or IP addresses will clients use to reach your CA?
✔ (e.g. ca.example.com[,10.1.2.3,etc.]): ca.k0r0pt.int,192.168.1.230█
Next was the bind IP and port. Since I already have this blog running on port 443, I needed to use a different one. I settled for 50443.
root@k0r0pt-Rog-Strix:/home/sudipto# step ca init
✔ Deployment Type: Standalone
What would you like to name your new PKI?
✔ (e.g. Smallstep): k0r0pt-pki█
What DNS names or IP addresses will clients use to reach your CA?
✔ (e.g. ca.example.com[,10.1.2.3,etc.]): ca.k0r0pt.int█
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): :50443█
Next was the CA’s first provisioner. I used a non-existent Email for this, although for a real world use case, we’d probably need a real one here.
root@k0r0pt-Rog-Strix:/home/sudipto# step ca init
✔ Deployment Type: Standalone
What would you like to name your new PKI?
✔ (e.g. Smallstep): k0r0pt-pki█
What DNS names or IP addresses will clients use to reach your CA?
✔ (e.g. ca.example.com[,10.1.2.3,etc.]): ca.k0r0pt█int
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): :50443█
What would you like to name the CA's first provisioner?
✔ (e.g. [email protected]): [email protected]█
Next was the password for the CA keys and first provisioner. Being paranoid when it comes to security, I of course left it empty for it to auto generate a random one.
root@k0r0pt-Rog-Strix:/home/sudipto# step ca init
✔ Deployment Type: Standalone
What would you like to name your new PKI?
✔ (e.g. Smallstep): k0r0pt-pki█
What DNS names or IP addresses will clients use to reach your CA?
✔ (e.g. ca.example.com[,10.1.2.3,etc.]): ca.k0r0pt█int
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): :50443█
What would you like to name the CA's first provisioner?
✔ (e.g. [email protected]): [email protected]
Choose a password for your CA keys and first provisioner.
✔ [leave empty and we'll generate one]: █
And that was that
root@k0r0ptnas:/home/xtreme# step ca init
✔ Deployment Type: Standalone
What would you like to name your new PKI?
✔ (e.g. Smallstep): k0r0pt-pki█
What DNS names or IP addresses will clients use to reach your CA?
✔ (e.g. ca.example.com[,10.1.2.3,etc.]): ca.k0r0pt█int
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): :50443█
What would you like to name the CA's first provisioner?
✔ (e.g. [email protected]): [email protected]
Choose a password for your CA keys and first provisioner.
✔ Password: ...Redacted...█
Generating root certificate... done!
Generating intermediate certificate... done!
✔ Root certificate: /etc/step-ca/certs/root_ca.crt
✔ Root private key: /etc/step-ca/secrets/root_ca_key
✔ Root fingerprint: 5efc694ed5cfe783f8d04be84144477e14759151eeba51a07a7fd67a84b49e4b
✔ Intermediate certificate: /etc/step-ca/certs/intermediate_ca.crt
✔ Intermediate private key: /etc/step-ca/secrets/intermediate_ca_key
✔ Database folder: /etc/step-ca/db
✔ Default configuration: /etc/step-ca/config/defaults.json
✔ Certificate Authority configuration: /etc/step-ca/config/ca.json
Your PKI is ready to go. To generate certificates for individual services see 'step help ca'.
FEEDBACK 😍 🍻
The step utility is not instrumented for usage statistics. It does not phone
home. But your feedback is extremely valuable. Any information you can provide
regarding how you’re using `step` helps. Please send us a sentence or two,
good or bad at [email protected] or join GitHub Discussions
https://github.com/smallstep/certificates/discussions and our Discord
https://u.step.sm/discord.
Renaming the Acme (Optional)
I renamed the acme from acme to k0r0pt-acme by editing it in /etc/step-ca/config/ca.json, but you don’t need to. The difference, my Acme directory is at https://ca.k0r0pt.int:50443/acme/k0r0pt-acme/directory instead of https://ca.k0r0pt.int:50443/acme/acme/directory.
Install Step-CA
Next, we need to install Step-CA. This is what will run as a server, and will take care of certificate issuance. Again, your architecture and operating system might be different, so you can follow the Step-CA Installation guide.
wget https://dl.smallstep.com/certificates/docs-ca-install/latest/step-ca_arm64.deb
sudo dpkg -i step-ca_arm64.debRunning Step-CA as a daemon
I needed this thing to run as a daemon, so even if I restart (for kernel updates or because of unfortunate blackouts).
Caveat
There’s guide for configuring step-ca as a daemon in their documentation, but my steps will be a little different from that because we started with the path /etc/step-ca instead of the default ~/step-ca, which is what the documentation is written for.
If you did exactly what I did so far, you can skip this part
In case you didn’t set the environment variable STEPPATH like I did, you’ll need to be careful following their documentation, wherein the last part of Step # 2 is incomplete - it is only changing the path for db.dataSource in /etc/step-ca/config/ca.json, which is only 1 of a few more configs where you’d need to change the path. In that case, use vim /etc/step-ca/config/ca.json and change all paths that point to your home directory (~/.step-ca) to point to the new /etc/step-ca paths.
Creating a service user
First order of business was to create the service user, which would run the systemd service.
sudo useradd --user-group --system --home /etc/step-ca --shell /bin/false stepIf your CA will bind to port 443 (which won’t be the case if you used 50443 like me), the step-ca binary will need to be given low port-binding capabilities:
sudo setcap CAP_NET_BIND_SERVICE=+eip $(which step-ca)Set the step user as the owner of your CA configuration directory:
sudo chown -R step:step /etc/step-caCreating the systemd service descriptor
Create a systemd unit file
sudo vim /etc/systemd/system/step-ca.serviceAdd 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.targetIf you notice the ExecStart line in the Unit file, you’ll see that we need a password.txt file. This file needs to contain the password you set during step ca init in the Initialize the CA section above.
sudo echo '<passwordSetDuringCaInit>' > /etc/step-ca/password.txtEnable and start the Step CA Service
# Rescan the systemd unit files
sudo systemctl daemon-reload
# Check the current status of the step-ca service
sudo systemctl status step-ca
# Enable and start the `step-ca` process
sudo systemctl enable --now step-ca
# Follow the log messages for step-ca
sudo journalctl --follow --unit=step-caTrusting the Root CA certificate
Now that we have our CA up and running, I needed to trust the Root CA certificate. For that, first we need to get the Root CA certificate.
step ca root root.crtThis would save the root ca certificate to the current path in a file named root.crt, which I then copied into other systems where it needed to be trusted.
On Ubuntu
For my (to-be) k8s nodes (including the one where I just finished setting up the CA Server), which are all running Ubuntu server, all I had to do to trust the root ca certificate was copy it and call update-ca-certificates. I followed the Ubuntu Server documentation on installing root ca certificates to the trust store.
sudo apt-get install -y ca-certificates
sudo cp root.crt /usr/local/share/ca-certificates
sudo update-ca-certificatesOn Windows
I didn’t do this part, but you can use certmgr to trust the CA certificate.
On Firefox
I did do this part. You can trust the root CA certificate from about:preferences → View Certificates. This Stackoverflow answer helped.
Verify that the acme enabled CA server is working
To do this, all I had to do is open the browser, and open this Url (which might be a little different in your case): https://ca.k0r0pt.int:50443/acme/k0r0pt-acme/directory
That gave me this response:
{
"newNonce":"https://ca.k0r0pt.int:50443/acme/k0r0pt-acme/new-nonce",
"newAccount":"https://ca.k0r0pt.int:50443/acme/k0r0pt-acme/new-account",
"newOrder":"https://ca.k0r0pt.int:50443/acme/k0r0pt-acme/new-order",
"revokeCert":"https://ca.k0r0pt.int:50443/acme/k0r0pt-acme/revoke-cert",
"keyChange":"https://ca.k0r0pt.int:50443/acme/k0r0pt-acme/key-change"
}