generated from coulomb/repo-seed
- .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>
111 lines
3.9 KiB
Bash
Executable File
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
|