From dbb95fbd3d3a4d3ef95ba863ac75098f6366db92 Mon Sep 17 00:00:00 2001 From: Piotr Mlocek Date: Mon, 2 Nov 2026 12:49:26 -0800 Subject: [PATCH 1/6] feat(middleware): add TCP connection hooks with STATS mode (spike) Add TcpHooks.EvaluateConnection: one stream per admitted upstream connection and stage, selected by TCP_CONNECTION/PRE_CONNECT bindings of services that require openshell.supervisor-middleware.tcp-hooks. The preflight runs before the upstream dial and can deny the connection. In STATS mode the stage receives cumulative upstream byte totals at its interval, only when they moved. A close result aborts the open connection through a metered upstream stream. TCP hooks always fail closed. The hook runs at the CONNECT, transparent TCP, and forward-proxy dial sites. TCP-only services no longer make traffic required to be inspectable: the runtime uninspectable gate ignores them, and the tls: skip conflict check moves from static policy validation to the gateway's registry-aware validation. Signed-off-by: Piotr Mlocek --- .../openshell-core/src/extension_protocol.rs | 31 + crates/openshell-core/src/middleware.rs | 29 +- crates/openshell-policy/src/lib.rs | 3 + crates/openshell-policy/src/middleware.rs | 6 +- crates/openshell-server/src/middleware.rs | 3 +- .../src/lib.rs | 149 ++++- .../src/remote.rs | 36 +- .../src/tcp_hooks.rs | 570 ++++++++++++++++++ .../src/l7/middleware.rs | 13 + .../src/l7/relay.rs | 5 +- .../openshell-supervisor-network/src/opa.rs | 40 +- .../openshell-supervisor-network/src/proxy.rs | 77 ++- .../src/proxy/tcp_hook.rs | 123 ++++ proto/sandbox.proto | 1 + proto/supervisor_middleware.proto | 140 +++++ 15 files changed, 1173 insertions(+), 53 deletions(-) create mode 100644 crates/openshell-supervisor-middleware/src/tcp_hooks.rs create mode 100644 crates/openshell-supervisor-network/src/proxy/tcp_hook.rs diff --git a/crates/openshell-core/src/extension_protocol.rs b/crates/openshell-core/src/extension_protocol.rs index 3ca57e07e7..e9a63f0f9a 100644 --- a/crates/openshell-core/src/extension_protocol.rs +++ b/crates/openshell-core/src/extension_protocol.rs @@ -37,6 +37,14 @@ pub const COMPUTE_UPDATE_SANDBOX_CONFIGURATION: &str = /// `Describe` instead of calling the deprecated legacy HTTP hooks. pub const SUPERVISOR_MIDDLEWARE_HTTP_HOOKS: &str = "openshell.supervisor-middleware.http-hooks"; +/// Capability for supervisor middleware that implements TCP connection hooks +/// (`TcpHooks.EvaluateConnection`). +/// +/// A service with a `TCP_CONNECTION` binding must list this capability in +/// `required_capabilities`, so peers without TCP hooks reject it at +/// `Describe`. +pub const SUPERVISOR_MIDDLEWARE_TCP_HOOKS: &str = "openshell.supervisor-middleware.tcp-hooks"; + const MAX_IMPLEMENTATION_NAME_BYTES: usize = 128; const MAX_IMPLEMENTATION_VERSION_BYTES: usize = 128; const MAX_CAPABILITY_BYTES: usize = 128; @@ -145,6 +153,7 @@ pub fn gateway_metadata(family: ExtensionFamily) -> PeerMetadata { } ExtensionFamily::SupervisorMiddleware => { supported_capabilities.push(SUPERVISOR_MIDDLEWARE_HTTP_HOOKS.to_string()); + supported_capabilities.push(SUPERVISOR_MIDDLEWARE_TCP_HOOKS.to_string()); } ExtensionFamily::Credentials | ExtensionFamily::GatewayInterceptor => {} } @@ -211,6 +220,28 @@ pub fn http_hooks_middleware_metadata( metadata } +/// Extension metadata for supervisor middleware that implements TCP +/// connection hooks. +/// +/// It supports and requires [`SUPERVISOR_MIDDLEWARE_TCP_HOOKS`], so peers +/// without TCP hooks reject the service at `Describe`. +#[must_use] +pub fn tcp_hooks_middleware_metadata( + implementation_name: impl Into, + implementation_version: impl Into, +) -> PeerMetadata { + let mut metadata = extension_metadata( + ExtensionFamily::SupervisorMiddleware, + implementation_name, + implementation_version, + [SUPERVISOR_MIDDLEWARE_TCP_HOOKS.to_string()], + ); + metadata + .required_capabilities + .push(SUPERVISOR_MIDDLEWARE_TCP_HOOKS.to_string()); + metadata +} + pub fn negotiate( family: ExtensionFamily, configured_name: impl Into, diff --git a/crates/openshell-core/src/middleware.rs b/crates/openshell-core/src/middleware.rs index f6ba747bfc..566a763699 100644 --- a/crates/openshell-core/src/middleware.rs +++ b/crates/openshell-core/src/middleware.rs @@ -14,10 +14,15 @@ use crate::proto::{ HttpHeader, HttpRequestEvaluation, HttpRequestHookEvent, HttpRequestHookResult, HttpRequestResult, HttpRequestTarget, HttpResponseEvent, HttpResponseEventResult, HttpResponseHookEvent, HttpResponseHookResult, MiddlewareDescribeRequest, MiddlewareManifest, - RequestContext, SupervisorMiddlewarePhase, ValidateConfigRequest, ValidateConfigResponse, - WebSocketSessionEvent, WebSocketSessionEventResult, + RequestContext, SupervisorMiddlewarePhase, TcpHookEvent, TcpHookResult, ValidateConfigRequest, + ValidateConfigResponse, WebSocketSessionEvent, WebSocketSessionEventResult, }; +/// Transport-neutral result stream for one `TcpHooks.EvaluateConnection` +/// exchange. +pub type TcpHookResultStream = + Pin> + Send + 'static>>; + /// Transport-neutral result stream for one `HttpHooks.EvaluateRequest` /// exchange. pub type HttpRequestHookResultStream = Pin< @@ -100,6 +105,16 @@ pub trait SupervisorMiddlewareEndpoint: Send + Sync { "middleware does not implement HTTP hook responses", )) } + + /// Open one TCP connection hook exchange (`TcpHooks.EvaluateConnection`). + async fn open_tcp_connection_hook( + &self, + _requests: mpsc::Receiver, + ) -> Result { + Err(Status::unimplemented( + "middleware does not implement TCP connection hooks", + )) + } } /// Borrowed request state exposed to one in-process middleware invocation. @@ -340,6 +355,16 @@ pub trait InProcessMiddleware: Send + Sync { "middleware does not implement HTTP hook responses", )) } + + /// Open one TCP connection hook exchange (`TcpHooks.EvaluateConnection`). + async fn open_tcp_connection_hook( + &self, + _requests: mpsc::Receiver, + ) -> std::result::Result { + Err(Status::unimplemented( + "middleware does not implement TCP connection hooks", + )) + } } /// Default timeout for one supervisor middleware RPC. diff --git a/crates/openshell-policy/src/lib.rs b/crates/openshell-policy/src/lib.rs index 056cf39460..f5c4b90502 100644 --- a/crates/openshell-policy/src/lib.rs +++ b/crates/openshell-policy/src/lib.rs @@ -3379,6 +3379,7 @@ network_policies: } #[test] + #[ignore = "spike 02b: tls: skip conflict moved to gateway validation"] fn validate_rejects_middleware_selector_matching_tls_skip_endpoint() { let mut policy = restrictive_default_policy(); add_middleware( @@ -3436,6 +3437,7 @@ network_policies: } #[test] + #[ignore = "spike 02b: tls: skip conflict moved to gateway validation"] fn validate_rejects_explicit_fail_closed_middleware_on_tls_skip_endpoint() { let mut policy = restrictive_default_policy(); let mut middleware = middleware_config("openshell/regex"); @@ -3464,6 +3466,7 @@ network_policies: } #[test] + #[ignore = "spike 02b: tls: skip conflict moved to gateway validation"] fn validate_rejects_concrete_selector_overlapping_tls_skip_wildcard() { let mut policy = restrictive_default_policy(); let mut middleware = middleware_config("openshell/regex"); diff --git a/crates/openshell-policy/src/middleware.rs b/crates/openshell-policy/src/middleware.rs index 20e9ee05b2..cf0e263a11 100644 --- a/crates/openshell-policy/src/middleware.rs +++ b/crates/openshell-policy/src/middleware.rs @@ -283,7 +283,11 @@ pub fn validate(policy: &SandboxPolicy) -> Vec { None }; - let requires_inspection = matches!(middleware.on_error.as_str(), "" | "fail_closed"); + // Spike 02b: TCP-only middleware may select tls: skip endpoints under + // fail_closed, which this static check cannot tell apart. The + // gateway's registry-aware validation (validate_tcp_hook_rules) + // enforces the conflict for services that inspect content. + let requires_inspection = false; for (key, rule) in &policy.network_policies { let policy_name = if rule.name.is_empty() { key diff --git a/crates/openshell-server/src/middleware.rs b/crates/openshell-server/src/middleware.rs index a86c64f9a7..7be904530b 100644 --- a/crates/openshell-server/src/middleware.rs +++ b/crates/openshell-server/src/middleware.rs @@ -13,7 +13,8 @@ pub async fn validate_policy( ) -> Result<(), Status> { async { registry.validate_policy_configs(policy).await?; - registry.validate_http_hook_rules(policy).await + registry.validate_http_hook_rules(policy).await?; + registry.validate_tcp_hook_rules(policy).await } .await .map_err(|error| { diff --git a/crates/openshell-supervisor-middleware/src/lib.rs b/crates/openshell-supervisor-middleware/src/lib.rs index d8edfb9cff..c294f46a0e 100644 --- a/crates/openshell-supervisor-middleware/src/lib.rs +++ b/crates/openshell-supervisor-middleware/src/lib.rs @@ -9,6 +9,7 @@ pub mod headers; mod http_hooks; mod remote; mod response; +mod tcp_hooks; mod websocket; pub use http_hooks::{ @@ -44,8 +45,8 @@ use miette::{Result, miette}; use prost::Message; use openshell_core::extension_protocol::{ - ExtensionFamily, NegotiatedExtension, SUPERVISOR_MIDDLEWARE_HTTP_HOOKS, gateway_metadata, - negotiate, + ExtensionFamily, NegotiatedExtension, SUPERVISOR_MIDDLEWARE_HTTP_HOOKS, + SUPERVISOR_MIDDLEWARE_TCP_HOOKS, gateway_metadata, negotiate, }; use openshell_core::proto::middleware::v1::supervisor_middleware_server::SupervisorMiddleware; use openshell_core::proto::{ @@ -62,7 +63,11 @@ use binding_policy::BindingPolicy; pub use openshell_core::middleware::{ HttpRequestHookResultStream, HttpRequestView, HttpResponseHookResultStream, HttpResponseResultStream, InProcessMiddleware, SupervisorMiddlewareEndpoint, - WebSocketResponseStream, + TcpHookResultStream, WebSocketResponseStream, +}; +pub use tcp_hooks::{ + MAX_TCP_STATS_INTERVAL, MIN_TCP_STATS_INTERVAL, MeteredStream, TcpCloseObserver, + TcpConnectionMeter, TcpHookAdmission, TcpHookDenial, TcpHookRequest, TcpHookSession, }; pub type MiddlewareService = @@ -235,6 +240,13 @@ impl InProcessMiddleware for EndpointInProcessAdapter { ) -> std::result::Result { self.endpoint.open_http_response_hook(requests).await } + + async fn open_tcp_connection_hook( + &self, + requests: tokio::sync::mpsc::Receiver, + ) -> std::result::Result { + self.endpoint.open_tcp_connection_hook(requests).await + } } /// Adapt a transport-neutral endpoint to the in-process registry contract. @@ -490,6 +502,41 @@ fn validate_manifest_http_hooks_capability( Ok(()) } +/// True when the service requires the TCP hooks capability. +fn requires_tcp_hooks(manifest: &MiddlewareManifest) -> bool { + manifest.extension.as_ref().is_some_and(|extension| { + extension + .required_capabilities + .iter() + .any(|capability| capability == SUPERVISOR_MIDDLEWARE_TCP_HOOKS) + }) +} + +fn is_tcp_binding(binding: &MiddlewareBinding) -> bool { + binding.operation == SupervisorMiddlewareOperation::TcpConnection as i32 +} + +/// A service with a TCP connection binding must require the TCP hooks +/// capability, so peers without TCP hooks reject it at Describe. +fn validate_manifest_tcp_hooks_capability( + source: &str, + manifest: &MiddlewareManifest, +) -> Result<()> { + if manifest.bindings.iter().any(is_tcp_binding) && !requires_tcp_hooks(manifest) { + return Err(miette!( + "{source} declares a TCP_CONNECTION binding without requiring {SUPERVISOR_MIDDLEWARE_TCP_HOOKS}" + )); + } + Ok(()) +} + +/// True when every binding of the service is a TCP connection binding. Such a +/// service observes connections without inspecting content, so it never +/// requires traffic to be inspectable. +fn manifest_is_tcp_only(manifest: &MiddlewareManifest) -> bool { + !manifest.bindings.is_empty() && manifest.bindings.iter().all(is_tcp_binding) +} + /// Legacy HTTP hooks and WebSocket bindings carry payloads. An HTTP hook /// binding that advertises no payload limit is offered no body modes, so it /// can only continue or deny at preflight. @@ -497,6 +544,9 @@ fn binding_requires_payload_limit( manifest: &MiddlewareManifest, binding: &MiddlewareBinding, ) -> bool { + if is_tcp_binding(binding) { + return false; + } binding_http_hook_version(manifest, binding) != Some(HttpHookVersion::Current) || binding.max_payload_bytes > 0 } @@ -869,6 +919,16 @@ impl MiddlewareDispatch { Self::Grpc(service) => service.open_http_response_hook(receiver).await, } } + + async fn open_tcp_connection_hook( + &self, + receiver: tokio::sync::mpsc::Receiver, + ) -> std::result::Result { + match self { + Self::InProcess(service) => service.open_tcp_connection_hook(receiver).await, + Self::Grpc(service) => service.open_tcp_connection_hook(receiver).await, + } + } } struct MiddlewareServiceState { @@ -1119,6 +1179,10 @@ fn supported_binding( Some(SupervisorMiddlewareOperation::WebsocketMessage), Some(SupervisorMiddlewarePhase::PreCredentials), ) => Ok(SupervisorMiddlewareBinding::WebsocketMessagePreCredentials), + ( + Some(SupervisorMiddlewareOperation::TcpConnection), + Some(SupervisorMiddlewarePhase::PreConnect), + ) => Ok(SupervisorMiddlewareBinding::TcpConnectionPreConnect), ( Some(SupervisorMiddlewareOperation::WebsocketMessage), Some(SupervisorMiddlewarePhase::PreReturn), @@ -1140,6 +1204,7 @@ fn validate_manifest_bindings( } validate_manifest_http_hooks_capability(source, manifest)?; + validate_manifest_tcp_hooks_capability(source, manifest)?; let mut bindings = BTreeMap::new(); for binding in &manifest.bindings { let selector = supported_binding(source, binding)?; @@ -1541,6 +1606,78 @@ impl MiddlewareRegistry { }) } + /// True unless this registry described the middleware and every one of its + /// bindings is a TCP connection binding. Only such a service may select + /// traffic `OpenShell` cannot inspect (`tls: skip`, raw TCP) under + /// `fail_closed`: it observes connections without inspecting content. + #[must_use] + pub fn requires_inspection(&self, implementation: &str) -> bool { + !self.services.iter().any(|state| { + state.manifest.get().is_some_and(|manifest| { + ChainRunner::attachment_name(state, manifest) == implementation + && manifest_is_tcp_only(manifest) + }) + }) + } + + /// Gateway rules for policies that use TCP hook middleware. + /// + /// - `on_error: fail_open` is rejected on an entry whose service has a TCP + /// connection binding: TCP hooks always fail closed. + /// - A `fail_closed` entry whose service inspects content (any HTTP or + /// WebSocket binding, or an undescribed service) must not select a + /// `tls: skip` endpoint. TCP-only services may. + pub async fn validate_tcp_hook_rules(&self, policy: &SandboxPolicy) -> Result<()> { + use openshell_core::host_pattern::{HostPattern, HostSelector}; + let manifests = ChainRunner::from_registry(self.clone()).manifests().await?; + let manifest_for = |implementation: &str| { + manifests + .iter() + .find(|(state, manifest)| { + ChainRunner::attachment_name(state, manifest) == implementation + }) + .map(|(_, manifest)| manifest) + }; + let mut configs: Vec<_> = policy.network_middlewares.iter().collect(); + configs.sort_unstable_by_key(|(name, _)| name.as_str()); + for (name, config) in configs { + let manifest = manifest_for(&config.middleware); + let on_error = OnError::parse(&config.on_error)?; + if on_error == OnError::FailOpen + && manifest.is_some_and(|manifest| manifest.bindings.iter().any(is_tcp_binding)) + { + return Err(miette!( + "middleware config '{name}' cannot use on_error: fail_open with '{}': TCP hook middleware is always fail-closed; use fail_closed", + config.middleware + )); + } + if on_error == OnError::FailOpen || manifest.is_some_and(manifest_is_tcp_only) { + continue; + } + let Some(selector) = config + .endpoints + .as_ref() + .and_then(|selector| HostSelector::new(&selector.include, &selector.exclude).ok()) + else { + continue; + }; + for (key, rule) in &policy.network_policies { + for endpoint in &rule.endpoints { + if endpoint.tls == openshell_core::proto::NetworkTlsMode::Skip as i32 + && HostPattern::new(&endpoint.host) + .is_ok_and(|pattern| selector.may_match_pattern(&pattern)) + { + return Err(miette!( + "fail-closed middleware config '{name}' selects tls: skip endpoint '{}' in network policy '{key}'; its service inspects content it cannot see there. Exclude the host, or use a TCP-only middleware service", + endpoint.host + )); + } + } + } + } + Ok(()) + } + /// Gateway rules for policies that use HTTP hook middleware. /// /// - `on_error: fail_open` is rejected on an entry whose service runs HTTP @@ -1984,6 +2121,12 @@ impl ChainRunner { }) } + /// See [`MiddlewareRegistry::requires_inspection`]. + #[must_use] + pub fn requires_inspection(&self, implementation: &str) -> bool { + self.registry.requires_inspection(implementation) + } + /// HTTP hook version of a registered middleware, without a network call. See /// [`MiddlewareRegistry::http_hook_version_of`]. #[must_use] diff --git a/crates/openshell-supervisor-middleware/src/remote.rs b/crates/openshell-supervisor-middleware/src/remote.rs index ad354a844b..f06660a294 100644 --- a/crates/openshell-supervisor-middleware/src/remote.rs +++ b/crates/openshell-supervisor-middleware/src/remote.rs @@ -4,15 +4,17 @@ use miette::{IntoDiagnostic, Result, WrapErr}; use openshell_core::middleware::{ HttpRequestHookResultStream, HttpRequestView, HttpResponseHookResultStream, - HttpResponseResultStream, SupervisorMiddlewareEndpoint, WebSocketResponseStream, + HttpResponseResultStream, SupervisorMiddlewareEndpoint, TcpHookResultStream, + WebSocketResponseStream, }; use openshell_core::proto::middleware::v1::http_hooks_client::HttpHooksClient; use openshell_core::proto::middleware::v1::http_response_pre_return_client::HttpResponsePreReturnClient; use openshell_core::proto::middleware::v1::supervisor_middleware_client::SupervisorMiddlewareClient; +use openshell_core::proto::middleware::v1::tcp_hooks_client::TcpHooksClient; use openshell_core::proto::{ HttpRequestEvaluation, HttpRequestHookEvent, HttpRequestResult, HttpResponseEvent, - HttpResponseHookEvent, MiddlewareDescribeRequest, MiddlewareManifest, ValidateConfigRequest, - ValidateConfigResponse, WebSocketSessionEvent, + HttpResponseHookEvent, MiddlewareDescribeRequest, MiddlewareManifest, TcpHookEvent, + ValidateConfigRequest, ValidateConfigResponse, WebSocketSessionEvent, }; use openshell_extension_core::{ BearerTokenInterceptor, BearerTokenSlot, ExtensionChannelConfig, ExtensionServerTrust, @@ -137,6 +139,14 @@ impl GrpcMiddlewareService { ) -> std::result::Result { self.service.open_http_response_hook(receiver).await } + + /// Open a remote TCP connection hook exchange through the gRPC adapter. + pub async fn open_tcp_connection_hook( + &self, + receiver: tokio::sync::mpsc::Receiver, + ) -> std::result::Result { + self.service.open_tcp_connection_hook(receiver).await + } } #[derive(Clone)] @@ -144,6 +154,7 @@ pub struct RemoteMiddlewareService { client: SupervisorMiddlewareClient, response_client: HttpResponsePreReturnClient, http_hooks_client: HttpHooksClient, + tcp_hooks_client: TcpHooksClient, } impl RemoteMiddlewareService { @@ -177,7 +188,10 @@ impl RemoteMiddlewareService { response_client: HttpResponsePreReturnClient::new(channel.clone()) .max_decoding_message_size(MIDDLEWARE_GRPC_MESSAGE_BYTES) .max_encoding_message_size(MIDDLEWARE_GRPC_MESSAGE_BYTES), - http_hooks_client: HttpHooksClient::new(channel) + http_hooks_client: HttpHooksClient::new(channel.clone()) + .max_decoding_message_size(MIDDLEWARE_GRPC_MESSAGE_BYTES) + .max_encoding_message_size(MIDDLEWARE_GRPC_MESSAGE_BYTES), + tcp_hooks_client: TcpHooksClient::new(channel) .max_decoding_message_size(MIDDLEWARE_GRPC_MESSAGE_BYTES) .max_encoding_message_size(MIDDLEWARE_GRPC_MESSAGE_BYTES), }) @@ -269,6 +283,20 @@ impl SupervisorMiddlewareEndpoint for RemoteMiddlewareService { .into_inner(); Ok(Box::pin(responses)) } + + async fn open_tcp_connection_hook( + &self, + receiver: tokio::sync::mpsc::Receiver, + ) -> std::result::Result { + let mut client = self.tcp_hooks_client.clone(); + let responses = client + .evaluate_connection(Request::new(tokio_stream::wrappers::ReceiverStream::new( + receiver, + ))) + .await? + .into_inner(); + Ok(Box::pin(responses)) + } } #[cfg(test)] diff --git a/crates/openshell-supervisor-middleware/src/tcp_hooks.rs b/crates/openshell-supervisor-middleware/src/tcp_hooks.rs new file mode 100644 index 0000000000..08d4a4917f --- /dev/null +++ b/crates/openshell-supervisor-middleware/src/tcp_hooks.rs @@ -0,0 +1,570 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! TCP connection hooks (`TcpHooks.EvaluateConnection`). +//! +//! One stream per admitted upstream connection and stage. The preflight runs +//! before the upstream is dialed and can deny the connection. A stage that +//! selects STATS receives cumulative upstream byte totals at its interval. Any +//! stage can close the connection at any time. TCP hooks always fail closed. + +use std::io; +use std::pin::Pin; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::task::{Context, Poll}; +use std::time::{Duration, Instant}; + +use futures::StreamExt; +use futures::task::AtomicWaker; +use miette::Result; +use openshell_core::proto::{ + MiddlewareSessionEndReason, RequestContext, SupervisorMiddlewareOperation, + SupervisorMiddlewarePhase, TcpEnd, TcpHookEvent, TcpHookResult, TcpInspection, TcpLimits, + TcpMode, TcpPreflight, TcpStats, tcp_hook_event, tcp_hook_result, tcp_inspect, + tcp_preflight_result, +}; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; +use tokio::sync::{OwnedSemaphorePermit, Semaphore, mpsc, oneshot}; + +use crate::{ChainEntry, ChainRunner, DescribedChainEntry, MAX_MIDDLEWARE_REASON_CODE_BYTES}; + +/// Shortest stats interval a stage may select. +pub const MIN_TCP_STATS_INTERVAL: Duration = Duration::from_secs(1); +/// Longest stats interval a stage may select. +pub const MAX_TCP_STATS_INTERVAL: Duration = Duration::from_mins(1); +/// Process-wide cap on concurrently observed connections. Spike: one shared +/// cap, not a budget per service. +pub const MAX_TCP_HOOK_CONNECTIONS: usize = 4096; + +static TCP_HOOK_CONNECTIONS: std::sync::LazyLock> = + std::sync::LazyLock::new(|| Arc::new(Semaphore::new(MAX_TCP_HOOK_CONNECTIONS))); + +/// Upstream byte counters and the close switch for one connection. +#[derive(Debug)] +pub struct TcpConnectionMeter { + bytes_out: AtomicU64, + bytes_in: AtomicU64, + closed: AtomicBool, + read_waker: AtomicWaker, + write_waker: AtomicWaker, + started: Instant, +} + +impl Default for TcpConnectionMeter { + fn default() -> Self { + Self { + bytes_out: AtomicU64::new(0), + bytes_in: AtomicU64::new(0), + closed: AtomicBool::new(false), + read_waker: AtomicWaker::new(), + write_waker: AtomicWaker::new(), + started: Instant::now(), + } + } +} + +impl TcpConnectionMeter { + /// Bytes written to and read from the upstream so far. + #[must_use] + pub fn totals(&self) -> (u64, u64) { + ( + self.bytes_out.load(Ordering::Relaxed), + self.bytes_in.load(Ordering::Relaxed), + ) + } + + #[must_use] + pub fn elapsed(&self) -> Duration { + self.started.elapsed() + } + + #[must_use] + pub fn is_closed(&self) -> bool { + self.closed.load(Ordering::Acquire) + } + + /// Abort the connection: pending and later upstream reads and writes fail. + pub fn close(&self) { + self.closed.store(true, Ordering::Release); + self.read_waker.wake(); + self.write_waker.wake(); + } +} + +fn aborted() -> io::Error { + io::Error::new( + io::ErrorKind::ConnectionAborted, + "connection closed by TCP middleware", + ) +} + +/// Upstream stream wrapper that counts bytes and honors the meter's close. +pub struct MeteredStream { + inner: S, + meter: Arc, +} + +impl MeteredStream { + pub fn new(inner: S, meter: Arc) -> Self { + Self { inner, meter } + } +} + +impl AsyncRead for MeteredStream { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let this = self.get_mut(); + this.meter.read_waker.register(cx.waker()); + if this.meter.is_closed() { + return Poll::Ready(Err(aborted())); + } + let before = buf.filled().len(); + let result = Pin::new(&mut this.inner).poll_read(cx, buf); + if matches!(&result, Poll::Ready(Ok(()))) { + let read = buf.filled().len().saturating_sub(before); + this.meter + .bytes_in + .fetch_add(read as u64, Ordering::Relaxed); + } + result + } +} + +impl AsyncWrite for MeteredStream { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + let this = self.get_mut(); + this.meter.write_waker.register(cx.waker()); + if this.meter.is_closed() { + return Poll::Ready(Err(aborted())); + } + let result = Pin::new(&mut this.inner).poll_write(cx, buf); + if let Poll::Ready(Ok(written)) = &result { + this.meter + .bytes_out + .fetch_add(*written as u64, Ordering::Relaxed); + } + result + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.get_mut(); + if this.meter.is_closed() { + return Poll::Ready(Err(aborted())); + } + Pin::new(&mut this.inner).poll_flush(cx) + } + + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.get_mut().inner).poll_shutdown(cx) + } +} + +/// Called once when a stage closes an open connection: middleware name, +/// reason code, bytes out, bytes in. +pub type TcpCloseObserver = Arc; + +/// One admitted upstream connection to evaluate. +pub struct TcpHookRequest { + pub host: String, + pub port: u16, + pub inspection: TcpInspection, + pub policy_keys: Vec, + pub context: RequestContext, + pub on_close: Option, +} + +/// A stage denied the connection at preflight, or failed. +#[derive(Debug, Clone)] +pub struct TcpHookDenial { + pub middleware: String, + /// Stable reason code from the stage, or an `OpenShell` failure code. + pub reason_code: String, +} + +pub enum TcpHookAdmission { + /// Relay through [`TcpHookSession::wrap`]. + Allow(TcpHookSession), + Deny(TcpHookDenial), +} + +struct StageHandle { + end: Option>, +} + +/// Observed connection. Dropping it ends every stage stream with +/// `NORMAL`; [`TcpHookSession::finish`] supplies another reason. +pub struct TcpHookSession { + meter: Arc, + stages: Vec, + _permit: Option, +} + +impl TcpHookSession { + #[must_use] + pub fn meter(&self) -> &Arc { + &self.meter + } + + /// True when no stage observes the connection. + #[must_use] + pub fn is_empty(&self) -> bool { + self.stages.is_empty() + } + + /// Wrap the upstream stream so its bytes are counted and a stage can + /// close it. + pub fn wrap(&self, upstream: S) -> MeteredStream { + MeteredStream::new(upstream, Arc::clone(&self.meter)) + } + + /// End every stage stream with `reason`. + pub fn finish(mut self, reason: MiddlewareSessionEndReason) { + self.end_all(reason); + } + + fn end_all(&mut self, reason: MiddlewareSessionEndReason) { + for stage in &mut self.stages { + if let Some(end) = stage.end.take() { + let _ = end.send(reason); + } + } + } +} + +impl Drop for TcpHookSession { + fn drop(&mut self) { + let reason = if self.meter.is_closed() { + MiddlewareSessionEndReason::MiddlewareDenial + } else { + MiddlewareSessionEndReason::Normal + }; + self.end_all(reason); + } +} + +fn duration_proto(duration: Duration) -> prost_types::Duration { + prost_types::Duration::try_from(duration).unwrap_or_default() +} + +/// Accept the stage's code when it is a stable reason code, else a generic one. +fn stage_reason_code(code: &str, fallback: &str) -> String { + if !code.is_empty() + && code.len() <= MAX_MIDDLEWARE_REASON_CODE_BYTES + && crate::is_stable_reason_code(code) + { + code.to_string() + } else { + fallback.to_string() + } +} + +enum StagePreflight { + Continue, + Stats(Duration), + Close(String), +} + +impl ChainRunner { + /// Run the TCP connection preflight of every matching stage, in chain + /// order. The first deny or failure denies the connection. + pub async fn open_tcp_hooks( + &self, + entries: &[ChainEntry], + request: TcpHookRequest, + ) -> Result { + if entries.is_empty() { + return Ok(TcpHookAdmission::Allow(TcpHookSession { + meter: Arc::new(TcpConnectionMeter::default()), + stages: Vec::new(), + _permit: None, + })); + } + let described = self + .describe_chain_for( + entries, + SupervisorMiddlewareOperation::TcpConnection, + SupervisorMiddlewarePhase::PreConnect, + ) + .await? + .entries; + let meter = Arc::new(TcpConnectionMeter::default()); + if described.is_empty() { + return Ok(TcpHookAdmission::Allow(TcpHookSession { + meter, + stages: Vec::new(), + _permit: None, + })); + } + let Ok(permit) = Arc::clone(&TCP_HOOK_CONNECTIONS).try_acquire_owned() else { + return Ok(TcpHookAdmission::Deny(TcpHookDenial { + middleware: described[0].entry.name.clone(), + reason_code: "middleware_capacity_exhausted".to_string(), + })); + }; + let mut session = TcpHookSession { + meter, + stages: Vec::with_capacity(described.len()), + _permit: Some(permit), + }; + let request = Arc::new(request); + for entry in &described { + match open_stage(entry, &request, &session.meter).await { + Ok(stage) => session.stages.push(stage), + Err(reason_code) => { + session.end_all(MiddlewareSessionEndReason::MiddlewareDenial); + return Ok(TcpHookAdmission::Deny(TcpHookDenial { + middleware: entry.entry.name.clone(), + reason_code, + })); + } + } + } + Ok(TcpHookAdmission::Allow(session)) + } +} + +async fn open_stage( + described: &DescribedChainEntry, + request: &Arc, + meter: &Arc, +) -> std::result::Result { + let Some(state) = described.service.as_ref() else { + return Err("middleware_unavailable".to_string()); + }; + let (tx, rx) = mpsc::channel::(4); + let preflight = TcpPreflight { + host: request.host.clone(), + port: u32::from(request.port), + sni: String::new(), + inspection: request.inspection as i32, + policy_keys: request.policy_keys.clone(), + endpoint_id: format!("{}:{}", request.host, request.port), + context: Some(request.context.clone()), + middleware_name: described.entry.name.clone(), + config: Some(described.entry.config.clone()), + permitted_modes: vec![TcpMode::Stats as i32], + limits: Some(TcpLimits { + min_stats_interval: Some(duration_proto(MIN_TCP_STATS_INTERVAL)), + max_stats_interval: Some(duration_proto(MAX_TCP_STATS_INTERVAL)), + }), + phase: SupervisorMiddlewarePhase::PreConnect as i32, + }; + tx.send(TcpHookEvent { + event: Some(tcp_hook_event::Event::Preflight(preflight)), + }) + .await + .map_err(|_| "middleware_failure".to_string())?; + + let timeout = described.timeout; + let mut results = tokio::time::timeout(timeout, state.service.open_tcp_connection_hook(rx)) + .await + .map_err(|_| "middleware_timeout".to_string())? + .map_err(|status| { + tracing::warn!( + middleware = %described.entry.name, + error = %status, + "TCP middleware stream failed to open" + ); + "middleware_failure".to_string() + })?; + let first = tokio::time::timeout(timeout, results.next()) + .await + .map_err(|_| "middleware_timeout".to_string())?; + let decision = match first { + Some(Ok(TcpHookResult { + result: Some(tcp_hook_result::Result::PreflightResult(result)), + })) => match result.decision { + Some(tcp_preflight_result::Decision::ContinueWithoutInspection(_)) => { + StagePreflight::Continue + } + Some(tcp_preflight_result::Decision::Inspect(inspect)) => match inspect.mode { + Some(tcp_inspect::Mode::Stats(mode)) => { + let interval = mode + .interval + .as_ref() + .and_then(|value| openshell_core::time::duration_to_std(value).ok()) + .unwrap_or(MIN_TCP_STATS_INTERVAL) + .clamp(MIN_TCP_STATS_INTERVAL, MAX_TCP_STATS_INTERVAL); + StagePreflight::Stats(interval) + } + None => return Err("middleware_protocol_error".to_string()), + }, + None => return Err("middleware_protocol_error".to_string()), + }, + Some(Ok(TcpHookResult { + result: Some(tcp_hook_result::Result::Close(close)), + })) => StagePreflight::Close(stage_reason_code( + &close.diagnostics.unwrap_or_default().reason_code, + "middleware_denied", + )), + Some(Ok(_)) => return Err("middleware_protocol_error".to_string()), + Some(Err(status)) => { + tracing::warn!( + middleware = %described.entry.name, + error = %status, + "TCP middleware preflight failed" + ); + return Err("middleware_failure".to_string()); + } + None => return Err("middleware_failure".to_string()), + }; + let interval = match decision { + StagePreflight::Close(reason_code) => { + let _ = tx.try_send(end_event( + meter, + MiddlewareSessionEndReason::MiddlewareDenial, + )); + return Err(reason_code); + } + StagePreflight::Continue => None, + StagePreflight::Stats(interval) => Some(interval), + }; + let (end_tx, end_rx) = oneshot::channel(); + tokio::spawn(run_stage( + described.entry.name.clone(), + Arc::clone(request), + Arc::clone(meter), + tx, + results, + interval, + end_rx, + )); + Ok(StageHandle { end: Some(end_tx) }) +} + +fn end_event(meter: &TcpConnectionMeter, reason: MiddlewareSessionEndReason) -> TcpHookEvent { + let (bytes_out, bytes_in) = meter.totals(); + TcpHookEvent { + event: Some(tcp_hook_event::Event::End(TcpEnd { + bytes_out, + bytes_in, + duration: Some(duration_proto(meter.elapsed())), + reason: reason as i32, + })), + } +} + +/// Drive one stage after preflight: send stats when totals moved, apply a +/// close, and send the end. A broken stream closes the connection. +async fn run_stage( + middleware: String, + request: Arc, + meter: Arc, + tx: mpsc::Sender, + mut results: openshell_core::middleware::TcpHookResultStream, + interval: Option, + mut end_rx: oneshot::Receiver, +) { + let mut ticker = interval.map(|interval| { + let mut ticker = tokio::time::interval(interval); + ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + ticker + }); + let mut last = (0_u64, 0_u64); + let mut results_open = true; + let reason = loop { + tokio::select! { + reason = &mut end_rx => { + break reason.unwrap_or(MiddlewareSessionEndReason::Cancellation); + } + () = async { + match ticker.as_mut() { + Some(ticker) => { ticker.tick().await; } + None => std::future::pending::<()>().await, + } + } => { + let totals = meter.totals(); + if totals != last { + last = totals; + // Latest value wins: a full channel skips this update and + // the next tick sends newer cumulative totals. + let _ = tx.try_send(TcpHookEvent { + event: Some(tcp_hook_event::Event::Stats(TcpStats { + bytes_out: totals.0, + bytes_in: totals.1, + elapsed: Some(duration_proto(meter.elapsed())), + })), + }); + } + } + result = results.next(), if results_open => { + match result { + Some(Ok(TcpHookResult { result: Some(tcp_hook_result::Result::Close(close)) })) => { + let reason_code = stage_reason_code( + &close.diagnostics.unwrap_or_default().reason_code, + "middleware_closed", + ); + close_connection(&middleware, &request, &meter, &reason_code); + } + Some(Ok(_)) => { + close_connection(&middleware, &request, &meter, "middleware_protocol_error"); + } + Some(Err(status)) => { + tracing::warn!(middleware = %middleware, error = %status, "TCP middleware stream failed"); + close_connection(&middleware, &request, &meter, "middleware_failure"); + results_open = false; + } + None => { + // The stage ended its response stream. Fail closed. + close_connection(&middleware, &request, &meter, "middleware_failure"); + results_open = false; + } + } + } + } + }; + let _ = tx.send(end_event(&meter, reason)).await; +} + +fn close_connection( + middleware: &str, + request: &TcpHookRequest, + meter: &TcpConnectionMeter, + reason_code: &str, +) { + if meter.is_closed() { + return; + } + meter.close(); + let (bytes_out, bytes_in) = meter.totals(); + if let Some(on_close) = request.on_close.as_ref() { + on_close(middleware, reason_code, bytes_out, bytes_in); + } +} + +#[cfg(test)] +mod tests { + use tokio::io::{AsyncReadExt, AsyncWriteExt}; + + use super::*; + + #[tokio::test] + async fn metered_stream_counts_bytes_and_close_aborts_pending_read() { + let (client, mut server) = tokio::io::duplex(1024); + let meter = Arc::new(TcpConnectionMeter::default()); + let mut upstream = MeteredStream::new(client, Arc::clone(&meter)); + + upstream.write_all(b"hello").await.unwrap(); + server.write_all(b"world!").await.unwrap(); + let mut buf = [0_u8; 6]; + upstream.read_exact(&mut buf).await.unwrap(); + assert_eq!(meter.totals(), (5, 6)); + + let pending = tokio::spawn(async move { + let mut buf = [0_u8; 1]; + upstream.read(&mut buf).await + }); + tokio::task::yield_now().await; + meter.close(); + let error = pending.await.unwrap().unwrap_err(); + assert_eq!(error.kind(), io::ErrorKind::ConnectionAborted); + } +} diff --git a/crates/openshell-supervisor-network/src/l7/middleware.rs b/crates/openshell-supervisor-network/src/l7/middleware.rs index d44120b9dd..03db3a7ba5 100644 --- a/crates/openshell-supervisor-network/src/l7/middleware.rs +++ b/crates/openshell-supervisor-network/src/l7/middleware.rs @@ -337,6 +337,19 @@ pub fn uninspectable_traffic_gate( } } +/// Entries whose service inspects content. TCP-only services observe +/// connections without inspecting them, so they never make traffic +/// "required to be inspectable". +pub fn inspecting_entries( + runner: &openshell_supervisor_middleware::ChainRunner, + chain: Vec, +) -> Vec { + chain + .into_iter() + .filter(|entry| runner.requires_inspection(&entry.implementation)) + .collect() +} + /// Sandbox and originating process identity shown to middleware. The process /// is the one the connection's network policy resolved, without its pid. pub fn middleware_request_context( diff --git a/crates/openshell-supervisor-network/src/l7/relay.rs b/crates/openshell-supervisor-network/src/l7/relay.rs index 9be6870910..23a27f118d 100644 --- a/crates/openshell-supervisor-network/src/l7/relay.rs +++ b/crates/openshell-supervisor-network/src/l7/relay.rs @@ -1255,7 +1255,10 @@ where // The SQL relay is not implemented, so a matching middleware // chain can never inspect this stream: gate it like any other // uninspectable protocol. - let chain = engine.query_middleware_chain(&middleware_network_input(ctx))?; + let chain = crate::l7::middleware::inspecting_entries( + engine.middleware_runner(), + engine.query_middleware_chain(&middleware_network_input(ctx))?, + ); match uninspectable_traffic_gate(&chain) { UninspectableTrafficGate::Deny => { emit_middleware_uninspectable(ctx, "sql passthrough", true); diff --git a/crates/openshell-supervisor-network/src/opa.rs b/crates/openshell-supervisor-network/src/opa.rs index 9362f3ff9f..102b9fcaa1 100644 --- a/crates/openshell-supervisor-network/src/opa.rs +++ b/crates/openshell-supervisor-network/src/opa.rs @@ -12420,44 +12420,8 @@ network_middlewares: "#, "invalid middleware configuration", ), - ( - "tls skip selector", - r#" -network_middlewares: - redactor: - middleware: openshell/regex - endpoints: - include: ["api.example.com"] -network_policies: - api: - endpoints: - - host: api.example.com - port: 443 - tls: skip - binaries: - - { path: /usr/bin/curl } -"#, - "middleware conflicts with TLS inspection", - ), - ( - "tls skip wildcard overlap", - r#" -network_middlewares: - redactor: - middleware: openshell/regex - endpoints: - include: ["api.example.com"] -network_policies: - api: - endpoints: - - host: "*.example.com" - port: 443 - tls: skip - binaries: - - { path: /usr/bin/curl } -"#, - "middleware conflicts with TLS inspection", - ), + // Spike 02b: the tls: skip conflict moved to the gateway's + // registry-aware validation (validate_tcp_hook_rules). ]; for (name, data, expected) in cases { diff --git a/crates/openshell-supervisor-network/src/proxy.rs b/crates/openshell-supervisor-network/src/proxy.rs index 7048214be5..d743808823 100644 --- a/crates/openshell-supervisor-network/src/proxy.rs +++ b/crates/openshell-supervisor-network/src/proxy.rs @@ -6,6 +6,7 @@ pub(crate) mod destination; mod egress; mod relay; +mod tcp_hook; #[cfg(test)] pub(crate) use relay::relay_inspected_http_stream_for_test; @@ -1361,10 +1362,24 @@ async fn handle_transparent_tcp_connection( if middleware_gate == crate::l7::middleware::UninspectableTrafficGate::BypassWithFinding { crate::l7::middleware::emit_middleware_uninspectable(&ctx, "transparent tcp", false); } + let tcp_hook_session = match tcp_hook::admit( + &opa_engine, + &ctx, + openshell_core::proto::TcpInspection::TransparentTcp, + ) + .await + { + openshell_supervisor_middleware::TcpHookAdmission::Allow(session) => session, + openshell_supervisor_middleware::TcpHookAdmission::Deny(denial) => { + tcp_hook::emit_deny(&ctx, &denial); + emit_activity(&activity_tx, true, "tcp_middleware_denied"); + return Ok(()); + } + }; let approved_real_ip_candidates = connector.addrs().to_vec(); generation_guard.ensure_current()?; let dial_span = egress::dial_span(&connect_span); - let mut upstream = + let upstream = dial_transparent_upstream(&upstream_proxy, &host, port, &approved_real_ip_candidates) .instrument(dial_span.clone()) .await @@ -1433,6 +1448,7 @@ async fn handle_transparent_tcp_connection( } )); emit_activity(&activity_tx, false, "transparent_tcp"); + let mut upstream = tcp_hook_session.wrap(upstream); relay::relay_tcp(&mut client, &mut upstream, &generation_guard, &ctx).await } @@ -1830,6 +1846,7 @@ fn middleware_uninspectable_gate( ) -> Result { let input = crate::l7::middleware::middleware_network_input(ctx); let (chain, _generation) = opa_engine.query_middleware_chain_with_generation(&input)?; + let chain = crate::l7::middleware::inspecting_entries(&opa_engine.middleware_runner()?, chain); Ok(crate::l7::middleware::uninspectable_traffic_gate(&chain)) } @@ -3117,6 +3134,31 @@ async fn handle_mediated_connection( } } + let tcp_hook_inspection = if effective_tls_skip { + openshell_core::proto::TcpInspection::TlsPassthrough + } else { + openshell_core::proto::TcpInspection::ConnectTunnel + }; + let tcp_hook_session = match tcp_hook::admit(&opa_engine, &ctx, tcp_hook_inspection).await { + openshell_supervisor_middleware::TcpHookAdmission::Allow(session) => session, + openshell_supervisor_middleware::TcpHookAdmission::Deny(denial) => { + tcp_hook::emit_deny(&ctx, &denial); + emit_activity_simple(activity_tx.as_ref(), true, "tcp_middleware_denied"); + respond( + &mut client, + &build_json_error_response_with_reason( + 403, + "Forbidden", + "middleware_denied", + "connection denied by TCP middleware", + &denial.reason_code, + ), + ) + .await?; + return Ok(()); + } + }; + let dial_span = egress::dial_span(&connect_span); let upstream_result = tokio::select! { result = dial_upstream(&upstream_proxy, &host_lc, &raw_host_lc, port, connector.addrs()) @@ -3140,7 +3182,7 @@ async fn handle_mediated_connection( return Ok(()); }; let mut upstream = match upstream_result { - Ok(upstream) => upstream, + Ok(upstream) => tcp_hook_session.wrap(upstream), Err(error) => { egress::mark_error(&dial_span); if let Some(observer) = connect_endpoint_observer.as_ref() { @@ -6742,10 +6784,39 @@ async fn handle_forward_proxy( // would need absolute-form requests rather than a CONNECT tunnel. Dial // only after every local authorization and transformation step so a // rejected WebSocket preflight cannot contact the destination. + let tcp_hook_session = match tcp_hook::admit( + &opa_engine, + &l7_ctx, + openshell_core::proto::TcpInspection::HttpForward, + ) + .await + { + openshell_supervisor_middleware::TcpHookAdmission::Allow(session) => session, + openshell_supervisor_middleware::TcpHookAdmission::Deny(denial) => { + tcp_hook::emit_deny(&l7_ctx, &denial); + if let Some(session) = middleware_session.take() { + session + .end(openshell_core::proto::MiddlewareSessionEndReason::MiddlewareDenial) + .await; + } + respond( + client, + &build_json_error_response_with_reason( + 403, + "Forbidden", + "middleware_denied", + "connection denied by TCP middleware", + &denial.reason_code, + ), + ) + .await?; + return Ok(()); + } + }; let dial_span = egress::dial_span(&connect_span); let dial_result = connector.connect().instrument(dial_span.clone()).await; let mut upstream = match dial_result { - Ok(s) => s, + Ok(s) => tcp_hook_session.wrap(s), Err(e) => { egress::mark_error(&dial_span); let event = HttpActivityBuilder::new(openshell_ocsf::ctx::ctx()) diff --git a/crates/openshell-supervisor-network/src/proxy/tcp_hook.rs b/crates/openshell-supervisor-network/src/proxy/tcp_hook.rs new file mode 100644 index 0000000000..5243ac75a3 --- /dev/null +++ b/crates/openshell-supervisor-network/src/proxy/tcp_hook.rs @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! TCP connection hooks at the proxy's upstream dial sites. + +use std::sync::Arc; +use std::sync::atomic::{AtomicU64, Ordering}; + +use miette::Result; +use openshell_core::proto::TcpInspection; +use openshell_ocsf::{ + ActionId, ActivityId, DispositionId, Endpoint, NetworkActivityBuilder, SeverityId, StatusId, + ocsf_emit, +}; +use openshell_supervisor_middleware::{TcpHookAdmission, TcpHookDenial, TcpHookRequest}; + +use crate::l7::middleware::{middleware_network_input, middleware_request_context}; +use crate::l7::relay::L7EvalContext; +use crate::opa::OpaEngine; + +static NEXT_CONNECTION_ID: AtomicU64 = AtomicU64::new(1); + +/// Run the preflight of every TCP stage matching this admitted connection. +/// Any failure denies it: TCP hooks always fail closed. +pub(super) async fn admit( + opa_engine: &OpaEngine, + ctx: &L7EvalContext, + inspection: TcpInspection, +) -> TcpHookAdmission { + match try_admit(opa_engine, ctx, inspection).await { + Ok(admission) => admission, + Err(error) => { + tracing::warn!(host = %ctx.host, port = ctx.port, error = %error, "TCP middleware preflight failed"); + TcpHookAdmission::Deny(TcpHookDenial { + middleware: "-".to_string(), + reason_code: "middleware_failure".to_string(), + }) + } + } +} + +async fn try_admit( + opa_engine: &OpaEngine, + ctx: &L7EvalContext, + inspection: TcpInspection, +) -> Result { + let (chain, _generation) = + opa_engine.query_middleware_chain_with_generation(&middleware_network_input(ctx))?; + let runner = opa_engine.middleware_runner()?; + let connection_id = format!("tcp-{}", NEXT_CONNECTION_ID.fetch_add(1, Ordering::Relaxed)); + let host = ctx.host.clone(); + let port = ctx.port; + let policy = ctx.policy_name.clone(); + let request = TcpHookRequest { + host: ctx.host.clone(), + port: ctx.port, + inspection, + policy_keys: if ctx.policy_name.is_empty() { + Vec::new() + } else { + vec![ctx.policy_name.clone()] + }, + context: middleware_request_context(ctx, &connection_id), + on_close: Some(Arc::new( + move |middleware, reason_code, bytes_out, bytes_in| { + emit_close( + &host, + port, + &policy, + middleware, + reason_code, + bytes_out, + bytes_in, + ); + }, + )), + }; + runner.open_tcp_hooks(&chain, request).await +} + +/// OCSF event for a connection a TCP stage denied at preflight. +pub(super) fn emit_deny(ctx: &L7EvalContext, denial: &TcpHookDenial) { + let event = NetworkActivityBuilder::new(openshell_ocsf::ctx::ctx()) + .activity(ActivityId::Open) + .action(ActionId::Denied) + .disposition(DispositionId::Blocked) + .severity(SeverityId::Medium) + .status(StatusId::Failure) + .dst_endpoint(Endpoint::from_domain(&ctx.host, ctx.port)) + .firewall_rule(&denial.middleware, "tcp-middleware") + .message(format!( + "TCP middleware denied {}:{} [middleware:{} reason:{}]", + ctx.host, ctx.port, denial.middleware, denial.reason_code + )) + .status_detail(&denial.reason_code) + .build(); + ocsf_emit!(event); +} + +fn emit_close( + host: &str, + port: u16, + policy: &str, + middleware: &str, + reason_code: &str, + bytes_out: u64, + bytes_in: u64, +) { + let event = NetworkActivityBuilder::new(openshell_ocsf::ctx::ctx()) + .activity(ActivityId::Close) + .action(ActionId::Denied) + .disposition(DispositionId::Blocked) + .severity(SeverityId::Medium) + .status(StatusId::Failure) + .dst_endpoint(Endpoint::from_domain(host, port)) + .firewall_rule(middleware, "tcp-middleware") + .message(format!( + "TCP middleware closed {host}:{port} [middleware:{middleware} reason:{reason_code} policy:{policy} bytes_out:{bytes_out} bytes_in:{bytes_in}]" + )) + .status_detail(reason_code) + .build(); + ocsf_emit!(event); +} diff --git a/proto/sandbox.proto b/proto/sandbox.proto index 80344bd4a1..2369bba049 100644 --- a/proto/sandbox.proto +++ b/proto/sandbox.proto @@ -534,4 +534,5 @@ enum SupervisorMiddlewareBinding { SUPERVISOR_MIDDLEWARE_BINDING_HTTP_REQUEST_PRE_CREDENTIALS = 1; SUPERVISOR_MIDDLEWARE_BINDING_HTTP_RESPONSE_PRE_RETURN = 2; SUPERVISOR_MIDDLEWARE_BINDING_WEBSOCKET_MESSAGE_PRE_CREDENTIALS = 3; + SUPERVISOR_MIDDLEWARE_BINDING_TCP_CONNECTION_PRE_CONNECT = 4; } diff --git a/proto/supervisor_middleware.proto b/proto/supervisor_middleware.proto index 14ffe09f21..342def3b04 100644 --- a/proto/supervisor_middleware.proto +++ b/proto/supervisor_middleware.proto @@ -60,6 +60,19 @@ service HttpHooks { returns (stream HttpResponseHookResult); } +// TcpHooks observes and controls admitted upstream TCP connections for one +// operator-run middleware. It shares SupervisorMiddleware's registration, +// discovery, configuration, and endpoint. +service TcpHooks { + // One stream per admitted upstream connection and middleware stage. + // OpenShell calls it for the TCP_CONNECTION bindings of a service whose + // manifest lists openshell.supervisor-middleware.tcp-hooks in + // extension.required_capabilities. The first event is a preflight, sent + // after network-policy admission and before OpenShell dials the upstream. + // The first result answers it. A close result may follow at any time. + rpc EvaluateConnection(stream TcpHookEvent) returns (stream TcpHookResult); +} + message MiddlewareDescribeRequest { // Gateway or supervisor protocol metadata. Middleware must reject unmet // requirements before accepting traffic. @@ -916,6 +929,10 @@ enum SupervisorMiddlewareOperation { // hooks capability, and the deprecated HttpResponsePreReturn.Evaluate // otherwise. SUPERVISOR_MIDDLEWARE_OPERATION_HTTP_RESPONSE = 3; + // An admitted upstream TCP connection at PRE_CONNECT. OpenShell calls + // TcpHooks.EvaluateConnection; the service must require the + // openshell.supervisor-middleware.tcp-hooks capability. + SUPERVISOR_MIDDLEWARE_OPERATION_TCP_CONNECTION = 4; } // Ordered phase within a supervisor operation. @@ -923,6 +940,8 @@ enum SupervisorMiddlewarePhase { SUPERVISOR_MIDDLEWARE_PHASE_UNSPECIFIED = 0; SUPERVISOR_MIDDLEWARE_PHASE_PRE_CREDENTIALS = 1; SUPERVISOR_MIDDLEWARE_PHASE_PRE_RETURN = 2; + // After network-policy admission and before OpenShell dials the upstream. + SUPERVISOR_MIDDLEWARE_PHASE_PRE_CONNECT = 3; } // WebSocketSessionEvent is one ordered event in a stage-local stream. @@ -1183,3 +1202,124 @@ message HttpRequestResult { // OpenShell may return this code to the requester, unlike free-form reason. string reason_code = 8; } + +// TCP hooks. One ordered event in a TcpHooks.EvaluateConnection stream. +// Preflight is always first. stats events follow only in STATS mode. end is +// best effort and has no result. +message TcpHookEvent { + oneof event { + TcpPreflight preflight = 1; + TcpStats stats = 2; + TcpEnd end = 3; + } +} + +// One result in a TcpHooks.EvaluateConnection stream. The first result must +// be preflight_result or close. close may also follow a preflight result at +// any time to abort the open connection. +message TcpHookResult { + oneof result { + TcpPreflightResult preflight_result = 1; + TcpClose close = 2; + } +} + +// How OpenShell carries the connection's bytes after admission. +enum TcpInspection { + TCP_INSPECTION_UNSPECIFIED = 0; + // CONNECT tunnel with tls: skip: raw bytes, usually TLS ciphertext. + TCP_INSPECTION_TLS_PASSTHROUGH = 1; + // CONNECT tunnel whose payload OpenShell detects after the tunnel opens: + // terminated TLS, plaintext HTTP, or raw TCP. + TCP_INSPECTION_CONNECT_TUNNEL = 2; + // Transparent TCP redirected without a proxy request. + TCP_INSPECTION_TRANSPARENT_TCP = 3; + // Plain HTTP request sent to the forward proxy. + TCP_INSPECTION_HTTP_FORWARD = 4; +} + +// Observation mode a TCP stage can select at preflight. +enum TcpMode { + TCP_MODE_UNSPECIFIED = 0; + // Cumulative byte totals at the stage's interval. The stage never sees + // connection content. + TCP_MODE_STATS = 1; +} + +message TcpPreflight { + // Destination hostname selected by network policy. + string host = 1; + // Destination TCP port. + uint32 port = 2; + // Reserved: TLS server name, when known before dial. Currently empty. + string sni = 3; + TcpInspection inspection = 4; + // Network policy keys that admitted the connection. + repeated string policy_keys = 5; + // Stable endpoint identity, currently "host:port". + string endpoint_id = 6; + // Sandbox, connection, and originating process identity. request_id + // identifies this connection. + RequestContext context = 7; + // Built-in middleware name or operator-owned registration name. + string middleware_name = 8; + // Validated service configuration. + google.protobuf.Struct config = 9; + // Modes this connection is eligible for. + repeated TcpMode permitted_modes = 10; + TcpLimits limits = 11; + // Phase of the TCP_CONNECTION binding this stream serves: PRE_CONNECT. + SupervisorMiddlewarePhase phase = 12; +} + +message TcpLimits { + // Shortest and longest stats interval OpenShell accepts. + google.protobuf.Duration min_stats_interval = 1; + google.protobuf.Duration max_stats_interval = 2; +} + +message TcpPreflightResult { + oneof decision { + // Relay without this stage observing the connection. Only end follows. + TcpContinue continue_without_inspection = 1; + // Observe the connection in one permitted mode. + TcpInspect inspect = 2; + } + MiddlewareDiagnostics diagnostics = 3; +} + +message TcpContinue {} + +message TcpInspect { + oneof mode { + TcpStatsMode stats = 1; + } +} + +message TcpStatsMode { + // Stats interval, clamped to the offered limits. Unset uses the minimum. + google.protobuf.Duration interval = 1; +} + +// Cumulative totals since the upstream connection opened, counted at the +// upstream socket: what left for and arrived from the upstream after every +// transform. Sent only when a total changed since the previous stats event. +message TcpStats { + uint64 bytes_out = 1; + uint64 bytes_in = 2; + google.protobuf.Duration elapsed = 3; +} + +// Final totals. Best effort. +message TcpEnd { + uint64 bytes_out = 1; + uint64 bytes_in = 2; + google.protobuf.Duration duration = 3; + MiddlewareSessionEndReason reason = 4; +} + +// As the first result, denies the connection before OpenShell dials the +// upstream. Later, aborts the open connection in both directions. +message TcpClose { + MiddlewareDiagnostics diagnostics = 1; +} From 8b50fd30176d74c1351ea22f43772a6fcbd5c583 Mon Sep 17 00:00:00 2001 From: Piotr Mlocek Date: Mon, 2 Nov 2026 12:52:12 -0800 Subject: [PATCH 2/6] feat(examples): add egress-usage TCP middleware example (spike) The service selects STATS for every connection, sums upstream bytes per sandbox and policy key in a sliding window, and when a window passes the threshold closes that key's open connections and denies new ones. A smoke suite cuts a burst over tls: skip and checks denial of new connections. Signed-off-by: Piotr Mlocek --- .../Cargo.lock | 4051 +++++++++++++++++ .../Cargo.toml | 28 + .../README.md | 47 + .../policy.yaml | 50 + .../smoke.sh | 383 ++ .../src/main.rs | 229 + .../src/usage.rs | 269 ++ .../upstream.py | 122 + 8 files changed, 5179 insertions(+) create mode 100644 examples/supervisor-middleware-egress-usage/Cargo.lock create mode 100644 examples/supervisor-middleware-egress-usage/Cargo.toml create mode 100644 examples/supervisor-middleware-egress-usage/README.md create mode 100644 examples/supervisor-middleware-egress-usage/policy.yaml create mode 100755 examples/supervisor-middleware-egress-usage/smoke.sh create mode 100644 examples/supervisor-middleware-egress-usage/src/main.rs create mode 100644 examples/supervisor-middleware-egress-usage/src/usage.rs create mode 100644 examples/supervisor-middleware-egress-usage/upstream.py diff --git a/examples/supervisor-middleware-egress-usage/Cargo.lock b/examples/supervisor-middleware-egress-usage/Cargo.lock new file mode 100644 index 0000000000..9e134f3517 --- /dev/null +++ b/examples/supervisor-middleware-egress-usage/Cargo.lock @@ -0,0 +1,4051 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "asn1-rs" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure 0.13.2", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "aws-lc-rs" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e" +dependencies = [ + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "backon" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", + "gloo-timers", + "tokio", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3eb0f42d6c360dc3f8a821f6bf2fdea7f72bfd36b3076eb0e6d1e9e0752fff4" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "num-traits", + "serde", +] + +[[package]] +name = "clap" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "clap_lex" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "combine" +version = "4.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-queue" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctutils" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12218662ccac37d9714f2191252553d67585b97df8c839cbe49e0bde278dc865" +dependencies = [ + "cmov", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", + "ctutils", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" +dependencies = [ + "serde", +] + +[[package]] +name = "enum-ordinalize" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89dd01549b09589510cf0647475075d12071456586d70f5c75c98ae2a5537677" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65863d15a4ce2888bd2f0f543cc963d3879c3a022c8ee43f6141d479a3ac815" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "etcetera" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96" +dependencies = [ + "cfg-if", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +dependencies = [ + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "find-msvc-tools" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flume" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "h2" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hashlink" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "headers" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" +dependencies = [ + "base64 0.22.1", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1 0.10.7", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hickory-proto" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b92608f679a6fa515dd1d15c1ff89443026e391200a2c840c7afcba482893d" +dependencies = [ + "data-encoding", + "idna", + "ipnet", + "jni", + "once_cell", + "rand", + "ring", + "thiserror", + "tinyvec", + "tracing", + "url", +] + +[[package]] +name = "hkdf" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "hostname" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" +dependencies = [ + "cfg-if", + "libc", + "windows-link", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-http-proxy" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd1d471ea2f65ba45eddb1d6ab7d58ac2671d2d4ac14da5c6516ae1d97e1327a" +dependencies = [ + "bytes", + "futures-util", + "headers", + "http", + "hyper", + "hyper-rustls", + "hyper-util", + "pin-project-lite", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "serde", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locale_fallback" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "251af8e57c9400e3eb58242fe5b8b1152b2a64fdf4cf632f923c38ccee6f2fa9" +dependencies = [ + "icu_locale_core", + "icu_locale_fallback_data", + "icu_provider", + "potential_utf", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locale_fallback_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "decf2a22ec8fa68f1a0c1129a3f8583f8f8bc24e8b9ccbe98ead99f62a4dc3a8" + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "serde", + "stable_deref_trait", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_segmenter" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d07aafccd67af15d02512a6adf5896fbc5ed00f2e99b471d2efa14016db3db" +dependencies = [ + "icu_collections", + "icu_locale_fallback", + "icu_provider", + "icu_segmenter_data", + "potential_utf", + "smallvec", + "utf8_iter", + "zerovec", +] + +[[package]] +name = "icu_segmenter_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae293c039020f9ec10710af98d29ce6aa2051486638b49c9a6409f3b4a9e98ad" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7421438de105a0827e44fadd05377727847d717c80ce29a229f85fd04c427b72" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "jsonpath-rust" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c00ae348f9f8fd2d09f82a98ca381c60df9e0820d8d79fce43e649b4dc3128b" +dependencies = [ + "pest", + "pest_derive", + "regex", + "serde_json", + "thiserror", +] + +[[package]] +name = "jsonptr" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "jsonwebtoken" +version = "10.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc" +dependencies = [ + "aws-lc-rs", + "base64 0.22.1", + "getrandom 0.2.17", + "js-sys", + "pem 3.0.6", + "serde", + "serde_json", + "signature", + "simple_asn1", + "zeroize", +] + +[[package]] +name = "k8s-openapi" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c75b990324f09bef15e791606b7b7a296d02fc88a344f6eba9390970a870ad5" +dependencies = [ + "base64 0.22.1", + "chrono", + "serde", + "serde-value", + "serde_json", +] + +[[package]] +name = "kube" +version = "0.99.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4eb20010536b48abe97fec37d23d43069bcbe9686adcf9932202327bc5ca6e" +dependencies = [ + "k8s-openapi", + "kube-client", + "kube-core", + "kube-derive", + "kube-runtime", +] + +[[package]] +name = "kube-client" +version = "0.99.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc2ed952042df20d15ac2fe9614d0ec14b6118eab89633985d4b36e688dccf1" +dependencies = [ + "base64 0.22.1", + "bytes", + "chrono", + "either", + "futures", + "home", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-http-proxy", + "hyper-rustls", + "hyper-timeout", + "hyper-util", + "jsonpath-rust", + "k8s-openapi", + "kube-core", + "pem 3.0.6", + "rustls", + "secrecy", + "serde", + "serde_json", + "serde_yaml", + "thiserror", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tracing", +] + +[[package]] +name = "kube-core" +version = "0.99.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff0d0793db58e70ca6d689489183816cb3aa481673e7433dc618cf7e8007c675" +dependencies = [ + "chrono", + "form_urlencoded", + "http", + "json-patch", + "k8s-openapi", + "schemars", + "serde", + "serde-value", + "serde_json", + "thiserror", +] + +[[package]] +name = "kube-derive" +version = "0.99.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c562f58dc9f7ca5feac8a6ee5850ca221edd6f04ce0dd2ee873202a88cd494c9" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.119", +] + +[[package]] +name = "kube-runtime" +version = "0.99.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88f34cfab9b4bd8633062e0e85edb81df23cb09f159f2e31c60b069ae826ffdc" +dependencies = [ + "ahash", + "async-broadcast", + "async-stream", + "async-trait", + "backon", + "educe", + "futures", + "hashbrown 0.15.5", + "hostname", + "json-patch", + "k8s-openapi", + "kube-client", + "parking_lot", + "pin-project", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libsqlite3-sys" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "md-5" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" +dependencies = [ + "cfg-if", + "digest 0.11.3", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "backtrace", + "backtrace-ext", + "cfg-if", + "miette-derive", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size", + "textwrap", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "noyalib" +version = "0.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "962838abbd8dd2cd3844d2208dc502458bba6985f8bf1dc21761b30260fcbd7f" +dependencies = [ + "hashbrown 0.17.1", + "indexmap", + "libm", + "memchr", + "rustc-hash", + "serde_core", + "smallvec", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "openshell-core" +version = "0.0.0" +dependencies = [ + "async-trait", + "base64 0.22.1", + "glob", + "ipnet", + "libc", + "miette", + "nix", + "openshell-crypto", + "openshell-extension-core", + "openshell-policy-schema", + "prost", + "prost-types", + "protoc-bin-vendored", + "rustix", + "rustls", + "serde", + "serde_json", + "sha2 0.10.9", + "thiserror", + "tokio", + "tokio-stream", + "tonic", + "tonic-prost", + "tonic-prost-build", + "tonic-types", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "openshell-crypto" +version = "0.0.0" +dependencies = [ + "aws-lc-rs", + "jsonwebtoken", + "kube", + "pem 3.0.6", + "rcgen", + "rustls", + "serde", + "sqlx", + "thiserror", + "tonic", + "x509-parser", +] + +[[package]] +name = "openshell-extension-core" +version = "0.0.0" +dependencies = [ + "hyper-util", + "serde", + "thiserror", + "tokio", + "tonic", + "tower", +] + +[[package]] +name = "openshell-policy" +version = "0.0.0" +dependencies = [ + "hickory-proto", + "miette", + "openshell-core", + "openshell-policy-schema", + "prost-types", + "serde", + "serde_json", +] + +[[package]] +name = "openshell-policy-schema" +version = "0.0.0" +dependencies = [ + "miette", + "noyalib", + "serde", + "serde_json", + "serde_path_to_error", + "thiserror", +] + +[[package]] +name = "openshell-supervisor-middleware-egress-usage" +version = "0.0.0" +dependencies = [ + "clap", + "openshell-core", + "openshell-policy", + "prost-types", + "tokio", + "tokio-stream", + "tonic", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "owo-colors" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c45bb4a6ae1280ec0803b1ef9d3455eb50f01efbbe1447ab020f1d54fba9d8" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + +[[package]] +name = "pem" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d354a98a3d1251555de99e8fdd8afda05573c31b82f59063a7b0a29b5527f120" +dependencies = [ + "base64 0.23.1", + "serde_core", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d45aeb61b4bf818e12d4205f2466f8c4748f85f4fce0146d1c03d69d753f0ad" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89cc5a242e25ed4e7704d0be240f2cfbe20a8c27e7e252d94835be93d92dc39f" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abf21475cc3820fe4b2ca2dc2142902f67a02189f3b5b3a229f4febc01a43e5" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pest_meta" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adba4db388f687393c18c51348d44a41d870ca9df71a2c98172ea3035dc6936e" +dependencies = [ + "pest", +] + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "serde_core", + "writeable", + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" +dependencies = [ + "heck", + "itertools", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "pulldown-cmark", + "pulldown-cmark-to-cmark", + "regex", + "syn 2.0.119", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "prost-types" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" +dependencies = [ + "prost", +] + +[[package]] +name = "protoc-bin-vendored" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c381df33c98266b5f08186583660090a4ffa0889e76c7e9a5e175f645a67fa" +dependencies = [ + "protoc-bin-vendored-linux-aarch_64", + "protoc-bin-vendored-linux-ppcle_64", + "protoc-bin-vendored-linux-s390_64", + "protoc-bin-vendored-linux-x86_32", + "protoc-bin-vendored-linux-x86_64", + "protoc-bin-vendored-macos-aarch_64", + "protoc-bin-vendored-macos-x86_64", + "protoc-bin-vendored-win32", +] + +[[package]] +name = "protoc-bin-vendored-linux-aarch_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c350df4d49b5b9e3ca79f7e646fde2377b199e13cfa87320308397e1f37e1a4c" + +[[package]] +name = "protoc-bin-vendored-linux-ppcle_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55a63e6c7244f19b5c6393f025017eb5d793fd5467823a099740a7a4222440c" + +[[package]] +name = "protoc-bin-vendored-linux-s390_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dba5565db4288e935d5330a07c264a4ee8e4a5b4a4e6f4e83fad824cc32f3b0" + +[[package]] +name = "protoc-bin-vendored-linux-x86_32" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8854774b24ee28b7868cd71dccaae8e02a2365e67a4a87a6cd11ee6cdbdf9cf5" + +[[package]] +name = "protoc-bin-vendored-linux-x86_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b38b07546580df720fa464ce124c4b03630a6fb83e05c336fea2a241df7e5d78" + +[[package]] +name = "protoc-bin-vendored-macos-aarch_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89278a9926ce312e51f1d999fee8825d324d603213344a9a706daa009f1d8092" + +[[package]] +name = "protoc-bin-vendored-macos-x86_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81745feda7ccfb9471d7a4de888f0652e806d5795b61480605d4943176299756" + +[[package]] +name = "protoc-bin-vendored-win32" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95067976aca6421a523e491fce939a3e65249bac4b977adee0ee9771568e8aa3" + +[[package]] +name = "pulldown-cmark" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "278e9268c93b9e498a2da46c34d90ba93a3255eb99009646398f34bc73ab36eb" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + +[[package]] +name = "pulldown-cmark-to-cmark" +version = "22.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab1ad36992cead65f02aa399a373a42730922f1525d988172634fdefdecb8a60" +dependencies = [ + "pulldown-cmark", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rcgen" +version = "0.14.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8774e05a7d0de114588e6a28fe7e71694b82614ed569d86d8b389dfbc98b8ad8" +dependencies = [ + "aws-lc-rs", + "pem 4.0.0", + "rustls-pki-types", + "time", + "x509-parser", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.119", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] + +[[package]] +name = "sqlx" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "378620ccc25c62c89d8be1c819e76a88d59bdcc3304733330788948e619bfd71" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb" +dependencies = [ + "base64 0.22.1", + "bytes", + "cfg-if", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.16.1", + "hashlink", + "indexmap", + "log", + "memchr", + "percent-encoding", + "rustls", + "serde", + "serde_json", + "sha2 0.10.9", + "smallvec", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", + "webpki-roots", +] + +[[package]] +name = "sqlx-macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd2b84f2bc39a5705ef27ec785a11c934a41bbd4a24941e257927cddc26b60bf" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.119", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb8d96de5fdc85a5c4ec813432b523ec637e80ba98f046555f75f7908ddac7c3" +dependencies = [ + "cfg-if", + "dotenvy", + "either", + "heck", + "hex", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2 0.10.9", + "sqlx-core", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.119", + "thiserror", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27" +dependencies = [ + "bitflags", + "byteorder", + "bytes", + "crc", + "digest 0.11.3", + "dotenvy", + "either", + "futures-core", + "futures-util", + "generic-array", + "log", + "percent-encoding", + "serde", + "sha1 0.11.0", + "sha2 0.11.0", + "sqlx-core", + "thiserror", + "tracing", +] + +[[package]] +name = "sqlx-postgres" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "rand", + "serde", + "serde_json", + "sha2 0.11.0", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" +dependencies = [ + "atoi", + "flume", + "form_urlencoded", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "thiserror", + "tracing", + "url", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "supports-color" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" +dependencies = [ + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91" + +[[package]] +name = "supports-unicode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "terminal_size" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +dependencies = [ + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "textwrap" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ecfad6c3abc80a577f2b91c1e412ee57e7a060d430b553c1b0c940974ebcd49" +dependencies = [ + "icu_segmenter", + "unicode-width 0.2.2", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "serde_core", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" + +[[package]] +name = "tokio" +version = "1.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce3335fa71841cda333a58d7615b03901380ecf09d59b3296d21f8bbac0dde4e" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "libc", + "pin-project-lite", + "slab", + "tokio", +] + +[[package]] +name = "tonic" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" +dependencies = [ + "async-trait", + "axum", + "base64 0.22.1", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "rustls-native-certs", + "socket2", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f61875ac5293cf72e6c8cf0158086428c82c37229e98c840878f1706b0322" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tonic-prost" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" +dependencies = [ + "bytes", + "prost", + "tonic", +] + +[[package]] +name = "tonic-prost-build" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "654e5643eff75d7f8c99197ce1440ed19a3474eada74c12bbac488b2cafdae27" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn 2.0.119", + "tempfile", + "tonic-build", +] + +[[package]] +name = "tonic-types" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab1b02061f83d519bba3caa167f88f261ef05720ab8ebc954ade70de3348e8" +dependencies = [ + "prost", + "prost-types", + "tonic", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "base64 0.22.1", + "bitflags", + "bytes", + "http", + "http-body", + "mime", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.5", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "webpki-roots" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c1c8e20c8a38fc7d65abfe74eb7280e4aa667e8eec8bc84996d2874810748d9" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "whoami" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs", + "aws-lc-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "yasna" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282" +dependencies = [ + "bit-vec", + "time", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", + "synstructure 0.14.0", +] + +[[package]] +name = "zerocopy" +version = "0.8.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", + "synstructure 0.14.0", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "serde", + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/examples/supervisor-middleware-egress-usage/Cargo.toml b/examples/supervisor-middleware-egress-usage/Cargo.toml new file mode 100644 index 0000000000..cf505b54f4 --- /dev/null +++ b/examples/supervisor-middleware-egress-usage/Cargo.toml @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +[workspace] + +[package] +name = "openshell-supervisor-middleware-egress-usage" +description = "Example OpenShell TCP middleware that cuts egress bursts" +version = "0.0.0" +edition = "2024" +rust-version = "1.94" +license = "Apache-2.0" +publish = false + +[dependencies] +clap = { version = "4.5", features = ["derive"] } +openshell-core = { path = "../../crates/openshell-core", default-features = false } +prost-types = "0.14" +tokio = { version = "1.43", features = ["macros", "rt-multi-thread", "sync", "time"] } +tokio-stream = "0.1" +tonic = { version = "0.14", features = ["transport"] } + +[dev-dependencies] +openshell-policy = { path = "../../crates/openshell-policy" } + +[[bin]] +name = "supervisor-middleware-egress-usage" +path = "src/main.rs" diff --git a/examples/supervisor-middleware-egress-usage/README.md b/examples/supervisor-middleware-egress-usage/README.md new file mode 100644 index 0000000000..c6b88e5700 --- /dev/null +++ b/examples/supervisor-middleware-egress-usage/README.md @@ -0,0 +1,47 @@ + + +# Supervisor Middleware Egress Usage (spike) + +> [!WARNING] +> Spike for TCP connection hooks (`TcpHooks.EvaluateConnection`). The contract is not settled. + +The service binds `TCP_CONNECTION` at `PRE_CONNECT` and requires the `openshell.supervisor-middleware.tcp-hooks` capability. For every admitted upstream connection its selector matches, OpenShell sends a preflight before dialing. The service selects STATS mode and receives cumulative upstream byte totals at its interval. It sums them into a sliding window per sandbox and network policy key. When a window passes `--max-bytes`, it closes that key's open connections and denies new ones for `--block-for`, with reason code `egress_budget_exceeded`. + +Covers raw TCP, `tls: skip`, terminated TLS, and plain HTTP the same way, because bytes are counted at the upstream socket. + +## Run the smoke suite + +```shell +./examples/supervisor-middleware-egress-usage/smoke.sh --test-suite +``` + +The script builds the gateway, CLI, and a local supervisor image, starts the host-run upstream (`upstream.py`), the service, and a Docker-driver gateway, creates a sandbox with `policy.yaml`, and checks: + +- 1 MB over HTTP and raw TCP, and on the `other_svc` key, pass. +- A 200 MB download over `tls: skip` at 10 MB/s is cut shortly after 20 MB. +- New `pkg_proxy` connections are then denied; `other_svc` still passes. + +Set `EGRESS_USAGE_SUPERVISOR_IMAGE` to reuse a built supervisor image and skip the builds. + +## Run manually + +```shell +cd examples/supervisor-middleware-egress-usage +cargo run -- --bind 0.0.0.0:50061 --max-bytes 20MB --window 60s --interval 5s --block-for 300s +``` + +Register it in the gateway TOML. `max_payload_bytes` is required by the config schema but unused by TCP bindings: + +```toml +[[openshell.supervisor.middleware]] +name = "egress-usage-example" +grpc_endpoint = "http://:50061" +allow_insecure_transport = true +max_payload_bytes = 65536 +timeout = "500ms" +``` + +Select it in the sandbox policy with `network_middlewares`, as in `policy.yaml`. A TCP-only service may select `tls: skip` endpoints under `fail_closed`. diff --git a/examples/supervisor-middleware-egress-usage/policy.yaml b/examples/supervisor-middleware-egress-usage/policy.yaml new file mode 100644 index 0000000000..b56e16e2cb --- /dev/null +++ b/examples/supervisor-middleware-egress-usage/policy.yaml @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# pkg_proxy stands in for artifactory.internal. Docker sandboxes reach the +# host-run upstream only through host.openshell.internal, so the two policy +# keys are told apart by port. The middleware groups usage by sandbox and +# policy key. + +version: 1 + +network_middlewares: + egress-usage: + name: Egress usage + middleware: egress-usage-example + order: 10 + on_error: fail_closed + endpoints: + include: + - host.openshell.internal + +network_policies: + pkg_proxy: + name: pkg_proxy + endpoints: + # Inspected plain HTTP. + - host: host.openshell.internal + port: 47080 + protocol: rest + enforcement: enforce + access: full + # TLS passthrough: HTTP hooks never see this traffic. + - host: host.openshell.internal + port: 47443 + tls: skip + # Raw TCP. + - host: host.openshell.internal + port: 47900 + protocol: tcp + binaries: + - path: "/**" + other_svc: + name: other_svc + endpoints: + - host: host.openshell.internal + port: 47081 + protocol: rest + enforcement: enforce + access: full + binaries: + - path: "/**" diff --git a/examples/supervisor-middleware-egress-usage/smoke.sh b/examples/supervisor-middleware-egress-usage/smoke.sh new file mode 100755 index 0000000000..5113f52e28 --- /dev/null +++ b/examples/supervisor-middleware-egress-usage/smoke.sh @@ -0,0 +1,383 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +EXAMPLE_DIR="$ROOT/examples/supervisor-middleware-egress-usage" +RUN_TEST_SUITE=0 +PRINT_CONFIG=0 + +usage() { + cat <&2; usage >&2; exit 2 ;; + esac +done + +detect_service_host() { + if [[ -n "${EGRESS_USAGE_SMOKE_HOST:-}" ]]; then + printf '%s\n' "$EGRESS_USAGE_SMOKE_HOST" + return + fi + ip route get 1.1.1.1 2>/dev/null | awk '{ for (i = 1; i <= NF; i++) if ($i == "src") { print $(i + 1); exit } }' +} + +SERVICE_HOST="$(detect_service_host)" +[[ -n "$SERVICE_HOST" ]] || { echo "set EGRESS_USAGE_SMOKE_HOST" >&2; exit 1; } + +SMOKE_TMP_DIR="$(mktemp -d)" +LOG_DIR="$SMOKE_TMP_DIR/logs" +JWT_DIR="$SMOKE_TMP_DIR/jwt" +GATEWAY_CONFIG="$SMOKE_TMP_DIR/gateway.toml" +SETUP_LOG="$LOG_DIR/setup.log" +GATEWAY_LOG="$LOG_DIR/gateway.log" +MIDDLEWARE_LOG="$LOG_DIR/middleware.log" +UPSTREAM_LOG="$LOG_DIR/upstream.log" +SANDBOX_LOG="$LOG_DIR/sandbox.log" +RUN_ID="egress-usage-smoke-$$-$RANDOM" +SUPERVISOR_IMAGE="${EGRESS_USAGE_SUPERVISOR_IMAGE:-localhost/openshell-egress-usage/supervisor:$RUN_ID}" +SANDBOX_NAME="eu-$$-$RANDOM" +SANDBOX_CREATED=0 +MAX_BYTES="${EGRESS_USAGE_MAX_BYTES:-20MB}" +WINDOW="${EGRESS_USAGE_WINDOW:-60s}" +INTERVAL="${EGRESS_USAGE_INTERVAL:-1s}" +BLOCK_FOR="${EGRESS_USAGE_BLOCK_FOR:-120s}" +# The scenarios need curl and python3 in the sandbox. +SANDBOX_IMAGE="${EGRESS_USAGE_SANDBOX_IMAGE:-ghcr.io/nvidia/openshell-community/sandboxes/base:latest}" + +mkdir -p "$LOG_DIR" + +cleanup() { + local status=$? + trap - EXIT + if [[ "$SANDBOX_CREATED" -eq 1 && -n "${CLI+x}" ]]; then + "${CLI[@]}" sandbox delete "$SANDBOX_NAME" >>"$SETUP_LOG" 2>&1 || true + fi + for pid in "${GATEWAY_PID:-}" "${MIDDLEWARE_PID:-}" "${UPSTREAM_PID:-}"; do + if [[ -n "$pid" ]]; then + kill "$pid" 2>/dev/null || true + wait "$pid" 2>/dev/null || true + fi + done + if [[ "$status" -eq 0 ]]; then + rm -rf "$SMOKE_TMP_DIR" + else + echo "logs retained in $LOG_DIR" >&2 + fi + exit "$status" +} +trap cleanup EXIT + +choose_port_block() { + local start offset ok + for _ in {1..200}; do + start=$((20000 + RANDOM % 20000)) + ok=1 + for ((offset = 0; offset < 3; offset++)); do + if (exec 3<>"/dev/tcp/127.0.0.1/$((start + offset))") 2>/dev/null; then + ok=0 + break + fi + done + if [[ "$ok" == "1" ]]; then + printf '%s\n' "$start" + return + fi + done + echo "failed to find free local ports" >&2 + exit 1 +} + +PORT_BASE="$(choose_port_block)" +MIDDLEWARE_PORT="$PORT_BASE" +GATEWAY_PORT="$((PORT_BASE + 1))" +HEALTH_PORT="$((PORT_BASE + 2))" +GATEWAY_ENDPOINT="http://127.0.0.1:$GATEWAY_PORT" + +write_gateway_config() { + cat >"$GATEWAY_CONFIG" <&2 + tail -n 100 "$path" >&2 2>/dev/null || printf '(missing)\n' >&2 + done +} + +capture_logs() { + local container_id + container_id="$(docker ps -aq --format '{{.ID}} {{.Names}}' | awk -v name="--$SANDBOX_NAME-" 'index($2, name) && $2 ~ /-supervisor$/ { print $1; exit }')" + if [[ -n "$container_id" ]]; then + docker logs "$container_id" >"$SANDBOX_LOG" 2>&1 || true + fi +} + +fail() { + printf 'FAIL %s\n' "$1" >&2 + capture_logs + dump_logs + exit 1 +} + +run_setup_step() { + local label="$1" + shift + printf 'INFO %s\n' "$label" + printf '\n== %s ==\n' "$label" >>"$SETUP_LOG" + "$@" >>"$SETUP_LOG" 2>&1 || fail "$label" +} + +cargo_target_dir() { + cargo metadata --format-version=1 --no-deps --manifest-path "$1" | jq -er '.target_directory' +} + +generate_gateway_jwt_bundle() { + mkdir -p "$JWT_DIR" + openssl genpkey -algorithm ed25519 -out "$JWT_DIR/signing.pem" >/dev/null 2>&1 + openssl pkey -in "$JWT_DIR/signing.pem" -pubout -out "$JWT_DIR/public.pem" >/dev/null 2>&1 + printf '%s\n' "$RUN_ID" >"$JWT_DIR/kid" +} + +start_upstream() { + printf 'INFO starting package proxy stand-in on host.openshell.internal\n' + openssl req -x509 -newkey rsa:2048 -nodes -days 2 -subj "/CN=host.openshell.internal" \ + -addext "subjectAltName=DNS:host.openshell.internal" \ + -keyout "$SMOKE_TMP_DIR/key.pem" -out "$SMOKE_TMP_DIR/cert.pem" >/dev/null 2>&1 + UPSTREAM_TLS_CERT="$SMOKE_TMP_DIR/cert.pem" UPSTREAM_TLS_KEY="$SMOKE_TMP_DIR/key.pem" \ + python3 -u "$EXAMPLE_DIR/upstream.py" >"$UPSTREAM_LOG" 2>&1 & + UPSTREAM_PID=$! + for _ in {1..30}; do + kill -0 "$UPSTREAM_PID" 2>/dev/null || fail "upstream starts (ports 47080, 47081, 47443, 47900)" + if grep -q 'upstream ready' "$UPSTREAM_LOG"; then + return + fi + sleep 1 + done + fail "upstream is ready" +} + +start_middleware() { + printf 'INFO starting egress-usage service at %s:%s\n' "$SERVICE_HOST" "$MIDDLEWARE_PORT" + "$MIDDLEWARE_BIN" \ + --bind "0.0.0.0:$MIDDLEWARE_PORT" \ + --max-bytes "$MAX_BYTES" --window "$WINDOW" --interval "$INTERVAL" --block-for "$BLOCK_FOR" \ + >"$MIDDLEWARE_LOG" 2>&1 & + MIDDLEWARE_PID=$! + for _ in {1..60}; do + kill -0 "$MIDDLEWARE_PID" 2>/dev/null || fail "egress-usage service starts" + if (exec 3<>"/dev/tcp/$SERVICE_HOST/$MIDDLEWARE_PORT") 2>/dev/null; then + return + fi + sleep 1 + done + fail "egress-usage service is reachable" +} + +start_gateway() { + printf 'INFO starting gateway\n' + env -u OPENSHELL_DRIVERS -u OPENSHELL_COMPUTE_DRIVER "$GATEWAY_BIN" \ + --compute-driver docker \ + --config "$GATEWAY_CONFIG" \ + --bind-address 127.0.0.1 \ + --port "$GATEWAY_PORT" \ + --health-port "$HEALTH_PORT" \ + --metrics-port 0 \ + --log-level "${EGRESS_USAGE_SMOKE_LOG_LEVEL:-info}" \ + --disable-tls \ + --db-url "sqlite://$SMOKE_TMP_DIR/gateway.db" >"$GATEWAY_LOG" 2>&1 & + GATEWAY_PID=$! + for _ in {1..60}; do + kill -0 "$GATEWAY_PID" 2>/dev/null || fail "gateway starts with egress-usage" + if curl -fsS "http://127.0.0.1:$HEALTH_PORT/healthz" >/dev/null 2>&1; then + return + fi + sleep 1 + done + fail "gateway starts with egress-usage" +} + +create_sandbox() { + CLI=(env -u OPENSHELL_SANDBOX_POLICY "$CLI_BIN" --gateway-endpoint "$GATEWAY_ENDPOINT") + SANDBOX_CREATED=1 + run_setup_step "creating sandbox" \ + "${CLI[@]}" sandbox create --name "$SANDBOX_NAME" --from "$SANDBOX_IMAGE" --policy "$EXAMPLE_DIR/policy.yaml" --no-tty --detach -- sleep infinity +} + +sx() { + "${CLI[@]}" sandbox exec --name "$SANDBOX_NAME" --no-tty -- "$@" +} + +# Raw TCP client: the sandbox boundary mediates the connection transparently. +RAW_CLIENT=' +import socket, sys +host, port, count = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]) +received = 0 +try: + s = socket.create_connection((host, port), timeout=60) + s.sendall(("PULL %d\n" % count).encode()) + while received < count: + chunk = s.recv(65536) + if not chunk: + break + received += len(chunk) +except OSError as error: + print("error=%s" % error) +print("received=%d" % received) +sys.exit(0 if received == count else 4) +' + +raw_get() { + sx python3 -c "$RAW_CLIENT" "$1" "$2" "$3" +} + +http_get() { + sx curl -sS -o /dev/null -w 'status=%{http_code} size=%{size_download}\n' --max-time 60 "$@" +} + +run_suite() { + local out started elapsed + out="$(http_get http://host.openshell.internal:47080/bytes/1000000 2>&1)" || fail "baseline HTTP download: $out" + [[ "$out" == *"status=200 size=1000000"* ]] || fail "baseline HTTP download: $out" + printf 'PASS baseline HTTP 1MB under threshold (%s)\n' "$out" + + out="$(raw_get host.openshell.internal 47900 1000000 2>&1)" || fail "baseline raw TCP: $out" + printf 'PASS baseline raw TCP 1MB under threshold (%s)\n' "$out" + + out="$(http_get http://host.openshell.internal:47081/bytes/1000000 2>&1)" || fail "control host: $out" + printf 'PASS control key other_svc (%s)\n' "$out" + + printf 'INFO D2 burst: 200MB over tls: skip at 10MB/s, threshold %s per %s\n' "$MAX_BYTES" "$WINDOW" + started=$(date +%s.%N) + out="$(http_get -k --limit-rate 10M https://host.openshell.internal:47443/bytes/200000000 2>&1)" && fail "D2 burst was not cut: $out" + elapsed=$(echo "$(date +%s.%N) - $started" | bc) + [[ "$out" != *"size=200000000"* ]] || fail "D2 burst was not cut: $out" + printf 'PASS D2 burst over tls: skip cut after %ss (%s)\n' "$elapsed" "$(echo "$out" | tr '\n' ' ')" + + out="$(raw_get host.openshell.internal 47900 1000 2>&1)" && fail "new raw TCP connection was not denied: $out" + [[ "$out" == *"received=0"* ]] || fail "new raw TCP connection denial: $out" + printf 'PASS new pkg_proxy raw TCP connection denied (%s)\n' "$out" + + out="$(http_get http://host.openshell.internal:47080/bytes/1000 2>&1)" || true + # Transparently mediated connections have no CONNECT to answer: a + # preflight denial closes the connection before any response. + [[ "$out" == *"status=403"* || "$out" == *"Empty reply"* ]] || fail "new HTTP request denied: $out" + printf 'PASS new pkg_proxy HTTP request denied (%s)\n' "$(echo "$out" | tr '\n' ' ')" + + out="$(http_get http://host.openshell.internal:47081/bytes/1000000 2>&1)" || fail "control host after trip: $out" + printf 'PASS control key other_svc still allowed (%s)\n' "$out" + + grep -E 'TRIP|deny' "$MIDDLEWARE_LOG" || true + echo "ALL PASS egress-usage smoke" +} + +print_ready() { + cat </dev/null 2>&1 || fail "missing required command: $command" +done +ROOT_TARGET_DIR="$(cargo_target_dir "$ROOT/Cargo.toml")" +EXAMPLE_TARGET_DIR="$(cargo_target_dir "$EXAMPLE_DIR/Cargo.toml")" +GATEWAY_BIN="$ROOT_TARGET_DIR/debug/openshell-gateway" +CLI_BIN="$ROOT_TARGET_DIR/debug/openshell" +MIDDLEWARE_BIN="$EXAMPLE_TARGET_DIR/debug/supervisor-middleware-egress-usage" +if [[ -z "${EGRESS_USAGE_SUPERVISOR_IMAGE:-}" ]]; then + run_setup_step "building gateway" cargo build --quiet -p openshell-gateway --bin openshell-gateway + run_setup_step "building Linux sandbox supervisor image" \ + env -u CI -u DOCKER_PLATFORM -u DOCKER_PUSH -u DOCKER_OUTPUT \ + CONTAINER_ENGINE=docker PREBUILT_AUTO_STAGE=1 \ + IMAGE_REGISTRY=localhost/openshell-egress-usage IMAGE_TAG="$RUN_ID" \ + mise run docker:build:supervisor + run_setup_step "building CLI" cargo build --quiet -p openshell-cli --bin openshell +fi +run_setup_step "building egress-usage service" cargo build --quiet --manifest-path "$EXAMPLE_DIR/Cargo.toml" +generate_gateway_jwt_bundle +start_upstream +start_middleware +start_gateway +create_sandbox + +if [[ "$RUN_TEST_SUITE" -eq 1 ]]; then + run_suite +else + print_ready + while kill -0 "$GATEWAY_PID" 2>/dev/null && kill -0 "$MIDDLEWARE_PID" 2>/dev/null; do sleep 1; done + fail "gateway or middleware exited" +fi diff --git a/examples/supervisor-middleware-egress-usage/src/main.rs b/examples/supervisor-middleware-egress-usage/src/main.rs new file mode 100644 index 0000000000..af3c10247e --- /dev/null +++ b/examples/supervisor-middleware-egress-usage/src/main.rs @@ -0,0 +1,229 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Example OpenShell TCP middleware: egress usage. +//! +//! Binds `TCP_CONNECTION` at `PRE_CONNECT` (`TcpHooks.EvaluateConnection`) +//! and selects STATS mode for every connection. It sums upstream bytes into a +//! sliding window per sandbox and network policy key. When a window passes +//! `--max-bytes`, it closes that key's open connections and denies new ones +//! for `--block-for`. + +mod usage; + +use std::net::SocketAddr; +use std::time::Duration; + +use clap::Parser; +use openshell_core::extension_protocol::{ + ExtensionFamily, tcp_hooks_middleware_metadata, validate_gateway_metadata, +}; +use openshell_core::middleware::{TcpHookResultStream, WebSocketResponseStream}; +use openshell_core::proto::middleware::v1::supervisor_middleware_server::{ + SupervisorMiddleware, SupervisorMiddlewareServer, +}; +use openshell_core::proto::middleware::v1::tcp_hooks_server::{TcpHooks, TcpHooksServer}; +use openshell_core::proto::{ + HttpRequestEvaluation, HttpRequestResult, MiddlewareBinding, MiddlewareDescribeRequest, + MiddlewareManifest, SupervisorMiddlewareOperation, SupervisorMiddlewarePhase, TcpHookEvent, + ValidateConfigRequest, ValidateConfigResponse, WebSocketSessionEvent, +}; +use tonic::transport::Server; +use tonic::{Request, Response, Status}; + +use crate::usage::{Direction, Limits, Usage}; + +const MANIFEST_NAME: &str = "example/egress-usage"; + +#[derive(Debug, Parser)] +#[command(about = "Run the example OpenShell egress-usage TCP middleware")] +struct Cli { + /// Address on which to serve plaintext gRPC. + #[arg(long, default_value = "127.0.0.1:50061")] + bind: SocketAddr, + /// Bytes allowed per window per sandbox and policy key, such as 20MB. + #[arg(long, default_value = "20MB", value_parser = parse_bytes)] + max_bytes: u64, + /// Sliding window length, such as 60s. + #[arg(long, default_value = "60s", value_parser = parse_duration)] + window: Duration, + /// Stats interval requested from OpenShell, such as 5s. + #[arg(long, default_value = "5s", value_parser = parse_duration)] + interval: Duration, + /// How long a tripped key stays blocked, such as 300s. + #[arg(long, default_value = "300s", value_parser = parse_duration)] + block_for: Duration, + /// Which bytes count toward the window. + #[arg(long, value_enum, default_value_t = Direction::Both)] + direction: Direction, +} + +fn parse_bytes(value: &str) -> Result { + let upper = value.trim().to_ascii_uppercase(); + let (digits, multiplier) = [ + ("GB", 1_000_000_000), + ("MB", 1_000_000), + ("KB", 1_000), + ("B", 1), + ] + .iter() + .find_map(|(suffix, multiplier)| { + upper + .strip_suffix(suffix) + .map(|digits| (digits.trim().to_string(), *multiplier)) + }) + .unwrap_or((upper.clone(), 1)); + digits + .parse::() + .map(|value| value * multiplier) + .map_err(|_| format!("invalid byte size '{value}'")) +} + +fn parse_duration(value: &str) -> Result { + let value = value.trim(); + let (digits, unit) = value.split_at( + value + .find(|c: char| !c.is_ascii_digit()) + .unwrap_or(value.len()), + ); + let number: u64 = digits + .parse() + .map_err(|_| format!("invalid duration '{value}'"))?; + match unit { + "" | "s" => Ok(Duration::from_secs(number)), + "ms" => Ok(Duration::from_millis(number)), + "m" => Ok(Duration::from_secs(number * 60)), + _ => Err(format!("invalid duration '{value}', use ms, s, or m")), + } +} + +#[derive(Clone)] +struct EgressUsage { + usage: Usage, +} + +#[tonic::async_trait] +impl SupervisorMiddleware for EgressUsage { + type EvaluateWebSocketSessionStream = WebSocketResponseStream; + + async fn describe( + &self, + request: Request, + ) -> Result, Status> { + let manifest = MiddlewareManifest { + name: MANIFEST_NAME.into(), + service_version: env!("CARGO_PKG_VERSION").into(), + bindings: vec![MiddlewareBinding { + operation: SupervisorMiddlewareOperation::TcpConnection as i32, + phase: SupervisorMiddlewarePhase::PreConnect as i32, + ..Default::default() + }], + expected_audience: String::new(), + extension: Some(tcp_hooks_middleware_metadata( + MANIFEST_NAME, + openshell_core::VERSION, + )), + }; + validate_gateway_metadata( + ExtensionFamily::SupervisorMiddleware, + MANIFEST_NAME, + manifest.extension.as_ref(), + request.into_inner().gateway, + ) + .map_err(|error| Status::failed_precondition(error.to_string()))?; + Ok(Response::new(manifest)) + } + + async fn validate_config( + &self, + _request: Request, + ) -> Result, Status> { + // Thresholds come from flags; any policy config is accepted. + Ok(Response::new(ValidateConfigResponse { + valid: true, + reason: String::new(), + })) + } + + async fn evaluate_http_request( + &self, + _request: Request, + ) -> Result, Status> { + Err(Status::unimplemented( + "egress usage implements TCP hooks only", + )) + } + + async fn evaluate_web_socket_session( + &self, + _request: Request>, + ) -> Result, Status> { + Err(Status::unimplemented( + "egress usage implements TCP hooks only", + )) + } +} + +#[tonic::async_trait] +impl TcpHooks for EgressUsage { + type EvaluateConnectionStream = TcpHookResultStream; + + async fn evaluate_connection( + &self, + request: Request>, + ) -> Result, Status> { + Ok(Response::new( + self.usage.connection_stream(request.into_inner()), + )) + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let cli = Cli::parse(); + let limits = Limits { + max_bytes: cli.max_bytes, + window: cli.window, + interval: cli.interval, + block_for: cli.block_for, + direction: cli.direction, + }; + println!( + "serving {MANIFEST_NAME} on http://{} max_bytes={} window={:?} interval={:?} block_for={:?} direction={:?}", + cli.bind, + limits.max_bytes, + limits.window, + limits.interval, + limits.block_for, + limits.direction + ); + let service = EgressUsage { + usage: Usage::new(limits), + }; + Server::builder() + .add_service(SupervisorMiddlewareServer::new(service.clone())) + .add_service(TcpHooksServer::new(service)) + .serve(cli.bind) + .await?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_sizes_and_durations() { + assert_eq!(parse_bytes("20MB").unwrap(), 20_000_000); + assert_eq!(parse_bytes("512").unwrap(), 512); + assert_eq!(parse_duration("5s").unwrap(), Duration::from_secs(5)); + assert_eq!(parse_duration("2m").unwrap(), Duration::from_secs(120)); + } + + #[test] + fn example_policy_is_valid() { + let policy = openshell_policy::parse_sandbox_policy(include_str!("../policy.yaml")) + .expect("example policy must parse"); + openshell_policy::validate_sandbox_policy(&policy).expect("example policy must be valid"); + } +} diff --git a/examples/supervisor-middleware-egress-usage/src/usage.rs b/examples/supervisor-middleware-egress-usage/src/usage.rs new file mode 100644 index 0000000000..fb06ccc7b7 --- /dev/null +++ b/examples/supervisor-middleware-egress-usage/src/usage.rs @@ -0,0 +1,269 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Sliding egress windows per sandbox and policy key. + +use std::collections::{HashMap, VecDeque}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; + +use openshell_core::middleware::TcpHookResultStream; +use openshell_core::proto::{ + MiddlewareDiagnostics, TcpClose, TcpHookEvent, TcpHookResult, TcpInspect, TcpPreflight, + TcpPreflightResult, TcpStatsMode, tcp_hook_event, tcp_hook_result, tcp_inspect, + tcp_preflight_result, +}; +use tokio::sync::mpsc; +use tokio_stream::StreamExt; +use tokio_stream::wrappers::ReceiverStream; +use tonic::Status; + +/// Reason code sent with every close and deny. +pub const REASON_CODE: &str = "egress_budget_exceeded"; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)] +pub enum Direction { + /// bytes_in plus bytes_out. + Both, + /// bytes_in only (downloads). + In, + /// bytes_out only (uploads). + Out, +} + +#[derive(Debug, Clone, Copy)] +pub struct Limits { + pub max_bytes: u64, + pub window: Duration, + pub interval: Duration, + pub block_for: Duration, + pub direction: Direction, +} + +/// Sandbox id and network policy key. +type Key = (String, String); + +#[derive(Default)] +struct Window { + samples: VecDeque<(Instant, u64)>, + total: u64, +} + +impl Window { + fn add(&mut self, now: Instant, bytes: u64, window: Duration) -> u64 { + while let Some(&(at, bytes)) = self.samples.front() { + if now.duration_since(at) <= window { + break; + } + self.samples.pop_front(); + self.total -= bytes; + } + if bytes > 0 { + self.samples.push_back((now, bytes)); + self.total += bytes; + } + self.total + } +} + +type ResultSender = mpsc::Sender>; + +#[derive(Default)] +struct State { + windows: HashMap, + blocked_until: HashMap, + open: HashMap>, +} + +#[derive(Clone)] +pub struct Usage { + limits: Limits, + state: Arc>, + next_id: Arc, +} + +fn close_result() -> TcpHookResult { + TcpHookResult { + result: Some(tcp_hook_result::Result::Close(TcpClose { + diagnostics: Some(MiddlewareDiagnostics { + reason: "sandbox egress over budget".into(), + reason_code: REASON_CODE.into(), + ..Default::default() + }), + })), + } +} + +fn key_of(preflight: &TcpPreflight) -> Key { + let sandbox = preflight + .context + .as_ref() + .map(|context| context.sandbox_id.clone()) + .unwrap_or_default(); + let policy = preflight + .policy_keys + .first() + .cloned() + .unwrap_or_else(|| preflight.endpoint_id.clone()); + (sandbox, policy) +} + +impl Usage { + pub fn new(limits: Limits) -> Self { + Self { + limits, + state: Arc::default(), + next_id: Arc::default(), + } + } + + fn counted(&self, bytes_out: u64, bytes_in: u64) -> u64 { + match self.limits.direction { + Direction::Both => bytes_out + bytes_in, + Direction::In => bytes_in, + Direction::Out => bytes_out, + } + } + + pub fn connection_stream( + &self, + mut events: tonic::Streaming, + ) -> TcpHookResultStream { + let (tx, rx) = mpsc::channel(4); + let usage = self.clone(); + tokio::spawn(async move { + let Some(Ok(TcpHookEvent { + event: Some(tcp_hook_event::Event::Preflight(preflight)), + })) = events.next().await + else { + let _ = tx + .send(Err(Status::invalid_argument( + "first event must be preflight", + ))) + .await; + return; + }; + let key = key_of(&preflight); + let id = usage.next_id.fetch_add(1, Ordering::Relaxed); + let blocked = { + let mut state = usage.state.lock().unwrap(); + let now = Instant::now(); + let blocked = state + .blocked_until + .get(&key) + .is_some_and(|until| *until > now); + if !blocked { + state + .open + .entry(key.clone()) + .or_default() + .insert(id, tx.clone()); + } + blocked + }; + if blocked { + println!( + "deny sandbox={} policy={} host={}:{} reason={REASON_CODE}", + key.0, key.1, preflight.host, preflight.port + ); + let _ = tx.send(Ok(close_result())).await; + return; + } + let interval = prost_types::Duration::try_from(usage.limits.interval).ok(); + let _ = tx + .send(Ok(TcpHookResult { + result: Some(tcp_hook_result::Result::PreflightResult( + TcpPreflightResult { + decision: Some(tcp_preflight_result::Decision::Inspect(TcpInspect { + mode: Some(tcp_inspect::Mode::Stats(TcpStatsMode { interval })), + })), + diagnostics: None, + }, + )), + })) + .await; + let mut last = 0_u64; + while let Some(Ok(event)) = events.next().await { + let (bytes_out, bytes_in, end) = match event.event { + Some(tcp_hook_event::Event::Stats(stats)) => { + (stats.bytes_out, stats.bytes_in, false) + } + Some(tcp_hook_event::Event::End(end)) => (end.bytes_out, end.bytes_in, true), + _ => continue, + }; + let counted = usage.counted(bytes_out, bytes_in); + let delta = counted.saturating_sub(last); + last = counted; + usage.record(&key, &preflight, delta); + if end { + break; + } + } + let mut state = usage.state.lock().unwrap(); + if let Some(open) = state.open.get_mut(&key) { + open.remove(&id); + } + }); + Box::pin(ReceiverStream::new(rx)) + } + + /// Add `delta` to the key's window and trip it when over the limit. + fn record(&self, key: &Key, preflight: &TcpPreflight, delta: u64) { + let now = Instant::now(); + let mut state = self.state.lock().unwrap(); + let total = + state + .windows + .entry(key.clone()) + .or_default() + .add(now, delta, self.limits.window); + let already_blocked = state + .blocked_until + .get(key) + .is_some_and(|until| *until > now); + if total <= self.limits.max_bytes || already_blocked { + return; + } + state + .blocked_until + .insert(key.clone(), now + self.limits.block_for); + let open = state.open.remove(key).unwrap_or_default(); + println!( + "TRIP sandbox={} policy={} host={}:{} window_bytes={total} max_bytes={} over_by={} closing={} block_for={:?}", + key.0, + key.1, + preflight.host, + preflight.port, + self.limits.max_bytes, + total - self.limits.max_bytes, + open.len(), + self.limits.block_for, + ); + for sender in open.into_values() { + // Never block the lock holder: a full result channel already has + // a pending result, and the connection is denied on its next one. + let _ = sender.try_send(Ok(close_result())); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn window_expires_old_samples() { + let mut window = Window::default(); + let start = Instant::now(); + assert_eq!(window.add(start, 10, Duration::from_secs(60)), 10); + assert_eq!( + window.add(start + Duration::from_secs(30), 5, Duration::from_secs(60)), + 15 + ); + assert_eq!( + window.add(start + Duration::from_secs(61), 1, Duration::from_secs(60)), + 6 + ); + } +} diff --git a/examples/supervisor-middleware-egress-usage/upstream.py b/examples/supervisor-middleware-egress-usage/upstream.py new file mode 100644 index 0000000000..7417171f53 --- /dev/null +++ b/examples/supervisor-middleware-egress-usage/upstream.py @@ -0,0 +1,122 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Package proxy stand-in for the egress-usage example. + +Runs on the host. Docker sandboxes reach it as host.openshell.internal, so +policy keys are told apart by port. + +- 47080 (pkg_proxy) and 47081 (other_svc): plain HTTP. GET /bytes/ streams + n bytes. PUT or POST consumes the body and returns its length. +- 47443 (pkg_proxy): the same HTTP handler over TLS (cert and key from + UPSTREAM_TLS_CERT and UPSTREAM_TLS_KEY), for tls: skip passthrough. +- 47900 (pkg_proxy): raw TCP. Send "PULL \\n" to receive n bytes, or + "PUSH\\n" followed by bytes and a half-close to receive the count. A stream + that starts with an HTTP method is sniffed as HTTP, hence the verbs. +""" + +import os +import socketserver +import ssl +import threading +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer + +CHUNK = b"x" * 65536 + + +def stream_bytes(write, count): + while count > 0: + piece = CHUNK[: min(count, len(CHUNK))] + write(piece) + count -= len(piece) + + +class Handler(BaseHTTPRequestHandler): + protocol_version = "HTTP/1.1" + + def do_GET(self): + parts = self.path.strip("/").split("/") + if len(parts) == 2 and parts[0] == "bytes" and parts[1].isdigit(): + count = int(parts[1]) + self.send_response(200) + self.send_header("Content-Type", "application/octet-stream") + self.send_header("Content-Length", str(count)) + self.end_headers() + try: + stream_bytes(self.wfile.write, count) + except (BrokenPipeError, ConnectionResetError): + pass + return + body = b"ok\n" + self.send_response(200) + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def _consume(self): + length = int(self.headers.get("Content-Length", "0")) + received = 0 + while received < length: + data = self.rfile.read(min(65536, length - received)) + if not data: + break + received += len(data) + body = f"{received}\n".encode() + self.send_response(200) + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + do_PUT = _consume + do_POST = _consume + + def log_message(self, format, *args): + print(f"{self.client_address[0]} {format % args}", flush=True) + + +class RawHandler(socketserver.StreamRequestHandler): + def handle(self): + line = self.rfile.readline().decode(errors="replace").strip() + try: + if line.startswith("PULL "): + stream_bytes(self.wfile.write, int(line[5:])) + elif line == "PUSH": + received = 0 + while True: + data = self.rfile.read1(65536) + if not data: + break + received += len(data) + self.wfile.write(f"{received}\n".encode()) + except (BrokenPipeError, ConnectionResetError): + pass + print(f"raw {self.client_address[0]} {line}", flush=True) + + +class RawServer(socketserver.ThreadingTCPServer): + allow_reuse_address = True + daemon_threads = True + + +def main(): + bind = os.environ.get("UPSTREAM_BIND", "127.0.0.1") + servers = [ + ThreadingHTTPServer((bind, 47080), Handler), + ThreadingHTTPServer((bind, 47081), Handler), + RawServer((bind, 47900), RawHandler), + ] + cert, key = os.environ.get("UPSTREAM_TLS_CERT"), os.environ.get("UPSTREAM_TLS_KEY") + if cert and key: + tls = ThreadingHTTPServer((bind, 47443), Handler) + context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + context.load_cert_chain(cert, key) + tls.socket = context.wrap_socket(tls.socket, server_side=True) + servers.append(tls) + for server in servers[1:]: + threading.Thread(target=server.serve_forever, daemon=True).start() + print("upstream ready", flush=True) + servers[0].serve_forever() + + +if __name__ == "__main__": + main() From 1e25e3b0d9c163d5c67ab6fca53d9d0c4552b15b Mon Sep 17 00:00:00 2001 From: Piotr Mlocek Date: Mon, 2 Nov 2026 13:01:29 -0800 Subject: [PATCH 3/6] chore(examples): drop egress-usage example lockfile (spike) Signed-off-by: Piotr Mlocek --- .../.gitignore | 2 + .../Cargo.lock | 4051 ----------------- 2 files changed, 2 insertions(+), 4051 deletions(-) create mode 100644 examples/supervisor-middleware-egress-usage/.gitignore delete mode 100644 examples/supervisor-middleware-egress-usage/Cargo.lock diff --git a/examples/supervisor-middleware-egress-usage/.gitignore b/examples/supervisor-middleware-egress-usage/.gitignore new file mode 100644 index 0000000000..62f12e5090 --- /dev/null +++ b/examples/supervisor-middleware-egress-usage/.gitignore @@ -0,0 +1,2 @@ +# Spike: no tracked lockfile, so the PR diff stays comparable. +Cargo.lock diff --git a/examples/supervisor-middleware-egress-usage/Cargo.lock b/examples/supervisor-middleware-egress-usage/Cargo.lock deleted file mode 100644 index 9e134f3517..0000000000 --- a/examples/supervisor-middleware-egress-usage/Cargo.lock +++ /dev/null @@ -1,4051 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "addr2line" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "getrandom 0.3.4", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" -dependencies = [ - "memchr", -] - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - -[[package]] -name = "anyhow" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" - -[[package]] -name = "asn1-rs" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" -dependencies = [ - "asn1-rs-derive", - "asn1-rs-impl", - "displaydoc", - "nom", - "num-traits", - "rusticata-macros", - "thiserror", - "time", -] - -[[package]] -name = "asn1-rs-derive" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "synstructure 0.13.2", -] - -[[package]] -name = "asn1-rs-impl" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "async-broadcast" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" -dependencies = [ - "event-listener", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "async-trait" -version = "0.1.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "atoi" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" -dependencies = [ - "num-traits", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - -[[package]] -name = "aws-lc-rs" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e" -dependencies = [ - "aws-lc-sys", - "untrusted 0.7.1", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27" -dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", - "pkg-config", -] - -[[package]] -name = "axum" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" -dependencies = [ - "axum-core", - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "serde_core", - "sync_wrapper", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "axum-core" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "sync_wrapper", - "tower-layer", - "tower-service", -] - -[[package]] -name = "backon" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" -dependencies = [ - "fastrand", - "gloo-timers", - "tokio", -] - -[[package]] -name = "backtrace" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-link", -] - -[[package]] -name = "backtrace-ext" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" -dependencies = [ - "backtrace", -] - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" - -[[package]] -name = "bit-vec" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" -dependencies = [ - "serde", -] - -[[package]] -name = "bitflags" -version = "2.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" -dependencies = [ - "serde_core", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block-buffer" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "bumpalo" -version = "3.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" - -[[package]] -name = "cc" -version = "1.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3eb0f42d6c360dc3f8a821f6bf2fdea7f72bfd36b3076eb0e6d1e9e0752fff4" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" - -[[package]] -name = "chacha20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.1", - "rand_core 0.10.1", -] - -[[package]] -name = "chrono" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" -dependencies = [ - "num-traits", - "serde", -] - -[[package]] -name = "clap" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "clap_lex" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" - -[[package]] -name = "cmake" -version = "0.1.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" -dependencies = [ - "cc", -] - -[[package]] -name = "cmov" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" - -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - -[[package]] -name = "combine" -version = "4.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" -dependencies = [ - "libc", -] - -[[package]] -name = "crc" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" - -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - -[[package]] -name = "crossbeam-queue" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "crypto-common" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "ctutils" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12218662ccac37d9714f2191252553d67585b97df8c839cbe49e0bde278dc865" -dependencies = [ - "cmov", -] - -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.119", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "data-encoding" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" - -[[package]] -name = "der-parser" -version = "10.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" -dependencies = [ - "asn1-rs", - "displaydoc", - "nom", - "num-bigint", - "num-traits", - "rusticata-macros", -] - -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer 0.10.4", - "crypto-common 0.1.7", -] - -[[package]] -name = "digest" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" -dependencies = [ - "block-buffer 0.12.1", - "crypto-common 0.2.2", - "ctutils", -] - -[[package]] -name = "displaydoc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "educe" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" -dependencies = [ - "enum-ordinalize", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "either" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" -dependencies = [ - "serde", -] - -[[package]] -name = "enum-ordinalize" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89dd01549b09589510cf0647475075d12071456586d70f5c75c98ae2a5537677" -dependencies = [ - "enum-ordinalize-derive", -] - -[[package]] -name = "enum-ordinalize-derive" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65863d15a4ce2888bd2f0f543cc963d3879c3a022c8ee43f6141d479a3ac815" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "etcetera" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96" -dependencies = [ - "cfg-if", - "windows-sys 0.61.2", -] - -[[package]] -name = "event-listener" -version = "5.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" -dependencies = [ - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "fastrand" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" - -[[package]] -name = "find-msvc-tools" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" - -[[package]] -name = "fixedbitset" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" - -[[package]] -name = "flume" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" -dependencies = [ - "futures-core", - "futures-sink", - "spin", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - -[[package]] -name = "futures" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" - -[[package]] -name = "futures-executor" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-intrusive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot", -] - -[[package]] -name = "futures-io" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" - -[[package]] -name = "futures-macro" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "futures-sink" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" - -[[package]] -name = "futures-task" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" - -[[package]] -name = "futures-util" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", - "rand_core 0.10.1", -] - -[[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" - -[[package]] -name = "glob" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" - -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "h2" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.1.5", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.2.0", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "hashlink" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f" -dependencies = [ - "hashbrown 0.16.1", -] - -[[package]] -name = "headers" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" -dependencies = [ - "base64 0.22.1", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha1 0.10.7", -] - -[[package]] -name = "headers-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" -dependencies = [ - "http", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hickory-proto" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b92608f679a6fa515dd1d15c1ff89443026e391200a2c840c7afcba482893d" -dependencies = [ - "data-encoding", - "idna", - "ipnet", - "jni", - "once_cell", - "rand", - "ring", - "thiserror", - "tinyvec", - "tracing", - "url", -] - -[[package]] -name = "hkdf" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" -dependencies = [ - "digest 0.11.3", -] - -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "hostname" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" -dependencies = [ - "cfg-if", - "libc", - "windows-link", -] - -[[package]] -name = "http" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hybrid-array" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" -dependencies = [ - "typenum", -] - -[[package]] -name = "hyper" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-http-proxy" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd1d471ea2f65ba45eddb1d6ab7d58ac2671d2d4ac14da5c6516ae1d97e1327a" -dependencies = [ - "bytes", - "futures-util", - "headers", - "http", - "hyper", - "hyper-rustls", - "hyper-util", - "pin-project-lite", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" -dependencies = [ - "http", - "hyper", - "hyper-util", - "log", - "rustls", - "rustls-native-certs", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-timeout" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" -dependencies = [ - "hyper", - "hyper-util", - "pin-project-lite", - "tokio", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "libc", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "icu_collections" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" -dependencies = [ - "displaydoc", - "litemap", - "serde", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locale_fallback" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "251af8e57c9400e3eb58242fe5b8b1152b2a64fdf4cf632f923c38ccee6f2fa9" -dependencies = [ - "icu_locale_core", - "icu_locale_fallback_data", - "icu_provider", - "potential_utf", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locale_fallback_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "decf2a22ec8fa68f1a0c1129a3f8583f8f8bc24e8b9ccbe98ead99f62a4dc3a8" - -[[package]] -name = "icu_normalizer" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" - -[[package]] -name = "icu_properties" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" - -[[package]] -name = "icu_provider" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" -dependencies = [ - "displaydoc", - "icu_locale_core", - "serde", - "stable_deref_trait", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_segmenter" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d07aafccd67af15d02512a6adf5896fbc5ed00f2e99b471d2efa14016db3db" -dependencies = [ - "icu_collections", - "icu_locale_fallback", - "icu_provider", - "icu_segmenter_data", - "potential_utf", - "smallvec", - "utf8_iter", - "zerovec", -] - -[[package]] -name = "icu_segmenter_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae293c039020f9ec10710af98d29ce6aa2051486638b49c9a6409f3b4a9e98ad" - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "2.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" -dependencies = [ - "equivalent", - "hashbrown 0.17.1", - "serde", - "serde_core", -] - -[[package]] -name = "ipnet" -version = "2.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" - -[[package]] -name = "is_ci" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "jni" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" -dependencies = [ - "cfg-if", - "combine", - "jni-macros", - "jni-sys", - "log", - "simd_cesu8", - "thiserror", - "walkdir", - "windows-link", -] - -[[package]] -name = "jni-macros" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "simd_cesu8", - "syn 2.0.119", -] - -[[package]] -name = "jni-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" -dependencies = [ - "jni-sys-macros", -] - -[[package]] -name = "jni-sys-macros" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" -dependencies = [ - "quote", - "syn 2.0.119", -] - -[[package]] -name = "jobserver" -version = "0.1.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" -dependencies = [ - "getrandom 0.4.3", - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" -dependencies = [ - "cfg-if", - "futures-util", - "wasm-bindgen", -] - -[[package]] -name = "json-patch" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7421438de105a0827e44fadd05377727847d717c80ce29a229f85fd04c427b72" -dependencies = [ - "jsonptr", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "jsonpath-rust" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c00ae348f9f8fd2d09f82a98ca381c60df9e0820d8d79fce43e649b4dc3128b" -dependencies = [ - "pest", - "pest_derive", - "regex", - "serde_json", - "thiserror", -] - -[[package]] -name = "jsonptr" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "jsonwebtoken" -version = "10.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc" -dependencies = [ - "aws-lc-rs", - "base64 0.22.1", - "getrandom 0.2.17", - "js-sys", - "pem 3.0.6", - "serde", - "serde_json", - "signature", - "simple_asn1", - "zeroize", -] - -[[package]] -name = "k8s-openapi" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c75b990324f09bef15e791606b7b7a296d02fc88a344f6eba9390970a870ad5" -dependencies = [ - "base64 0.22.1", - "chrono", - "serde", - "serde-value", - "serde_json", -] - -[[package]] -name = "kube" -version = "0.99.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a4eb20010536b48abe97fec37d23d43069bcbe9686adcf9932202327bc5ca6e" -dependencies = [ - "k8s-openapi", - "kube-client", - "kube-core", - "kube-derive", - "kube-runtime", -] - -[[package]] -name = "kube-client" -version = "0.99.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc2ed952042df20d15ac2fe9614d0ec14b6118eab89633985d4b36e688dccf1" -dependencies = [ - "base64 0.22.1", - "bytes", - "chrono", - "either", - "futures", - "home", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-http-proxy", - "hyper-rustls", - "hyper-timeout", - "hyper-util", - "jsonpath-rust", - "k8s-openapi", - "kube-core", - "pem 3.0.6", - "rustls", - "secrecy", - "serde", - "serde_json", - "serde_yaml", - "thiserror", - "tokio", - "tokio-util", - "tower", - "tower-http", - "tracing", -] - -[[package]] -name = "kube-core" -version = "0.99.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff0d0793db58e70ca6d689489183816cb3aa481673e7433dc618cf7e8007c675" -dependencies = [ - "chrono", - "form_urlencoded", - "http", - "json-patch", - "k8s-openapi", - "schemars", - "serde", - "serde-value", - "serde_json", - "thiserror", -] - -[[package]] -name = "kube-derive" -version = "0.99.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c562f58dc9f7ca5feac8a6ee5850ca221edd6f04ce0dd2ee873202a88cd494c9" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "serde", - "serde_json", - "syn 2.0.119", -] - -[[package]] -name = "kube-runtime" -version = "0.99.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88f34cfab9b4bd8633062e0e85edb81df23cb09f159f2e31c60b069ae826ffdc" -dependencies = [ - "ahash", - "async-broadcast", - "async-stream", - "async-trait", - "backon", - "educe", - "futures", - "hashbrown 0.15.5", - "hostname", - "json-patch", - "k8s-openapi", - "kube-client", - "parking_lot", - "pin-project", - "serde", - "serde_json", - "thiserror", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.189" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" - -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - -[[package]] -name = "libsqlite3-sys" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - -[[package]] -name = "litemap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" - -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - -[[package]] -name = "md-5" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" -dependencies = [ - "cfg-if", - "digest 0.11.3", -] - -[[package]] -name = "memchr" -version = "2.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" - -[[package]] -name = "miette" -version = "7.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" -dependencies = [ - "backtrace", - "backtrace-ext", - "cfg-if", - "miette-derive", - "owo-colors", - "supports-color", - "supports-hyperlinks", - "supports-unicode", - "terminal_size", - "textwrap", - "unicode-width 0.1.14", -] - -[[package]] -name = "miette-derive" -version = "7.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "multimap" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "noyalib" -version = "0.0.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "962838abbd8dd2cd3844d2208dc502458bba6985f8bf1dc21761b30260fcbd7f" -dependencies = [ - "hashbrown 0.17.1", - "indexmap", - "libm", - "memchr", - "rustc-hash", - "serde_core", - "smallvec", -] - -[[package]] -name = "num-bigint" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" - -[[package]] -name = "num-integer" -version = "0.1.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr", -] - -[[package]] -name = "oid-registry" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" -dependencies = [ - "asn1-rs", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" -dependencies = [ - "critical-section", - "portable-atomic", -] - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "openshell-core" -version = "0.0.0" -dependencies = [ - "async-trait", - "base64 0.22.1", - "glob", - "ipnet", - "libc", - "miette", - "nix", - "openshell-crypto", - "openshell-extension-core", - "openshell-policy-schema", - "prost", - "prost-types", - "protoc-bin-vendored", - "rustix", - "rustls", - "serde", - "serde_json", - "sha2 0.10.9", - "thiserror", - "tokio", - "tokio-stream", - "tonic", - "tonic-prost", - "tonic-prost-build", - "tonic-types", - "tracing", - "url", - "uuid", -] - -[[package]] -name = "openshell-crypto" -version = "0.0.0" -dependencies = [ - "aws-lc-rs", - "jsonwebtoken", - "kube", - "pem 3.0.6", - "rcgen", - "rustls", - "serde", - "sqlx", - "thiserror", - "tonic", - "x509-parser", -] - -[[package]] -name = "openshell-extension-core" -version = "0.0.0" -dependencies = [ - "hyper-util", - "serde", - "thiserror", - "tokio", - "tonic", - "tower", -] - -[[package]] -name = "openshell-policy" -version = "0.0.0" -dependencies = [ - "hickory-proto", - "miette", - "openshell-core", - "openshell-policy-schema", - "prost-types", - "serde", - "serde_json", -] - -[[package]] -name = "openshell-policy-schema" -version = "0.0.0" -dependencies = [ - "miette", - "noyalib", - "serde", - "serde_json", - "serde_path_to_error", - "thiserror", -] - -[[package]] -name = "openshell-supervisor-middleware-egress-usage" -version = "0.0.0" -dependencies = [ - "clap", - "openshell-core", - "openshell-policy", - "prost-types", - "tokio", - "tokio-stream", - "tonic", -] - -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "ordered-float" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" -dependencies = [ - "num-traits", -] - -[[package]] -name = "owo-colors" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c45bb4a6ae1280ec0803b1ef9d3455eb50f01efbbe1447ab020f1d54fba9d8" - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "pem" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" -dependencies = [ - "base64 0.22.1", - "serde_core", -] - -[[package]] -name = "pem" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d354a98a3d1251555de99e8fdd8afda05573c31b82f59063a7b0a29b5527f120" -dependencies = [ - "base64 0.23.1", - "serde_core", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pest" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d45aeb61b4bf818e12d4205f2466f8c4748f85f4fce0146d1c03d69d753f0ad" -dependencies = [ - "memchr", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89cc5a242e25ed4e7704d0be240f2cfbe20a8c27e7e252d94835be93d92dc39f" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abf21475cc3820fe4b2ca2dc2142902f67a02189f3b5b3a229f4febc01a43e5" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "pest_meta" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adba4db388f687393c18c51348d44a41d870ca9df71a2c98172ea3035dc6936e" -dependencies = [ - "pest", -] - -[[package]] -name = "petgraph" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" -dependencies = [ - "fixedbitset", - "hashbrown 0.15.5", - "indexmap", -] - -[[package]] -name = "pin-project" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pkg-config" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" - -[[package]] -name = "portable-atomic" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" - -[[package]] -name = "potential_utf" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" -dependencies = [ - "serde_core", - "writeable", - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.119", -] - -[[package]] -name = "proc-macro2" -version = "1.0.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prost" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-build" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" -dependencies = [ - "heck", - "itertools", - "log", - "multimap", - "petgraph", - "prettyplease", - "prost", - "prost-types", - "pulldown-cmark", - "pulldown-cmark-to-cmark", - "regex", - "syn 2.0.119", - "tempfile", -] - -[[package]] -name = "prost-derive" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "prost-types" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" -dependencies = [ - "prost", -] - -[[package]] -name = "protoc-bin-vendored" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1c381df33c98266b5f08186583660090a4ffa0889e76c7e9a5e175f645a67fa" -dependencies = [ - "protoc-bin-vendored-linux-aarch_64", - "protoc-bin-vendored-linux-ppcle_64", - "protoc-bin-vendored-linux-s390_64", - "protoc-bin-vendored-linux-x86_32", - "protoc-bin-vendored-linux-x86_64", - "protoc-bin-vendored-macos-aarch_64", - "protoc-bin-vendored-macos-x86_64", - "protoc-bin-vendored-win32", -] - -[[package]] -name = "protoc-bin-vendored-linux-aarch_64" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c350df4d49b5b9e3ca79f7e646fde2377b199e13cfa87320308397e1f37e1a4c" - -[[package]] -name = "protoc-bin-vendored-linux-ppcle_64" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55a63e6c7244f19b5c6393f025017eb5d793fd5467823a099740a7a4222440c" - -[[package]] -name = "protoc-bin-vendored-linux-s390_64" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dba5565db4288e935d5330a07c264a4ee8e4a5b4a4e6f4e83fad824cc32f3b0" - -[[package]] -name = "protoc-bin-vendored-linux-x86_32" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8854774b24ee28b7868cd71dccaae8e02a2365e67a4a87a6cd11ee6cdbdf9cf5" - -[[package]] -name = "protoc-bin-vendored-linux-x86_64" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b38b07546580df720fa464ce124c4b03630a6fb83e05c336fea2a241df7e5d78" - -[[package]] -name = "protoc-bin-vendored-macos-aarch_64" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89278a9926ce312e51f1d999fee8825d324d603213344a9a706daa009f1d8092" - -[[package]] -name = "protoc-bin-vendored-macos-x86_64" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81745feda7ccfb9471d7a4de888f0652e806d5795b61480605d4943176299756" - -[[package]] -name = "protoc-bin-vendored-win32" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95067976aca6421a523e491fce939a3e65249bac4b977adee0ee9771568e8aa3" - -[[package]] -name = "pulldown-cmark" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "278e9268c93b9e498a2da46c34d90ba93a3255eb99009646398f34bc73ab36eb" -dependencies = [ - "bitflags", - "memchr", - "unicase", -] - -[[package]] -name = "pulldown-cmark-to-cmark" -version = "22.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab1ad36992cead65f02aa399a373a42730922f1525d988172634fdefdecb8a60" -dependencies = [ - "pulldown-cmark", -] - -[[package]] -name = "quote" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "rand" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" -dependencies = [ - "chacha20", - "getrandom 0.4.3", - "rand_core 0.10.1", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - -[[package]] -name = "rand_core" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" - -[[package]] -name = "rcgen" -version = "0.14.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8774e05a7d0de114588e6a28fe7e71694b82614ed569d86d8b389dfbc98b8ad8" -dependencies = [ - "aws-lc-rs", - "pem 4.0.0", - "rustls-pki-types", - "time", - "x509-parser", - "yasna", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted 0.9.0", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" - -[[package]] -name = "rustc-hash" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rusticata-macros" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" -dependencies = [ - "nom", -] - -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" -dependencies = [ - "aws-lc-rs", - "log", - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pki-types" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" -dependencies = [ - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" -dependencies = [ - "aws-lc-rs", - "ring", - "rustls-pki-types", - "untrusted 0.9.0", -] - -[[package]] -name = "rustversion" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "schemars" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" -dependencies = [ - "dyn-clone", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.119", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "secrecy" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" -dependencies = [ - "zeroize", -] - -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" - -[[package]] -name = "serde" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde-value" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" -dependencies = [ - "ordered-float", - "serde", -] - -[[package]] -name = "serde_core" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "serde_json" -version = "1.0.151" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_path_to_error" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" -dependencies = [ - "itoa", - "serde", - "serde_core", -] - -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - -[[package]] -name = "sha1" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", -] - -[[package]] -name = "sha1" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.1", - "digest 0.11.3", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", -] - -[[package]] -name = "sha2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.1", - "digest 0.11.3", -] - -[[package]] -name = "shlex" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "rand_core 0.6.4", -] - -[[package]] -name = "simd_cesu8" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" -dependencies = [ - "rustc_version", - "simdutf8", -] - -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - -[[package]] -name = "simple_asn1" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" -dependencies = [ - "num-bigint", - "num-traits", - "thiserror", - "time", -] - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" -dependencies = [ - "serde", -] - -[[package]] -name = "socket2" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "spin" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" -dependencies = [ - "lock_api", -] - -[[package]] -name = "sqlx" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "378620ccc25c62c89d8be1c819e76a88d59bdcc3304733330788948e619bfd71" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - -[[package]] -name = "sqlx-core" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb" -dependencies = [ - "base64 0.22.1", - "bytes", - "cfg-if", - "crc", - "crossbeam-queue", - "either", - "event-listener", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashbrown 0.16.1", - "hashlink", - "indexmap", - "log", - "memchr", - "percent-encoding", - "rustls", - "serde", - "serde_json", - "sha2 0.10.9", - "smallvec", - "thiserror", - "tokio", - "tokio-stream", - "tracing", - "url", - "webpki-roots", -] - -[[package]] -name = "sqlx-macros" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd2b84f2bc39a5705ef27ec785a11c934a41bbd4a24941e257927cddc26b60bf" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn 2.0.119", -] - -[[package]] -name = "sqlx-macros-core" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8d96de5fdc85a5c4ec813432b523ec637e80ba98f046555f75f7908ddac7c3" -dependencies = [ - "cfg-if", - "dotenvy", - "either", - "heck", - "hex", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2 0.10.9", - "sqlx-core", - "sqlx-postgres", - "sqlx-sqlite", - "syn 2.0.119", - "thiserror", - "tokio", - "url", -] - -[[package]] -name = "sqlx-mysql" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27" -dependencies = [ - "bitflags", - "byteorder", - "bytes", - "crc", - "digest 0.11.3", - "dotenvy", - "either", - "futures-core", - "futures-util", - "generic-array", - "log", - "percent-encoding", - "serde", - "sha1 0.11.0", - "sha2 0.11.0", - "sqlx-core", - "thiserror", - "tracing", -] - -[[package]] -name = "sqlx-postgres" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e" -dependencies = [ - "atoi", - "base64 0.22.1", - "bitflags", - "byteorder", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-util", - "hex", - "hkdf", - "hmac", - "itoa", - "log", - "md-5", - "memchr", - "rand", - "serde", - "serde_json", - "sha2 0.11.0", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror", - "tracing", - "whoami", -] - -[[package]] -name = "sqlx-sqlite" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" -dependencies = [ - "atoi", - "flume", - "form_urlencoded", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "sqlx-core", - "thiserror", - "tracing", - "url", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "supports-color" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" -dependencies = [ - "is_ci", -] - -[[package]] -name = "supports-hyperlinks" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91" - -[[package]] -name = "supports-unicode" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" - -[[package]] -name = "syn" -version = "2.0.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "3.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "synstructure" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.3.4", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "terminal_size" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" -dependencies = [ - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "textwrap" -version = "0.16.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ecfad6c3abc80a577f2b91c1e412ee57e7a060d430b553c1b0c940974ebcd49" -dependencies = [ - "icu_segmenter", - "unicode-width 0.2.2", -] - -[[package]] -name = "thiserror" -version = "2.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "time" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" -dependencies = [ - "deranged", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" - -[[package]] -name = "time-macros" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" -dependencies = [ - "displaydoc", - "serde_core", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" - -[[package]] -name = "tokio" -version = "1.53.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce3335fa71841cda333a58d7615b03901380ecf09d59b3296d21f8bbac0dde4e" -dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "libc", - "pin-project-lite", - "slab", - "tokio", -] - -[[package]] -name = "tonic" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" -dependencies = [ - "async-trait", - "axum", - "base64 0.22.1", - "bytes", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-timeout", - "hyper-util", - "percent-encoding", - "pin-project", - "rustls-native-certs", - "socket2", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tokio-stream", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tonic-build" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68f61875ac5293cf72e6c8cf0158086428c82c37229e98c840878f1706b0322" -dependencies = [ - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "tonic-prost" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" -dependencies = [ - "bytes", - "prost", - "tonic", -] - -[[package]] -name = "tonic-prost-build" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "654e5643eff75d7f8c99197ce1440ed19a3474eada74c12bbac488b2cafdae27" -dependencies = [ - "prettyplease", - "proc-macro2", - "prost-build", - "prost-types", - "quote", - "syn 2.0.119", - "tempfile", - "tonic-build", -] - -[[package]] -name = "tonic-types" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ab1b02061f83d519bba3caa167f88f261ef05720ab8ebc954ade70de3348e8" -dependencies = [ - "prost", - "prost-types", - "tonic", -] - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "indexmap", - "pin-project-lite", - "slab", - "sync_wrapper", - "tokio", - "tokio-util", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-http" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" -dependencies = [ - "base64 0.22.1", - "bitflags", - "bytes", - "http", - "http-body", - "mime", - "pin-project-lite", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typenum" -version = "1.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" - -[[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" - -[[package]] -name = "unicase" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" - -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-normalization" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-properties" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "unicode-width" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" - -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "uuid" -version = "1.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" -dependencies = [ - "getrandom 0.4.3", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 3.0.5", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "webpki-roots" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c1c8e20c8a38fc7d65abfe74eb7280e4aa667e8eec8bc84996d2874810748d9" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "whoami" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c" - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[package]] -name = "writeable" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" - -[[package]] -name = "x509-parser" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" -dependencies = [ - "asn1-rs", - "aws-lc-rs", - "data-encoding", - "der-parser", - "lazy_static", - "nom", - "oid-registry", - "rusticata-macros", - "thiserror", - "time", -] - -[[package]] -name = "yasna" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282" -dependencies = [ - "bit-vec", - "time", -] - -[[package]] -name = "yoke" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", - "synstructure 0.14.0", -] - -[[package]] -name = "zerocopy" -version = "0.8.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "zerofrom" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", - "synstructure 0.14.0", -] - -[[package]] -name = "zeroize" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "zerotrie" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "zerovec" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" -dependencies = [ - "serde", - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "zmij" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" From f573803ac3391cdd3923931ecd6b531bef929edd Mon Sep 17 00:00:00 2001 From: Piotr Mlocek Date: Mon, 2 Nov 2026 14:04:39 -0800 Subject: [PATCH 4/6] fix(middleware): deliver TCP hook end totals before closing the stream The stage task dropped its response stream right after queueing TcpEnd, which cancelled the RPC before the end was flushed. Connections that closed before their first stats tick reported nothing, so a transfer split into short connections was never counted. Half-close the request side and drain the stage's responses, bounded by 5 s. Add a split-transfer scenario to the egress-usage smoke suite. Signed-off-by: Piotr Mlocek --- .../src/tcp_hooks.rs | 14 ++++++++++++++ .../supervisor-middleware-egress-usage/smoke.sh | 16 ++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/crates/openshell-supervisor-middleware/src/tcp_hooks.rs b/crates/openshell-supervisor-middleware/src/tcp_hooks.rs index 08d4a4917f..59a5cd3654 100644 --- a/crates/openshell-supervisor-middleware/src/tcp_hooks.rs +++ b/crates/openshell-supervisor-middleware/src/tcp_hooks.rs @@ -522,8 +522,22 @@ async fn run_stage( } }; let _ = tx.send(end_event(&meter, reason)).await; + // Dropping the response stream cancels the RPC, which can discard the + // queued end before it is flushed. Half-close the request side and let + // the stage finish its responses, so short connections that end before + // their first stats tick still report their totals. + drop(tx); + if results_open { + let _ = tokio::time::timeout(TCP_END_DRAIN_TIMEOUT, async { + while let Some(Ok(_)) = results.next().await {} + }) + .await; + } } +/// How long a stage may take to finish its response stream after the end. +const TCP_END_DRAIN_TIMEOUT: Duration = Duration::from_secs(5); + fn close_connection( middleware: &str, request: &TcpHookRequest, diff --git a/examples/supervisor-middleware-egress-usage/smoke.sh b/examples/supervisor-middleware-egress-usage/smoke.sh index 5113f52e28..acb6820ba7 100755 --- a/examples/supervisor-middleware-egress-usage/smoke.sh +++ b/examples/supervisor-middleware-egress-usage/smoke.sh @@ -327,6 +327,22 @@ run_suite() { out="$(http_get http://host.openshell.internal:47081/bytes/1000000 2>&1)" || fail "control host after trip: $out" printf 'PASS control key other_svc still allowed (%s)\n' "$out" + # Split transfer in a fresh sandbox: each connection ends before its first + # stats tick, so only the end totals count toward the window. + "${CLI[@]}" sandbox delete "$SANDBOX_NAME" >>"$SETUP_LOG" 2>&1 || true + SANDBOX_NAME="${SANDBOX_NAME}b" + create_sandbox + local count denied=0 + for count in $(seq 1 40); do + out="$(raw_get host.openshell.internal 47900 1000000 2>&1)" || true + if [[ "$out" == *"received=0"* ]]; then + denied="$count" + break + fi + done + [[ "$denied" -gt 0 ]] || fail "split transfer of 40 x 1MB raw TCP connections was not stopped" + printf 'PASS split transfer denied at connection %s (%s MB through)\n' "$denied" "$((denied - 1))" + grep -E 'TRIP|deny' "$MIDDLEWARE_LOG" || true echo "ALL PASS egress-usage smoke" } From 1d891186b38a84c42fafbb4b29b013b0345a9cc5 Mon Sep 17 00:00:00 2001 From: Piotr Mlocek Date: Mon, 2 Nov 2026 18:30:18 -0800 Subject: [PATCH 5/6] feat(examples): read egress-usage limits from policy config (spike) The middleware config in the sandbox policy now sets max_bytes, window, interval, block_for, and direction, with per-policy-key overrides. Flags supply defaults. ValidateConfig rejects invalid values at policy set, and the smoke suite lowers a budget through a policy update. Signed-off-by: Piotr Mlocek --- .../README.md | 32 +- .../policy.yaml | 16 +- .../smoke.sh | 37 ++- .../src/config.rs | 302 ++++++++++++++++++ .../src/main.rs | 90 ++---- .../src/usage.rs | 68 ++-- 6 files changed, 450 insertions(+), 95 deletions(-) create mode 100644 examples/supervisor-middleware-egress-usage/src/config.rs diff --git a/examples/supervisor-middleware-egress-usage/README.md b/examples/supervisor-middleware-egress-usage/README.md index c6b88e5700..49da178e51 100644 --- a/examples/supervisor-middleware-egress-usage/README.md +++ b/examples/supervisor-middleware-egress-usage/README.md @@ -8,10 +8,35 @@ SPDX-License-Identifier: Apache-2.0 > [!WARNING] > Spike for TCP connection hooks (`TcpHooks.EvaluateConnection`). The contract is not settled. -The service binds `TCP_CONNECTION` at `PRE_CONNECT` and requires the `openshell.supervisor-middleware.tcp-hooks` capability. For every admitted upstream connection its selector matches, OpenShell sends a preflight before dialing. The service selects STATS mode and receives cumulative upstream byte totals at its interval. It sums them into a sliding window per sandbox and network policy key. When a window passes `--max-bytes`, it closes that key's open connections and denies new ones for `--block-for`, with reason code `egress_budget_exceeded`. +The service binds `TCP_CONNECTION` at `PRE_CONNECT` and requires the `openshell.supervisor-middleware.tcp-hooks` capability. For every admitted upstream connection its selector matches, OpenShell sends a preflight before dialing. The service selects STATS mode and receives cumulative upstream byte totals at its interval. It sums them into a sliding window per sandbox and network policy key. When a window passes `max_bytes`, it closes that key's open connections and denies new ones for `block_for`, with reason code `egress_budget_exceeded`. Covers raw TCP, `tls: skip`, terminated TLS, and plain HTTP the same way, because bytes are counted at the upstream socket. +## Configure limits in the policy + +The limits come from the middleware's `config` in the sandbox policy, so changing the policy changes the budget: + +```yaml +network_middlewares: + egress-usage: + middleware: egress-usage-example + config: + max_bytes: 20MB # per sandbox and policy key + window: 60s + interval: 1s # stats interval, 1s to 60s + block_for: 120s + direction: both # both, in, or out + policies: # overrides for one network policy key + other_svc: + max_bytes: 50MB + on_error: fail_closed + endpoints: + include: + - host.openshell.internal +``` + +Every field is optional; the service flags supply defaults. Sizes accept `B`, `KB`, `MB`, `GB` (decimal) or a number of bytes; durations accept `ms`, `s`, `m` or a number of seconds. The gateway calls `ValidateConfig` when a policy is set, so an invalid value is rejected by `openshell policy set`. A policy update applies to connections opened after the sandbox loads it; open connections keep the limits they started with. + ## Run the smoke suite ```shell @@ -23,6 +48,9 @@ The script builds the gateway, CLI, and a local supervisor image, starts the hos - 1 MB over HTTP and raw TCP, and on the `other_svc` key, pass. - A 200 MB download over `tls: skip` at 10 MB/s is cut shortly after 20 MB. - New `pkg_proxy` connections are then denied; `other_svc` still passes. +- 40 short 1 MB raw TCP connections in a second sandbox are denied after 20 MB. +- `policy set` with `max_bytes: lots` is rejected. +- 15 MB on `other_svc` passes under its 50 MB budget; after `policy set` lowers it to 10 MB, the same transfer is cut. Set `EGRESS_USAGE_SUPERVISOR_IMAGE` to reuse a built supervisor image and skip the builds. @@ -30,7 +58,7 @@ Set `EGRESS_USAGE_SUPERVISOR_IMAGE` to reuse a built supervisor image and skip t ```shell cd examples/supervisor-middleware-egress-usage -cargo run -- --bind 0.0.0.0:50061 --max-bytes 20MB --window 60s --interval 5s --block-for 300s +cargo run -- --bind 0.0.0.0:50061 ``` Register it in the gateway TOML. `max_payload_bytes` is required by the config schema but unused by TCP bindings: diff --git a/examples/supervisor-middleware-egress-usage/policy.yaml b/examples/supervisor-middleware-egress-usage/policy.yaml index b56e16e2cb..76270e2de0 100644 --- a/examples/supervisor-middleware-egress-usage/policy.yaml +++ b/examples/supervisor-middleware-egress-usage/policy.yaml @@ -4,7 +4,8 @@ # pkg_proxy stands in for artifactory.internal. Docker sandboxes reach the # host-run upstream only through host.openshell.internal, so the two policy # keys are told apart by port. The middleware groups usage by sandbox and -# policy key. +# policy key, and reads its limits from config below: change the policy to +# change the budget. version: 1 @@ -13,6 +14,19 @@ network_middlewares: name: Egress usage middleware: egress-usage-example order: 10 + config: + # Per sandbox and policy key. + max_bytes: 20MB + window: 60s + # How often OpenShell reports byte totals (1s to 60s). Bytes over the + # budget before a cut are roughly rate x interval. + interval: 1s + block_for: 120s + direction: both + # Overrides for one network policy key. + policies: + other_svc: + max_bytes: 50MB on_error: fail_closed endpoints: include: diff --git a/examples/supervisor-middleware-egress-usage/smoke.sh b/examples/supervisor-middleware-egress-usage/smoke.sh index acb6820ba7..e362d326fc 100755 --- a/examples/supervisor-middleware-egress-usage/smoke.sh +++ b/examples/supervisor-middleware-egress-usage/smoke.sh @@ -15,7 +15,7 @@ usage: $0 [--test-suite|--test|--print-config] Without flags, starts a local Docker-driver gateway, the egress-usage TCP middleware, and a package proxy stand-in, creates an example sandbox, and keeps -the stack running. +the stack running. Limits come from the config in policy.yaml. Options: --test-suite, --test Run the burst scenarios, then stop. @@ -24,10 +24,6 @@ Options: Environment: EGRESS_USAGE_SMOKE_HOST Non-loopback host address reachable from supervisor containers. - EGRESS_USAGE_MAX_BYTES Default 20MB. - EGRESS_USAGE_WINDOW Default 60s. - EGRESS_USAGE_INTERVAL Default 1s. - EGRESS_USAGE_BLOCK_FOR Default 120s. EGRESS_USAGE_SUPERVISOR_IMAGE Use a prebuilt supervisor image and skip the gateway, CLI, and image builds. USAGE @@ -66,10 +62,6 @@ RUN_ID="egress-usage-smoke-$$-$RANDOM" SUPERVISOR_IMAGE="${EGRESS_USAGE_SUPERVISOR_IMAGE:-localhost/openshell-egress-usage/supervisor:$RUN_ID}" SANDBOX_NAME="eu-$$-$RANDOM" SANDBOX_CREATED=0 -MAX_BYTES="${EGRESS_USAGE_MAX_BYTES:-20MB}" -WINDOW="${EGRESS_USAGE_WINDOW:-60s}" -INTERVAL="${EGRESS_USAGE_INTERVAL:-1s}" -BLOCK_FOR="${EGRESS_USAGE_BLOCK_FOR:-120s}" # The scenarios need curl and python3 in the sandbox. SANDBOX_IMAGE="${EGRESS_USAGE_SANDBOX_IMAGE:-ghcr.io/nvidia/openshell-community/sandboxes/base:latest}" @@ -221,7 +213,6 @@ start_middleware() { printf 'INFO starting egress-usage service at %s:%s\n' "$SERVICE_HOST" "$MIDDLEWARE_PORT" "$MIDDLEWARE_BIN" \ --bind "0.0.0.0:$MIDDLEWARE_PORT" \ - --max-bytes "$MAX_BYTES" --window "$WINDOW" --interval "$INTERVAL" --block-for "$BLOCK_FOR" \ >"$MIDDLEWARE_LOG" 2>&1 & MIDDLEWARE_PID=$! for _ in {1..60}; do @@ -307,7 +298,7 @@ run_suite() { out="$(http_get http://host.openshell.internal:47081/bytes/1000000 2>&1)" || fail "control host: $out" printf 'PASS control key other_svc (%s)\n' "$out" - printf 'INFO D2 burst: 200MB over tls: skip at 10MB/s, threshold %s per %s\n' "$MAX_BYTES" "$WINDOW" + printf 'INFO D2 burst: 200MB over tls: skip at 10MB/s, policy budget 20MB per 60s\n' started=$(date +%s.%N) out="$(http_get -k --limit-rate 10M https://host.openshell.internal:47443/bytes/200000000 2>&1)" && fail "D2 burst was not cut: $out" elapsed=$(echo "$(date +%s.%N) - $started" | bc) @@ -343,6 +334,24 @@ run_suite() { [[ "$denied" -gt 0 ]] || fail "split transfer of 40 x 1MB raw TCP connections was not stopped" printf 'PASS split transfer denied at connection %s (%s MB through)\n' "$denied" "$((denied - 1))" + # The budget lives in the policy: changing the policy changes it. + sed 's/max_bytes: 20MB/max_bytes: lots/' "$EXAMPLE_DIR/policy.yaml" >"$SMOKE_TMP_DIR/invalid.yaml" + out="$("${CLI[@]}" policy set "$SANDBOX_NAME" --policy "$SMOKE_TMP_DIR/invalid.yaml" 2>&1)" \ + && fail "invalid budget was accepted: $out" + [[ "$out" == *"invalid byte size"* ]] || fail "invalid budget rejection: $out" + printf 'PASS invalid budget rejected by ValidateConfig (%s)\n' "$(echo "$out" | grep -m1 'invalid byte size')" + + out="$(http_get --limit-rate 10M http://host.openshell.internal:47081/bytes/15000000 2>&1)" \ + || fail "15MB on other_svc under its 50MB budget: $out" + printf 'PASS 15MB on other_svc under its 50MB policy budget (%s)\n' "$out" + + sed 's/max_bytes: 50MB/max_bytes: 10MB/' "$EXAMPLE_DIR/policy.yaml" >"$SMOKE_TMP_DIR/tight.yaml" + run_setup_step "lowering the other_svc budget to 10MB in the policy" \ + "${CLI[@]}" policy set "$SANDBOX_NAME" --policy "$SMOKE_TMP_DIR/tight.yaml" --wait + out="$(http_get --limit-rate 10M http://host.openshell.internal:47081/bytes/15000000 2>&1)" \ + && fail "15MB on other_svc was not cut after lowering its budget: $out" + printf 'PASS same 15MB on other_svc cut after the policy lowered its budget to 10MB (%s)\n' "$(echo "$out" | tr '\n' ' ')" + grep -E 'TRIP|deny' "$MIDDLEWARE_LOG" || true echo "ALL PASS egress-usage smoke" } @@ -357,8 +366,12 @@ Middleware endpoint: http://$SERVICE_HOST:$MIDDLEWARE_PORT Sandbox: $SANDBOX_NAME Gateway config: $GATEWAY_CONFIG Middleware log: $MIDDLEWARE_LOG +Policy: $EXAMPLE_DIR/policy.yaml + +Change the budget by editing the policy's config and applying it: + ${CLI[*]} policy set $SANDBOX_NAME --policy --wait -Burst over tls: skip (cut at $MAX_BYTES per $WINDOW): +Burst over tls: skip (cut at the policy budget, 20MB per 60s): ${CLI[*]} sandbox exec --name $SANDBOX_NAME --no-tty -- curl -k --limit-rate 10M -o /dev/null https://host.openshell.internal:47443/bytes/200000000 Press Ctrl-C to clean up. diff --git a/examples/supervisor-middleware-egress-usage/src/config.rs b/examples/supervisor-middleware-egress-usage/src/config.rs new file mode 100644 index 0000000000..616df873c7 --- /dev/null +++ b/examples/supervisor-middleware-egress-usage/src/config.rs @@ -0,0 +1,302 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Limits from the policy's middleware config, with flag defaults. +//! +//! ```yaml +//! config: +//! max_bytes: 20MB +//! window: 60s +//! interval: 5s +//! block_for: 300s +//! direction: both +//! policies: +//! other_svc: +//! max_bytes: 5MB +//! ``` +//! +//! Every field is optional. Top-level fields override the service flags, and +//! `policies.` overrides them for one network policy key. + +use std::collections::HashMap; +use std::time::Duration; + +use prost_types::Struct; +use prost_types::value::Kind; + +use crate::usage::{Direction, Limits}; + +/// OpenShell clamps stats intervals to this range. +const MIN_INTERVAL: Duration = Duration::from_secs(1); +const MAX_INTERVAL: Duration = Duration::from_mins(1); + +#[derive(Debug, PartialEq, Eq)] +pub struct Config { + limits: Limits, + policies: HashMap, +} + +impl Config { + pub fn parse(config: Option<&Struct>, defaults: Limits) -> Result { + let Some(config) = config else { + return Ok(Self { + limits: defaults, + policies: HashMap::new(), + }); + }; + let limits = parse_limits(config, defaults, "config", true)?; + let mut policies = HashMap::new(); + if let Some(value) = config.fields.get("policies") { + let Some(Kind::StructValue(entries)) = &value.kind else { + return Err("config.policies must be a map of policy key to limits".into()); + }; + for (key, value) in &entries.fields { + let Some(Kind::StructValue(fields)) = &value.kind else { + return Err(format!("config.policies.{key} must be a map")); + }; + let path = format!("config.policies.{key}"); + policies.insert(key.clone(), parse_limits(fields, limits, &path, false)?); + } + } + Ok(Self { limits, policies }) + } + + pub fn limits_for(&self, policy_key: &str) -> Limits { + self.policies + .get(policy_key) + .copied() + .unwrap_or(self.limits) + } +} + +fn parse_limits( + fields: &Struct, + base: Limits, + path: &str, + top_level: bool, +) -> Result { + if let Some(field) = fields.fields.keys().find(|field| match field.as_str() { + "max_bytes" | "window" | "interval" | "block_for" | "direction" => false, + "policies" => !top_level, + _ => true, + }) { + return Err(format!("unsupported field '{path}.{field}'")); + } + let mut limits = base; + for (name, value) in &fields.fields { + let field = format!("{path}.{name}"); + match name.as_str() { + "max_bytes" => { + limits.max_bytes = value_with(value, &field, parse_bytes, |bytes| bytes)? + } + "window" => limits.window = duration(value, &field)?, + "interval" => limits.interval = duration(value, &field)?, + "block_for" => limits.block_for = duration(value, &field)?, + "direction" => { + let Some(Kind::StringValue(direction)) = &value.kind else { + return Err(format!("{field} must be 'both', 'in', or 'out'")); + }; + limits.direction = match direction.as_str() { + "both" => Direction::Both, + "in" => Direction::In, + "out" => Direction::Out, + _ => return Err(format!("{field} must be 'both', 'in', or 'out'")), + }; + } + _ => {} + } + } + if limits.max_bytes == 0 { + return Err(format!("{path}.max_bytes must be greater than zero")); + } + if !(MIN_INTERVAL..=MAX_INTERVAL).contains(&limits.interval) { + return Err(format!("{path}.interval must be between 1s and 60s")); + } + if limits.window < limits.interval { + return Err(format!("{path}.window must be at least the interval")); + } + Ok(limits) +} + +fn duration(value: &prost_types::Value, field: &str) -> Result { + value_with(value, field, parse_duration, Duration::from_secs) +} + +/// Accepts a string with units, or a number in base units (bytes, seconds). +fn value_with( + value: &prost_types::Value, + field: &str, + parse: fn(&str) -> Result, + from_number: impl Fn(u64) -> T, +) -> Result { + match &value.kind { + Some(Kind::StringValue(text)) => parse(text).map_err(|error| format!("{field}: {error}")), + Some(Kind::NumberValue(number)) if *number >= 0.0 && number.fract() == 0.0 => + { + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + Ok(from_number(*number as u64)) + } + _ => Err(format!( + "{field} must be a string such as '20MB' or '60s', or a whole number" + )), + } +} + +pub fn parse_bytes(value: &str) -> Result { + let upper = value.trim().to_ascii_uppercase(); + let (digits, multiplier) = [ + ("GB", 1_000_000_000), + ("MB", 1_000_000), + ("KB", 1_000), + ("B", 1), + ] + .iter() + .find_map(|(suffix, multiplier)| { + upper + .strip_suffix(suffix) + .map(|digits| (digits.trim().to_string(), *multiplier)) + }) + .unwrap_or((upper.clone(), 1)); + digits + .parse::() + .map(|value| value * multiplier) + .map_err(|_| format!("invalid byte size '{value}'")) +} + +pub fn parse_duration(value: &str) -> Result { + let value = value.trim(); + let (digits, unit) = value.split_at( + value + .find(|c: char| !c.is_ascii_digit()) + .unwrap_or(value.len()), + ); + let number: u64 = digits + .parse() + .map_err(|_| format!("invalid duration '{value}'"))?; + match unit { + "" | "s" => Ok(Duration::from_secs(number)), + "ms" => Ok(Duration::from_millis(number)), + "m" => Ok(Duration::from_secs(number * 60)), + _ => Err(format!("invalid duration '{value}', use ms, s, or m")), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use prost_types::Value; + + fn defaults() -> Limits { + Limits { + max_bytes: 20_000_000, + window: Duration::from_secs(60), + interval: Duration::from_secs(5), + block_for: Duration::from_secs(300), + direction: Direction::Both, + } + } + + fn text(value: &str) -> Value { + Value { + kind: Some(Kind::StringValue(value.into())), + } + } + + fn map(fields: Vec<(&str, Value)>) -> Struct { + Struct { + fields: fields + .into_iter() + .map(|(name, value)| (name.to_string(), value)) + .collect(), + } + } + + #[test] + fn parses_sizes_and_durations() { + assert_eq!(parse_bytes("20MB").unwrap(), 20_000_000); + assert_eq!(parse_bytes("512").unwrap(), 512); + assert_eq!(parse_duration("5s").unwrap(), Duration::from_secs(5)); + assert_eq!(parse_duration("2m").unwrap(), Duration::from_secs(120)); + } + + #[test] + fn missing_config_uses_flag_defaults() { + let config = Config::parse(None, defaults()).unwrap(); + assert_eq!(config.limits_for("pkg_proxy"), defaults()); + } + + #[test] + fn policy_overrides_inherit_top_level_fields() { + let config = map(vec![ + ("max_bytes", text("10MB")), + ( + "interval", + Value { + kind: Some(Kind::NumberValue(1.0)), + }, + ), + ( + "policies", + Value { + kind: Some(Kind::StructValue(map(vec![( + "other_svc", + Value { + kind: Some(Kind::StructValue(map(vec![ + ("max_bytes", text("5MB")), + ("direction", text("in")), + ]))), + }, + )]))), + }, + ), + ]); + let config = Config::parse(Some(&config), defaults()).unwrap(); + let pkg = config.limits_for("pkg_proxy"); + assert_eq!(pkg.max_bytes, 10_000_000); + assert_eq!(pkg.interval, Duration::from_secs(1)); + let other = config.limits_for("other_svc"); + assert_eq!(other.max_bytes, 5_000_000); + assert_eq!(other.interval, Duration::from_secs(1)); + assert_eq!(other.direction, Direction::In); + } + + #[test] + fn rejects_bad_config() { + for (config, error) in [ + (map(vec![("max_bytes", text("lots"))]), "invalid byte size"), + (map(vec![("max_bytes", text("0"))]), "greater than zero"), + (map(vec![("interval", text("100ms"))]), "between 1s and 60s"), + (map(vec![("window", text("2s"))]), "at least the interval"), + ( + map(vec![("direction", text("up"))]), + "'both', 'in', or 'out'", + ), + ( + map(vec![("budget", text("1MB"))]), + "unsupported field 'config.budget'", + ), + ( + map(vec![( + "policies", + Value { + kind: Some(Kind::StructValue(map(vec![( + "other_svc", + Value { + kind: Some(Kind::StructValue(map(vec![("policies", text("x"))]))), + }, + )]))), + }, + )]), + "unsupported field 'config.policies.other_svc.policies'", + ), + ] { + let result = Config::parse(Some(&config), defaults()); + assert!( + result + .as_ref() + .is_err_and(|message| message.contains(error)), + "{result:?} should contain {error}" + ); + } + } +} diff --git a/examples/supervisor-middleware-egress-usage/src/main.rs b/examples/supervisor-middleware-egress-usage/src/main.rs index af3c10247e..68519ee39f 100644 --- a/examples/supervisor-middleware-egress-usage/src/main.rs +++ b/examples/supervisor-middleware-egress-usage/src/main.rs @@ -6,9 +6,13 @@ //! Binds `TCP_CONNECTION` at `PRE_CONNECT` (`TcpHooks.EvaluateConnection`) //! and selects STATS mode for every connection. It sums upstream bytes into a //! sliding window per sandbox and network policy key. When a window passes -//! `--max-bytes`, it closes that key's open connections and denies new ones -//! for `--block-for`. +//! `max_bytes`, it closes that key's open connections and denies new ones for +//! `block_for`. +//! +//! Limits come from the policy's `config` (see `config.rs`), so changing the +//! policy changes the budget. The flags are the defaults for unset fields. +mod config; mod usage; use std::net::SocketAddr; @@ -31,12 +35,14 @@ use openshell_core::proto::{ use tonic::transport::Server; use tonic::{Request, Response, Status}; +use crate::config::{parse_bytes, parse_duration}; use crate::usage::{Direction, Limits, Usage}; const MANIFEST_NAME: &str = "example/egress-usage"; #[derive(Debug, Parser)] #[command(about = "Run the example OpenShell egress-usage TCP middleware")] +/// Limit flags are defaults; the policy's middleware config overrides them. struct Cli { /// Address on which to serve plaintext gRPC. #[arg(long, default_value = "127.0.0.1:50061")] @@ -58,45 +64,6 @@ struct Cli { direction: Direction, } -fn parse_bytes(value: &str) -> Result { - let upper = value.trim().to_ascii_uppercase(); - let (digits, multiplier) = [ - ("GB", 1_000_000_000), - ("MB", 1_000_000), - ("KB", 1_000), - ("B", 1), - ] - .iter() - .find_map(|(suffix, multiplier)| { - upper - .strip_suffix(suffix) - .map(|digits| (digits.trim().to_string(), *multiplier)) - }) - .unwrap_or((upper.clone(), 1)); - digits - .parse::() - .map(|value| value * multiplier) - .map_err(|_| format!("invalid byte size '{value}'")) -} - -fn parse_duration(value: &str) -> Result { - let value = value.trim(); - let (digits, unit) = value.split_at( - value - .find(|c: char| !c.is_ascii_digit()) - .unwrap_or(value.len()), - ); - let number: u64 = digits - .parse() - .map_err(|_| format!("invalid duration '{value}'"))?; - match unit { - "" | "s" => Ok(Duration::from_secs(number)), - "ms" => Ok(Duration::from_millis(number)), - "m" => Ok(Duration::from_secs(number * 60)), - _ => Err(format!("invalid duration '{value}', use ms, s, or m")), - } -} - #[derive(Clone)] struct EgressUsage { usage: Usage, @@ -136,13 +103,21 @@ impl SupervisorMiddleware for EgressUsage { async fn validate_config( &self, - _request: Request, + request: Request, ) -> Result, Status> { - // Thresholds come from flags; any policy config is accepted. - Ok(Response::new(ValidateConfigResponse { - valid: true, - reason: String::new(), - })) + // The gateway calls this when a policy is set, so a bad budget is + // rejected before any sandbox loads it. + let response = match self.usage.config(request.into_inner().config.as_ref()) { + Ok(_) => ValidateConfigResponse { + valid: true, + reason: String::new(), + }, + Err(reason) => ValidateConfigResponse { + valid: false, + reason, + }, + }; + Ok(Response::new(response)) } async fn evaluate_http_request( @@ -212,18 +187,23 @@ async fn main() -> Result<(), Box> { mod tests { use super::*; - #[test] - fn parses_sizes_and_durations() { - assert_eq!(parse_bytes("20MB").unwrap(), 20_000_000); - assert_eq!(parse_bytes("512").unwrap(), 512); - assert_eq!(parse_duration("5s").unwrap(), Duration::from_secs(5)); - assert_eq!(parse_duration("2m").unwrap(), Duration::from_secs(120)); - } - #[test] fn example_policy_is_valid() { let policy = openshell_policy::parse_sandbox_policy(include_str!("../policy.yaml")) .expect("example policy must parse"); openshell_policy::validate_sandbox_policy(&policy).expect("example policy must be valid"); + let defaults = Cli::parse_from(["egress-usage"]); + let usage = Usage::new(Limits { + max_bytes: defaults.max_bytes, + window: defaults.window, + interval: defaults.interval, + block_for: defaults.block_for, + direction: defaults.direction, + }); + let config = usage + .config(policy.network_middlewares["egress-usage"].config.as_ref()) + .expect("example config must be valid"); + assert_eq!(config.limits_for("pkg_proxy").max_bytes, 20_000_000); + assert_eq!(config.limits_for("other_svc").max_bytes, 50_000_000); } } diff --git a/examples/supervisor-middleware-egress-usage/src/usage.rs b/examples/supervisor-middleware-egress-usage/src/usage.rs index fb06ccc7b7..7ecc31a2ab 100644 --- a/examples/supervisor-middleware-egress-usage/src/usage.rs +++ b/examples/supervisor-middleware-egress-usage/src/usage.rs @@ -19,6 +19,8 @@ use tokio_stream::StreamExt; use tokio_stream::wrappers::ReceiverStream; use tonic::Status; +use crate::config::Config; + /// Reason code sent with every close and deny. pub const REASON_CODE: &str = "egress_budget_exceeded"; @@ -32,7 +34,7 @@ pub enum Direction { Out, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Limits { pub max_bytes: u64, pub window: Duration, @@ -78,7 +80,8 @@ struct State { #[derive(Clone)] pub struct Usage { - limits: Limits, + /// Flag values, used where the policy config leaves a field unset. + defaults: Limits, state: Arc>, next_id: Arc, } @@ -110,22 +113,29 @@ fn key_of(preflight: &TcpPreflight) -> Key { } impl Usage { - pub fn new(limits: Limits) -> Self { + pub fn new(defaults: Limits) -> Self { Self { - limits, + defaults, state: Arc::default(), next_id: Arc::default(), } } - fn counted(&self, bytes_out: u64, bytes_in: u64) -> u64 { - match self.limits.direction { - Direction::Both => bytes_out + bytes_in, - Direction::In => bytes_in, - Direction::Out => bytes_out, - } + /// Parses a policy config, filling unset fields from the flags. + pub fn config(&self, config: Option<&prost_types::Struct>) -> Result { + Config::parse(config, self.defaults) } +} +fn counted(limits: &Limits, bytes_out: u64, bytes_in: u64) -> u64 { + match limits.direction { + Direction::Both => bytes_out + bytes_in, + Direction::In => bytes_in, + Direction::Out => bytes_out, + } +} + +impl Usage { pub fn connection_stream( &self, mut events: tonic::Streaming, @@ -145,6 +155,15 @@ impl Usage { return; }; let key = key_of(&preflight); + // The policy config is read per connection, so a policy update + // applies to connections opened after it loads. + let limits = match usage.config(preflight.config.as_ref()) { + Ok(config) => config.limits_for(&key.1), + Err(error) => { + let _ = tx.send(Err(Status::invalid_argument(error))).await; + return; + } + }; let id = usage.next_id.fetch_add(1, Ordering::Relaxed); let blocked = { let mut state = usage.state.lock().unwrap(); @@ -170,7 +189,7 @@ impl Usage { let _ = tx.send(Ok(close_result())).await; return; } - let interval = prost_types::Duration::try_from(usage.limits.interval).ok(); + let interval = prost_types::Duration::try_from(limits.interval).ok(); let _ = tx .send(Ok(TcpHookResult { result: Some(tcp_hook_result::Result::PreflightResult( @@ -192,10 +211,10 @@ impl Usage { Some(tcp_hook_event::Event::End(end)) => (end.bytes_out, end.bytes_in, true), _ => continue, }; - let counted = usage.counted(bytes_out, bytes_in); + let counted = counted(&limits, bytes_out, bytes_in); let delta = counted.saturating_sub(last); last = counted; - usage.record(&key, &preflight, delta); + usage.record(&key, &preflight, &limits, delta); if end { break; } @@ -209,25 +228,24 @@ impl Usage { } /// Add `delta` to the key's window and trip it when over the limit. - fn record(&self, key: &Key, preflight: &TcpPreflight, delta: u64) { + fn record(&self, key: &Key, preflight: &TcpPreflight, limits: &Limits, delta: u64) { let now = Instant::now(); let mut state = self.state.lock().unwrap(); - let total = - state - .windows - .entry(key.clone()) - .or_default() - .add(now, delta, self.limits.window); + let total = state + .windows + .entry(key.clone()) + .or_default() + .add(now, delta, limits.window); let already_blocked = state .blocked_until .get(key) .is_some_and(|until| *until > now); - if total <= self.limits.max_bytes || already_blocked { + if total <= limits.max_bytes || already_blocked { return; } state .blocked_until - .insert(key.clone(), now + self.limits.block_for); + .insert(key.clone(), now + limits.block_for); let open = state.open.remove(key).unwrap_or_default(); println!( "TRIP sandbox={} policy={} host={}:{} window_bytes={total} max_bytes={} over_by={} closing={} block_for={:?}", @@ -235,10 +253,10 @@ impl Usage { key.1, preflight.host, preflight.port, - self.limits.max_bytes, - total - self.limits.max_bytes, + limits.max_bytes, + total - limits.max_bytes, open.len(), - self.limits.block_for, + limits.block_for, ); for sender in open.into_values() { // Never block the lock holder: a full result channel already has From a511a8b1aa4ef63d2c35afdcbecde82b6ffcf8c3 Mon Sep 17 00:00:00 2001 From: Piotr Mlocek Date: Mon, 2 Nov 2026 19:23:22 -0800 Subject: [PATCH 6/6] docs(examples): note egress-usage config shape is service-defined (spike) Signed-off-by: Piotr Mlocek --- examples/supervisor-middleware-egress-usage/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/supervisor-middleware-egress-usage/README.md b/examples/supervisor-middleware-egress-usage/README.md index 49da178e51..b78638101a 100644 --- a/examples/supervisor-middleware-egress-usage/README.md +++ b/examples/supervisor-middleware-egress-usage/README.md @@ -14,7 +14,10 @@ Covers raw TCP, `tls: skip`, terminated TLS, and plain HTTP the same way, becaus ## Configure limits in the policy -The limits come from the middleware's `config` in the sandbox policy, so changing the policy changes the budget: +The limits come from the middleware's `config` in the sandbox policy, so changing the policy changes the budget. + +> [!NOTE] +> The `config` shape below is for demonstration only. OpenShell passes `config` to the middleware service as an opaque structure and does not define its fields: each service defines and validates its own. This example's service reads the fields below. ```yaml network_middlewares: