Files
certctl/scripts/ci-guards/B6-no-private-keys-in-tree.sh
T
shankar0123 476022ca59 docs(b6): secret-custody reference + config-encryption upgrade runbook + private-key CI guard
Closes acquisition-diligence Bundle 6 findings on secret custody, config
encryption, and local artifact hygiene. Source IDs: S6, R4, SEC-M2,
RT-M1, RT-M2, RT-L1.

Surgical closures (artifact-only audit-framed memos stay out of the
public repo per the Bundle 5 lesson):

R4 / RT-L1 — local EC private key artifact
  rm cmd/agent/mc-001.key (gitignored, never in git history, leftover
  from a 2025-era agent dev run on the operator's workstation).
  Added scripts/ci-guards/B6-no-private-keys-in-tree.sh that fails the
  build if any TRACKED non-test file contains a PEM private-key block,
  so the next attempt to commit similar material gets caught at CI.
  Allowlist: *_test.go (hermetic-test PEMs), examples/*.md (sample
  walkthroughs), internal/scep/intune/testdata/ (certificates, not
  keys).

RT-M1 — landing-page HSM implication
  certctl.io/index.html: 'their hardware' / 'your hardware' colloquial
  comparisons rephrased to 'their custody' / 'your servers'. The phrase
  'Your keys. Your hardware. Your data. Your terms.' becomes 'Your
  keys. Your servers. Your data. Your terms.' to remove any inferred
  HSM-backed key-storage claim. The technical disclosure now lives in
  docs/operator/secret-custody.md (linked below); the landing page no
  longer makes a claim it cannot back.

S6 + SEC-M2 + RT-M2 (composite documentation closure)
  Added docs/operator/secret-custody.md — public operator reference
  enumerating every secret material on the control plane and on
  agents:
    - Local CA private key (FileDriver, file-on-disk, heap-resident
      with the L-014 carve-out documented in
      internal/connector/issuer/local/local.go).
    - Agent ECDSA P-256 keys (file on agent host, never transmitted).
    - OIDC client secret (AES-256-GCM v3, PBKDF2 600k).
    - Session signing key (same encryption regime).
    - Break-glass credential (Argon2id, never encrypted).
    - API-key bearer tokens (SHA-256 hash only; plaintext shown once).
    - CSR private keys mid-issuance (agent memory only).
    - Issuer-connector backend secrets (encrypted_config column,
      fail-closed for source='database', plaintext-by-design for
      source='env' with rationale).
  The Env-seeded-vs-DB-seeded plaintext policy is explained in plain
  text so a buyer review can independently verify the startup guard at
  cmd/server/main.go:222-262 makes sense.

  Added docs/operator/runbooks/config-encryption-upgrade.md — the
  procedural arm: how to force v1/v2 -> v3 re-seal across the
  database, plus the passphrase-rotation order. Documents the
  AEAD-driven read fallback (v3 -> v2 -> v1) and the fact that
  re-sealing happens passively on UPDATE. Open roadmap item: a
  certctl admin reseal --all command (tracked in
  WORKSPACE-ROADMAP.md).

  Both docs wired into docs/README.md Operator + Runbooks tables.

Verification:
  rg -n 'CONFIG_ENCRYPTION|encrypt|v1|private key|HSM|PKCS11|mc-001.key|\.key|Local CA' \
     internal cmd docs .gitignore README.md   # ambient (no NEW leaks)
  find . -name '*.key' \
     -not -path './.git/*' -not -path './web/node_modules/*'   # empty
  git ls-files | xargs grep -lE 'BEGIN .* PRIVATE KEY' \
     | grep -vE '_test\.go$|^examples/|^internal/scep/intune/testdata/'   # empty
  bash scripts/ci-guards/B6-no-private-keys-in-tree.sh   # PASS
  bash scripts/ci-guards/G-3-env-docs-drift.sh           # PASS
  bash scripts/ci-guards/doc-rot-detector.sh             # PASS

Residual roadmap (deliberately deferred):
  - signer.PKCS11Driver (HSM-token-backed CA-key custody).
  - signer.CloudKMSDriver (AWS/GCP/Azure KMS-backed CA-key custody).
  - FIPS 140-3 mode for the whole control plane.
  - HSM-backed session signing key.
  - Built-in 'certctl admin reseal --all' command.
  All five tracked in WORKSPACE-ROADMAP.md, not retracted.
2026-05-13 01:48:40 +00:00

62 lines
2.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# scripts/ci-guards/B6-no-private-keys-in-tree.sh
#
# Bundle 6 closure (R4 / RT-L1): a real EC P-256 private key
# (cmd/agent/mc-001.key) lived in the working tree as a leftover from a
# 2025-era agent dev run. `.gitignore` line 49 (`cmd/agent/*.key`)
# meant it never reached `git ls-files`, but the file was still on
# every operator's workstation and on every CI runner's checkout, so
# `find . -name '*.key'` and any future overzealous `git add` could
# have surfaced it.
#
# This guard catches the same class of mistake from the OTHER
# direction: it scans every TRACKED file (i.e. files already in git,
# what an external acquirer's clone sees) for PEM private-key blocks
# and fails the build if any of them contain real key material outside
# the known-good test-fixture set.
#
# Allowlist rationale:
# - *_test.go files generate ephemeral keys for hermetic tests. They
# never carry real production material — the keys are RSA/ECDSA pairs
# minted by the test setup at runtime, then embedded as PEM so test
# fixtures stay self-contained. Buyer review can confirm by grepping
# for `GenerateKey(` / `crypto/rand` nearby.
# - examples/*.md sample walkthroughs deliberately include throwaway
# keys so the operator can paste-and-run.
# - internal/scep/intune/testdata/*.pem are CERTIFICATES (no private
# key block) — verified at write time but excluded here as a belt-
# and-suspenders precaution.
#
# If a new file legitimately needs a sample PEM private key (e.g. a
# new connector's test fixture), add it to the allowlist below with a
# short rationale comment. Real production material — Local CA keys,
# agent keys, OIDC client secrets, session signing keys — NEVER
# appears in a checked-in file.
set -e
BAD=$(git ls-files -z \
| xargs -0 grep -lE 'BEGIN (RSA |EC |DSA |OPENSSH |)PRIVATE KEY' 2>/dev/null \
| grep -vE '_test\.go$' \
| grep -vE '^examples/' \
| grep -vE '^internal/scep/intune/testdata/' \
|| true)
if [ -n "$BAD" ]; then
echo "::error::B6 regression: PEM private-key block found in tracked non-test file(s):"
echo "$BAD"
echo ""
echo "Real private-key material MUST NOT be checked into the repo."
echo "If this is a legitimate sample fixture, add the path to the"
echo "allowlist in scripts/ci-guards/B6-no-private-keys-in-tree.sh"
echo "with a rationale comment, and verify the key is throwaway."
echo ""
echo "If this is leftover dev/demo material (the original Bundle 6"
echo "trigger was cmd/agent/mc-001.key from an agent test run),"
echo "delete the file and add the file pattern to .gitignore so it"
echo "stops landing in working trees."
exit 1
fi
echo "B6 guard OK: no PEM private-key blocks in tracked non-test files"