SHELL := /usr/bin/env bash .DEFAULT_GOAL := help # Operator age public key — used as bundle encryption recipient OPERATOR_AGE_PUBKEY := $(shell cat keys/age.pub 2>/dev/null | tr -d '[:space:]') # ── Help ────────────────────────────────────────────────────────────────────── help: ## Show this help @echo "net-kingdom — available targets" @echo "" @grep -E '^[a-zA-Z0-9_-]+:.*?## ' $(MAKEFILE_LIST) \ | sort \ | awk 'BEGIN {FS = ":.*?## "}; {printf " %-28s %s\n", $$1, $$2}' # ── Git hooks ───────────────────────────────────────────────────────────────── hooks: ## Configure git to use repo-local hooks (.githooks) @mkdir -p .githooks git config core.hooksPath .githooks @test -f .githooks/pre-commit \ || (echo "ERROR: .githooks/pre-commit not found"; exit 1) chmod +x .githooks/pre-commit @echo "✔ git hooks enabled (.githooks/pre-commit is active)" hooks-test: ## Test that the pre-commit hook blocks plaintext secrets @mkdir -p sso-mfa/bootstrap/secrets/_hooktest @echo 'PI_SECRET_KEY=deadbeef' > sso-mfa/bootstrap/secrets/_hooktest/test.env @git add sso-mfa/bootstrap/secrets/_hooktest/test.env 2>/dev/null || true @if git commit -m "TEST: hook must block this" 2>/dev/null; then \ echo "FAIL: hook did NOT block plaintext commit"; \ git reset --soft HEAD~1 2>/dev/null || true; \ else \ echo "✔ Hook blocked plaintext as expected"; \ fi @git restore --staged sso-mfa/bootstrap/secrets/_hooktest/test.env 2>/dev/null || true @rm -rf sso-mfa/bootstrap/secrets/_hooktest # ── SOPS / age ──────────────────────────────────────────────────────────────── sops-setup: ## Copy age key to SOPS default path (~/.config/sops/age/keys.txt) @mkdir -p ~/.config/sops/age @if [[ -f ~/.config/age/key.txt ]]; then \ cp -n ~/.config/age/key.txt ~/.config/sops/age/keys.txt || true; \ chmod 600 ~/.config/sops/age/keys.txt; \ echo "✔ SOPS key ready at ~/.config/sops/age/keys.txt"; \ elif [[ -f ~/.config/sops/age/keys.txt ]]; then \ echo "✔ SOPS key already present at ~/.config/sops/age/keys.txt"; \ else \ echo "ERROR: age key not found at ~/.config/age/key.txt"; \ echo " Generate one with: age-keygen -o ~/.config/age/key.txt"; \ exit 1; \ fi sops-edit: ## Edit an encrypted file with SOPS: make sops-edit FILE=secrets/foo.yaml @[[ -n "$(FILE)" ]] || (echo "Usage: make sops-edit FILE=secrets/path/to/file.yaml"; exit 1) sops $(FILE) sops-encrypt: ## Encrypt a file in place: make sops-encrypt FILE=secrets/foo.yaml @[[ -n "$(FILE)" ]] || (echo "Usage: make sops-encrypt FILE=secrets/path/to/file.yaml"; exit 1) sops --encrypt --in-place $(FILE) @echo "✔ Encrypted $(FILE)" sops-decrypt: ## Decrypt a file to stdout (never writes plaintext to disk): make sops-decrypt FILE=secrets/foo.yaml @[[ -n "$(FILE)" ]] || (echo "Usage: make sops-decrypt FILE=secrets/path/to/file.yaml"; exit 1) sops -d $(FILE) sops-rotate: ## Rotate SOPS recipients after updating .sops.yaml: make sops-rotate FILE=secrets/foo.yaml @[[ -n "$(FILE)" ]] || (echo "Usage: make sops-rotate FILE=secrets/path/to/file.yaml"; exit 1) sops --rotate --in-place $(FILE) @echo "✔ Recipients rotated for $(FILE)" check-secrets: ## Fail if any file under secrets/ is not SOPS-encrypted @echo "Checking for unencrypted files under secrets/..." @bad=0; \ while IFS= read -r f; do \ [[ -z "$$f" ]] && continue; \ if grep -qE '^[[:space:]]*sops:[[:space:]]*$$|"sops"[[:space:]]*:' "$$f" 2>/dev/null; then \ continue; \ fi; \ case "$$f" in *.age|*.gpg) continue ;; esac; \ echo " UNENCRYPTED: $$f"; bad=1; \ done < <(git ls-files --others --cached 'secrets' 2>/dev/null | grep -v '/$'); \ if [[ "$$bad" -ne 0 ]]; then \ echo ""; \ echo "ERROR: Unencrypted secret(s) detected. Encrypt with: sops --encrypt --in-place "; \ exit 1; \ fi; \ echo "✔ All secrets/ files appear SOPS-encrypted" # ── Credential lifecycle ────────────────────────────────────────────────────── creds-init: sops-setup hooks ## One-time setup: verify prerequisites, configure SOPS and git hooks @echo "=== creds-init: checking prerequisites ===" @command -v age >/dev/null 2>&1 || (echo "ERROR: age not installed (apt install age)"; exit 1) @command -v sops >/dev/null 2>&1 || (echo "ERROR: sops not installed (see https://github.com/getsops/sops/releases)"; exit 1) @command -v kubectl >/dev/null 2>&1 \ && echo "✔ kubectl found" \ || echo "WARN: kubectl not found (required for creds-apply/verify)" @test -f ~/.config/sops/age/keys.txt \ || (echo "ERROR: age key not found — run 'make sops-setup' after placing your key at ~/.config/age/key.txt"; exit 1) @echo "" @echo "✔ creds-init complete. Next: make creds-generate" creds-generate: ## Generate all service secrets and print KeePassXC entry guide @command -v openssl >/dev/null 2>&1 || (echo "ERROR: openssl not found"; exit 1) cd sso-mfa/bootstrap && bash gen-secrets.sh @sed -i "s|^generated_at: .*|generated_at: $$(date -Iseconds)|" sso-mfa/bootstrap/creds-state.yaml @echo "" @echo "State: generated_at updated in sso-mfa/bootstrap/creds-state.yaml" @echo "Next : enter values into KeePassXC, then set keepass_confirmed: true" @echo " Run 'make creds-bundle' to create an encrypted offsite backup." creds-bundle: ## Age-encrypt the ops bundle for offsite storage @[[ -n "$(OPERATOR_AGE_PUBKEY)" ]] \ || (echo "ERROR: keys/age.pub not found — cannot determine encryption recipient"; exit 1) @[[ -d sso-mfa/bootstrap/secrets ]] \ || (echo "ERROR: sso-mfa/bootstrap/secrets/ not found — run 'make creds-generate' first"; exit 1) cd sso-mfa/bootstrap && bash pack-bundle.sh secrets "$(OPERATOR_AGE_PUBKEY)" @sed -i "s|^bundle_at: .*|bundle_at: $$(date -Iseconds)|" sso-mfa/bootstrap/creds-state.yaml @echo "" @echo "State: bundle_at updated in sso-mfa/bootstrap/creds-state.yaml" @echo "Store the .tar.age bundle offsite (external drive, encrypted cloud, second location)." creds-apply: ## Apply all create-secrets.sh scripts to the cluster in dependency order bash sso-mfa/bootstrap/creds-apply.sh creds-verify: ## Check all expected K8s secrets exist and update creds-state.yaml bash sso-mfa/bootstrap/creds-verify.sh creds-status: ## Print human-readable credential state from creds-state.yaml bash sso-mfa/bootstrap/creds-status.sh creds-rotate: ## Guided rotation for one secret: make creds-rotate SECRET= @[[ -n "$(SECRET)" ]] \ || (echo "Usage: make creds-rotate SECRET="; \ echo ""; \ echo "Known secrets:"; \ echo " PI_SECRET_KEY PI_PEPPER PI_DB_PASSWORD LLDAP_JWT_SECRET"; \ echo " LLDAP_LDAP_USER_PASS AUTHELIA_SESSION_SECRET"; \ echo " AUTHELIA_KEYCAPE_CLIENT_SECRET KEYCAPE_RSA_KEY"; \ echo " BREAKGLASS_PASSWORD"; \ exit 1) SECRET=$(SECRET) bash sso-mfa/bootstrap/creds-rotate.sh ## ── Agent-driven credential lifecycle (NK-WP-0005) ────────────────────────── creds-agent-init: ## Fully automated credential bootstrap — generates, encrypts, injects, delivers emergency bundle @bash sso-mfa/bootstrap/creds-bootstrap-agent.sh creds-agent-status: ## Show current v2 bootstrap state (agent mode) @bash sso-mfa/bootstrap/creds-status.sh --v2 creds-emergency-reprint: ## Re-deliver emergency bundle (if lost/stolen — reprints, rotates nothing) @bash sso-mfa/bootstrap/emergency-bundle.sh --reprint .PHONY: help hooks hooks-test sops-setup sops-edit sops-encrypt sops-decrypt sops-rotate \ check-secrets creds-init creds-generate creds-bundle creds-apply creds-verify \ creds-status creds-rotate \ creds-agent-init creds-agent-status creds-emergency-reprint