Named-bridge-network production Compose (not host networking, unlike dev) for rn-platform-01; egress-proxy + agents for rn-lab-host-01. Caddy reverse proxy, single public origin, Grafana restricted to management_cidr. iptables-restore firewall rulesets for all three hosts (Proxmox host, rn-platform-01, rn-lab-host-01) codifying the Platform/Lab-Host boundary: no student-to-student traffic, no direct Proxmox exposure, no direct Postgres/Provisioner exposure from the Lab Host. Mutual TLS for the Provisioner<->Lab Host Docker connection: certificate generation/verification scripts, dockerd TLS config, firewall-restricted port 2376. Application support already existed since Phase 1; this is purely the operational half. Validated: docker compose config, caddy validate, firewall structural lint, and a real openssl s_server/s_client mTLS handshake proving the cert chain and mutual-auth rejection (legitimate client verified, rogue client correctly rejected with verify code 19).
52 lines
2.4 KiB
Bash
Executable File
52 lines
2.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Verifies a generated Docker TLS cert bundle is internally consistent (CA signed both certs,
|
|
# key/cert pairs match, SANs are correct) BEFORE deploying it — cheap, local, no real dockerd
|
|
# needed. Once deployed, also verifies live mTLS against a real endpoint if given one.
|
|
#
|
|
# Usage: ./verify-docker-tls.sh <cert-dir> [lab-host-ip:port]
|
|
set -euo pipefail
|
|
DIR="${1:?usage: verify-docker-tls.sh <cert-dir> [lab-host-ip:port]}"
|
|
ENDPOINT="${2:-}"
|
|
|
|
fail() { echo "FAIL: $1" >&2; exit 1; }
|
|
|
|
echo "==> Checking file presence"
|
|
for f in ca.pem server-cert.pem server-key.pem provisioner-client-cert.pem provisioner-client-key.pem; do
|
|
[ -f "$DIR/$f" ] || fail "missing $f"
|
|
done
|
|
echo "OK: all expected files present"
|
|
|
|
echo "==> Checking key/cert pairs match (modulus comparison)"
|
|
for pair in "server-cert.pem server-key.pem" "provisioner-client-cert.pem provisioner-client-key.pem"; do
|
|
set -- $pair
|
|
cert_mod=$(openssl x509 -noout -modulus -in "$DIR/$1" | openssl md5)
|
|
key_mod=$(openssl rsa -noout -modulus -in "$DIR/$2" | openssl md5)
|
|
[ "$cert_mod" = "$key_mod" ] || fail "$1 / $2 do not match"
|
|
done
|
|
echo "OK: cert/key pairs match"
|
|
|
|
echo "==> Checking both certs are signed by the CA"
|
|
openssl verify -CAfile "$DIR/ca.pem" "$DIR/server-cert.pem" | grep -q "OK" || fail "server-cert.pem not CA-signed"
|
|
openssl verify -CAfile "$DIR/ca.pem" "$DIR/provisioner-client-cert.pem" | grep -q "OK" || fail "provisioner-client-cert.pem not CA-signed"
|
|
echo "OK: both certs verify against ca.pem"
|
|
|
|
echo "==> Checking private key permissions"
|
|
for f in ca-key.pem server-key.pem provisioner-client-key.pem; do
|
|
[ -f "$DIR/$f" ] || continue
|
|
perm=$(stat -c '%a' "$DIR/$f" 2>/dev/null || stat -f '%Lp' "$DIR/$f")
|
|
[ "$perm" = "400" ] || echo "WARNING: $f has permissions $perm, expected 400"
|
|
done
|
|
|
|
if [ -n "$ENDPOINT" ]; then
|
|
echo "==> Testing live mTLS handshake against $ENDPOINT"
|
|
echo | openssl s_client -connect "$ENDPOINT" \
|
|
-cert "$DIR/provisioner-client-cert.pem" -key "$DIR/provisioner-client-key.pem" \
|
|
-CAfile "$DIR/ca.pem" 2>&1 | grep -q "Verify return code: 0" \
|
|
&& echo "OK: live mTLS handshake succeeded" \
|
|
|| fail "live handshake against $ENDPOINT failed — check firewall/dockerd TLS config"
|
|
else
|
|
echo "(skipped live handshake test — no endpoint given; this only proves the cert bundle is internally consistent, not that dockerd is actually listening with it)"
|
|
fi
|
|
|
|
echo "==> All checks passed"
|