diff --git a/examples/ops-warden/README.md b/examples/ops-warden/README.md index f09efee..6d2d383 100644 --- a/examples/ops-warden/README.md +++ b/examples/ops-warden/README.md @@ -28,6 +28,7 @@ flex-auth validate --kind subject-manifest --file examples/ops-warden/subject_ma flex-auth load-registry --file examples/ops-warden/registry_snapshot.json flex-auth test-policy --file examples/ops-warden/policy_package.md flex-auth check --registry examples/ops-warden/registry_snapshot.json --policy examples/ops-warden/policy_package.md --request examples/ops-warden/check_request_allow_adm.json +flex-auth check --registry examples/ops-warden/registry_snapshot.json --policy examples/ops-warden/policy_package.md --request examples/ops-warden/check_request_deny_wrong_tenant.json ``` The fixture public-key fingerprints are examples only. Do not put real keys, diff --git a/examples/ops-warden/check_request_deny_wrong_tenant.json b/examples/ops-warden/check_request_deny_wrong_tenant.json new file mode 100644 index 0000000..5263598 --- /dev/null +++ b/examples/ops-warden/check_request_deny_wrong_tenant.json @@ -0,0 +1,23 @@ +{ + "id": "check:ops-warden-wrong-tenant", + "tenant": "tenant:other", + "subject": { + "id": "platform-steward", + "type": "adm" + }, + "action": "sign", + "resource": { + "id": "ssh-cert:actor/platform-steward", + "type": "ssh-certificate", + "system": "ops-warden" + }, + "context": { + "principals": [ + "platform", + "root" + ], + "actor_type": "adm", + "ttl_hours": 4, + "pubkey_fingerprint": "SHA256:example-adm-fingerprint" + } +} diff --git a/examples/ops-warden/policy_fixtures.yaml b/examples/ops-warden/policy_fixtures.yaml index 1bb4c15..756ffdd 100644 --- a/examples/ops-warden/policy_fixtures.yaml +++ b/examples/ops-warden/policy_fixtures.yaml @@ -394,5 +394,50 @@ "effect": "audit_only", "reason": "advisory_would_signing_policy_matched" } + }, + { + "id": "fixture:ops-warden-wrong-tenant-deny", + "request": { + "id": "check:ops-warden-wrong-tenant", + "tenant": "tenant:other", + "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": "z2-protected", + "security_zone_admission": "satisfied" + } + }, + "context": { + "principals": [ + "platform", + "root" + ], + "actor_type": "adm", + "ttl_hours": 4, + "pubkey_fingerprint": "SHA256:example-adm-fingerprint" + } + }, + "expect": { + "effect": "deny", + "reason": "wrong_tenant" + } } ] diff --git a/examples/railiance-platform/README.md b/examples/railiance-platform/README.md index 5a614fd..05d0dd1 100644 --- a/examples/railiance-platform/README.md +++ b/examples/railiance-platform/README.md @@ -2,7 +2,8 @@ This package provides the flex-auth side of `FLEX-WP-0012`: registered grant metadata, representative requester subjects, a default-deny `issue` policy, -and fixtures for TTL, actor type, purpose, delivery mode, and unknown grants. +and fixtures for TTL, actor type, purpose, delivery mode, unknown grants, and +wrong tenant. The wire translation is intentionally outside the policy. Both candidate integration shapes must emit `requested_ttl_seconds` as a positive number; @@ -13,4 +14,5 @@ go run ./cmd/flex-auth load-registry --file examples/railiance-platform/registry go run ./cmd/flex-auth test-policy --file examples/railiance-platform/policy_package.md go run ./cmd/flex-auth check --registry examples/railiance-platform/registry_snapshot.json --policy examples/railiance-platform/policy_package.md --request examples/railiance-platform/check_request_allow.json go run ./cmd/flex-auth check --registry examples/railiance-platform/registry_snapshot.json --policy examples/railiance-platform/policy_package.md --request examples/railiance-platform/check_request_deny_unknown.json +go run ./cmd/flex-auth check --registry examples/railiance-platform/registry_snapshot.json --policy examples/railiance-platform/policy_package.md --request examples/railiance-platform/check_request_deny_wrong_tenant.json ``` diff --git a/examples/railiance-platform/check_request_deny_wrong_tenant.json b/examples/railiance-platform/check_request_deny_wrong_tenant.json new file mode 100644 index 0000000..1a16103 --- /dev/null +++ b/examples/railiance-platform/check_request_deny_wrong_tenant.json @@ -0,0 +1,21 @@ +{ + "id": "check:credential-grant-wrong-tenant-http-shape", + "tenant": "tenant:other", + "subject": { + "id": "agent:codex/railiance-platform", + "type": "Agent" + }, + "action": "issue", + "resource": { + "id": "credential-grant:rapp-postgres/audit-core-runtime", + "type": "credential-grant", + "system": "railiance-platform" + }, + "context": { + "actor_type": "approved-agent", + "bound_subject": "agent:codex/railiance-platform", + "purpose": "audit-core-runtime", + "delivery_mode": "exec-env", + "requested_ttl_seconds": 900 + } +} diff --git a/examples/railiance-platform/policy_fixtures.yaml b/examples/railiance-platform/policy_fixtures.yaml index 81c7249..9037d7a 100644 --- a/examples/railiance-platform/policy_fixtures.yaml +++ b/examples/railiance-platform/policy_fixtures.yaml @@ -57,3 +57,9 @@ type: credential-grant system: railiance-platform expect: {effect: deny, reason: unknown_grant} +- id: fixture:credential-grant-wrong-tenant-deny + request: + <<: *allow_request + id: check:credential-grant-wrong-tenant-deny + tenant: tenant:other + expect: {effect: deny, reason: wrong_tenant} diff --git a/internal/policy/package_test.go b/internal/policy/package_test.go index 796422e..edd1f13 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) != 9 { - t.Fatalf("Validation.Fixtures len = %d; want 9", len(pkg.Validation.Fixtures)) + if len(pkg.Validation.Fixtures) != 10 { + t.Fatalf("Validation.Fixtures len = %d; want 10", len(pkg.Validation.Fixtures)) } for _, fixture := range pkg.Validation.Fixtures { if !fixture.Passed { diff --git a/workplans/FLEX-WP-0022-tenant-scope-coverage.md b/workplans/FLEX-WP-0022-tenant-scope-coverage.md index 4aab94f..b9b0624 100644 --- a/workplans/FLEX-WP-0022-tenant-scope-coverage.md +++ b/workplans/FLEX-WP-0022-tenant-scope-coverage.md @@ -4,8 +4,8 @@ type: workplan title: "Tenant scoping is unstated in tenant-engine and untested in two more packages" domain: infotech repo: flex-auth -status: proposed -flavor: planning +status: active +flavor: implementation depends_on: - FLEX-WP-0021 owner: claude @@ -18,7 +18,7 @@ related_workplans: - FLEX-WP-0010 - FLEX-WP-0014 created: "2026-09-06" -updated: "2026-09-06" +updated: "2026-09-15" state_hub_workstream_id: "804c588c-f47a-50c4-bdd7-51b24bbf9539" --- @@ -60,7 +60,7 @@ look identical in the artifact. That is the same publishing-shape argument ```task id: FLEX-WP-0022-T01 -status: todo +status: progress priority: high state_hub_task_id: "a84dcee5-9426-5b30-8dd3-f4c076d00174" ``` @@ -75,6 +75,11 @@ Owner: `flex-auth` to ask; `tenant-engine` owns the answer. Gate: the relation is named by `tenant-engine`, not inferred here. This is the `FLEX-WP-0021-T01` rule applied to a field rather than to an action list. +2026-09-15: asked `tenant-engine` to name the relation +(`e8ba6a53-0093-4dc0-ad70-01f6b8c8e76b`). Their live +`FlexAuthWriteAuthorizer.authorize` copies `tenant_id` onto both +`CheckRequest.tenant` and `resource.id`. That is observation, not admission. + ## 2. Encode the relation, or record that there is none ```task @@ -100,7 +105,7 @@ Either way the fixtures must vary `tenant`, so the suite reports on the field. ```task id: FLEX-WP-0022-T03 -status: todo +status: done priority: medium state_hub_task_id: "3058f171-99d2-526b-a1bb-bd7aed87d10a" ``` @@ -113,3 +118,9 @@ fixture suite covers what the rule claims. No policy change and no version bump: the behaviour is already correct, only the evidence is thin. Gate: no package's fixture suite holds `tenant` constant. + +2026-09-15: added `fixture:ops-warden-wrong-tenant-deny` and +`fixture:credential-grant-wrong-tenant-deny` plus matching +`check_request_deny_wrong_tenant.json` files. Both suites now vary `tenant`. +`test-policy` and `flex-auth check` return `deny` / `wrong_tenant`. No policy +or version change.