{ "apiVersion": "netkingdom.io/security-case-render/v0.1", "kind": "SecurityCaseRender", "case": "forgejo-netkingdom-oidc", "type": "oidc-browser-relying-party@1.2", "composition": { "apiVersion": "netkingdom.io/security-scenario-composition/v0.1", "kind": "SecurityScenarioComposition", "scenario": "scenario:case:forgejo-netkingdom-oidc", "authority": "netkingdom", "composition_digest": "sha256:f51e45eab2f2c15c93e2f7b8ef7d8fcc1daf1059da5749429ff5678b45c5214f", "requested_capabilities": [ "c1.lightweight-sso" ], "capability_providers": { "c1.lightweight-sso": "net-kingdom.keycape-client-registration" }, "initial_trust": [ "runtime_identity_trust", "runtime_secret_trust" ], "planned_final_trust": [ "runtime_identity_trust", "runtime_secret_trust", "tenant_onboarding_trust" ], "execution": { "mode": "plan-only", "permitted": false, "reason": "Composition does not authorize or perform provider execution." }, "execution_steps": [ { "order": 1, "declaration_id": "net-kingdom.keycape-client-registration", "executor_owner": "net-kingdom", "repo": "net-kingdom", "capabilities": [ "c1.lightweight-sso" ], "entry_point": { "path": "sso-mfa/k8s/keycape/keycape-client.py", "type": "cli-sequence", "invocation": "keycape-client.py apply --client-file --case-acceptance --expected-uid --expected-resource-version ", "description": "Add or remove exactly one client in the live KeyCape client registry without exporting secret values: contained kubectl, cluster and issuer pins, byte-preserving merge, compare-and-swap, read-back. Replaces the per-client helpers beside it. Field rules follow key-cape's canonical model (KEY-WP-0028). Modes: inspect, dry-run, apply; operations add and remove." }, "parameters": { "allowed_scopes": { "value": "openid,profile,email,groups", "source": "override", "sensitivity": "security_sensitive", "tuning_authority": "netkingdom_tunable" }, "client_id": { "value": "forgejo", "source": "override", "sensitivity": "operational", "tuning_authority": "netkingdom_tunable" }, "client_type": { "value": "confidential", "source": "override", "sensitivity": "security_sensitive", "tuning_authority": "netkingdom_tunable" }, "display_name": { "value": "Forgejo source forge", "source": "override", "sensitivity": "public", "tuning_authority": "netkingdom_tunable" }, "mfa_required": { "value": "true", "source": "override", "sensitivity": "security_sensitive", "tuning_authority": "netkingdom_tunable" }, "redirect_uris": { "value": "https://forgejo.coulomb.social/user/oauth2/netkingdom/callback", "source": "override", "sensitivity": "security_sensitive", "tuning_authority": "netkingdom_tunable" } }, "requires_trust": [ { "state": "runtime_secret_trust", "readiness_checks": [] }, { "state": "runtime_identity_trust", "readiness_checks": [] } ], "satisfies_trust": [ { "state": "tenant_onboarding_trust", "readiness_checks": [ { "id": "client-authorize-accepts-registered-redirect", "description": "The authorization endpoint accepts the client with each registered redirect URI.", "evidence": "GET /authorize with client_id and each redirect_uri returns the login page, not an error." }, { "id": "client-authorize-refuses-other-redirect", "description": "The authorization endpoint refuses an unregistered redirect URI for the client.", "evidence": "GET /authorize with an unregistered redirect_uri returns an OAuth error." } ] } ], "readiness_obligations": [ { "state": "tenant_onboarding_trust", "checks": [ { "id": "client-authorize-accepts-registered-redirect", "description": "The authorization endpoint accepts the client with each registered redirect URI.", "evidence": "GET /authorize with client_id and each redirect_uri returns the login page, not an error." }, { "id": "client-authorize-refuses-other-redirect", "description": "The authorization endpoint refuses an unregistered redirect URI for the client.", "evidence": "GET /authorize with an unregistered redirect_uri returns an OAuth error." } ] } ] } ], "responsibility_map": [ { "declaration_id": "net-kingdom.keycape-client-registration", "resource_kind": "identities", "owner": "net-kingdom", "resources": [ "oidc:static-clients" ], "repo_owns": "Live registry change for exactly this client, its idempotency and removal; field rules are key-cape's canonical model.", "netkingdom_orchestrates": "Supply the case's validated, non-secret client fields." }, { "declaration_id": "net-kingdom.keycape-client-registration", "resource_kind": "roles_scopes_policies", "owner": "key-cape", "resources": [ "iam-profile:client-scope-allowlists" ], "repo_owns": "Enforce exact redirect URIs, allowed scopes and the MFA pin at authorization time.", "netkingdom_orchestrates": "Choose scopes and MFA posture per SecurityCaseType." } ] }, "materialization": [ { "id": "client-secret-path", "kind": "kv_path", "template": "platform/workloads/keycape/forgejo", "body": "field CLIENT_SECRET; minted by the credential broker, never written by hand" }, { "id": "keycape-reader-policy", "kind": "openbao_policy", "template": "workload-kv-read-keycape-forgejo-eso", "body": "path \"platform/data/workloads/keycape/forgejo\" {\n capabilities = [\"read\"]\n}\n", "description": "KeyCape reads its client secret only through ESO. No metadata, list, parent or sibling access." }, { "id": "consumer-reader-policy", "kind": "openbao_policy", "template": "workload-kv-read-keycape-forgejo-forgejo", "body": "path \"platform/data/workloads/keycape/forgejo\" {\n capabilities = [\"read\"]\n}\n", "description": "The consuming service reads the same secret. No metadata, list, parent or sibling access." }, { "id": "eso-role", "kind": "openbao_role", "template": "external-secrets-keycape-forgejo", "body": "auth: kubernetes\nbound_service_account_names: [external-secrets]\nbound_service_account_namespaces: [external-secrets]\ntoken_policies: [workload-kv-read-keycape-forgejo-eso]\ntoken_no_default_policy: true\ntoken_ttl: 15m\ntoken_max_ttl: 15m\n", "description": "The Kubernetes-auth role deliver-secret uses; bound to the ESO service account only." }, { "id": "consumer-eso-role", "kind": "openbao_role", "template": "external-secrets-keycape-forgejo-forgejo", "body": "auth: kubernetes\nbound_service_account_names: [forgejo-keycape-forgejo-eso]\nbound_service_account_namespaces: [forgejo]\naudience: openbao\ntoken_policies: [workload-kv-read-keycape-forgejo-forgejo]\ntoken_no_default_policy: true\ntoken_ttl: 15m\ntoken_max_ttl: 15m\n", "description": "Consumer ESO requests a token for this separate reference identity. Common ESO controller remains trusted and can request tokens; no cryptographic controller isolation is claimed." }, { "id": "catalog-entry", "kind": "catalog_entry", "template": "forgejo-keycape-client", "body": "id: forgejo-keycape-client\nowner_repo: key-cape\nwarden_executes: false\nrisk: high\nstatus: active once verification passes\nworkload_ref: applicable, name forgejo, declaration_ref of the consumer's published declaration or the explicit unknown form, never inferred\npath_template: platform/workloads/keycape/forgejo\nfield: CLIENT_SECRET\ndelegation: native, intended_owner key-cape\nrotation: mint-secret then verification; automatable false until a run has been proven\n", "description": "One entry per case (ops-mason item 5, ops-warden R4'). It carries the consumer's workload reference, which ADR-0009 needs for zone selection." } ], "follow_through": [ { "id": "ensure-groups", "owner": "net-kingdom", "entry_point": "identity-provisioner ensure_service_groups, run inside the provisioner pod through the session's contained cluster access (groups forge-admins,forge-coulomb-owners,forge-coulomb-readers; members exactly forge-admins=tegwick;forge-coulomb-owners=tegwick as recorded in the accepted case, nothing else)", "auth": "operator cluster access in the attended register-client session; the in-pod call uses the provisioner's own directory binding, and no provisioner or user-engine token is read or sent", "lane": "key-cape-oidc-login", "warden_executes": false, "attended": true, "value_handling": "none", "attended_justification": "The provisioner's only HTTP credential is user-engine's runtime token, and its NetworkPolicy admits only user-engine pods. A proxy lane would act as user-engine (ops-warden review R8; canon section 3; ops-warden ADR-0002). Running it in the session register-client already needs adds no attended session. It can become unattended once the provisioner has an operator credential of its own and an admitted path." }, { "id": "mint-secret", "owner": "railiance-platform", "entry_point": "railiance-platform credential broker: generate a client secret inside custody at bao:platform/workloads/keycape/forgejo", "auth": "the caller's broker grant for the case's KV path, through its lane; the value is generated in custody and never returned", "lane": "keycape-client-secret-generate", "warden_executes": true, "attended": false, "value_handling": "generated_in_custody" }, { "id": "deliver-secret", "owner": "railiance-platform", "entry_point": "ExternalSecret sso/keycape-forgejo-client (key client-secret) from bao:platform/workloads/keycape/forgejo", "auth": "railiance-platform ESO delivery through role external-secrets-keycape-forgejo; no value leaves custody", "warden_executes": false, "attended": false, "value_handling": "none", "ready_check": "the delivery for sso/keycape-forgejo-client reports Ready=True (status only, no value read)" }, { "id": "bind-env", "owner": "net-kingdom", "entry_point": "patch Deployment sso/keycape: env KEYCAPE_FORGEJO_CLIENT_SECRET from sso/keycape-forgejo-client", "auth": "operator kube credentials for namespace sso", "lane": "key-cape-oidc-login", "warden_executes": false, "attended": true, "value_handling": "none", "attended_justification": "A change to the live identity provider's Deployment. It runs in the same attended operator session as register-client, before it, because KeyCape resolves a confidential client's secret at start and fails closed if the binding is missing." }, { "id": "register-client", "owner": "net-kingdom", "entry_point": "sso-mfa/k8s/keycape/keycape-client.py apply --client-file --case-acceptance --check-config (net-kingdom.keycape-client-registration)", "auth": "operator write access to the live KeyCape client registry in namespace sso", "lane": "key-cape-oidc-login", "warden_executes": false, "attended": true, "value_handling": "none", "attended_justification": "The live KeyCape client registry is a Kubernetes Secret, and agents do not read or write Secrets. Until KeyCape loads clients from a non-secret registry with secrets referenced from custody, one attended operator session applies the rendered registration. ops-warden groups all attended steps of a run into that one session. KeyCape loads clients at startup and keeps login, authorization and handoff state in memory, so the restart drops in-flight logins and sessions for every client; runs go in a quiet window (key-cape type review R4). The rendered registry must pass KeyCape's own startup checks (keycape check-config, key-cape c528daa, which keycape-client.py runs before it patches) before the restart: a refused entry stops the single replica and every client loses sign-in (R1)." }, { "id": "deliver-consumer-secret", "owner": "railiance-apps", "entry_point": "Owner applies dedicated ServiceAccount forgejo/forgejo-keycape-forgejo-eso (automount=false, no legacy tokens), namespace SecretStore openbao-keycape-forgejo-consumer with serviceAccountRef and audiences [openbao], and ExternalSecret forgejo-netkingdom-oidc-client with Owner target, Retain remote deletion and only CLIENT_SECRET from workloads/keycape/forgejo. Admission validates actual installed ESO TokenRequest support and common controller RBAC before any owner-approved apply.", "auth": "Consumer owner contained Kubernetes deployment authority; ESO authenticates to OpenBao role external-secrets-keycape-forgejo-forgejo with a TokenRequest for the dedicated namespaced serviceAccountRef; controller remains trusted", "warden_executes": false, "attended": false, "value_handling": "none", "ready_check": "Namespace SecretStore and ExternalSecret both report Ready=True; dedicated SA has no legacy token refs; status and fixed metadata only, never Secret values." }, { "id": "hand-over", "owner": "railiance-apps", "entry_point": "configure forgejo with issuer https://kc.coulomb.social, client forgejo and custody bao:platform/workloads/keycape/forgejo, and make it require one of forge-admins,forge-coulomb-owners,forge-coulomb-readers in the groups claim before sign-in completes", "auth": "the consumer owner's own deployment path", "warden_executes": false, "attended": false, "value_handling": "none" } ], "verification": [ { "id": "authorize-accepts-redirects", "check": "GET https://kc.coulomb.social/authorize with client forgejo and each of https://forgejo.coulomb.social/user/oauth2/netkingdom/callback returns the login page", "expect": "present", "reads_secret_value": false }, { "id": "authorize-refuses-other-redirect", "check": "GET https://kc.coulomb.social/authorize with client forgejo and an unregistered redirect returns an OAuth error", "expect": "deny", "reads_secret_value": false }, { "id": "custody-denied-to-default", "check": "a default-only OpenBao identity has no read capability on bao:platform/workloads/keycape/forgejo", "expect": "deny", "reads_secret_value": false }, { "id": "custody-allowed-to-consumer", "check": "policy workload-kv-read-keycape-forgejo-forgejo grants read on bao:platform/workloads/keycape/forgejo (capabilities check only)", "expect": "allow", "reads_secret_value": false }, { "id": "groups-present", "check": "LLDAP groups forge-admins,forge-coulomb-owners,forge-coulomb-readers exist, with exactly the memberships of the accepted case added", "expect": "present", "reads_secret_value": false }, { "id": "consumer-enforces-groups", "check": "forgejo's OIDC source for forgejo (owner railiance-apps) requires one of forge-admins,forge-coulomb-owners,forge-coulomb-readers in the groups claim (configuration read, no secret value)", "expect": "present", "reads_secret_value": false }, { "id": "secret-delivered", "check": "the delivery for sso/keycape-forgejo-client reports Ready=True (status only)", "expect": "present", "reads_secret_value": false }, { "id": "mfa-pinned", "check": "keycape-client.py inspect for forgejo shows the MFA pin on the registered client (non-secret client view)", "expect": "present", "reads_secret_value": false }, { "id": "consumer-secret-delivered", "check": "forgejo SecretStore openbao-keycape-forgejo-consumer and ExternalSecret forgejo-netkingdom-oidc-client Ready=True, exact dedicated SA/audience role binding, fixed CLIENT_SECRET mapping; no Secret value read", "expect": "present", "reads_secret_value": false } ], "rollback": [ { "reverses": "hand-over", "action": "remove the OIDC source for forgejo from forgejo (owner railiance-apps)" }, { "reverses": "deliver-consumer-secret", "action": "After reverting consumer secret reference, delete only authenticated created ExternalSecret/owned target by UID and ownerReference, then SecretStore and dedicated SA; preserve preexisting-matched resources and never delete a shared target. Retain audit and ownership receipts; removing SecretStore/SA alone is not immediate OpenBao token revocation." }, { "reverses": "register-client", "action": "keycape-client.py apply --operation remove for forgejo" }, { "reverses": "bind-env", "action": "remove env KEYCAPE_FORGEJO_CLIENT_SECRET from Deployment sso/keycape, after register-client is reversed" }, { "reverses": "deliver-secret", "action": "delete ExternalSecret sso/keycape-forgejo-client and its target" }, { "reverses": "mint-secret", "action": "covered by client-secret-path: one metadata delete, after deliver-secret is reversed" }, { "reverses": "ensure-groups", "action": "identity-provisioner revoke_service_groups inside the provisioner pod (attended, as ensure-groups) with the case's memberships; delete_if_empty only if no other case uses forge-admins,forge-coulomb-owners,forge-coulomb-readers" }, { "reverses": "client-secret-path", "action": "delete metadata and all versions at platform/workloads/keycape/forgejo, after the ExternalSecret is gone (this also reverses mint-secret)" }, { "reverses": "eso-role", "action": "delete Kubernetes-auth role external-secrets-keycape-forgejo" }, { "reverses": "keycape-reader-policy", "action": "delete policy workload-kv-read-keycape-forgejo-eso, after eso-role" }, { "reverses": "consumer-eso-role", "action": "Delete only this case-created role external-secrets-keycape-forgejo-forgejo, before deleting consumer-reader-policy; revoke case-issued OpenBao tokens or prove expiration before declaring deactivation." }, { "reverses": "consumer-reader-policy", "action": "delete policy workload-kv-read-keycape-forgejo-forgejo" }, { "reverses": "catalog-entry", "action": "remove catalog entry forgejo-keycape-client" } ], "execution": { "permitted": false }, "digest": "sha256:c0afd5576cdae0cbc807eccb2b9fa87ac668f15e32ec7cf79d295bdb23c75b35" }