feat(cmd): wire the crypto fallback-unwrap counter

What

Step 3 (final) of the S04-C encryption wiring plan: the fallback-unwrap counter.

  • gitlab_artifact_registry_crypto_fallback_unwraps_total (labels record_root_key_uri, active_root_key_uri) declared at the composition root in cmd/artifact-registry/metrics_crypto.go, registered unconditionally at boot beside the oci/npm/storage owners.
  • The encryption tier builds the hook closure over the boot-derived known root-key URI set and threads it through the manager-factory seam; record and active URIs outside the known set coerce to the literal unknown before becoming label values.
  • Cardinality budgets (5 per label) documented at the registration site and in internal/metrics/cardinality.go; catalog row in docs/dev/observability.md carrying the counter's one-directional meaning (non-zero proves a rotation sweep unfinished; zero proves nothing — the S04-A zero-live-rows verification remains the authority before removing a root key).
  • Splits wire_encryption_test.go at the plan's named failure-path seam (pure move — identical go test -list before and after) and repairs the adjacent metric-exposition and panic-source comments the new counter made inconsistent.

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))
AC-2 Single loader TTL Owned by Step 2 (!1297 (merged))
AC-3 No database client Owned by Step 1 (!1295 (merged))
AC-4 Secret-read failure Owned by Step 2 (!1297 (merged))
AC-5 Malformed secret Owned by Step 2 (!1297 (merged))
AC-6 Retained-key GetKey increments with both URIs; out-of-set record URI lands under unknown TestWireEncryption_FallbackUnwrapIncrementsCounter, TestWireEncryption_TamperedRecordURICoercesToUnknown, TestWireEncryption_ConflictAdoptionIncrementsCounterOnce, TestWireEncryption_WiresFallbackUnwrapHook, TestFallbackUnwrapHook_Coercion
AC-7 Wiring cardinality audit passes with the counter registered TestWiring_CardinalityAuditPasses, TestWiring_CryptoCounterRegisteredWithoutEncryption, in-test live-series audit
AC-8 Sweeper shutdown paths Owned by Step 2 (!1297 (merged)); goleak suite kept green here
AC-9 -race Race lanes + TestFallbackUnwrapHook_ConcurrentCalls
AC-10 Boot-outcome log Owned by Steps 1–2 (!1295 (merged), !1297 (merged))

Error cases

# Condition Tests
E-1 – E-5, E-7 Boot-failure and skip conditions Owned by Steps 1–2 (!1295 (merged), !1297 (merged))
E-6 Empty key slice Defense in depth, unreachable; owned by internal/crypto
E-8 Unknown record URI: counter increments under unknown, never the raw value TestWireEncryption_TamperedRecordURICoercesToUnknown (end to end), TestFallbackUnwrapHook_Coercion (both directions, plus the out-of-set active arm and the nil-set arm)

Security considerations

# Concern Tests
S-1 Root-key handling and zeroization Owned by Step 2 (!1297 (merged))
S-2 Fail-closed enabled path Owned by Steps 1–2 (!1295 (merged), !1297 (merged))
S-3 Label coercion bounds attacker-influenced values; both labels coerced against the boot-derived set TestWireEncryption_TamperedRecordURICoercesToUnknown, TestFallbackUnwrapHook_Coercion (record and active arms), TestFallbackUnwrapHook_ConcurrentCalls
S-4 Key-material confinement limits S04-A properties
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 Counter registers unconditionally but increments only on an active stack (TestWiring_CryptoCounterRegisteredWithoutEncryption)

Notes

  • Was stacked on !1297 (merged) (Step 2, merged); targets main. Size: 937+/311− across 10 files; both sides are inflated by the failure-suite move into wire_encryption_failure_test.go, whose function bodies are byte-identical to their old locations on current main. Tests dominate ~3:1.
  • E2e scenario impact: none — boot wiring and metrics only, no client-visible behavior.

Related to https://gitlab.com/gitlab-org/ops/artifact-registry/-/work_items/491

Edited by Suleimi Ahmed

Merge request reports

Loading
Loading