[#800] prod step-ca ACME endpoint LIVE on tsys-ca (fleet-root chain); http-01 validation fetch open
ci / audit (push) Failing after 1m1s

compose.yaml: host networking (bridge+embedded-DNS broke rooted-name
lookups), :8443 via ca.json address, image digest-pinned; init-stepca.sh:
scaffold + ECDSA-P256 intermediate signed by /root/ca-root fleet root
(verified), ACME provisioner, managed password. Directory serving
RFC8555 on tailnet. OPEN: step-ca validation GET fails instantly with
zero dial attempts (authz says could-not-connect) — bisected caps,
read_only, bridge vs host, container DNS. Debug continues next run
(GODEBUG=netdns=2 + strace plan).
https://projects.knownelement.com/issues/800
This commit is contained in:
2026-09-05 09:35:25 -05:00
parent c66430a397
commit 6e4f794b03
3 changed files with 110 additions and 1 deletions
+83
View File
@@ -0,0 +1,83 @@
#!/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)"
[ -f "$ROOT_CRT" ] && [ -f "$ROOT_KEY" ] || { echo "FAIL: fleet root not found at $ROOT_CRT/$ROOT_KEY" >&2; exit 1; }
[ -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"