Files
net-kingdom/.githooks/pre-commit
Bernd Worsch c10d7d2f8a feat(creds): implement NK-WP-0004 Credential Management Foundation
- .sops.yaml + keys/age.pub: SOPS age encryption for all secrets/ paths
- .gitignore: broad secrets/ catch-all (any depth)
- .githooks/pre-commit: blocks unencrypted secrets/, *.env outside bootstrap/,
  and known plaintext patterns (PI_SECRET_KEY=, LLDAP_JWT_SECRET=, etc.)
- Makefile: full credential lifecycle (creds-init/generate/bundle/apply/verify/
  status/rotate) + SOPS helpers (sops-setup/edit/encrypt/decrypt/rotate/check-secrets)
  + hooks/hooks-test
- creds-apply.sh: runs create-secrets.sh in dependency order (postgresql → lldap →
  authelia → privacyidea), skips keycape with printed instructions, updates state
- creds-verify.sh: checks all K8s secrets exist, updates creds-state.yaml
- creds-status.sh: human-readable state table from creds-state.yaml
- creds-rotate.sh: guided rotation for all 9 secret types with impact descriptions
  and atomic multi-component update sequences
- creds-state.yaml: committable state file tracking generation, bundle, KeePassXC
  confirmation, per-component apply status, enckey and pi-admin bootstrap flags

NK-WP-0003-T01 unblocked. /creds-bootstrap skill registered separately.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 23:39:35 +00:00

111 lines
3.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# net-kingdom pre-commit hook — blocks plaintext secrets from entering git.
#
# Enabled by: make hooks (sets core.hooksPath = .githooks)
# Tested by: make hooks-test
#
# BLOCKS:
# 1. Any file under secrets/ without a SOPS marker
# 2. Any *.env file outside sso-mfa/bootstrap/ (env files contain raw secrets)
# 3. Any staged file containing known plaintext secret patterns
#
# WARNS:
# - Bundle archives (*-bundle*.tar.age) — these belong offsite, not in git
set -euo pipefail
fail=0
# ── Helper ────────────────────────────────────────────────────────────────────
staged_files() {
git diff --cached --name-only --diff-filter=ACMR
}
file_content() {
git show ":$1" 2>/dev/null || true
}
# ── Check 1: secrets/ files must be SOPS-encrypted ───────────────────────────
secrets_files=$(staged_files | grep -E '(^|/)secrets/' || true)
if [[ -n "$secrets_files" ]]; then
while IFS= read -r f; do
[[ -z "$f" ]] && continue
content="$(file_content "$f")"
[[ -z "$content" ]] && continue
# SOPS-encrypted files contain a top-level 'sops:' or '"sops":' key
if echo "$content" | grep -qE '^[[:space:]]*sops:[[:space:]]*$|"sops"[[:space:]]*:'; then
continue
fi
# Also allow binary age-encrypted or gpg-encrypted blobs
case "$f" in
*.age|*.gpg) continue ;;
esac
echo " [secrets/] not SOPS-encrypted: $f"
fail=1
done <<< "$secrets_files"
fi
# ── Check 2: *.env files outside sso-mfa/bootstrap/ ─────────────────────────
env_files=$(staged_files | grep -E '\.env$' | grep -v '^sso-mfa/bootstrap/' || true)
if [[ -n "$env_files" ]]; then
while IFS= read -r f; do
[[ -z "$f" ]] && continue
echo " [*.env outside bootstrap/] $f"
fail=1
done <<< "$env_files"
fi
# ── Check 3: known plaintext secret patterns in any staged file ───────────────
# These patterns appear only in generated secrets files — never in code.
FORBIDDEN_PATTERNS=(
'PI_SECRET_KEY='
'PI_PEPPER='
'LLDAP_JWT_SECRET='
'AUTHELIA_JWT_SECRET='
'AUTHELIA_SESSION_SECRET='
'AUTHELIA_STORAGE_ENCRYPTION_KEY='
'AUTHELIA_OIDC_HMAC_SECRET='
'AUTHELIA_KEYCAPE_CLIENT_SECRET='
'BREAKGLASS_PASSWORD='
'PG_ROOT_PASSWORD='
)
while IFS= read -r f; do
[[ -z "$f" ]] && continue
content="$(file_content "$f")"
[[ -z "$content" ]] && continue
for pat in "${FORBIDDEN_PATTERNS[@]}"; do
if echo "$content" | grep -qF "$pat"; then
echo " [secret pattern '$pat'] $f"
fail=1
fi
done
done <<< "$(staged_files)"
# ── Warning: bundle archives in git ──────────────────────────────────────────
bundle_files=$(staged_files | grep -E '\-bundle.*\.tar\.age$' || true)
if [[ -n "$bundle_files" ]]; then
echo ""
echo "WARN: ops bundle archives detected — these belong offsite, not in git:"
while IFS= read -r f; do
echo " $f"
done <<< "$bundle_files"
echo ""
fi
# ── Result ────────────────────────────────────────────────────────────────────
if [[ "$fail" -ne 0 ]]; then
echo ""
echo "Commit blocked: plaintext secret(s) detected (see above)."
echo ""
echo "To encrypt a file with SOPS: sops --encrypt --in-place <file>"
echo "To edit an encrypted file: sops <file>"
echo "To bypass (never in prod): git commit --no-verify"
exit 1
fi
exit 0