ci / audit (pull_request) Successful in 23s
Unambiguous if-form for the two-condition guard. CI (node:20-bookworm, Debian shellcheck 0.9) flags the A && B || C pattern where the koalaman stable docker path passed. No behavior change. CI evidence: https://git.knownelement.com/KNEL/ca/actions/runs/117821 Ticket: https://projects.knownelement.com/issues/784
86 lines
3.8 KiB
Bash
Executable File
86 lines
3.8 KiB
Bash
Executable File
#!/bin/bash
|
|
# init-stepca.sh — provision the PROD step-ca ACME endpoint on tsys-ca [#800]
|
|
#
|
|
# Run ON tsys-ca as root, from /opt/stepca-prod (this script + compose.yaml
|
|
# live there; data/ is created). Refuses to touch an existing data/ unless
|
|
# --reinit.
|
|
#
|
|
# What it does:
|
|
# 1. scaffolds a step home (docker one-shot init; throwaway self-signed CA)
|
|
# 2. generates an ECDSA P-256 ACME intermediate and signs it with the
|
|
# FLEET ROOT (/root/ca-root) — same constraints as ca-init.sh
|
|
# 3. swaps in the fleet chain (root cert + intermediate), adds an ACME
|
|
# provisioner, writes the runtime password
|
|
# The root KEY never leaves /root/ca-root. The ACME intermediate key and the
|
|
# runtime password live only in /opt/stepca-prod (0700, root-owned).
|
|
set -euo pipefail
|
|
|
|
BASE="/opt/stepca-prod"
|
|
DATA="$BASE/data"
|
|
ROOT_CRT="/root/ca-root/root.crt"
|
|
ROOT_KEY="/root/ca-root/root.key"
|
|
IMAGE="smallstep/step-ca@sha256:e9e8fa3262bf37b130962ffddbf6a64ac188f0bbb80959cf3ddc04c6bf294c3d"
|
|
TAILNET_IP="$(tailscale ip -4)"
|
|
|
|
if [ ! -f "$ROOT_CRT" ] || [ ! -f "$ROOT_KEY" ]; then
|
|
echo "FAIL: fleet root not found at $ROOT_CRT/$ROOT_KEY" >&2; exit 1
|
|
fi
|
|
[ -n "$TAILNET_IP" ] || { echo "FAIL: no tailscale IP" >&2; exit 1; }
|
|
if [ -d "$DATA" ] && [ "${1:-}" != "--reinit" ]; then
|
|
echo "FAIL: $DATA exists (pass --reinit to wipe and redo)" >&2; exit 1
|
|
fi
|
|
|
|
mkdir -p "$BASE" "$BASE/secrets"
|
|
chmod 700 "$BASE"; chmod 711 "$BASE/secrets"
|
|
rm -rf "$DATA"; mkdir -p "$DATA/certs" "$DATA/secrets" "$DATA/config" "$DATA/db"
|
|
# scaffold + runtime run as container uid 1000 (step) — tree must be writable by it
|
|
chown -R 1000:1000 "$DATA"
|
|
|
|
# runtime + provisioner password (generated once; container uid 1000 must read it)
|
|
if [ ! -s "$BASE/secrets/password" ]; then
|
|
openssl rand -base64 18 > "$BASE/secrets/password"
|
|
fi
|
|
chown 1000:1000 "$BASE/secrets/password"
|
|
chmod 600 "$BASE/secrets/password"
|
|
|
|
# 1. scaffold (throwaway self-signed CA; replaced below)
|
|
docker run --rm -v "$DATA:/home/step" -v "$BASE/secrets:/secrets:ro" \
|
|
--entrypoint /bin/sh "$IMAGE" -c '
|
|
step ca init --name "KNEL Fleet CA" \
|
|
--dns "localhost" --dns "tsys-ca.knel.net" --dns "'"$TAILNET_IP"'" \
|
|
--address ":9000" --provisioner "admin" \
|
|
--password-file /secrets/password --provisioner-password-file /secrets/password \
|
|
--deployment-type standalone >/dev/null'
|
|
echo "scaffold done"
|
|
|
|
# 2. ACME intermediate signed by the fleet root (ECDSA P-256, pathlen 0)
|
|
openssl ecparam -name prime256v1 -genkey -noout -out /tmp/acme-int.key
|
|
chmod 400 /tmp/acme-int.key
|
|
openssl req -new -key /tmp/acme-int.key -out /tmp/acme-int.csr -sha256 \
|
|
-subj "/C=US/ST=Texas/O=Known Element Enterprises/OU=TechOps/CN=KNEL Fleet ACME Intermediate"
|
|
openssl x509 -req -in /tmp/acme-int.csr -CA "$ROOT_CRT" -CAkey "$ROOT_KEY" \
|
|
-CAcreateserial -days 1825 -sha256 -out /tmp/acme-int.crt \
|
|
-extfile <(printf 'basicConstraints=critical,CA:TRUE,pathlen:0\nkeyUsage=critical,keyCertSign,cRLSign\nsubjectKeyIdentifier=hash\nauthorityKeyIdentifier=keyid:always')
|
|
openssl verify -CAfile "$ROOT_CRT" /tmp/acme-int.crt
|
|
rm -f /tmp/acme-int.csr
|
|
|
|
# 3. swap in the fleet chain (root KEY stays offline in /root/ca-root)
|
|
cp "$ROOT_CRT" "$DATA/certs/root_ca.crt"
|
|
cp /tmp/acme-int.crt "$DATA/certs/intermediate_ca.crt"
|
|
mv /tmp/acme-int.key "$DATA/secrets/intermediate_ca_key"
|
|
rm -f "$DATA/secrets/root_ca_key"
|
|
|
|
# 4. ACME provisioner (JWK password re-read from the same file)
|
|
docker run --rm -v "$DATA:/home/step" -v "$BASE/secrets:/secrets:ro" \
|
|
--entrypoint /bin/sh "$IMAGE" -c '
|
|
step ca provisioner add acme --type ACME --password-file /secrets/password >/dev/null'
|
|
|
|
# 5. runtime password where the container entrypoint expects it
|
|
cp "$BASE/secrets/password" "$DATA/secrets/password"
|
|
|
|
# ownership to uid 1000 (step)
|
|
chown -R 1000:1000 "$DATA"
|
|
chmod 400 "$DATA/secrets/intermediate_ca_key"
|
|
|
|
echo "init complete: $DATA ready — bring up with: cd $BASE && docker compose -f compose.yaml up -d"
|