From e521e7b715805dfb0053123b791f9f7de8c02b73 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 22 Aug 2026 15:17:13 +0200 Subject: [PATCH] feat(policy): adopt security zone stances Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a0291a-1e87-7151-9934-fcbfe3f65eb1 --- cmd/flex-auth/main_test.go | 36 +++++---- .../security-zone-admission-2026-08-22.md | 19 +++++ examples/ops-warden/policy_fixtures.yaml | 77 +++++++++++++++++-- examples/ops-warden/policy_package.md | 65 +++++++++++++++- .../production_registry_snapshot.json | 31 +++++--- examples/ops-warden/registry_snapshot.json | 24 +++--- internal/policy/package_test.go | 4 +- tenancy.yaml | 41 ++++++++++ 8 files changed, 248 insertions(+), 49 deletions(-) create mode 100644 docs/evidence/security-zone-admission-2026-08-22.md diff --git a/cmd/flex-auth/main_test.go b/cmd/flex-auth/main_test.go index d210c5f..10b62f5 100644 --- a/cmd/flex-auth/main_test.go +++ b/cmd/flex-auth/main_test.go @@ -262,31 +262,34 @@ func TestOpsWardenProductionRegistryActors(t *testing.T) { wantReason string }{ { - name: "state hub bridge agent allow", + name: "state hub bridge agent advisory while zone is unknown", subjectID: "agt-state-hub-bridge", actor: "agt-state-hub-bridge", actorType: "agt", principal: "agt-task-bridge", ttlHours: 1, - wantEffect: api.DecisionEffectAllow, + wantEffect: api.DecisionEffectAuditOnly, + wantReason: "advisory_would_signing_policy_matched", }, { - name: "state hub bridge IAM subject allow", + name: "state hub bridge IAM subject advisory while zone is unknown", subjectID: "iam:agt-state-hub-bridge", actor: "agt-state-hub-bridge", actorType: "agt", principal: "agt-task-bridge", ttlHours: 1, - wantEffect: api.DecisionEffectAllow, + wantEffect: api.DecisionEffectAuditOnly, + wantReason: "advisory_would_signing_policy_matched", }, { - name: "codex interhub bootstrap agent allow", + name: "codex interhub bootstrap advisory while zone is unknown", subjectID: "agt-codex-interhub-bootstrap", actor: "agt-codex-interhub-bootstrap", actorType: "agt", principal: "agt-interhub-bootstrap", ttlHours: 1, - wantEffect: api.DecisionEffectAllow, + wantEffect: api.DecisionEffectAuditOnly, + wantReason: "advisory_would_signing_policy_matched", }, { name: "admin actor allow", @@ -298,33 +301,34 @@ func TestOpsWardenProductionRegistryActors(t *testing.T) { wantEffect: api.DecisionEffectAllow, }, { - name: "automation actor allow", + name: "automation actor advisory while zone is unknown", subjectID: "atm-backup-daily", actor: "atm-backup-daily", actorType: "atm", principal: "atm-backup-daily", ttlHours: 1, - wantEffect: api.DecisionEffectAllow, + wantEffect: api.DecisionEffectAuditOnly, + wantReason: "advisory_would_signing_policy_matched", }, { - name: "ttl above production max denies", + name: "ttl above production max is advisory while zone is unknown", subjectID: "agt-state-hub-bridge", actor: "agt-state-hub-bridge", actorType: "agt", principal: "agt-task-bridge", ttlHours: 999, - wantEffect: api.DecisionEffectDeny, - wantReason: "ttl_out_of_bounds", + wantEffect: api.DecisionEffectAuditOnly, + wantReason: "advisory_would_ttl_out_of_bounds", }, { - name: "unregistered production actor denies", + name: "unregistered production actor is advisory unknown", subjectID: "agt-missing", actor: "agt-missing", actorType: "agt", principal: "agt-missing", ttlHours: 1, - wantEffect: api.DecisionEffectDeny, - wantReason: "unknown_actor_resource", + wantEffect: api.DecisionEffectAuditOnly, + wantReason: "advisory_would_unknown_actor_resource", }, } @@ -340,8 +344,8 @@ func TestOpsWardenProductionRegistryActors(t *testing.T) { if tt.wantReason != "" && decision.Reason != tt.wantReason { t.Fatalf("decision.Reason = %q; want %q; decision: %+v", decision.Reason, tt.wantReason, decision) } - if tt.wantEffect == api.DecisionEffectAllow && decision.ID == "" { - t.Fatal("allow decision ID is empty") + if (tt.wantEffect == api.DecisionEffectAllow || tt.wantEffect == api.DecisionEffectAuditOnly) && decision.ID == "" { + t.Fatal("proceeding decision ID is empty") } }) } diff --git a/docs/evidence/security-zone-admission-2026-08-22.md b/docs/evidence/security-zone-admission-2026-08-22.md new file mode 100644 index 0000000..fdd8d74 --- /dev/null +++ b/docs/evidence/security-zone-admission-2026-08-22.md @@ -0,0 +1,19 @@ +# flex-auth security-zone admission — 2026-08-22 + +The `flex-auth` workload qualifies for `z1-operational` and does not claim a +higher zone. + +- Scope is limited to three separately configured platform consumers. Each pin + has an explicit policy, registry, and authenticated caller binding. +- The basic service objective is 95% successful `/healthz` and policy checks + during each consumer's operating window. Decision ids and the existing + ops-warden registry-sync runbook provide the measurement path. No SLO history + is claimed; that is one reason M2 is not admitted. +- Requests contain internal identity, authorization, and resource metadata. + The service stores no tenant data or secret values and production policy and + registry inputs are read-only. +- The zone-aware ops-warden package is total across every v0.1 zone plus + `unknown`; flex-auth owns stance while the calling PEP owns failure behavior. + +Owner: `team:platform-security`. Re-review with the zone declaration by +2026-11-22. diff --git a/examples/ops-warden/policy_fixtures.yaml b/examples/ops-warden/policy_fixtures.yaml index 9dd80b6..1bb4c15 100644 --- a/examples/ops-warden/policy_fixtures.yaml +++ b/examples/ops-warden/policy_fixtures.yaml @@ -24,7 +24,9 @@ "platform", "root" ], - "max_ttl_hours": 8 + "max_ttl_hours": 8, + "security_zone": "z2-protected", + "security_zone_admission": "satisfied" } }, "context": { @@ -67,7 +69,9 @@ "deploy", "git" ], - "max_ttl_hours": 2 + "max_ttl_hours": 2, + "security_zone": "z2-protected", + "security_zone_admission": "satisfied" } }, "context": { @@ -108,7 +112,9 @@ "allowed_principals": [ "backup" ], - "max_ttl_hours": 1 + "max_ttl_hours": 1, + "security_zone": "z2-protected", + "security_zone_admission": "satisfied" } }, "context": { @@ -150,7 +156,9 @@ "platform", "root" ], - "max_ttl_hours": 8 + "max_ttl_hours": 8, + "security_zone": "z2-protected", + "security_zone_admission": "satisfied" } }, "context": { @@ -192,7 +200,9 @@ "deploy", "git" ], - "max_ttl_hours": 2 + "max_ttl_hours": 2, + "security_zone": "z2-protected", + "security_zone_admission": "satisfied" } }, "context": { @@ -234,7 +244,9 @@ "platform", "root" ], - "max_ttl_hours": 8 + "max_ttl_hours": 8, + "security_zone": "z2-protected", + "security_zone_admission": "satisfied" } }, "context": { @@ -276,7 +288,9 @@ "deploy", "git" ], - "max_ttl_hours": 2 + "max_ttl_hours": 2, + "security_zone": "z2-protected", + "security_zone_admission": "satisfied" } }, "context": { @@ -318,7 +332,9 @@ "platform", "root" ], - "max_ttl_hours": 8 + "max_ttl_hours": 8, + "security_zone": "z2-protected", + "security_zone_admission": "satisfied" } }, "context": { @@ -333,5 +349,50 @@ "effect": "deny", "reason": "missing_pubkey_fingerprint" } + }, + { + "id": "fixture:ops-warden-unknown-zone-advisory", + "request": { + "id": "check:ops-warden-unknown-zone", + "tenant": "tenant:platform", + "subject": { + "id": "platform-steward", + "type": "adm" + }, + "action": "sign", + "resource": { + "id": "ssh-cert:actor/platform-steward", + "type": "ssh-certificate", + "system": "ops-warden", + "attributes": { + "actor_id": "platform-steward", + "actor_type": "adm", + "allowed_subjects": [ + "platform-steward", + "iam:platform-steward" + ], + "allowed_principals": [ + "platform", + "root" + ], + "max_ttl_hours": 8, + "security_zone": "unknown", + "security_zone_admission": "unknown" + } + }, + "context": { + "principals": [ + "platform", + "root" + ], + "actor_type": "adm", + "ttl_hours": 4, + "pubkey_fingerprint": "SHA256:example-adm-fingerprint" + } + }, + "expect": { + "effect": "audit_only", + "reason": "advisory_would_signing_policy_matched" + } } ] diff --git a/examples/ops-warden/policy_package.md b/examples/ops-warden/policy_package.md index ad81d20..23563eb 100644 --- a/examples/ops-warden/policy_package.md +++ b/examples/ops-warden/policy_package.md @@ -2,7 +2,7 @@ id: ops-warden.ssh-certificate.sign name: Ops-Warden SSH certificate signing namespace: ops-warden:ssh-certificate -version: v1 +version: v2 status: ready package: flexauth.ops_warden.ssh_signing actions: @@ -41,12 +41,13 @@ activation: mode: local metadata: source: examples/ops-warden/policy_package.md - ops_warden_policy_gate: v2 + ops_warden_policy_gate: security-zones-v0.1 + security_zone_standard: security-zones_v0.1 --- # Ops-Warden SSH Certificate Signing -This package authorizes ops-warden's opt-in pre-sign policy gate. The caller +This package authorizes ops-warden's zone-aware pre-sign policy gate. The caller keeps SSH CA custody, actor inventory, and OpenBao signing; flex-auth decides whether a specific `sign` request is allowed now. @@ -60,11 +61,45 @@ import future.keywords.in actor_types := {"adm", "agt", "atm"} decision := {"effect": "allow", "reason": "signing_policy_matched"} if { + security_zone_stance == "enforced" allowed } else := {"effect": "deny", "reason": first_denial} if { + security_zone_stance == "enforced" +} else := {"effect": "audit_only", "reason": advisory_reason} if { true } +default security_zone := "unknown" + +security_zone := input.resource.attributes.security_zone if { + input.resource.attributes.security_zone in { + "z0-experimental", + "z1-operational", + "z2-protected", + "z2-continuity", + "z3-critical", + "unknown", + } +} + +default security_zone_stance := "advisory" + +security_zone_stance := "enforced" if { + security_zone in {"z2-protected", "z2-continuity", "z3-critical"} +} + +security_zone_stance := "enforced" if { + input.resource.attributes.security_zone_admission == "not-applicable" +} + +base_reason := "signing_policy_matched" if { + allowed +} else := first_denial if { + true +} + +advisory_reason := sprintf("advisory_would_%s", [base_reason]) + allowed if { input.action == "sign" input.resource.system == "ops-warden" @@ -211,7 +246,9 @@ adm_request := { "actor_type": "adm", "allowed_subjects": ["platform-steward", "iam:platform-steward"], "allowed_principals": ["platform", "root"], - "max_ttl_hours": 8 + "max_ttl_hours": 8, + "security_zone": "z2-protected", + "security_zone_admission": "satisfied" } }, "context": { @@ -254,4 +291,24 @@ test_missing_fingerprint_denied if { } } } + +test_unknown_zone_is_advisory if { + ssh_signing.decision.effect == "audit_only" with input as object.union( + adm_request, + {"resource": object.union(adm_request.resource, {"attributes": object.union( + adm_request.resource.attributes, + {"security_zone": "unknown", "security_zone_admission": "unknown"}, + )})}, + ) +} + +test_native_non_workload_subject_keeps_enforced_policy if { + ssh_signing.decision.effect == "allow" with input as object.union( + adm_request, + {"resource": object.union(adm_request.resource, {"attributes": object.union( + adm_request.resource.attributes, + {"security_zone": "unknown", "security_zone_admission": "not-applicable"}, + )})}, + ) +} ``` diff --git a/examples/ops-warden/production_registry_snapshot.json b/examples/ops-warden/production_registry_snapshot.json index 3110228..cba4291 100644 --- a/examples/ops-warden/production_registry_snapshot.json +++ b/examples/ops-warden/production_registry_snapshot.json @@ -48,8 +48,8 @@ ], "metadata": { "flex_auth_contract": "protected-system-v0", - "ops_warden_policy_gate": "v2", - "policy_enabled_config": "policy.enabled", + "ops_warden_policy_gate": "security-zones-v0.1", + "security_zone_standard": "security-zones_v0.1", "tenant": "tenant:platform" } } @@ -66,7 +66,6 @@ "ssh-signing", "adm" ], - "trust_zone": "platform", "owner": "team:platform-security", "attributes": { "actor_id": "adm-example", @@ -78,7 +77,10 @@ "allowed_principals": [ "adm-full" ], - "max_ttl_hours": 48 + "max_ttl_hours": 48, + "security_zone": "unknown", + "security_zone_admission": "not-applicable", + "security_zone_reason": "human operator retains native actor identity" } }, { @@ -88,7 +90,6 @@ "ssh-signing", "agt" ], - "trust_zone": "platform", "owner": "team:platform-security", "attributes": { "actor_id": "agt-codex-interhub-bootstrap", @@ -100,7 +101,11 @@ "allowed_principals": [ "agt-interhub-bootstrap" ], - "max_ttl_hours": 2 + "max_ttl_hours": 2, + "workload_id": "codex-interhub-bootstrap", + "security_zone": "unknown", + "security_zone_admission": "unknown", + "security_zone_reason": "workload_resolution_absent" } }, { @@ -110,7 +115,6 @@ "ssh-signing", "agt" ], - "trust_zone": "platform", "owner": "team:platform-security", "attributes": { "actor_id": "agt-state-hub-bridge", @@ -122,7 +126,11 @@ "allowed_principals": [ "agt-task-bridge" ], - "max_ttl_hours": 24 + "max_ttl_hours": 24, + "workload_id": "ops-bridge-tunnel", + "security_zone": "unknown", + "security_zone_admission": "unknown", + "security_zone_reason": "workload_resolution_absent" } }, { @@ -132,7 +140,6 @@ "ssh-signing", "atm" ], - "trust_zone": "platform", "owner": "team:platform-security", "attributes": { "actor_id": "atm-backup-daily", @@ -144,7 +151,11 @@ "allowed_principals": [ "atm-backup-daily" ], - "max_ttl_hours": 8 + "max_ttl_hours": 8, + "workload_id": "backup-daily", + "security_zone": "unknown", + "security_zone_admission": "unknown", + "security_zone_reason": "workload_resolution_absent" } } ], diff --git a/examples/ops-warden/registry_snapshot.json b/examples/ops-warden/registry_snapshot.json index c5a924e..3294584 100644 --- a/examples/ops-warden/registry_snapshot.json +++ b/examples/ops-warden/registry_snapshot.json @@ -48,9 +48,9 @@ ], "metadata": { "flex_auth_contract": "protected-system-v0", - "ops_warden_policy_gate": "v2", - "policy_enabled_config": "policy.enabled", - "tenant": "tenant:platform" + "ops_warden_policy_gate": "security-zones-v0.1", + "tenant": "tenant:platform", + "security_zone_standard": "security-zones_v0.1" } } ], @@ -66,7 +66,6 @@ "ssh-signing", "adm" ], - "trust_zone": "platform", "owner": "team:platform-security", "attributes": { "actor_id": "platform-steward", @@ -79,7 +78,10 @@ "platform", "root" ], - "max_ttl_hours": 8 + "max_ttl_hours": 8, + "security_zone": "z2-protected", + "security_zone_admission": "satisfied", + "security_zone_reason": "fixture_enforced" } }, { @@ -89,7 +91,6 @@ "ssh-signing", "agt" ], - "trust_zone": "platform", "owner": "team:platform-security", "attributes": { "actor_id": "ci-deploy-agent", @@ -102,7 +103,10 @@ "deploy", "git" ], - "max_ttl_hours": 2 + "max_ttl_hours": 2, + "security_zone": "z2-protected", + "security_zone_admission": "satisfied", + "security_zone_reason": "fixture_enforced" } }, { @@ -112,7 +116,6 @@ "ssh-signing", "atm" ], - "trust_zone": "platform", "owner": "team:platform-security", "attributes": { "actor_id": "backup-automation", @@ -124,7 +127,10 @@ "allowed_principals": [ "backup" ], - "max_ttl_hours": 1 + "max_ttl_hours": 1, + "security_zone": "z2-protected", + "security_zone_admission": "satisfied", + "security_zone_reason": "fixture_enforced" } } ], diff --git a/internal/policy/package_test.go b/internal/policy/package_test.go index a2bc599..7b4c718 100644 --- a/internal/policy/package_test.go +++ b/internal/policy/package_test.go @@ -86,8 +86,8 @@ func TestOpsWardenPolicyPackageMarkdownValidates(t *testing.T) { if pkg.Metadata.Namespace != "ops-warden:ssh-certificate" { t.Fatalf("metadata.Namespace = %q; want ops-warden:ssh-certificate", pkg.Metadata.Namespace) } - if len(pkg.Validation.Fixtures) != 8 { - t.Fatalf("Validation.Fixtures len = %d; want 8", len(pkg.Validation.Fixtures)) + if len(pkg.Validation.Fixtures) != 9 { + t.Fatalf("Validation.Fixtures len = %d; want 9", len(pkg.Validation.Fixtures)) } for _, fixture := range pkg.Validation.Fixtures { if !fixture.Passed { diff --git a/tenancy.yaml b/tenancy.yaml index 9bdefff..3c1ac26 100644 --- a/tenancy.yaml +++ b/tenancy.yaml @@ -8,6 +8,27 @@ framework: netkingdom-tenancy-posture service: flex-auth role: policy-decision-point +workload_identity: + name: flex-auth + kind: platform-service + responsible_repo: flex-auth + identity_bindings: + - scheme: kubernetes-service-account + authority: railiance01 + subject: system:serviceaccount:flex-auth:flex-auth-ops-warden + principal_type: service + environment: prod + - scheme: kubernetes-service-account + authority: railiance01 + subject: system:serviceaccount:flex-auth:flex-auth-user-engine + principal_type: service + environment: prod + - scheme: kubernetes-service-account + authority: railiance01 + subject: system:serviceaccount:flex-auth:flex-auth-tenant-engine + principal_type: service + environment: prod + tenancy: current: I: 1 @@ -77,6 +98,26 @@ provider: evidence: - "docs/tenancy-posture-review.md" +zones: + standard: security-zones_v0.1 + membership: z1-operational + responsible_party: team:platform-security + justification: >- + Three isolated production policy pins have a bounded platform scope and + internal authorization metadata exposure. M1 is evidenced; absent SLO + history, on-call, and incident exercises, z2-protected would overclaim. + context: + maturity: M1 + criticality: medium + data_classification: internal + evidence: + - ref: docs/evidence/security-zone-admission-2026-08-22.md + supports: [M1, platform-only-scope, basic-slo, data-handling-note] + - ref: docs/ops-warden-registry-sync.md + supports: [production-runbook, decision-observability] + reviewed: "2026-08-22" + review_due: "2026-11-22" + evidence: E1: - "internal/decision/engine.go:188"