Files
Joerg Heinemann cf391086e5 Step ca update (#14058)
* Patch for step-ca.sh

Patch for making $STD happy (/usr/bin/step is a symlink to /usr/bin/step-cli)

* Refactor step-ca installation script

Refactor step-ca installation script to improve configuration and template handling.

- Carve out step-ca-admin.sh
- Patch for making $STD happy (/usr/bin/step is a symlink to /usr/bin/step-cli)
- Define enhanced x509 CA and Certificate Templates
- Configure CA Provisioners, DB and CRL settings
- Generate Root CA Certificate and Key
   - Validity: 219168h (~25 Years)
   - maxPathLen: 1 (Root -> Intermediate -> Leaf) => Only one Intermediate CA allowed below Root CA
   - Active revocation on Intermediate CA and Leaf Certificates by the usage of build-in Certificate Revocation List (CRL)
- Generate Intermediate CA Certificate Bundle and Key
   - Validity: 175368h (~20 Years)
   - maxPathLen: 0 (Root -> Intermediate -> Leaf) => Intermediate CA is only allowed to issue Leaf Certificates
   - Active revocation on Leaf Certificates by the usage of build-in Certificate Revocation List (CRL)
   - Bundle: Certificate Chain (including Root CA Certificate)

* Update source URL in step-ca.sh script
2026-05-01 23:07:30 +02:00

342 lines
12 KiB
Bash

#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: Joerg Heinemann (heinemannj)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/smallstep/certificates
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
setup_deb822_repo \
"smallstep" \
"https://packages.smallstep.com/keys/apt/repo-signing-key.gpg" \
"https://packages.smallstep.com/stable/debian" \
"debs" \
"main"
msg_info "Installing step-ca and step-cli"
$STD apt install -y step-ca step-cli
STEPPATH="/etc/step-ca"
STEPHOME="/etc/step"
export STEPPATH=$STEPPATH
echo "export STEPPATH=${STEPPATH}" >> /etc/profile
export STEPHOME=$STEPHOME
echo "export STEPHOME=${STEPHOME}" >> /etc/profile
mkdir -p "$STEPHOME"
# Patch for making $STD happy (/usr/bin/step is a symlink to /usr/bin/step-cli)
STEPBIN="$(which step)"
rm -f "$STEPBIN"
cp -f "$(which step-cli)" "$STEPBIN"
# Low port-binding capabilities (ports < 1024)
# - Default step-ca listener port: 443
setcap CAP_NET_BIND_SERVICE=+eip "$(which step-ca)"
# Service User used by systemd step-ca.service
$STD useradd --user-group --system --home "$(step path)" --shell /bin/false step
msg_ok "Installed step-ca and step-cli"
DomainName="$(hostname -d)"
PKIName="$(prompt_input "Enter PKIName" "MyHomePKI" 30)"
PKICountry="$(prompt_input "Enter PKICountry" "DE" 30)"
PKIOrganizationalUnit="$(prompt_input "Enter PKIOrganizationalUnit" "MyHomeLab" 30)"
PKIProvisioner="$(prompt_input "Enter PKIProvisioner" "pki@$DomainName" 30)"
AcmeProvisioner="$(prompt_input "Enter AcmeProvisioner" "acme@$DomainName" 30)"
X509MinDur="$(prompt_input "Enter X509MinDur" "48h" 30)"
X509MaxDur="$(prompt_input "Enter X509MaxDur" "87600h" 30)"
X509DefaultDur="$(prompt_input "Enter X509DefaultDur" "168h" 30)"
msg_info "Initializing step-ca"
# Initialize step-ca
DeploymentType="standalone"
FQDN="$(hostname -f)"
IP="${LOCAL_IP}"
LISTENER=":443"
LISTENER_INSECURE=":80"
# Set different signing CA and Provisioner Passwords
EncryptionPwdDir="$(step path)/encryption"
PwdFile="$EncryptionPwdDir/ca.pwd"
ProvisionerPwdFile="$EncryptionPwdDir/provisioner.pwd"
mkdir -p "$EncryptionPwdDir"
gpg -q --gen-random --armor 2 32 >"$PwdFile"
gpg -q --gen-random --armor 2 32 >"$ProvisionerPwdFile"
# Used by systemd step-ca.service
ln -s "$PwdFile" "$(step path)/password.txt"
# Usage of:
# - SSH feature of step-ca
# - BadgerDB (badgerv2) => Default DB backend of step-ca
# - badgerFileLoadingMode: FileIO (instead of MemoryMap) for LXC with low RAM
$STD step ca init \
--deployment-type="$DeploymentType" \
--ssh \
--name="$PKIName" \
--dns="$FQDN" \
--dns="$IP" \
--address="$LISTENER" \
--provisioner="$PKIProvisioner" \
--password-file="$PwdFile" \
--provisioner-password-file="$ProvisionerPwdFile"
# Define enhanced x509 CA and Certificate Templates
mkdir -p "$(step path)/templates/ca"
mkdir -p "$(step path)/templates/x509"
CARootTemplate="$(step path)/templates/ca/root.tpl"
CAIntermediateTemplate="$(step path)/templates/ca/intermediate.tpl"
X509LeafTemplate="$(step path)/templates/x509/leaf.tpl"
X509LeafTemplateData="$(step path)/templates/x509/leaf_data.tpl"
cat <<'EOF' >"$CARootTemplate"
{
"subject": {
"country": {{ toJson .Insecure.User.country }},
"organization": {{ toJson .Insecure.User.organization }},
"organizationalUnit": {{ toJson .Insecure.User.organizationalUnit }},
"commonName": {{ toJson .Subject.CommonName }}
},
"issuer": {{ toJson .Subject }},
"keyUsage": ["certSign", "crlSign"],
"basicConstraints": {
"isCA": true,
"maxPathLen": 1
},
"issuingCertificateURL": [{{ toJson .Insecure.User.issuingCertificateURL }}],
"crlDistributionPoints": [{{ toJson .Insecure.User.crlDistributionPoints }}]
}
EOF
cat <<'EOF' >"$CAIntermediateTemplate"
{
"subject": {
"country": {{ toJson .Insecure.User.country }},
"organization": {{ toJson .Insecure.User.organization }},
"organizationalUnit": {{ toJson .Insecure.User.organizationalUnit }},
"commonName": {{ toJson .Subject.CommonName }}
},
"keyUsage": ["certSign", "crlSign"],
"basicConstraints": {
"isCA": true,
"maxPathLen": 0
},
"issuingCertificateURL": [{{ toJson .Insecure.User.issuingCertificateURL }}],
"crlDistributionPoints": [{{ toJson .Insecure.User.crlDistributionPoints }}]
}
EOF
cat <<'EOF' >"$X509LeafTemplate"
{
"subject": {
{{- if .Insecure.User.Country }}
"country": {{ toJson .Insecure.User.country }},
{{- else }}
"country": {{ toJson .country }},
{{- end }}
{{- if .Insecure.User.organization }}
"organization": {{ toJson .Insecure.User.organization }},
{{- else }}
"organization": {{ toJson .organization }},
{{- end }}
{{- if .Insecure.User.organizationalUnit }}
"organizationalUnit": {{ toJson .Insecure.User.organizationalUnit }},
{{- else }}
"organizationalUnit": {{ toJson .organizationalUnit }},
{{- end }}
"commonName": {{ toJson .Subject.CommonName }}
},
"sans": {{ toJson .SANs }},
{{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }}
"keyUsage": ["keyEncipherment", "digitalSignature"],
{{- else }}
"keyUsage": ["digitalSignature"],
{{- end }}
"extKeyUsage": ["serverAuth", "clientAuth"],
{{- if .Insecure.User.issuingCertificateURL }}
"issuingCertificateURL": [{{ toJson .Insecure.User.issuingCertificateURL }}],
{{- else }}
"issuingCertificateURL": [{{ toJson .issuingCertificateURL }}],
{{- end }}
{{- if .Insecure.User.crlDistributionPoints }}
"crlDistributionPoints": [{{ toJson .Insecure.User.crlDistributionPoints }}]
{{- else }}
"crlDistributionPoints": [{{ toJson .crlDistributionPoints }}]
{{- end }}
}
EOF
cat <<EOF >"$X509LeafTemplateData"
{
"country": "${PKICountry}",
"organization": "${PKIName}",
"organizationalUnit": "${PKIOrganizationalUnit}",
"issuingCertificateURL": ["https://${FQDN}${LISTENER}/intermediates.pem"],
"crlDistributionPoints": ["https://${FQDN}${LISTENER}/crl"]
}
EOF
# Configure CA Provisioners, DB and CRL settings
$STD step ca provisioner add "$AcmeProvisioner" \
--type ACME \
--admin-name "$AcmeProvisioner"
$STD step ca provisioner update "$PKIProvisioner" \
--x509-min-dur="$X509MinDur" \
--x509-max-dur="$X509MaxDur" \
--x509-default-dur="$X509DefaultDur" \
--x509-template="$X509LeafTemplate" \
--x509-template-data="$X509LeafTemplateData" \
--allow-renewal-after-expiry
$STD step ca provisioner update "$AcmeProvisioner" \
--x509-min-dur="$X509MinDur" \
--x509-max-dur="$X509MaxDur" \
--x509-default-dur="$X509DefaultDur" \
--x509-template="$X509LeafTemplate" \
--x509-template-data="$X509LeafTemplateData" \
--allow-renewal-after-expiry
CAConfig="$(step path)/config/ca.json"
jq --arg a "${PKICountry}" '.country = $a' "${CAConfig}" > "${CAConfig}_tmp" && mv "${CAConfig}_tmp" "${CAConfig}"
jq --arg a "${PKIName}" '.organization = $a' "${CAConfig}" > "${CAConfig}_tmp" && mv "${CAConfig}_tmp" "${CAConfig}"
jq --arg a "${PKIOrganizationalUnit}" '.organizationalUnit = $a' "${CAConfig}" > "${CAConfig}_tmp" && mv "${CAConfig}_tmp" "${CAConfig}"
jq --arg a "${PKIName} Online CA" '.commonName = $a' "${CAConfig}" > "${CAConfig}_tmp" && mv "${CAConfig}_tmp" "${CAConfig}"
jq '.db.badgerFileLoadingMode = "FileIO"' "${CAConfig}" > "${CAConfig}_tmp" && mv "${CAConfig}_tmp" "${CAConfig}"
jq '.crl.enabled = true' "${CAConfig}" > "${CAConfig}_tmp" && mv "${CAConfig}_tmp" "${CAConfig}"
jq '.crl.generateOnRevoke = true' "${CAConfig}" > "${CAConfig}_tmp" && mv "${CAConfig}_tmp" "${CAConfig}"
jq '.crl.cacheDuration = "24h0m0s"' "${CAConfig}" > "${CAConfig}_tmp" && mv "${CAConfig}_tmp" "${CAConfig}"
jq '.crl.renewPeriod = "16h0m0s"' "${CAConfig}" > "${CAConfig}_tmp" && mv "${CAConfig}_tmp" "${CAConfig}"
jq --arg a "https://${FQDN}${LISTENER}/crl" '.crl.idpURL = $a' "${CAConfig}" > "${CAConfig}_tmp" && mv "${CAConfig}_tmp" "${CAConfig}"
jq --arg a "$LISTENER_INSECURE" '.insecureAddress = $a' "${CAConfig}" > "${CAConfig}_tmp" && mv "${CAConfig}_tmp" "${CAConfig}"
# Generate Root CA Certificate and Key
# - Validity: 219168h (~25 Years)
# - maxPathLen: 1 (Root -> Intermediate -> Leaf) => Only one Intermediate CA allowed below Root CA
# - Active revocation on Intermediate CA and Leaf Certificates by the usage of build-in Certificate Revocation List (CRL)
FLAGS=(--force
--template="${CARootTemplate}"
--not-after="219168h"
--password-file="${PwdFile}"
--set country="${PKICountry}"
--set organization="${PKIName}"
--set organizationalUnit="${PKIOrganizationalUnit}"
--set issuingCertificateURL="https://${FQDN}${LISTENER}/roots.pem"
--set crlDistributionPoints="https://${FQDN}${LISTENER}/crl")
$STD step certificate create "${PKIName} Root CA" \
"$(step path)/certs/root_ca.crt" \
"$(step path)/secrets/root_ca_key" \
"${FLAGS[@]}"
# Generate Intermediate CA Certificate Bundle and Key
# - Validity: 175368h (~20 Years)
# - maxPathLen: 0 (Root -> Intermediate -> Leaf) => Intermediate CA is only allowed to issue Leaf Certificates
# - Active revocation on Leaf Certificates by the usage of build-in Certificate Revocation List (CRL)
# - Bundle: Certificate Chain (including Root CA Certificate)
FLAGS=(--force
--template="${CAIntermediateTemplate}"
--ca="$(step path)/certs/root_ca.crt"
--ca-key="$(step path)/secrets/root_ca_key"
--not-after="175368h"
--ca-password-file="${PwdFile}"
--password-file="${PwdFile}"
--bundle
--set country="${PKICountry}"
--set organization="${PKIName}"
--set organizationalUnit="${PKIOrganizationalUnit}"
--set issuingCertificateURL="https://${FQDN}${LISTENER}/roots.pem"
--set crlDistributionPoints="https://${FQDN}${LISTENER}/crl")
$STD step certificate create "${PKIName} Intermediate CA" \
"$(step path)/certs/intermediate_ca.crt" \
"$(step path)/secrets/intermediate_ca_key" \
"${FLAGS[@]}"
# Install Root CA Certificate to System Trust Store
$STD step certificate install --all "$(step path)/certs/root_ca.crt"
$STD update-ca-certificates
chown -R step:step "$(step path)"
chmod -R 700 "$(step path)"
msg_ok "Initialized step-ca"
msg_info "Start step-ca as a Daemon"
# https://smallstep.com/docs/step-ca/certificate-authority-server-production/#running-step-ca-as-a-daemon
cat <<'EOF' >/etc/systemd/system/step-ca.service
[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 -USR1 $MAINPID
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
StartLimitAction=reboot
; Process capabilities & privileges
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
SecureBits=keep-caps
NoNewPrivileges=yes
; Sandboxing
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@resources @privileged
RestrictNamespaces=yes
LockPersonality=yes
MemoryDenyWriteExecute=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
PrivateMounts=yes
ProtectControlGroups=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/etc/step-ca/db
; Read only paths
ReadOnlyPaths=/etc/step-ca
[Install]
WantedBy=multi-user.target
EOF
$STD systemctl enable -q --now step-ca
msg_ok "Started step-ca as a Daemon"
fetch_and_deploy_gh_release "step-badger" "lukasz-lobocki/step-badger" "prebuild" "latest" "/opt/step-badger" "step-badger_Linux_x86_64.tar.gz"
ln -s /opt/step-badger/step-badger /usr/local/bin/step-badger
motd_ssh
customize
cleanup_lxc