fix(ci): pin the cluster CA in the repo rather than in a secret
The CA fingerprint is not a secret. It is a public value that anyone can read straight off the certificate, so pinning it in a Gitea secret was the wrong shape: - A change to it would be invisible - no commit, no review. - Nobody with secret-write access could silently repoint the CI runner at a different CA. It now lives in `scripts/cluster-ca.sha256`, so changing it is a reviewable commit. The script reads it from there, and still accepts an override as its first argument. This also removes the need for a `KUBE_SERVER_CA_FINGERPRINT` secret, which the bot token cannot create anyway - the Actions secrets API is owner-only. The behaviour is unchanged: the CA is fetched over an unverified connection but only USED if it matches the pinned fingerprint, so an intercepted CA is refused. Re-tested against the live API server - the matching fingerprint refreshes the CA and kubectl then works; a wrong one is refused and the stale CA is left in place.
This commit is contained in:
@@ -12,13 +12,21 @@
|
||||
#
|
||||
# The refresh is PINNED: the server's CA is fetched over an unverified
|
||||
# connection, but it is only used if its SHA-256 fingerprint matches the value
|
||||
# in the KUBE_SERVER_CA_FINGERPRINT secret. An attacker able to intercept
|
||||
# the fetch cannot use it - their CA will not hash to the pinned fingerprint.
|
||||
# pinned in scripts/cluster-ca.sha256. An attacker able to intercept the
|
||||
# fetch cannot use it - their CA will not hash to the pinned fingerprint.
|
||||
# The runner has cluster-admin credentials, so trusting an unverified CA here
|
||||
# would be a privilege-escalation hole.
|
||||
#
|
||||
# The fingerprint is a PUBLIC value - it can be read straight off the
|
||||
# certificate - so it lives in the repo rather than in a secret. That way a
|
||||
# change to it is a reviewable commit, and nobody with secret-write access
|
||||
# can silently repoint the CI runner at a different CA.
|
||||
#
|
||||
# The expected fingerprint is read from scripts/cluster-ca.sha256 next to
|
||||
# this script. It can be overridden with the first argument.
|
||||
#
|
||||
# Usage:
|
||||
# kube-ca-refresh.sh <expected-fingerprint>
|
||||
# kube-ca-refresh.sh [expected-fingerprint]
|
||||
#
|
||||
# Exit codes:
|
||||
# 0 the cluster is reachable
|
||||
@@ -26,7 +34,12 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
EXPECTED="${1:-}"
|
||||
if [ -z "$EXPECTED" ] && [ -f "$SCRIPT_DIR/cluster-ca.sha256" ]; then
|
||||
# The file may carry comments; take the last non-comment line.
|
||||
EXPECTED=$(grep -vE '^[[:space:]]*(#|$)' "$SCRIPT_DIR/cluster-ca.sha256" | tail -1 | tr -d '[:space:]')
|
||||
fi
|
||||
|
||||
if kubectl version --request-timeout=15s >/dev/null 2>&1; then
|
||||
echo "cluster reachable"
|
||||
@@ -36,8 +49,8 @@ fi
|
||||
if [ -z "$EXPECTED" ]; then
|
||||
echo "ERROR: the kubeconfig cannot verify the API server, and no expected CA" >&2
|
||||
echo " fingerprint was supplied, so the CA cannot be refreshed safely." >&2
|
||||
echo " Set KUBE_SERVER_CA_FINGERPRINT to the SHA-256 fingerprint of the" >&2
|
||||
echo " cluster's CA:" >&2
|
||||
echo " and no pinned CA fingerprint could be found." >&2
|
||||
echo " Set one in scripts/cluster-ca.sha256:" >&2
|
||||
echo " openssl x509 -in server-ca.crt -noout -fingerprint -sha256" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -72,7 +85,7 @@ echo "actual CA fingerprint: $ACTUAL"
|
||||
if [ "$ACTUAL" != "$WANTED" ]; then
|
||||
echo "ERROR: the cluster CA does not match the pinned fingerprint." >&2
|
||||
echo " Refusing to use it. Either the CA has been rotated and the" >&2
|
||||
echo " fingerprint secret needs updating, or the connection was" >&2
|
||||
echo " pinned fingerprint needs updating, or the connection was" >&2
|
||||
echo " intercepted." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user