mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
117 lines
3.3 KiB
Bash
Executable File
117 lines
3.3 KiB
Bash
Executable File
#!/bin/sh
|
|
set -eu
|
|
|
|
# Validates deployed TLS certificates.
|
|
#
|
|
# Local mode (argument): checks the cert material script.sh deployed into a
|
|
# compose instance directory — cert.pem/key.pem exist, parse, match each
|
|
# other, and are not about to expire.
|
|
#
|
|
# Remote mode (environment): checks live TLS endpoints, e.g. production
|
|
# servers, so a misconfigured host surfaces in CI instead of in an outage.
|
|
#
|
|
# Usage:
|
|
# verify.sh [<docker_compose_instance_directory>]
|
|
#
|
|
# Environment:
|
|
# CERT_TEST_REMOTE_ENDPOINTS Space-separated "host[:port]" list (port
|
|
# defaults to 443). Intended to be fed from a
|
|
# CI/GitHub variable.
|
|
# CERT_TEST_MIN_DAYS Minimum remaining validity in days (default 14).
|
|
#
|
|
# At least one mode must be selected. Exit code is non-zero on any failure.
|
|
|
|
min_days="${CERT_TEST_MIN_DAYS:-14}"
|
|
min_seconds=$((min_days * 86400))
|
|
failures=0
|
|
|
|
fail() {
|
|
echo "FAIL: $*" >&2
|
|
failures=$((failures + 1))
|
|
}
|
|
|
|
check_local() {
|
|
instance_dir="$1"
|
|
cert_dir="${instance_dir%/}/volumes/certs"
|
|
cert="${cert_dir}/cert.pem"
|
|
key="${cert_dir}/key.pem"
|
|
|
|
echo "Checking deployed certificate in: $cert_dir"
|
|
|
|
if [ ! -r "$cert" ] || [ ! -r "$key" ]; then
|
|
fail "cert.pem/key.pem missing or unreadable in $cert_dir"
|
|
return
|
|
fi
|
|
|
|
if ! openssl x509 -in "$cert" -noout 2>/dev/null; then
|
|
fail "$cert does not parse as an X.509 certificate"
|
|
return
|
|
fi
|
|
|
|
cert_pub="$(openssl x509 -in "$cert" -pubkey -noout 2>/dev/null)"
|
|
key_pub="$(openssl pkey -in "$key" -pubout 2>/dev/null)"
|
|
if [ -z "$key_pub" ] || [ "$cert_pub" != "$key_pub" ]; then
|
|
fail "key.pem does not match cert.pem in $cert_dir"
|
|
fi
|
|
|
|
if ! openssl x509 -in "$cert" -noout -checkend "$min_seconds" >/dev/null; then
|
|
fail "$cert expires within ${min_days} days"
|
|
else
|
|
echo "OK: $cert valid, key matches, >= ${min_days} days remaining"
|
|
fi
|
|
}
|
|
|
|
check_remote() {
|
|
endpoint="$1"
|
|
host="${endpoint%%:*}"
|
|
port="${endpoint#*:}"
|
|
[ "$port" = "$host" ] && port=443
|
|
|
|
echo "Checking remote endpoint: ${host}:${port}"
|
|
|
|
probe="$(printf '' | openssl s_client -connect "${host}:${port}" \
|
|
-servername "$host" -verify_hostname "$host" 2>/dev/null)" || {
|
|
fail "${host}:${port} TLS handshake failed"
|
|
return
|
|
}
|
|
|
|
if ! printf '%s\n' "$probe" | grep -q "Verify return code: 0 (ok)"; then
|
|
code="$(printf '%s\n' "$probe" | sed -n 's/^ *Verify return code: //p' | head -n1)"
|
|
fail "${host}:${port} certificate verification failed (${code:-no verify result})"
|
|
return
|
|
fi
|
|
|
|
if ! printf '%s\n' "$probe" | openssl x509 -noout -checkend "$min_seconds" >/dev/null 2>&1; then
|
|
fail "${host}:${port} certificate expires within ${min_days} days"
|
|
else
|
|
echo "OK: ${host}:${port} chain verified, >= ${min_days} days remaining"
|
|
fi
|
|
}
|
|
|
|
ran_any=0
|
|
|
|
if [ "$#" -ge 1 ]; then
|
|
ran_any=1
|
|
check_local "$1"
|
|
fi
|
|
|
|
if [ -n "${CERT_TEST_REMOTE_ENDPOINTS:-}" ]; then
|
|
ran_any=1
|
|
for endpoint in $CERT_TEST_REMOTE_ENDPOINTS; do
|
|
check_remote "$endpoint"
|
|
done
|
|
fi
|
|
|
|
if [ "$ran_any" = "0" ]; then
|
|
echo "Usage: $0 [<docker_compose_instance_directory>]" >&2
|
|
echo "Set CERT_TEST_REMOTE_ENDPOINTS=\"host[:port] ...\" for remote checks." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$failures" -gt 0 ]; then
|
|
echo "Certificate test finished with ${failures} failure(s)." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Certificate test passed."
|