Back to skills
SKILL.md
Smallstep
DSecurityExpert agent for smallstep step-ca and step CLI. Covers lightweight internal PKI, OIDC-based certificate issuance, short-lived certificates, SSH certificates (user and host), Kubernetes integration, ACME support, and zero-trust patterns. WHEN: \"smallstep\", \"step-ca\", \"step CLI\", \"step certificate\", \"SSH certificate authority\", \"OIDC certificate\", \"short-lived cert\", \"step-ca ACME\", \"step-ca Kubernetes\".
- 4 stars
- 0 votes
- 0 copies
- 0 views
- Added September 24, 2026
Works with
Security analysis
46/100- Uses curl or wget to download content
- Accesses sensitive system or user directories
- Reads or references SSH private keys
- Exfiltrates credentials via HTTP — exact pattern from Snyk ToxicSkills study
- Sends environment variables or credentials to an external URL
npx -y skills add chrishuffman5/domain-expert --skill smallstep --agent claude-codeAre you the author of Smallstep?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/chrishuffman5-smallstep)---
name: smallstep
description: "Expert agent for smallstep step-ca and step CLI. Covers lightweight internal PKI, OIDC-based certificate issuance, short-lived certificates, SSH certificates (user and host), Kubernetes integration, ACME support, and zero-trust patterns. WHEN: \"smallstep\", \"step-ca\", \"step CLI\", \"step certificate\", \"SSH certificate authority\", \"OIDC certificate\", \"short-lived cert\", \"step-ca ACME\", \"step-ca Kubernetes\"."
license: MIT
---
# smallstep step-ca
This skill covers smallstep's step-ca (open-source CA) and step CLI. It has deep knowledge of internal PKI, OIDC-based issuance, short-lived certificates, SSH certificate authority patterns, and Kubernetes integration.
## How to Approach Tasks
1. **Classify the request**:
- **Setup** — step-ca initialization, first-time configuration
- **X.509 certificates** — TLS server, client (mTLS), leaf cert patterns
- **SSH certificates** — User certificates, host certificates, certificate authority setup
- **OIDC integration** — OIDC provisioner, identity-based issuance
- **Kubernetes** — Helm deployment, cert-manager integration
- **ACME** — step-ca as ACME server for internal services
- **Provisioner configuration** — JWK, OIDC, ACME, K8sSA, AWS, GCP, Azure
2. **Identify use case**: Internal TLS/mTLS, SSH certificate authority, workload identity, or developer certificate automation.
## Why smallstep?
smallstep excels at:
- **Zero-trust internal PKI**: Short-lived certificates (1h-24h) for services
- **SSH certificate authority**: Replace static SSH keys with short-lived certificates
- **OIDC-based issuance**: Use your existing SSO (Okta, Google, Azure AD) to issue certificates
- **Developer experience**: Simple CLI (`step ca certificate`) vs. complex openssl commands
- **Lightweight**: Single binary, runs anywhere, minimal dependencies
---
## Installation
```bash
# Install step CLI
# macOS
brew install step
# Linux
curl -L https://dl.smallstep.com/install/linux/amd64/step/latest/step.tar.gz | tar xz
sudo install step /usr/local/bin/
# Windows (scoop)
scoop bucket add smallstep https://github.com/smallstep/scoop-bucket.git
scoop install step
# Install step-ca
# macOS
brew install step-ca
# Linux
curl -L https://dl.smallstep.com/install/linux/amd64/step-ca/latest/step-ca.tar.gz | tar xz
sudo install step-ca /usr/local/bin/
```
---
## Initializing a CA
```bash
# Initialize a new CA (interactive)
step ca init
# Initialize non-interactively
step ca init \
--name "Example Internal CA" \
--dns ca.internal.example.com \
--address :8443 \
--provisioner admin@example.com \
--password-file /path/to/password.txt
# This creates ~/.step/config/ca.json and generates:
# Root CA: ~/.step/certs/root_ca.crt
# Intermediate CA: ~/.step/certs/intermediate_ca.crt
# Root CA key: ~/.step/secrets/root_ca_key (encrypted)
# Intermediate CA key: ~/.step/secrets/intermediate_ca_key (encrypted)
```
### Start the CA
```bash
# Start step-ca (uses ~/.step/config/ca.json)
step-ca ~/.step/config/ca.json
# Or with password file
step-ca --password-file password.txt ~/.step/config/ca.json
# As a systemd service
cat > /etc/systemd/system/step-ca.service << 'EOF'
[Unit]
Description=step-ca service
After=network.target
[Service]
User=step
ExecStart=/usr/local/bin/step-ca /etc/step-ca/config/ca.json \
--password-file /etc/step-ca/password.txt
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
```
---
## Provisioners
Provisioners define how clients authenticate to get certificates.
### JWK Provisioner (Default)
Simple key-based provisioner using JOSE/JWK. Good for testing and service-to-service.
```bash
# Issue a certificate using JWK provisioner
step ca certificate myservice.internal.example.com myservice.crt myservice.key \
--ca-url https://ca.internal.example.com:8443 \
--root ~/.step/certs/root_ca.crt \
--provisioner admin@example.com
# Prompts for provisioner password
# Short-lived certificate (1 hour)
step ca certificate myservice.internal.example.com myservice.crt myservice.key \
--not-after 1h \
--provisioner admin@example.com
```
### ACME Provisioner
Enables ACME clients (certbot, cert-manager, acme.sh) to use step-ca:
```bash
# Add ACME provisioner
step ca provisioner add acme --type ACME
# ACME directory URL
# https://ca.internal.example.com:8443/acme/acme/directory
# certbot with step-ca
step ca root root.crt # Download root certificate
certbot certonly \
--server https://ca.internal.example.com:8443/acme/acme/directory \
--standalone \
-d myservice.internal.example.com \
--email admin@example.com \
--agree-tos \
--no-verify-ssl # Only if TLS uses private root (configure properly in production)
# Better: Install step root CA in system trust store
sudo step certificate install root.crt
```
### OIDC Provisioner
Use your OIDC provider (Okta, Google, Azure AD) to issue certificates:
```bash
# Add OIDC provisioner (Google example)
step ca provisioner add google-workspace \
--type OIDC \
--client-id <google-oauth-client-id> \
--client-secret <client-secret> \
--configuration-endpoint https://accounts.google.com/.well-known/openid-configuration \
--domain example.com # Restrict to your Google Workspace domain
# Issue certificate via OIDC (user opens browser for Google login)
step ca certificate user@example.com user.crt user.key \
--provisioner google-workspace \
--san user@example.com
```
**How it works**: User authenticates with Google, gets an OIDC token, step CLI exchanges token for a certificate. Certificate subject and SANs derived from OIDC claims.
### Kubernetes Service Account Provisioner
For pods to get certificates without static credentials:
```bash
# Add K8sSA provisioner
step ca provisioner add k8s-sa \
--type K8sSA \
--pem-keys k8s-ca.crt # K8s API server CA cert
# Pod authentication: uses projected service account token
step ca certificate myapp myapp.crt myapp.key \
--provisioner k8s-sa \
--token $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
```
### Cloud Instance Provisioners
For cloud VM instances using platform identity:
```bash
# AWS EC2 instance identity document
step ca provisioner add aws-dev \
--type AWS \
--aws-account 123456789
# GCP instance
step ca provisioner add gcp-prod \
--type GCP \
--gcp-project my-project
# Azure VM
step ca provisioner add azure-prod \
--type Azure \
--azure-tenant <tenant-id> \
--azure-resource-group my-rg
```
---
## SSH Certificate Authority
step-ca can act as an SSH CA, issuing SSH certificates for users and hosts.
### SSH User Certificates
SSH user certificates authenticate users to hosts without distributing public keys:
```bash
# Initialize SSH CA (add to existing step-ca config)
step ca provisioner add ssh-users \
--type OIDC \
--ssh \
--client-id <okta-client-id> \
--configuration-endpoint https://dev-xxx.okta.com/.well-known/openid-configuration
# User requests SSH certificate (after Okta login)
step ssh certificate user@example.com \
--provisioner ssh-users
# Creates: ~/.step/ssh/id_ecdsa-cert.pub
# Certificate is valid for default duration (8-12 hours)
# Extensions in certificate define principals, force-command, etc.
```
**Host trust**: Users' SSH config or authorized_keys replacement:
```bash
# Add step-ca as SSH CA to known_hosts (for host cert verification)
step ssh config --host --roots >> /etc/ssh/ssh_known_hosts
```
### SSH Host Certificates
Hosts present certificates signed by step-ca instead of generating self-signed host keys:
```bash
# On the server: get a host certificate (via systemd or init)
step ssh certificate $(hostname) /etc/ssh/ssh_host_ecdsa_key-cert.pub \
--host \
--provisioner aws-prod \ # Uses EC2 instance identity for auth
--san $(hostname) \
--san $(hostname).internal.example.com
# sshd_config: point to host certificate
HostCertificate /etc/ssh/ssh_host_ecdsa_key-cert.pub
```
**User trust**: Clients trust the step-ca SSH CA instead of individual host keys:
```bash
# In ~/.ssh/known_hosts
@cert-authority *.internal.example.com <step-ca-ssh-host-public-key>
```
### SSH Certificate Rotation
Automate host certificate renewal with a systemd timer:
```bash
# /usr/local/bin/renew-ssh-cert.sh
#!/bin/bash
step ssh certificate $(hostname) /etc/ssh/ssh_host_ecdsa_key-cert.pub \
--host \
--provisioner aws-prod \
--san $(hostname) \
--force
systemctl reload sshd
```
```ini
# /etc/systemd/system/renew-ssh-cert.timer
[Timer]
OnActiveSec=8h
OnUnitActiveSec=8h
[Install]
WantedBy=timers.target
```
---
## Kubernetes Deployment
```bash
# Install step-ca via Helm
helm repo add smallstep https://smallstep.github.io/helm-charts
helm install step-certificates smallstep/step-certificates \
--namespace step-ca \
--create-namespace
# Or cert-manager integration:
# step-ca as ClusterIssuer backend via step-issuer
helm install step-issuer smallstep/step-issuer \
--namespace step-ca
```
```yaml
# StepClusterIssuer (cert-manager integration)
apiVersion: certmanager.step.sm/v1beta1
kind: StepClusterIssuer
metadata:
name: step-cluster-issuer
spec:
url: https://step-ca.step-ca.svc.cluster.local
caBundle: <base64-root-ca-cert>
provisioner:
name: k8s-sa
kid: <provisioner-key-id>
passwordRef:
name: step-ca-provisioner-password
key: password
---
# Certificate using step-ca
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: myservice-tls
spec:
secretName: myservice-tls
issuerRef:
group: certmanager.step.sm
kind: StepClusterIssuer
name: step-cluster-issuer
dnsNames:
- myservice.production.svc.cluster.local
duration: 24h
renewBefore: 4h
```
---
## Configuration — ca.json
```json
{
"root": "/etc/step-ca/certs/root_ca.crt",
"federatedRoots": [],
"crt": "/etc/step-ca/certs/intermediate_ca.crt",
"key": "/etc/step-ca/secrets/intermediate_ca_key",
"address": ":8443",
"dnsNames": ["ca.internal.example.com"],
"logger": {"format": "json"},
"db": {
"type": "badger",
"dataSource": "/etc/step-ca/db"
},
"tls": {
"minVersion": 1.2,
"maxVersion": 1.3
},
"authority": {
"claims": {
"minTLSCertDuration": "5m",
"maxTLSCertDuration": "24h",
"defaultTLSCertDuration": "24h",
"disableRenewalUntilPercentage": 66,
"minHostSSHCertDuration": "5m",
"maxHostSSHCertDuration": "1680h",
"defaultHostSSHCertDuration": "720h",
"minUserSSHCertDuration": "5m",
"maxUserSSHCertDuration": "24h",
"defaultUserSSHCertDuration": "16h"
},
"provisioners": [...],
"template": {
"subject": {
"country": "US",
"organization": "Example Corp",
"commonName": "{{ .Subject.CommonName }}"
},
"sans": {{ toJson .SANs }},
"keyUsage": ["digitalSignature"],
"extKeyUsage": ["serverAuth", "clientAuth"]
}
}
}
```
---
## Certificate Renewal
```bash
# Renew certificate (must be within renewal window — last 1/3 of lifetime)
step ca renew myservice.crt myservice.key \
--ca-url https://ca.internal.example.com:8443 \
--root root.crt
# Force renewal (before window)
step ca renew --force myservice.crt myservice.key
# Daemon mode: auto-renew continuously
step ca renew --daemon myservice.crt myservice.key \
--exec "systemctl reload myservice"
# Renews at 66% of lifetime, executes --exec command after successful renewal
```
The `--daemon` mode is ideal for long-running services that need certificate rotation without restarts.
---
## Practical Patterns
### Short-Lived mTLS for Services
```bash
# Service A requests cert on startup
step ca certificate service-a service-a.crt service-a.key \
--not-after 1h \
--san service-a.internal.example.com \
--provisioner k8s-sa \
--token $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
# Service B trusts service-a's certificate because it's signed by the same CA
# Both services' TLS library verifies the cert chain to the internal root CA
# Renewal daemon keeps cert fresh
step ca renew --daemon service-a.crt service-a.key \
--not-after 1h
```
### Replace SSH Keys with Certificates Org-Wide
1. Initialize step-ca with OIDC + SSH provisioner
2. Add step-ca SSH host public key to all hosts' `ssh_known_hosts`
3. Add step-ca SSH user CA public key to all hosts' `TrustedUserCAKeys`
4. Provision host certificates on all servers
5. Users authenticate with `step ssh login` (browser-based OIDC)
6. Step CLI places signed certificate in SSH agent
7. Access works — no static keys distributed
Attribution
Comments
Loading comments…