Policy reference¶
This page is the source-checked Cedar reference. The compatibility
policy.md route retains the explanatory guide and old anchors;
this page records the vocabulary, action names, and context contract from
crates/gdsgate-policy/src/schema.cedarschema.
Action map¶
Every action below is declared in the schema and has a production call site. The action string is case-sensitive. An action absent from this list is not a supported policy action.
connect kubeRequest dbQuery mcpCallTool mcpReadResource mcpGetPrompt llmCall httpRequest viewTools view enrollNode revokeNode manageCatalog disableUser issueJoinToken viewNodes viewAudit rotateCA editPolicy approvePolicyEdit viewRecordings approveRequest killSession sshForwardLocal sshForwardRemote sshForwardAgent mintOnwardSshCert
Action matrix table¶
The table uses a compact delta notation. Every row has the common required context mfa_satisfied:Bool, mfa_age_seconds:Long, step_up_satisfied:Bool, step_up_age_seconds:Long, totp_satisfied:Bool, totp_age_seconds:Long, webauthn_satisfied:Bool, webauthn_age_seconds:Long, webauthn_uv_satisfied:Bool, in_corp_vpn:Bool, timestamp:Long, hour:Long, weekday:Long, ticket_open:Bool, ticket_id:String, and recheck_confirmed:Bool. Every row may also receive optional src_ip:ipaddr; code must guard it with context has src_ip because derived credentials do not have a client address. The required and optional columns below are only the action-specific delta. A dash means that the action adds no field. Struct fields use the exact Cedar shape from the marker immediately below this table.
| Action | Principal -> resource | Required context delta | Optional context delta | Catalog / target prerequisite | Audit evidence | Tested request + task |
|---|---|---|---|---|---|---|
| connect | User, Agent -> SshHost, Database, KubernetesCluster, TcpService, McpServer, WebApp, WebEgress | presented:String | db_role:String; approved_request; step_up_grant | A served catalog row of the listed kind; a Database child inherits its catalogued host | policy.decision POL-0101; admitted sessions emit session.open and session.close | policy_examples::developer_may_ssh_non_prod; one connect |
| kubeRequest | User, Agent -> KubernetesCluster | verb:String; resource_kind:String; namespace:String; resource_name:String; subresource:String; read_only:Bool | approved_request; step_up_grant | The cluster must resolve to a served KubernetesCluster catalog row | Denial is policy.decision POL-0201; an allowed request emits kube.request SES-0501 | tenant_baseline::idp_admins_get_the_working_set; Kubernetes request authorization |
| dbQuery | User, Agent -> Database | sql_category:String (read, write, ddl, dcl, or other) | db_role; approved_request | The database host must be served; a nested database is resolved through its parent host | POL-0202 is evaluated by category at session open; a refused statement emits db.query SES-0301 | session_role_live::restricted_session_blocks_writes_and_evasions; per-statement authorization |
| mcpCallTool | User, Agent -> Tool | - | approved_request; step_up_grant | The parent McpServer must be catalogued; the transient Tool child supplies its name and destructive flag | Denial is policy.decision POL-0203; an allowed call emits mcp.tool_call MCP-0101 | proxy_mcp_decider::every_data_path_is_put_to_the_decider; MCP per-tool policy |
| mcpReadResource | User, Agent -> McpResource | - | approved_request; step_up_grant | The parent McpServer must be catalogued; the URI is a transient McpResource child | Denial is policy.decision POL-0204; an allowed read emits mcp.resource_read MCP-0102 | proxy_mcp_decider::every_data_path_is_put_to_the_decider; MCP per-tool policy |
| mcpGetPrompt | User, Agent -> McpPrompt | - | approved_request; step_up_grant | The parent McpServer must be catalogued; the prompt name is a transient McpPrompt child | Denial is policy.decision POL-0205; an allowed get emits mcp.prompt_get MCP-0103 | proxy_mcp_decider::every_data_path_is_put_to_the_decider; MCP per-tool policy |
| llmCall | User, Agent -> LlmModel | - | approved_request; step_up_grant | The parent LlmService must be catalogued; LlmModel is a transient child resolved under that service | Denial is policy.decision POL-0206; an allowed call emits llm.call SES-0401 | authorize_llm_call::a_permitted_model_is_allowed; model calls |
| httpRequest | User, Agent -> WebApp, WebEgress | method:String; host:String; port:Long; route:String; path:String; read_only:Bool; inspected:Bool; presented:String; upgrade:String | approved_request; step_up_grant | The web endpoint must resolve to a served WebApp or WebEgress row | Denial is policy.decision POL-0207; allowed traffic emits http.request or http.tunnel SES-0701/SES-0702 | proxy_tcp_mcp::a_browser_opens_a_real_page_through_the_web_lane; HTTP routes |
| viewTools | User, Agent -> Tool | - | - | The listing is filtered from the catalogued MCP server inventory; the transient tool is not a grant target | No audit row for a listing filter by design; POL-0302 is the schema action identity | tenant_baseline::destructive_tool_is_refused_even_for_admins; listing visibility |
| view | User, Agent -> SshHost, Database, KubernetesCluster, TcpService, McpServer, LlmService, WebApp, WebEgress | - | - | The catalog listing is the source of visible resources; no backend string bypasses it | No audit row for a listing filter by design; POL-0301 is the schema action identity | policy_examples::view_allows_non_prod_resource; catalog visibility |
| enrollNode | User -> EnrolledNode | - | - | Imports a leaf into the enrolled-node registry; it does not require a data-plane catalog row | Admission denial is enroll.node_admission IDN-0401; successful import is identity.import IDN-0301 | tenant_baseline::admin_certificate_identity_runs_control_plane_actions; registration lifecycle |
| revokeNode | User -> EnrolledNode | - | - | The target must be an enrolled identity; workload revocation also removes its registry row | Admission denial is identity.revoke_admission IDN-0402; effects emit identity.revoke IDN-0302 or identity.delete IDN-0303 | admin_rpc::admin_can_create_token_list_and_revoke_through_grpc; per-node revocation |
| manageCatalog | User -> SshHost, Database, KubernetesCluster, TcpService, McpServer, LlmService, Catalog | - | - | This action creates, changes, removes, or reads catalog rows; Catalog::all is the collection handle | catalog.manage CAT-0101 records allow and deny outcomes | catalog_admin_rpc::catalog_write_is_refused_when_policy_denies; resource catalog lifecycle |
| disableUser | User -> User | - | - | The user and owner delegations are identity state, not catalog resources | Owner cascade emits delegation.revoke_by_owner DLG-0201; schema code POL-0405 is not a separate policy row | four_actions_that_write_no_decision::the_disable_user_decision_arrives_as_the_cascade_it_gates; administrative actions |
| issueJoinToken | User -> EnrolledNode | - | step_up_grant | The target is a positive role allowlist: gdsgate-connector, gdsgate-proxy, or gdsgate-workload for the provisioner and people baseline | Denial is enroll.token_admission IDN-0403; creation emits enroll.token.create IDN-0101 | spending_a_one_time_grant::minting_a_join_token_costs_a_ceremony_each_time; bootstrap tokens |
| viewNodes | User -> EnrolledNode | - | - | Registry-wide handle EnrolledNode::*; no catalog row is needed | A denied listing emits view_nodes.admission IDN-0404; an allowed listing has no row | catalog_admin_rpc::the_listing_is_gated_on_view_nodes; registration lifecycle |
| viewAudit | User -> AuditLog | - | - | Chain handle AuditLog::chain; it reads the audit store rather than a served backend | audit.view AUD-0101 records both allow and deny | admin_through_gateway::a_person_granted_the_chain_reads_it_and_the_reading_is_on_it; audit export |
| rotateCA | User -> CertificateAuthority | - | step_up_grant | The authority is a control-plane CA object; mTLS and emergency paths do not supply a factor automatically | ca.rotate CAS-0101 records the outcome | step_up_on_administrative_actions::rotate_ca_denies_without_a_key_confirmation_and_allows_after_one; CA rotation |
| editPolicy | User -> PolicyDoc | - | - | The singleton policy document is the target; the break-glass permit is checked before a push | policy.edit PED-0101 records the proposal outcome | step_up_on_operator_actions::edit_policy_denies_without_a_step_up_and_allows_after_one; policy lifecycle |
| approvePolicyEdit | User -> PolicyDoc | - | step_up_grant | The target is a pending policy proposal; approval requires the separate approver lane and min_approvers >= 2 | policy.edit_approve PED-0102 records each vote/outcome | step_up_on_operator_actions::approve_policy_edit_denies_without_a_step_up_and_allows_after_one; policy lifecycle |
| viewRecordings | User -> AuditLog | recording_session_uid:String; recording_actor:String; recording_target:String; recording_kind:String | step_up_grant | Recording metadata is in the audit store; list requests use empty strings, row requests use actual values | recordings.view REC-0101 records allow and deny, including per-row gates | spending_a_one_time_grant::reading_one_recording_costs_a_ceremony_each_time; audit export |
| approveRequest | User -> AccessRequest | - | step_up_grant | The access-request row names requester, action, target, and expiry; its target resource must also be catalog-served before use | access_request.approve JIT-0102 records each review | step_up_on_operator_actions::approving_an_elevation_denies_without_a_step_up_and_allows_after_one; JIT approvers |
| killSession | User -> Session | - | step_up_grant | The target must be a live session registry entry; it is not a catalog row | A real termination emits session.kill SES-0601; list visibility does not consume a grant or emit a row | data_plane::kill_session_terminates_a_live_relay_and_reports_it; session termination |
| sshForwardLocal | User, Agent -> SshHost | forward_target:{host:String,port:Long} | - | The SSH host must resolve in the catalog and the active session must allow local forwarding | policy.decision POL-0102 records allow or deny | a_decision_names_its_session::every_gate_inside_a_session_names_the_session; SSH -L/-R |
| sshForwardRemote | User, Agent -> SshHost | forward_bind:{host:String,port:Long} | - | The SSH host must resolve in the catalog; config and Cedar both constrain the bind address | policy.decision POL-0103 records allow or deny | No dedicated request test found; source-checked authorize_forward, not tested in this inventory; SSH -L/-R |
| sshForwardAgent | User, Agent -> SshHost | - | - | The SSH host must resolve in the catalog and the backend must set allow_agent_forward = true | policy.decision POL-0105 records allow or deny | agent_forward_is_a_decision::permission_to_forward_a_port_is_not_permission_to_forward_the_agent; SSH -A |
| mintOnwardSshCert | User, Agent -> SshHost | login_user:String | - | The SSH host must resolve in the catalog and the gateway CA mints the downstream certificate | policy.decision POL-0104 records allow or deny | authority_relay_failure_model::a_severed_control_plane_refuses_the_jump_host_session; downstream certificate minting |
The exact source serialization used by the standalone inventory check is below. Each row is action|principal types|resource types|required fields|optional fields.
connect|User,Agent|SshHost,Database,KubernetesCluster,TcpService,McpServer,WebApp,WebEgress|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool,presented:String|src_ip:ipaddr,db_role:String,approved_request:{expires:Long,for_action:String},step_up_grant:{method:String,for_action:String} kubeRequest|User,Agent|KubernetesCluster|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool,verb:String,resource_kind:String,namespace:String,resource_name:String,subresource:String,read_only:Bool|src_ip:ipaddr,approved_request:{expires:Long,for_action:String},step_up_grant:{method:String,for_action:String} dbQuery|User,Agent|Database|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool,sql_category:String|src_ip:ipaddr,db_role:String,approved_request:{expires:Long,for_action:String} mcpCallTool|User,Agent|Tool|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool|src_ip:ipaddr,approved_request:{expires:Long,for_action:String},step_up_grant:{method:String,for_action:String} mcpReadResource|User,Agent|McpResource|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool|src_ip:ipaddr,approved_request:{expires:Long,for_action:String},step_up_grant:{method:String,for_action:String} mcpGetPrompt|User,Agent|McpPrompt|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool|src_ip:ipaddr,approved_request:{expires:Long,for_action:String},step_up_grant:{method:String,for_action:String} llmCall|User,Agent|LlmModel|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool|src_ip:ipaddr,approved_request:{expires:Long,for_action:String},step_up_grant:{method:String,for_action:String} httpRequest|User,Agent|WebApp,WebEgress|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool,method:String,host:String,port:Long,route:String,path:String,read_only:Bool,inspected:Bool,presented:String,upgrade:String|src_ip:ipaddr,approved_request:{expires:Long,for_action:String},step_up_grant:{method:String,for_action:String} viewTools|User,Agent|Tool|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool|src_ip:ipaddr view|User,Agent|SshHost,Database,KubernetesCluster,TcpService,McpServer,LlmService,WebApp,WebEgress|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool|src_ip:ipaddr enrollNode|User|EnrolledNode|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool|src_ip:ipaddr revokeNode|User|EnrolledNode|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool|src_ip:ipaddr manageCatalog|User|SshHost,Database,KubernetesCluster,TcpService,McpServer,LlmService,Catalog|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool|src_ip:ipaddr disableUser|User|User|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool|src_ip:ipaddr issueJoinToken|User|EnrolledNode|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool|src_ip:ipaddr,step_up_grant:{method:String,for_action:String} viewNodes|User|EnrolledNode|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool|src_ip:ipaddr viewAudit|User|AuditLog|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool|src_ip:ipaddr rotateCA|User|CertificateAuthority|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool|src_ip:ipaddr,step_up_grant:{method:String,for_action:String} editPolicy|User|PolicyDoc|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool|src_ip:ipaddr approvePolicyEdit|User|PolicyDoc|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool|src_ip:ipaddr,step_up_grant:{method:String,for_action:String} viewRecordings|User|AuditLog|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool,recording_session_uid:String,recording_actor:String,recording_target:String,recording_kind:String|src_ip:ipaddr,step_up_grant:{method:String,for_action:String} approveRequest|User|AccessRequest|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool|src_ip:ipaddr,step_up_grant:{method:String,for_action:String} killSession|User|Session|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool|src_ip:ipaddr,step_up_grant:{method:String,for_action:String} sshForwardLocal|User,Agent|SshHost|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool,forward_target:{host:String,port:Long}|src_ip:ipaddr sshForwardRemote|User,Agent|SshHost|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool,forward_bind:{host:String,port:Long}|src_ip:ipaddr sshForwardAgent|User,Agent|SshHost|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool|src_ip:ipaddr mintOnwardSshCert|User,Agent|SshHost|mfa_satisfied:Bool,mfa_age_seconds:Long,step_up_satisfied:Bool,step_up_age_seconds:Long,totp_satisfied:Bool,totp_age_seconds:Long,webauthn_satisfied:Bool,webauthn_age_seconds:Long,webauthn_uv_satisfied:Bool,in_corp_vpn:Bool,timestamp:Long,hour:Long,weekday:Long,ticket_open:Bool,ticket_id:String,recheck_confirmed:Bool,login_user:String|src_ip:ipaddr
connect is the general resource session action. Protocol-specific request
gates such as kubeRequest, dbQuery, mcpCallTool, mcpReadResource,
mcpGetPrompt, llmCall, and httpRequest carry protocol context and do not
replace the base catalog decision. viewTools and view control catalog
visibility. Administrative actions target control-plane entities and remain
separate from data-plane sessions.
One connect, seven kinds of resource¶
The action applies to SshHost, KubernetesCluster, Database, McpServer,
TcpService, WebApp, and WebEgress resources. llmCall targets an
LlmModel child, whose catalogued parent is an LlmService. No connect
session is opened for an LlmService or LlmModel. The catalog kind and
parent relationships are resolved before Cedar evaluation; an arbitrary backend
string is not a resource identity.
kubeRequest and context.read_only¶
Kubernetes request policy receives the cluster resource and a read_only
boolean. A mutating request must not be treated as read-only because the HTTP
method is merely an input to the request gate. Use an explicit policy condition
for the boolean.
Credential type: context.presented¶
context.presented describes how the credential reached the gateway. Policies
can distinguish a user-presented identity from a machine or delegated path,
but the field does not prove that the principal has access. Pair it with the
principal, resource, and action.
Entity types¶
The schema uses structural, principal, target-resource, control-plane, and runtime entities. Required fields are deliberately typed so Cedar validation can reject an ill-typed policy before activation.
Organization|- Project|Organization Group|Group User|Group,Organization Agent|Group,Project SshHost|Project KubernetesCluster|Project Database|Project,Database McpServer|Project Tool|McpServer McpResource|McpServer McpPrompt|McpServer TcpService|Project WebApp|Project WebEgress|Project LlmService|Project LlmModel|LlmService Catalog|- EnrolledNode|- CertificateAuthority|- PolicyDoc|- AccessRequest|- AuditLog|- Session|-
Entity parent table¶
| Entity | Declared parents |
|---|---|
| Organization | - |
| Project | Organization |
| Group | Group |
| User | Group, Organization |
| Agent | Group, Project |
| SshHost | Project |
| KubernetesCluster | Project |
| Database | Project, Database |
| McpServer | Project |
| Tool | McpServer |
| McpResource | McpServer |
| McpPrompt | McpServer |
| TcpService | Project |
| WebApp | Project |
| WebEgress | Project |
| LlmService | Project |
| LlmModel | LlmService |
| Catalog | - |
| EnrolledNode | - |
| CertificateAuthority | - |
| PolicyDoc | - |
| AccessRequest | - |
| AuditLog | - |
| Session | - |
Reserved baseline groups¶
gdsgate-admin gdsgate-break-glass gdsgate-provisioner
| Group | Source role |
|---|---|
| gdsgate-admin | Control-plane mTLS identity |
| gdsgate-break-glass | Synthetic policy-edit invariant probe |
| gdsgate-provisioner | External provisioning mTLS identity |
Structural¶
Organization, Project, and nested Group provide grouping. A Project is
in an Organization; a Group can be nested in another Group.
Principals¶
User may carry email, org, and mfa_enrolled. Agent may carry
spiffe_id, project, kind, and owner. EnrolledNode is a control-plane
resource, not a principal. Optional principal attributes require guarded Cedar
access.
Target resources¶
SshHost, KubernetesCluster, Database, McpServer, Tool, McpResource,
McpPrompt, TcpService, WebApp, WebEgress, LlmService, and LlmModel
represent data-plane targets. Infrastructure resources carry required
environment; Database may carry engine. MCP child entities belong to an
MCP server.
Control-plane resources¶
Catalog, EnrolledNode, CertificateAuthority, PolicyDoc, and
AccessRequest represent administrative state. AuditLog is the audit record
resource used by audit visibility rules.
Runtime resources¶
Session represents a live or recorded session and may carry owner, resource,
kind, source IP, and start time. Runtime attributes describe observed state;
they do not create a catalog declaration.
Context fields per action¶
The schema declares context fields on each action. Runtime code supplies the
relevant subset; an action whose policy reads a missing required field evaluates
to deny. Optional src_ip and approved_request must be guarded with Cedar
has checks. Typical fields include environment, project, source address,
credential presentation, read-only mode, MCP tool metadata, database query
metadata, and approval state.
Use the action-specific declaration as the contract. Do not copy a field from one action into another without validating the complete Cedar schema. The schema tests reject unknown attributes and ill-typed policies, while the unwired-action test requires every declared action to have a production call site.
For the table shorthand, db_role means db_role:String, approved_request means approved_request:{expires:Long,for_action:String}, and step_up_grant means step_up_grant:{method:String,for_action:String}. The canonical marker remains the byte-for-byte inventory source for these shapes.
JIT approvals and one-time step-up grants¶
approved_request and step_up_grant are different controls. An approved_request is an active access request approval with an expiry and an exact principal/resource/action key. It can be used while active; it is not a general-purpose permit, and it is not supplied to listing actions such as viewTools. A listing must remain reachable before an operator can select an individual MCP, recording, request, or session target.
step_up_grant is one-time. The grant is spent only when every allowing rule needed for the decision names the grant marker. A denial does not spend it, and another permit that allows the operation without the marker does not spend it. The schema and authority code narrow the one-time key as follows:
| Lane | One-time grant scope |
|---|---|
| connect | The selected served resource |
| kubeRequest | The cluster; the Cedar rule must further narrow verb, kind, namespace, name, or subresource |
| mcpCallTool | The transient Tool child |
| mcpReadResource | The transient McpResource child |
| mcpGetPrompt | The transient McpPrompt child |
| llmCall | The transient LlmModel child |
| httpRequest | The WebApp or WebEgress resource; the rule must further narrow route or path |
| issueJoinToken | The EnrolledNode role named by the token |
| rotateCA | The CertificateAuthority |
| approvePolicyEdit | The pending proposal version |
| viewRecordings | The recording session UID; a list does not spend a grant |
| approveRequest | The AccessRequest ID |
| killSession | The live Session UID; listing visibility does not spend a grant |
The method is checked by the policy context and the grant is consumed by the authority only for the decisive operation. dbQuery has approved_request but no step_up_grant in the schema; its categories are enforced at database session open and by the connector query path. Workload requests in the Kubernetes lane do not receive an interactive ceremony. These rules are source-checked against the action comments in schema.cedarschema and the corresponding authority authorization functions.
Hierarchy and subresources¶
Cedar parent membership is part of the entity contract. Project is below Organization; User is below Group and Organization; Agent is below Group and Project. SshHost, KubernetesCluster, Database, TcpService, McpServer, WebApp, WebEgress, and LlmService are below Project. Database has Database as a nested parent so Database::host.database inherits the served host's catalog membership. Tool, McpResource, and McpPrompt are transient children of an McpServer. LlmModel is a transient child of an LlmService.
The LLM lane is therefore a call decision over LlmModel, not a connect decision over LlmService. The service must be catalogued before the model child can be resolved, and the model body is read only after the per-call policy decision. The same parent-resolution rule applies to MCP children and database subresources. Catalog::all, EnrolledNode::*, and AuditLog::chain are explicit collection handles for control-plane operations, not wildcard substitutes for data-plane catalog rows.
Reserved baseline capabilities¶
The shipped tenant baseline is deny-by-default and uses separate identities for recovery, provisioning, and people. These are capabilities of the source baseline, not an assertion that every tenant policy must copy them:
| Baseline identity | Source-checked capabilities and boundary |
|---|---|
| gdsgate-admin | enrollNode, revokeNode, disableUser, rotateCA, manageCatalog, issueJoinToken, viewNodes, and viewAudit; it never opens a data-plane session |
| gdsgate-provisioner | manageCatalog, viewNodes, and issueJoinToken only for gdsgate-connector, gdsgate-proxy, and gdsgate-workload; it cannot enroll, revoke, disable, rotate, edit policy, read recordings, kill sessions, or connect |
| admins | The people group can view and manage the working catalog, view nodes, revoke nodes, disable users, issue the three connector/proxy/workload tokens, and use the baseline data-plane grants; it cannot rotate the CA, enroll arbitrary identities, mint control-plane tokens, or use the SSH forwarding/onward-cert lanes |
| gdsgate-break-glass | A synthetic User used by the policy push invariant probe; it retains editPolicy on PolicyDoc::0 and is not a real client identity |
The gdsgate- namespace is reserved for cluster-populated machine groups. The baseline refuses an identity-provider token that tries to claim a new gdsgate-* group, and the source tests require every namespaced group named by the template to be one the cluster actually populates. gdsgate-admin and gdsgate-provisioner are mTLS identities, not human IdP groups.
Strict validation, missing fields, and forbids¶
Cedar validation rejects an unknown attribute or an ill-typed policy before activation. During evaluation, a policy that reads a required field not present in the action context raises a Cedar evaluation error and is skipped. With deny-by-default, skipping the matching permit leaves the request denied. An optional field is safe only behind a context has guard. The source tests schema_validation_rejects_ill_typed_policy, schema_validation_rejects_unknown_attribute, and connector_denied_for_unrelated_action_by_default exercise these boundaries.
Forbid has precedence over permit. The destructive MCP tool baseline proves that a forbid still denies an administrator even when a broader permit would otherwise allow mcpCallTool. The same deny-by-default rule means an unknown principal or unsupported action has no implicit access. The policy engine is not the catalog gate: authority resolves the resource first and refuses a withdrawn or unknown catalog resource before Cedar. The source test the_policy_engine_cannot_be_the_catalogue_gate pins that ordering.
Control-plane lanes¶
The audit registry distinguishes data-plane authorization from administrative operations. Eleven actions carry a policy.decision row directly: connect, sshForwardLocal, sshForwardRemote, sshForwardAgent, mintOnwardSshCert, kubeRequest, httpRequest, mcpCallTool, mcpReadResource, mcpGetPrompt, and llmCall. The control-plane actions enrollNode, revokeNode, issueJoinToken, viewNodes, manageCatalog, disableUser, rotateCA, editPolicy, approvePolicyEdit, approveRequest, viewAudit, viewRecordings, and killSession emit their own administration event and carry cedar_action in that event where the source registry specifies it.
dbQuery is intentionally different: the authority evaluates its five SQL categories at session open, records the categories with the connect decision, and the connector emits db.query for a refused statement. view and viewTools are listing filters. A successful filter is not an audited denial or a security event; a denied control-plane listing is still recorded where the action audit contract says so.
The catalog prerequisite applies before every data-plane row in the action table. A catalog withdrawal is a resource-not-served outcome, not a Cedar permit that happens to fail. This distinction prevents stale policy from resurrecting a backend.
Audit semantics¶
The policy audit code registry is the authority for codes and event names. policy.decision rows include cedar_action, code, deciding, deciding_at, session_uid, presented, and src_ip when a source address exists. Derived credentials therefore have no fabricated source IP. SessionActivity rows carry the operation-specific evidence: kube.request, db.query, MCP events, llm.call, http.request, and http.tunnel.
Control-plane rows use their own evidence codes: catalog.manage CAT-0101, delegation.revoke_by_owner DLG-0201, ca.rotate CAS-0101, policy.edit PED-0101, policy.edit_approve PED-0102, recordings.view REC-0101, access_request.approve JIT-0102, audit.view AUD-0101, and session.kill SES-0601. Node admission and identity effects use the IDN codes shown in the action table. The registry explicitly has no audit row for successful view or viewTools filtering and no separate dbQuery policy row per statement. Those are source-checked absences, not omissions from this reference.
Verification workflow¶
- Run
gdsgate authority policy validateagainst the candidate policy. - Compare action names and entity fields with this reference and the schema.
- Push only through the policy editor and approval workflow.
- Verify the policy version and audit event after activation.
A policy edit cannot grant an unsupported action, and a valid syntax parse does not prove that the intended principal or resource is in scope.