Repository navigation
feat(auth): emit OCSF events for gateway authorization denials - #4728
Conversation
|
🌿 Preview your docs: https://nvidia-preview-pr-4728.docs.buildwithfern.com/openshell |
56c9019 to
37d4e7d
Compare
|
@krishicks Did some spelunking with my agent and it got caught up on an issue about the container profile not being included, which sounds okay to me. Here's what I got: Nonblocking question: The new gateway finding correctly omits container information. Existing Windows MXC findings use the same builder with sandbox context, though, so they still emit a This does not affect correlation, and I have not demonstrated a downstream consumer failure, so I would not hold up this PR on this point. |
|
Otherwise this makes sense to me |
|
Label |
When the gateway refuses a caller that has already authenticated, it now emits an OCSF API Activity event. The caller gets exactly the same error as before. The event records: - Who: the principal (user, mTLS operator, sandbox, or gateway peer). - What: the gRPC method, plus the sandbox or workspace that was refused when there is one. - Why: a reason such as cross_sandbox, missing_role or not_workspace_member. - Correlation: the caller's address and request ID, the same fields the authentication events carry. That covers the denials made centrally by the router and the shared guards, plus the checks inside handlers that have no shared guard: a sandbox naming another sandbox (in either sandbox-name resolver), a non-user caller refused by mutation admission, a sandbox sending a config update it isn't allowed to, and a non-admin granting the workspace admin role. Denials raised in the mutation admission worker, which runs in a spawned task, keep the request's context. When a sandbox credential is refused for calling a method it may not call, touching another sandbox, or acting across workspaces, the gateway also emits a High-severity Detection Finding. It carries the same correlation_uid as the denial event. A real supervisor never makes these requests, so a finding means the credential is probably being misused. Two cases are recorded without a finding: a method the gateway doesn't recognize (unknown_method), so a newer supervisor doesn't look like a compromised one, and a supervisor whose own sandbox was deleted mid-request, which is refused but isn't misuse. The shorthand line names the refused sandbox or workspace, for example sandbox:<id>, so operators reading console logs can see the target without JSONL or OTLP export. To support this, the OCSF crate's Actor can now hold a user instead of a process, and the API Activity builder gained the fields gateway events need. Supervisor inference events come out unchanged. Gateway events declare the container profile only when they concern a sandbox, as MXC findings do. The OCSF 1.8.0 resource_details schema is vendored so the refused sandbox or workspace goes in the standard resources field. Refs #2912 Signed-off-by: Kris Hicks <khicks@nvidia.com>
37d4e7d to
5a19aef
Compare
Summary
When the gateway refuses a caller that has already authenticated, it now emits an OCSF API Activity event. The caller gets exactly the same error as before. The event records:
cross_sandbox,missing_roleornot_workspace_member.That covers the denials made centrally by the router and the shared guards, plus the checks inside handlers that have no shared guard: a sandbox naming another sandbox (in either sandbox-name resolver), a non-user caller refused by mutation admission, a sandbox sending a config update it isn't allowed to, and a non-admin granting the workspace admin role. Denials raised in the mutation admission worker, which runs in a spawned task, keep the request's context.
When a sandbox credential is refused for calling a method it may not call, touching another sandbox, or acting across workspaces, the gateway also emits a High-severity Detection Finding. It carries the same
correlation_uidas the denial event, which covers the two "dual-event" criteria in #2912. A real supervisor never makes these requests, so a finding means the credential is probably being misused. Two cases are recorded without a finding: a method the gateway doesn't recognize (unknown_method), so a newer supervisor doesn't look like a compromised one, and a supervisor whose own sandbox was deleted mid-request, which is refused but isn't misuse.The shorthand line names the refused sandbox or workspace, for example
sandbox:<id>, so operators reading console logs can see the target without JSONL or OTLP export.To support this, the OCSF crate's
Actorcan now hold a user instead of a process, and the API Activity builder gained the fields gateway events need. Supervisor inference events come out unchanged. The OCSF 1.8.0resource_detailsschema is vendored so the refused sandbox or workspace goes in the standardresourcesfield.Related Issue
Refs #2912
This is the fourth of several PRs that together satisfy #2912, so it does not close the issue. The remaining work is listed under Follow-ups.
Changes
Where denials are recorded. Each denial is emitted where it is decided:
AuthzPolicy::checknow returns anRbacDenialthat says which one failed.ensure_sandbox_scope), supervisor-only methods, operator-only methods, workspace membership and role, and platform admin.resolve_and_authorize_sandbox_name, used by over 30 handlers, andSubmitPolicyAnalysis's own resolver), a non-user caller sending arequest_idto mutation admission, a sandbox caller sending a non-syncUpdateConfig, and a non-platform-admin assigning the workspace admin role. Both name resolvers distinguish a deleted caller record from a name that belongs to another sandbox, so deletion races don't raise findings.PushSandboxLogsnow runs the sandbox-scope guard on every batch, so a stream that switches to another sandbox's ID after its first batch is recorded.The router runs each handler inside a task-local scope holding the request path and origin, so guards record them without new parameters at their ~80 call sites.
audit::in_current_requestcarries that scope into the mutation admission worker's spawned task.Event contents.
actor.useris the principal:User(OIDC subject),Admin(mTLS operator),Sandbox(sandbox ID), orGateway Peer(replica ID).api.operationandapi.servicecome from the gRPC path.resourcesnames the sandbox or workspace, when one applies: a sandbox ID goes inuid, and a sandbox or workspace name goes inname. Request-supplied identifiers are capped at 128 bytes.status_detailis one ofmethod_not_permitted,unknown_method,missing_role,missing_scope,cross_sandbox,not_workspace_member,insufficient_workspace_role,platform_admin_required, oroperator_required.src_endpointandmetadata.correlation_uidmatch the authentication events.Findings. A sandbox principal refused with
method_not_permitted,cross_sandbox, orplatform_admin_requiredalso emits a Detection Finding (sandbox-authorization-boundary). Supervisors never make these requests. When the request has no usablex-request-id, the pair shares a generated ID.Not-found responses. Methods that answer a refused request with "not found", to avoid revealing which resources exist, still record the denial.
openshell-ocsf:Actorcan carry auserinstead of aprocess, and both fields are now optional.ApiActivityBuildergainsactor_user,src_endpoint,action/disposition,status_detail,correlation_uid,api_service, andresource. Supervisor inference events are unchanged.containerprofile only when they carry a container, that is when they concern a sandbox. Authorization denials don't; MXC findings still do.ResourceDetailsobject (byuidor byname), with the 1.8.0resource_detailsschema vendored.API:OTHER [MED] DENIED sandbox:<id> from <peer> [op:<path> <resource-type>:<id> reason:<category>].Removed. The plain
info!cross-sandbox log line, now replaced by the OCSF event.Docs.
ocsf-json-export.mdxdocuments the event, the reason table, and the finding.logging.mdxadds theAPI:prefix and an example.Events never include credentials, request bodies, or headers other than the canonical request ID.
Testing
cargo nextest run -p openshell-server -p openshell-ocsf: all 2,594 tests pass.cargo clippy --workspace --all-targets -- -D warningsunknown_methodand no finding.cross_sandboxdenial and a finding.request_idto mutation admission emits a denial and a finding.openshell-supervisor-network'smediated_connect_keeps_workload_bytes_read_with_the_synthesized_headerfails locally onmainas well. This PR doesn't cause it.Follow-ups
IssueSandboxToken. The principal-type and runtime-identity checks insideIssueSandboxToken,RefreshSandboxToken, andGetCurrentUsermove with them, as does the "extension service not selected by the sandbox policy" refusal inRefreshSandboxToken.Checklist