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
97 changes: 94 additions & 3 deletions crates/sidecar/src/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ use crate::protocol::{
KillProcessRequest, ListenerSnapshotResponse, OwnershipScope, ProcessExitedEvent,
ProcessKilledResponse, ProcessOutputEvent, ProcessSnapshotEntry, ProcessSnapshotResponse,
ProcessSnapshotStatus, ProcessStartedResponse, RequestFrame, ResponsePayload,
SidecarRequestPayload, SignalDispositionAction, SignalHandlerRegistration, SignalStateResponse,
SocketStateEntry, StdinClosedResponse, StdinWrittenResponse, StreamChannel, WasmPermissionTier,
SidecarRequestPayload, SidecarResponsePayload, SignalDispositionAction,
SignalHandlerRegistration, SignalStateResponse, SocketStateEntry, StdinClosedResponse,
StdinWrittenResponse, StreamChannel, TransformHttpRequest, WasmPermissionTier,
WriteStdinRequest, ZombieTimerCountResponse,
};
use crate::service::{
Expand Down Expand Up @@ -5323,6 +5324,17 @@ where
let resource_limits = vm.kernel.resource_limits().clone();
let network_counts = vm_network_resource_counts(vm);
let socket_paths = build_javascript_socket_path_context(vm)?;
let enable_transform = vm.configuration.enable_http_request_transform;
let ownership = OwnershipScope::vm(
vm.connection_id.clone(),
vm.session_id.clone(),
vm_id.to_owned(),
);
let http_transform = if enable_transform {
Some((&self.sidecar_requests, &ownership))
} else {
None
};
let root = vm
.active_processes
.get_mut(process_id)
Expand All @@ -5349,6 +5361,7 @@ where
&request,
&resource_limits,
network_counts,
http_transform,
)
};

