Loading
feat(cmd): activate the encryption tier at the composition root
What
Step 2 of the S04-C encryption wiring plan: the present-block activation of the encryption tier.
- Root-key load chain (
infra.Secrets.Get→crypto.DecodeKeys→crypto.NewLocalProviderFromKeys), with the known root-key URI set derived before the decoded slices are zeroized. - Secrets-provider resolution: nil infrastructure config resolves through LabKit's
DefaultConfig(); a non-nil config with a nilSecretsprovider fails boot. - Store and manager constructed off the single loader TTL; the manager's sweep-goroutine cleanup registers as a shutdown component and
wireServicesinvokes it on its error path, so no boot path leaks the sweeper (including the panic arm). - The row encryptor constructed over the manager; both exposed on the wiring bundle as the enablement signal.
- Wired-outcome boot log with the active root-key URI fingerprint (never key material).
- Paired docs: the two fips.md corrections and the new
docs/dev/column-encryption.mdconsumer guide (consumption + the consumer-table contract; migration excluded).
Carried dependency
The first commit (chore: apply the S04-A row-encryptor delta pending its merge) carries open MR !1266 (merged)'s content so this tier compiles ahead of that MR's merge; it drops at rebase once !1266 (merged) lands on main. Reviewing after !1266 (merged) merges shows this MR's own diff only.
Spec coverage
Spec: docs/specs/S04-c-column-level-encryption-wiring.md
Acceptance criteria
| # | Criterion | Tests |
|---|---|---|
| AC-1 | Absent block: skip path | Owned by Step 1 (!1295 (merged)); suite kept green here |
| AC-2 | Single loader TTL to both constructors | TestWireEncryption_PresentBlockActivates |
| AC-3 | Present block + no client: boot fails | Owned by Step 1 (!1295 (merged)) |
| AC-4 | Secret-read failure names encryption/root-key, no key material |
TestWireEncryption_SecretReadFailureFailsBoot |
| AC-5 | Malformed secret: ErrKeySizeMismatch / ErrNoKeysFound / decode error |
TestWireEncryption_MalformedSecretFailsBoot (three arms) |
| AC-6 | Fallback-unwrap increment + coercion | Not in this MR — owned by Step 3 (!1298 (merged)) |
| AC-7 | Cardinality audit | Not in this MR — owned by Step 3 (!1298 (merged)) |
| AC-8 | Sweeper exits on graceful shutdown and failed boot | TestWireEncryption_SweeperStopsOnGracefulShutdown, ...WhenLaterTierFails, ...WhenLaterTierPanics (goleak) |
| AC-9 | -race |
Standard race lanes; goleak trio runs under -race |
| AC-10 | Boot-outcome log | Wired half: TestLogEncryptionTierWired + the wired-record assertions (URI fingerprint present, key material absent). Skip half owned by Step 1 (!1295 (merged)) |
Error cases
| # | Condition | Tests |
|---|---|---|
| E-1 | Absent block skip | Owned by Step 1 (!1295 (merged)) |
| E-2 | No database client | Owned by Step 1 (!1295 (merged)) |
| E-3 | Secret read fails, error names the key | TestWireEncryption_SecretReadFailureFailsBoot |
| E-4 | Zero usable keys | TestWireEncryption_MalformedSecretFailsBoot |
| E-5 | Wrong size / invalid base64 | TestWireEncryption_MalformedSecretFailsBoot |
| E-6 | Empty key slice | Spec-noted defense in depth, unreachable after a successful decode; owned by internal/crypto |
| E-7 | TTL outside bounds | Loader-owned; the tier never sees the value |
| E-8 | Unknown record URI coercion | Not in this MR — owned by Step 3 (!1298 (merged)) |
| — | Nil Secrets on a non-nil config (Design rule, no AC) |
TestWireEncryption_NilSecretsProviderFailsBoot |
Security considerations
| # | Concern | Tests |
|---|---|---|
| S-1 | Root key read once, decoded slices zeroized, never in config/logs/errors | TestWireEncryption_DerivesKnownURIsBeforeZeroize (both arms), NotContains assertions in the secret-failure and wired-log tests |
| S-2 | Enabled path fails closed | AC-4/AC-5 tests + TestWireEncryption_LaterTierFailureJoinsCleanupError |
| S-3 | Label coercion | Not in this MR — owned by Step 3 (!1298 (merged)) |
| S-4 | Key-material confinement limits | S04-A properties; not owned by this spec's MRs |
| S-5 | FIPS posture | Service-wide compliance work, outside S04 |
| S-6 | &8 review gates before enablement | Process gate (#513) |
| S-7 | Inert until enabled | Skip-path suite kept green; no fixture carries the block |
Notes
- Stacked on !1295 (merged) (Step 1); Draft until it merges. Size: ~550–650 LOC net of the carried delta, per the plan's pre-justified split (code half inside the ceiling; ~250 doc lines pair with the tier per the documentation-pairing convention).
- E2e scenario impact: none — boot wiring only, no client-visible behavior.
Related to https://gitlab.com/gitlab-org/ops/artifact-registry/-/work_items/491
Edited by Suleimi Ahmed