Skip to content

phase2(s12.d): SignerPolicy ConfigMap (Fulcio issuer + SAN allowlist) - #117

Merged
Pal Lakatos-Toth (pallakatos) merged 1 commit into
devfrom
phase2-s12-d-signerpolicy
Apr 30, 2026
Merged

Pal Lakatos-Toth (pallakatos) merged 1 commit into
devfrom
phase2-s12-d-signerpolicy

Conversation

@pallakatos

Copy link
Copy Markdown
Collaborator

Summary

Replaces the env-var path used by S12.b for cosign signer-identity policy with a watched cluster-scoped SignerPolicy ConfigMap (azureclaw-signer-policy in the controller namespace). The env-var path (AZURECLAW_SIGNER_FULCIO_ISSUERS / AZURECLAW_SIGNER_SAN_PATTERNS) remains as an emergency-override fallback that engages only when the ConfigMap is absent; a malformed ConfigMap surfaces as a new SignerPolicyMalformed condition reason and does not silently fall back.

Trust-model improvement: the cluster's identity-pinning policy is now a first-class, watched, fail-closed configuration object that cluster-admins manage via Helm. Combined with S12.b (consumer verify) and S12.c (producer sign), the cluster has end-to-end coverage of "the bytes the operator sealed are the bytes the controller verifies — under an authority the operator has explicitly trusted." S12.e now has a reliable SignerPolicy to flip authoritative-ref mode against.

Surveyed seams (reused, not parallel-implemented)

  • controller/src/policy_fetcher.rs — SignerPolicyConfig, FetchError, reason_for_error, maybe_verify_allowlist. Added the SignerPolicyMalformed variant + reason; rewired maybe_verify_allowlist to consult a SharedSignerPolicy handle.
  • controller/src/leader_election.rs — POD_NAMESPACE downward-API idiom; reused. The Helm controller-deployment.yaml was extended to wire POD_NAMESPACE + POD_NAME (previously only relied on with a hard-coded fallback).
  • controller/src/status/conditions.rs — TYPE_ALLOWLIST_VERIFIED, preserve_transition_time. The new reason is a literal returned by reason_for_error, matching the existing pattern.
  • deploy/helm/azureclaw/templates/rbac.yaml — controller ClusterRole already grants get/list/watch on configmaps; no broadening introduced.

Key safety properties (locked in by tests)

  • SignerPolicyMissing (absent + env-empty) → fail closed.
  • SignerPolicyMalformed (ConfigMap broken) → fail closed; does not fall back to env (test with_handle_malformed_does_not_fall_back_to_env).
  • ConfigMap configured → takes precedence over env (test with_handle_configmap_takes_precedence_over_env).
  • Empty issuers AND empty SANs → still missing; parser rejects either-empty up-front.

Test delta

Controller tests: 383 → 401 (+18). Breakdown:

  • 13 new unit tests in controller/src/signer_policy.rs (parser strict-rejection cases + SharedSignerPolicy apply/clear/snapshot/clone semantics).
  • 5 new tests in controller/src/policy_fetcher.rs exercising maybe_verify_allowlist_with_handle for each SignerPolicyState (FromConfigMap precedence, Malformed surfaces SignerPolicyMalformed without env fallback, Absent falls back to env).
  • 1 augmented reason_for_error_maps_each_variant test.

Existing env-fallback tests are preserved unchanged (the env path remains a documented emergency-override).

CI

  • cargo fmt --all clean
  • cargo clippy --all-targets -- -D warnings clean
  • cargo test --all — all green (controller 401, router 608, others unchanged)
  • helm lint deploy/helm/azureclaw clean
  • CLI npm run lint + npx tsc --noEmit clean

Audit doc

docs/security-audits/2026-04-30-phase2-s12-d-signer-policy.md — covers the threat (signer-key misconfiguration → forged allowlist accepted), mitigations (cluster-scoped ConfigMap; controller-only watch; namespace-scoped Api + name field-selector; malformed-detection condition; env fallback for emergency override), and a surveyed-existing-implementation section listing every reused seam.

Plan slot

§S12.d — "identity-pinned verification config. Cluster-level SignerPolicy ConfigMap (watched by controller) listing allowed Fulcio issuer + SAN patterns. Default config provided for GitHub Actions OIDC + Entra workload-identity issuers." Default config now lives in deploy/helm/azureclaw/values.yaml under signerPolicy.*.

