Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion crates/openshell-driver-podman/src/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ use openshell_core::{driver_mounts, proto_struct};
use serde::Serialize;
use serde_json::Value;
use std::collections::{BTreeMap, HashSet};
#[cfg(target_os = "linux")]
use std::path::Path;

/// Returns `true` when `SELinux` is enabled (enforcing or permissive).
Expand Down
28 changes: 26 additions & 2 deletions crates/openshell-supervisor-middleware/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,8 @@ pub enum TransformedBodyPolicy<'a> {
pub struct HttpRequestInput {
pub request_id: String,
pub sandbox_id: String,
pub sandbox_name: String,
pub workspace: String,
pub scheme: String,
pub host: String,
pub port: u16,
Expand Down Expand Up @@ -1581,6 +1583,8 @@ impl ChainRunner {
let HttpRequestInput {
request_id,
sandbox_id,
sandbox_name,
workspace,
scheme,
host,
port,
Expand All @@ -1597,6 +1601,8 @@ impl ChainRunner {
let context = RequestContext {
request_id,
sandbox_id,
sandbox_name,
workspace,
originating_process: None,
};
let target = HttpRequestTarget {
Expand Down Expand Up @@ -2047,7 +2053,9 @@ mod tests {
fn input(body: &str) -> HttpRequestInput {
HttpRequestInput {
request_id: "req".into(),
sandbox_id: "sbx".into(),
sandbox_id: "sbx-id".into(),
sandbox_name: "sbx-name".into(),
workspace: "wrks-default".into(),
scheme: "https".into(),
host: "api.example.com".into(),
port: 443,
Expand Down Expand Up @@ -3215,7 +3223,9 @@ mod tests {
assert_eq!(received[0].config.as_ref(), Some(&evaluation_config));
let context = received[0].context.as_ref().expect("request context");
assert_eq!(context.request_id, "req");
assert_eq!(context.sandbox_id, "sbx");
assert_eq!(context.sandbox_id, "sbx-id");
assert_eq!(context.sandbox_name, "sbx-name");
assert_eq!(context.workspace, "wrks-default");
assert!(context.originating_process.is_none());
let target = received[0].target.as_ref().expect("request target");
assert_eq!(target.scheme, "https");
Expand Down Expand Up @@ -4678,6 +4688,8 @@ mod tests {
session_id: session_id.into(),
request_id: "request".into(),
sandbox_id: "sandbox".into(),
sandbox_name: "sandbox-name".into(),
workspace: "wrks-default".into(),
scheme: "wss".into(),
host: "api.openai.com".into(),
port: 443,
Expand Down Expand Up @@ -5111,6 +5123,8 @@ mod tests {
session_id: "builtin-regex-session".into(),
request_id: "request".into(),
sandbox_id: "sandbox".into(),
sandbox_name: "sandbox-name".into(),
workspace: "wrks-default".into(),
scheme: "wss".into(),
host: "api.openai.com".into(),
port: 443,
Expand Down Expand Up @@ -5177,6 +5191,8 @@ mod tests {
session_id: "builtin-regex-gap-session".into(),
request_id: "request".into(),
sandbox_id: "sandbox".into(),
sandbox_name: "sandbox-name".into(),
workspace: "wrks-default".into(),
scheme: "wss".into(),
host: "api.openai.com".into(),
port: 443,
Expand Down Expand Up @@ -5239,6 +5255,8 @@ mod tests {
session_id: "in-process-session".into(),
request_id: "request".into(),
sandbox_id: "sandbox".into(),
sandbox_name: "sandbox-name".into(),
workspace: "wrks-default".into(),
scheme: "wss".into(),
host: "api.openai.com".into(),
port: 443,
Expand Down Expand Up @@ -5310,6 +5328,8 @@ mod tests {
session_id: "ws-session".into(),
request_id: "request".into(),
sandbox_id: "sandbox".into(),
sandbox_name: "sandbox-name".into(),
workspace: "wrks-default".into(),
scheme: "wss".into(),
host: "api.openai.com".into(),
port: 443,
Expand Down Expand Up @@ -5394,6 +5414,8 @@ mod tests {
session_id: "ws-session".into(),
request_id: "request".into(),
sandbox_id: "sandbox".into(),
sandbox_name: "sandbox-name".into(),
workspace: "wrks-default".into(),
scheme: "ws".into(),
host: "api.openai.com".into(),
port: 80,
Expand Down Expand Up @@ -5689,6 +5711,8 @@ mod tests {
session_id: "session".into(),
request_id: "request".into(),
sandbox_id: "sandbox".into(),
sandbox_name: "sandbox-name".into(),
workspace: "wrks-default".into(),
scheme: "wss".into(),
host: "api.openai.com".into(),
port: 443,
Expand Down
4 changes: 4 additions & 0 deletions crates/openshell-supervisor-middleware/src/websocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ pub struct WebSocketPreflightInput {
pub session_id: String,
pub request_id: String,
pub sandbox_id: String,
pub sandbox_name: String,
pub workspace: String,
pub scheme: String,
pub host: String,
pub port: u16,
Expand Down Expand Up @@ -920,6 +922,8 @@ async fn open_stage(entry: DescribedChainEntry, input: WebSocketPreflightInput)
context: Some(RequestContext {
request_id: input.request_id,
sandbox_id: input.sandbox_id,
sandbox_name: input.sandbox_name,
workspace: input.workspace,
originating_process: None,
}),
target: Some(HttpRequestTarget {
Expand Down
55 changes: 54 additions & 1 deletion crates/openshell-supervisor-network/src/l7/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ pub async fn apply_middleware_chain_for_scheme<C: AsyncRead + AsyncWrite + Unpin
// unresolved binding is handled before the body is read) and forward
// the original request unchanged if the chain allows.
let input = middleware_request_input(
openshell_ocsf::ctx::ctx(),
scheme,
&req,
ctx,
Expand Down Expand Up @@ -525,6 +526,7 @@ pub async fn apply_middleware_chain_for_scheme<C: AsyncRead + AsyncWrite + Unpin
let headers = safe_middleware_headers(&buffered.headers)?;
let query = raw_query_from_request_headers(&buffered.headers)?;
let input = middleware_request_input(
openshell_ocsf::ctx::ctx(),
scheme,
&req,
ctx,
Expand Down Expand Up @@ -607,7 +609,9 @@ pub async fn send_middleware_admission_exhausted_response<
.await
}

#[allow(clippy::too_many_arguments)]
pub(super) fn middleware_request_input(
sandbox: &openshell_ocsf::SandboxContext,
scheme: &str,
req: &crate::l7::provider::L7Request,
ctx: &L7EvalContext,
Expand All @@ -618,7 +622,9 @@ pub(super) fn middleware_request_input(
) -> openshell_supervisor_middleware::HttpRequestInput {
openshell_supervisor_middleware::HttpRequestInput {
request_id: uuid::Uuid::new_v4().to_string(),
sandbox_id: openshell_ocsf::ctx::ctx().sandbox_id.clone(),
sandbox_id: sandbox.sandbox_id.clone(),
sandbox_name: sandbox.sandbox_name.clone(),
workspace: ctx.workspace.clone(),
scheme: scheme.into(),
host: ctx.host.clone(),
port: ctx.port,
Expand Down Expand Up @@ -1079,6 +1085,53 @@ mod tests {
assert!(!body.to_string().contains("secret-value"));
}

#[test]
fn middleware_input_carries_real_sandbox_name() {
let sandbox = openshell_ocsf::SandboxContext {
sandbox_id: "sbx-123".into(),
sandbox_name: "nightly-build".into(),
container_image: String::new(),
hostname: "h".into(),
product_version: "0".into(),
proxy_ip: [127, 0, 0, 1].into(),
proxy_port: 3128,
};

let eval = L7EvalContext {
host: "api.example.test".into(),
port: 443,
workspace: "wrks-default".into(),
policy_name: "api-policy".into(),
binary_path: "/usr/bin/curl".into(),
ancestors: Vec::new(),
cmdline_paths: Vec::new(),
secret_resolver: None,
..Default::default()
};
let req = crate::l7::provider::L7Request {
action: "POST".into(),
target: "/v1/messages".into(),
query_params: std::collections::HashMap::new(),
raw_header: Vec::new(),
body_length: crate::l7::provider::BodyLength::None,
};

let input = super::middleware_request_input(
&sandbox,
"https",
&req,
&eval,
Vec::new(),
Vec::new(),
String::new(),
Vec::new(),
);

assert_eq!(input.sandbox_name, "nightly-build");
assert_eq!(input.sandbox_id, "sbx-123");
assert_eq!(input.workspace, "wrks-default");
}

#[tokio::test]
async fn middleware_failure_uses_platform_response_without_policy_guidance() {
let ctx = L7EvalContext {
Expand Down
90 changes: 75 additions & 15 deletions crates/openshell-supervisor-network/src/l7/relay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ pub struct L7EvalContext {
pub host: String,
/// Port from the CONNECT request.
pub port: u16,
/// Workspace the sandbox belongs to, learned from `GetSandboxConfigResponse`.
pub workspace: String,
/// Default authority port for the inspected HTTP transport (80 for
/// plaintext, 443 after TLS termination).
pub(crate) request_default_port: Option<u16>,
Expand Down Expand Up @@ -983,21 +985,39 @@ pub(crate) async fn websocket_middleware_preflight(
.map_or(req.raw_header.len(), |position| position + 4);
let requested_subprotocols =
crate::l7::rest::websocket_requested_subprotocols(&req.raw_header[..header_end])?;
runner
.preflight_websocket(
chain,
openshell_supervisor_middleware::WebSocketPreflightInput {
session_id: uuid::Uuid::new_v4().to_string(),
request_id: uuid::Uuid::new_v4().to_string(),
sandbox_id: openshell_ocsf::ctx::ctx().sandbox_id.clone(),
scheme: scheme.to_string(),
host: ctx.host.clone(),
port: ctx.port,
path: req.target.clone(),
requested_subprotocols,
},
)
.await
let input = websocket_preflight_input(
openshell_ocsf::ctx::ctx(),
ctx,
req,
scheme,
requested_subprotocols,
);
runner.preflight_websocket(chain, input).await
}

/// Build the WebSocket preflight input from the sandbox and evaluation
/// contexts. Kept separate from `websocket_middleware_preflight` (and taking an
/// explicit `SandboxContext`) so the identifier copy is unit-testable with a
/// real sandbox name, mirroring `middleware_request_input` on the HTTP path.
fn websocket_preflight_input(
sandbox: &openshell_ocsf::SandboxContext,
ctx: &L7EvalContext,
req: &crate::l7::provider::L7Request,
scheme: &str,
requested_subprotocols: Vec<String>,
) -> openshell_supervisor_middleware::WebSocketPreflightInput {
openshell_supervisor_middleware::WebSocketPreflightInput {
session_id: uuid::Uuid::new_v4().to_string(),
request_id: uuid::Uuid::new_v4().to_string(),
sandbox_id: sandbox.sandbox_id.clone(),
sandbox_name: sandbox.sandbox_name.clone(),
workspace: ctx.workspace.clone(),
scheme: scheme.to_string(),
host: ctx.host.clone(),
port: ctx.port,
path: req.target.clone(),
requested_subprotocols,
}
}

/// Handle an upgraded connection (101 Switching Protocols).
Expand Down Expand Up @@ -2832,6 +2852,45 @@ mod tests {
(state, resolver)
}

#[test]
fn websocket_preflight_input_carries_real_sandbox_name() {
let sandbox = openshell_ocsf::SandboxContext {
sandbox_id: "sbx-123".into(),
sandbox_name: "nightly-build".into(),
container_image: String::new(),
hostname: "h".into(),
product_version: "0".into(),
proxy_ip: [127, 0, 0, 1].into(),
proxy_port: 3128,
};

let eval = L7EvalContext {
host: "api.example.test".into(),
port: 443,
workspace: "team-a".into(),
policy_name: "api-policy".into(),
binary_path: "/usr/bin/curl".into(),
ancestors: Vec::new(),
cmdline_paths: Vec::new(),
secret_resolver: None,
..Default::default()
};
let req = crate::l7::provider::L7Request {
action: "GET".into(),
target: "/v1/stream".into(),
query_params: std::collections::HashMap::new(),
raw_header: Vec::new(),
body_length: crate::l7::provider::BodyLength::None,
};

let input =
websocket_preflight_input(&sandbox, &eval, &req, "wss", vec!["chat".to_string()]);

assert_eq!(input.sandbox_id, "sbx-123");
assert_eq!(input.sandbox_name, "nightly-build");
assert_eq!(input.workspace, "team-a");
}

#[test]
fn scoped_context_captures_endpoint_resolver_and_revision_together() {
let state = ProviderCredentialState::from_bound_environment(
Expand Down Expand Up @@ -5870,6 +5929,7 @@ network_policies:
};

let input = middleware_request_input(
openshell_ocsf::ctx::ctx(),
"http",
&req,
&ctx,
Expand Down
Loading
Loading