Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a12d13-27f2-7163-b8c0-f682af02772c |
||
|---|---|---|
| .. | ||
| secrets.enc | ||
| creds-apply.sh | ||
| creds-bootstrap-agent.sh | ||
| creds-rotate.sh | ||
| creds-state.yaml | ||
| creds-status.sh | ||
| creds-verify.sh | ||
| decrypt-secrets.sh | ||
| emergency-bundle.sh | ||
| encrypt-secrets.sh | ||
| gen-secrets.sh | ||
| openbao-init-unseal.sh | ||
| pack-bundle.sh | ||
| README.md | ||
| sops-custody-unlock.sh | ||
Phase 0a — Pre-cluster Secret Bootstrap
This directory contains tooling for the KeePassXC bootstrap phase (T01 Phase 0a). No secrets are stored here — only scripts and documentation.
Why KeePassXC first?
The single-credential bootstrap principle (Decision D1): one master password unlocks the KeePassXC vault; all other credentials are generated inside it. KeePassXC is the pre-cluster source of truth. After the K3s cluster is running, secrets migrate into HashiCorp Vault (T01 Phase 0b) and KeePassXC becomes the break-glass / dev-local backup.
KeePassXC database structure
Create a new .kdbx database. Use a strong master password stored in your
personal password manager (Bitwarden, 1Password, etc.).
Recommended group structure:
net-kingdom/
├── privacyIDEA/
│ ├── pi-admin (username: pi-admin, password: PI_ADMIN_PASSWORD)
│ ├── database (username: privacyidea, password: PI_DB_PASSWORD)
│ ├── SECRET_KEY (password field only — PI_SECRET_KEY value)
│ ├── PI_PEPPER (password field only — PI_PEPPER value)
│ └── PI_ENCFILE (binary attachment: pi.enc — generate after deploy)
├── PostgreSQL/
│ ├── postgres root (username: postgres, password: PG_ROOT_PASSWORD)
│ ├── keycloak user (username: keycloak, password: PG_KEYCLOAK_PASSWORD)
│ └── privacyidea user (username: privacyidea — same password as PI_DB_PASSWORD)
├── Keycloak/
│ ├── admin (username: admin, password: KC_ADMIN_PASSWORD)
│ └── database (username: keycloak — same password as PG_KEYCLOAK_PASSWORD)
├── Break-glass/
│ ├── break-glass (username: break-glass, password: BREAKGLASS_PASSWORD)
│ └── recovery-otp (TOTP seed — enroll manually after Keycloak is up)
└── Vault/
└── (populated in Phase 0b after Vault is deployed)
Workflow
Step 1 — Generate secrets
chmod +x gen-secrets.sh
./gen-secrets.sh ./secrets
This writes .env files to ./secrets/ (gitignored). Inspect each file,
then copy each value into the appropriate KeePassXC entry.
Step 2 — Create the age encryption key (one-time)
age-keygen -o ~/net-kingdom-ops-bundle.key
The public key prints to stdout. The private key is in the .key file.
Store the .key file somewhere safe (NOT in this repo; NOT in the secrets/ dir).
Step 3 — Create the encrypted ops bundle
chmod +x pack-bundle.sh
./pack-bundle.sh ./secrets "age1..." ops-bundle.tar.age
Store ops-bundle.tar.age offsite (cloud storage, external drive, separate location).
Step 4 — Shred the generated files
find ./secrets -type f -exec shred -u {} \;
rm -rf ./secrets
Step 5 — PI_ENCFILE (after privacyIDEA container is running — T04)
# Generate the encryption key inside the running container:
kubectl exec -n mfa <pi-pod-name> -- pi-manage create_enckey
# Extract it:
kubectl cp -n mfa <pi-pod-name>:/etc/privacyidea/enckey ./pi.enc
# Store as a binary attachment in KeePassXC → net-kingdom/privacyIDEA/PI_ENCFILE
# Create the K8s Secret:
kubectl create secret generic privacyidea-enckey \
--from-file=PI_ENCFILE=./pi.enc \
--namespace mfa
# Shred the local copy:
shred -u ./pi.enc
Notes on secret reuse
Some secrets appear in multiple components — this is intentional to avoid drift. When adding to KeePassXC, note the cross-references rather than duplicating the value:
PI_DB_PASSWORD== PostgreSQLprivacyideauser passwordKC_DB_PASSWORD== PostgreSQLkeycloakuser password
Use KeePassXC references ({REF:P@T:UUID}) to avoid maintaining two copies.
Phase 0b — HashiCorp Vault (after T02, once K3s is running)
See ../vault/ (created in T01 Phase 0b) for:
- Vault Helm chart values
- ESO (External Secrets Operator) configuration
- Vault secret path layout
- Migration procedure: KeePassXC → Vault
Current bootstrap and recovery handoff
make creds-agent-init is now attended and public-recipient based. Register
keys/age.pub from independently established custody first. No root private key
is generated or discovered by bootstrap. --dry-run is a non-mutating plan.
make creds-emergency-reprint prints only the non-secret recovery guide.
For a resume that needs decryption, use the custody unlock helper below. After apply, the operator must store and independently verify the fresh encrypted ops bundle before acknowledging its handoff. Source tests use disposable keys and a simulated cluster; native acceptance remains NK-WP-0049.
Custody Age Key Unlock
For SOPS-backed drills and incident work, do not copy the custodian age private key into a permanent workstation path. Use the custody helper from the repo root:
make sops-custody-run COMMAND='make -C /home/worsch/inter-hub recovery-drill'
The helper prompts for the AGE-SECRET-KEY-1... line from the password safe or
offline custody packet, validates it against keys/age.pub, writes a temporary
SOPS_AGE_KEY_FILE, runs the command, and removes the temporary file on exit.
For multi-step recovery work:
make sops-custody-shell
Exit the shell when finished so the temporary key file is removed.