Expand Down Expand Up @@ -10256,6 +10269,7 @@ pub(crate) fn service_javascript_sync_rpc<B>(
request: &JavascriptSyncRpcRequest,
resource_limits: &ResourceLimits,
network_counts: NetworkResourceCounts,
http_transform: Option<(&SharedSidecarRequestClient, &OwnershipScope)>,
) -> Result<Value, SidecarError>
where
B: NativeSidecarBridge + Send + 'static,
Expand Down Expand Up @@ -10302,6 +10316,7 @@ where
request,
resource_limits,
network_counts,
http_transform,
),
"net.http2_server_listen"
| "net.http2_server_poll"
Expand Down Expand Up @@ -10365,6 +10380,7 @@ where
request,
resource_limits,
network_counts,
http_transform,
),
"dgram.createSocket"
| "dgram.bind"
Expand Down Expand Up @@ -12682,6 +12698,7 @@ where
&request,
resource_limits,
network_counts,
None,
);
match response {
Ok(result) => process
Expand Down Expand Up @@ -15345,14 +15362,15 @@ pub(crate) fn service_javascript_net_sync_rpc<B>(
request: &JavascriptSyncRpcRequest,
resource_limits: &ResourceLimits,
network_counts: NetworkResourceCounts,
http_transform: Option<(&SharedSidecarRequestClient, &OwnershipScope)>,
) -> Result<Value, SidecarError>
where
B: NativeSidecarBridge + Send + 'static,
BridgeError<B>: fmt::Debug + Send + Sync + 'static,
{
match request.method.as_str() {
"net.http_request" => {
let (url, options, headers) = parse_http_request_options(request)?;
let (mut url, mut options, mut headers) = parse_http_request_options(request)?;
let host = url.host_str().ok_or_else(|| {
SidecarError::Execution(String::from("ERR_INVALID_URL: missing host"))
})?;
Expand Down Expand Up @@ -15403,6 +15421,79 @@ where
}
}

if let Some((sidecar_requests, ownership)) = &http_transform {
let header_json = json!(headers.normalized);
let transform_request = TransformHttpRequest {
request_id: format!("http-{}", request.id),
url: url.to_string(),
method: options.method.clone().unwrap_or_else(|| String::from("GET")),
headers: header_json,
body: options.body.clone(),
};
let transform_response = sidecar_requests.invoke(
(*ownership).clone(),
SidecarRequestPayload::TransformHttpRequest(transform_request),
Duration::from_secs(10),
);
match transform_response {
Ok(SidecarResponsePayload::TransformHttpResult(result)) => {
if let Some(error) = result.error {
return Err(SidecarError::Execution(format!(
"ERR_HTTP_TRANSFORM_FAILED: {error}"
)));
}
if let Some(new_url) = result.url {
url = Url::parse(&new_url).map_err(|error| {
SidecarError::Execution(format!(
"ERR_HTTP_TRANSFORM_INVALID_URL: {error}"
))
})?;
}
if let Some(new_method) = result.method {
options.method = Some(new_method);
}
if let Some(new_headers) = result.headers {
if let Some(map) = new_headers.as_object() {
let mut normalized = BTreeMap::new();
let mut raw_pairs = Vec::new();
for (key, value) in map {
let values: Vec<String> = match value {
Value::Array(arr) => arr
.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect(),
Value::String(s) => vec![s.clone()],
_ => continue,
};
for v in &values {
raw_pairs.push((key.clone(), v.clone()));
}
normalized.insert(key.clone(), values);
}
headers = HttpHeaderCollection {
normalized,
raw_pairs,
};
}
}
if result.body.is_some() {
options.body = result.body;
}
}
Ok(unexpected) => {
return Err(SidecarError::Execution(format!(
"ERR_HTTP_TRANSFORM_FAILED: unexpected response type: {}",
unexpected.kind_name()
)));
}
Err(error) => {
return Err(SidecarError::Execution(format!(
"ERR_HTTP_TRANSFORM_FAILED: {error}"
)));
}
}
}

issue_outbound_http_request(&url, &options, &headers)
}
"net.http_listen" => {
Expand Down
38 changes: 37 additions & 1 deletion crates/sidecar/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,13 +542,15 @@ pub enum SidecarRequestPayload {
ToolInvocation(ToolInvocationRequest),
PermissionRequest(SidecarPermissionRequest),
JsBridgeCall(JsBridgeCallRequest),
TransformHttpRequest(TransformHttpRequest),
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SidecarResponsePayload {
ToolInvocationResult(ToolInvocationResultResponse),
PermissionRequestResult(SidecarPermissionResultResponse),
JsBridgeResult(JsBridgeResultResponse),
TransformHttpResult(TransformHttpResultResponse),
}

#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -981,6 +983,8 @@ pub struct ConfigureVmRequest {
pub allowed_node_builtins: Vec<String>,
#[serde(default)]
pub loopback_exempt_ports: Vec<u16>,
#[serde(default)]
pub enable_http_request_transform: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
Expand Down Expand Up @@ -1215,6 +1219,32 @@ pub struct JsBridgeCallRequest {
pub args: Value,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TransformHttpRequest {
pub request_id: String,
pub url: String,
pub method: String,
#[serde(with = "json_utf8_value")]
pub headers: Value,
#[serde(default)]
pub body: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TransformHttpResultResponse {
pub request_id: String,
#[serde(default)]
pub url: Option<String>,
#[serde(default)]
pub method: Option<String>,
#[serde(default, with = "json_utf8_option")]
pub headers: Option<Value>,
#[serde(default)]
pub body: Option<String>,
#[serde(default)]
pub error: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuthenticatedResponse {
pub sidecar_id: String,
Expand Down Expand Up @@ -1757,6 +1787,7 @@ impl_bare_newtype_union_enum!(
ToolInvocation(ToolInvocationRequest) = 1,
PermissionRequest(SidecarPermissionRequest) = 2,
JsBridgeCall(JsBridgeCallRequest) = 3,
TransformHttpRequest(TransformHttpRequest) = 4,
}
);

Expand All @@ -1768,6 +1799,7 @@ impl_bare_newtype_union_enum!(
ToolInvocationResult(ToolInvocationResultResponse) = 1,
PermissionRequestResult(SidecarPermissionResultResponse) = 2,
JsBridgeResult(JsBridgeResultResponse) = 3,
TransformHttpResult(TransformHttpResultResponse) = 4,
}
);

Expand Down Expand Up @@ -2808,6 +2840,7 @@ enum ExpectedSidecarResponseKind {
ToolInvocationResult,
PermissionRequestResult,
JsBridgeResult,
TransformHttpResult,
}

impl ExpectedResponseKind {
Expand Down Expand Up @@ -2861,6 +2894,7 @@ impl ExpectedSidecarResponseKind {
Self::ToolInvocationResult => "tool_invocation_result",
Self::PermissionRequestResult => "permission_request_result",
Self::JsBridgeResult => "js_bridge_result",
Self::TransformHttpResult => "transform_http_result",
}
}

Expand Down Expand Up @@ -2952,6 +2986,7 @@ impl SidecarRequestPayload {
Self::ToolInvocation(_) => ExpectedSidecarResponseKind::ToolInvocationResult,
Self::PermissionRequest(_) => ExpectedSidecarResponseKind::PermissionRequestResult,
Self::JsBridgeCall(_) => ExpectedSidecarResponseKind::JsBridgeResult,
Self::TransformHttpRequest(_) => ExpectedSidecarResponseKind::TransformHttpResult,
}
}
}
Expand Down Expand Up @@ -3036,11 +3071,12 @@ impl SidecarResponsePayload {
OwnershipRequirement::Vm
}

fn kind_name(&self) -> &'static str {
pub(crate) fn kind_name(&self) -> &'static str {
match self {
Self::ToolInvocationResult(_) => "tool_invocation_result",
Self::PermissionRequestResult(_) => "permission_request_result",
Self::JsBridgeResult(_) => "js_bridge_result",
Self::TransformHttpResult(_) => "transform_http_result",
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions crates/sidecar/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1858,6 +1858,17 @@ where
let resource_limits = vm.kernel.resource_limits().clone();
let network_counts = vm_network_resource_counts(vm);
let socket_paths = build_javascript_socket_path_context(vm)?;
let enable_transform = vm.configuration.enable_http_request_transform;
let ownership = OwnershipScope::vm(
vm.connection_id.clone(),
vm.session_id.clone(),
vm_id.to_owned(),
);
let http_transform = if enable_transform {
Some((&self.sidecar_requests, &ownership))
} else {
None
};
let process = vm
.active_processes
.get_mut(process_id)
Expand All @@ -1872,6 +1883,7 @@ where
&request,
&resource_limits,
network_counts,
http_transform,
)
}
};
Expand Down
1 change: 1 addition & 0 deletions crates/sidecar/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ pub(crate) struct VmConfiguration {
pub(crate) command_permissions: BTreeMap<String, WasmPermissionTier>,
pub(crate) allowed_node_builtins: Vec<String>,
pub(crate) loopback_exempt_ports: Vec<u16>,
pub(crate) enable_http_request_transform: bool,
}

#[allow(dead_code)]
Expand Down
1 change: 1 addition & 0 deletions crates/sidecar/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ where
command_permissions: payload.command_permissions.clone(),
allowed_node_builtins: payload.allowed_node_builtins.clone(),
loopback_exempt_ports: payload.loopback_exempt_ports.clone(),
enable_http_request_transform: payload.enable_http_request_transform,
};
if let Some(permissions) = payload.permissions.as_ref() {
self.bridge.set_vm_permissions(&vm_id, permissions)?;
Expand Down
Loading