Out of scope (per slice spec)

  • S12.e authoritative-ref mode flip — NetworkPolicy still derives from inline allowedEndpoints; AllowlistVerified remains status-only.
  • Tenant-scoped trust roots — single cluster-wide SignerPolicy per cluster.
  • Transparency-log freshness checks — deliberately out of scope (replay vector mitigated by RBAC + monotonic generation in canonical payload).

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Replaces the env-var path that S12.b used for cosign signer-identity
policy with a watched cluster-scoped ConfigMap (azureclaw-signer-policy
in the controller namespace). Env vars remain as an emergency-override
fallback when the ConfigMap is absent; malformed ConfigMaps surface as
SignerPolicyMalformed (no silent fallback).

- New controller/src/signer_policy.rs: ConfigMap parser + watcher +
  SharedSignerPolicy holder (Arc<RwLock<state>>). Atomic rebuild on
  watch-restart; namespace-scoped Api + name field-selector keep the
  watch tightly bounded.
- New FetchError::SignerPolicyMalformed variant + reason mapping.
  policy_fetcher::maybe_verify_allowlist now consults a process-global
  SharedSignerPolicy handle; new maybe_verify_allowlist_with_handle
  variant takes an injected handle for unit-test cleanliness.
- Helm: new signer-policy-configmap.yaml template; signerPolicy
  values block (enabled, fulcioIssuers, sanPatterns) with sensible
  defaults for GitHub Actions OIDC + Entra workload identity.
- Helm: controller deployment now wires POD_NAMESPACE / POD_NAME via
  downward API (previously only set conditionally for leader-election).
- RBAC: unchanged — controller ClusterRole already grants
  get/list/watch on configmaps cluster-wide; new watcher fits within
  the existing rule with no broadening introduced.
- 18 new unit tests; controller test count 383 -> 401. Workspace
  green; clippy clean; cargo fmt clean; helm lint clean; CLI
  typecheck + lint clean.

Audit doc: docs/security-audits/2026-04-30-phase2-s12-d-signer-policy.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@pallakatos
Pal Lakatos-Toth (pallakatos) merged commit 441ce86 into dev Apr 30, 2026
16 checks passed
Pal Lakatos-Toth (pallakatos) added a commit that referenced this pull request May 12, 2026
…#117)

Replaces the env-var path that S12.b used for cosign signer-identity
policy with a watched cluster-scoped ConfigMap (azureclaw-signer-policy
in the controller namespace). Env vars remain as an emergency-override
fallback when the ConfigMap is absent; malformed ConfigMaps surface as
SignerPolicyMalformed (no silent fallback).

- New controller/src/signer_policy.rs: ConfigMap parser + watcher +
  SharedSignerPolicy holder (Arc<RwLock<state>>). Atomic rebuild on
  watch-restart; namespace-scoped Api + name field-selector keep the
  watch tightly bounded.
- New FetchError::SignerPolicyMalformed variant + reason mapping.
  policy_fetcher::maybe_verify_allowlist now consults a process-global
  SharedSignerPolicy handle; new maybe_verify_allowlist_with_handle
  variant takes an injected handle for unit-test cleanliness.
- Helm: new signer-policy-configmap.yaml template; signerPolicy
  values block (enabled, fulcioIssuers, sanPatterns) with sensible
  defaults for GitHub Actions OIDC + Entra workload identity.
- Helm: controller deployment now wires POD_NAMESPACE / POD_NAME via
  downward API (previously only set conditionally for leader-election).
- RBAC: unchanged — controller ClusterRole already grants
  get/list/watch on configmaps cluster-wide; new watcher fits within
  the existing rule with no broadening introduced.
- 18 new unit tests; controller test count 383 -> 401. Workspace
  green; clippy clean; cargo fmt clean; helm lint clean; CLI
  typecheck + lint clean.

Audit doc: docs/security-audits/2026-04-30-phase2-s12-d-signer-policy.md

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@pallakatos
Pal Lakatos-Toth (pallakatos) deleted the phase2-s12-d-signerpolicy branch June 1, 2026 14:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant