Skip to main content

microsandbox_network/
builder.rs

1//! Fluent builder API for [`NetworkConfig`].
2//!
3//! Used by `SandboxBuilder::network(|n| n.port(8080, 80).policy(...))`.
4
5use std::net::IpAddr;
6use std::path::PathBuf;
7
8use crate::config::{InterfaceOverrides, NetworkConfig, PortProtocol, PublishedPort};
9use crate::policy::NetworkPolicy;
10use crate::secrets::config::{HostPattern, SecretEntry, SecretInjection, ViolationAction};
11use crate::tls::TlsConfig;
12
13//--------------------------------------------------------------------------------------------------
14// Types
15//--------------------------------------------------------------------------------------------------
16
17/// Fluent builder for [`NetworkConfig`].
18pub struct NetworkBuilder {
19    config: NetworkConfig,
20}
21
22/// Fluent builder for [`TlsConfig`].
23pub struct TlsBuilder {
24    config: TlsConfig,
25}
26
27/// Fluent builder for a single [`SecretEntry`].
28///
29/// ```ignore
30/// SecretBuilder::new()
31///     .env("OPENAI_API_KEY")
32///     .value(api_key)
33///     .allow_host("api.openai.com")
34///     .build()
35/// ```
36pub struct SecretBuilder {
37    env_var: Option<String>,
38    value: Option<String>,
39    placeholder: Option<String>,
40    allowed_hosts: Vec<HostPattern>,
41    injection: SecretInjection,
42    require_tls_identity: bool,
43}
44
45//--------------------------------------------------------------------------------------------------
46// Methods
47//--------------------------------------------------------------------------------------------------
48
49impl NetworkBuilder {
50    /// Start building a network configuration with defaults.
51    pub fn new() -> Self {
52        Self {
53            config: NetworkConfig::default(),
54        }
55    }
56
57    /// Start building from an existing network configuration.
58    pub fn from_config(config: NetworkConfig) -> Self {
59        Self { config }
60    }
61
62    /// Enable or disable networking.
63    pub fn enabled(mut self, enabled: bool) -> Self {
64        self.config.enabled = enabled;
65        self
66    }
67
68    /// Publish a TCP port: `host_port` on the host maps to `guest_port` in the guest.
69    pub fn port(self, host_port: u16, guest_port: u16) -> Self {
70        self.add_port(host_port, guest_port, PortProtocol::Tcp)
71    }
72
73    /// Publish a UDP port.
74    pub fn port_udp(self, host_port: u16, guest_port: u16) -> Self {
75        self.add_port(host_port, guest_port, PortProtocol::Udp)
76    }
77
78    fn add_port(mut self, host_port: u16, guest_port: u16, protocol: PortProtocol) -> Self {
79        self.config.ports.push(PublishedPort {
80            host_port,
81            guest_port,
82            protocol,
83            host_bind: IpAddr::V4(std::net::Ipv4Addr::LOCALHOST),
84        });
85        self
86    }
87
88    /// Set the network policy.
89    pub fn policy(mut self, policy: NetworkPolicy) -> Self {
90        self.config.policy = policy;
91        self
92    }
93
94    /// Block a specific domain via DNS interception.
95    pub fn block_domain(mut self, domain: impl Into<String>) -> Self {
96        self.config.dns.blocked_domains.push(domain.into());
97        self
98    }
99
100    /// Block a domain suffix via DNS interception.
101    pub fn block_domain_suffix(mut self, suffix: impl Into<String>) -> Self {
102        self.config.dns.blocked_suffixes.push(suffix.into());
103        self
104    }
105
106    /// Enable or disable DNS rebinding protection.
107    pub fn dns_rebind_protection(mut self, enabled: bool) -> Self {
108        self.config.dns.rebind_protection = enabled;
109        self
110    }
111
112    /// Configure TLS interception via a closure.
113    pub fn tls(mut self, f: impl FnOnce(TlsBuilder) -> TlsBuilder) -> Self {
114        self.config.tls = f(TlsBuilder::new()).build();
115        self
116    }
117
118    /// Add a secret via a closure builder.
119    ///
120    /// ```ignore
121    /// .secret(|s| s
122    ///     .env("OPENAI_API_KEY")
123    ///     .value(api_key)
124    ///     .allow_host("api.openai.com")
125    /// )
126    /// ```
127    pub fn secret(mut self, f: impl FnOnce(SecretBuilder) -> SecretBuilder) -> Self {
128        self.config
129            .secrets
130            .secrets
131            .push(f(SecretBuilder::new()).build());
132        self
133    }
134
135    /// Shorthand: add a secret with env var, value, placeholder, and allowed host.
136    pub fn secret_env(
137        mut self,
138        env_var: impl Into<String>,
139        value: impl Into<String>,
140        placeholder: impl Into<String>,
141        allowed_host: impl Into<String>,
142    ) -> Self {
143        self.config.secrets.secrets.push(SecretEntry {
144            env_var: env_var.into(),
145            value: value.into(),
146            placeholder: placeholder.into(),
147            allowed_hosts: vec![HostPattern::Exact(allowed_host.into())],
148            injection: SecretInjection::default(),
149            require_tls_identity: true,
150        });
151        self
152    }
153
154    /// Set the violation action for secrets.
155    pub fn on_secret_violation(mut self, action: ViolationAction) -> Self {
156        self.config.secrets.on_violation = action;
157        self
158    }
159
160    /// Set the maximum number of concurrent connections.
161    pub fn max_connections(mut self, max: usize) -> Self {
162        self.config.max_connections = Some(max);
163        self
164    }
165
166    /// Set guest interface overrides.
167    pub fn interface(mut self, overrides: InterfaceOverrides) -> Self {
168        self.config.interface = overrides;
169        self
170    }
171
172    /// Consume the builder and return the configuration.
173    pub fn build(self) -> NetworkConfig {
174        self.config
175    }
176}
177
178impl TlsBuilder {
179    /// Start building TLS configuration.
180    pub fn new() -> Self {
181        Self {
182            config: TlsConfig {
183                enabled: true,
184                ..TlsConfig::default()
185            },
186        }
187    }
188
189    /// Add a domain to the bypass list (no MITM). Supports `*.suffix` wildcards.
190    pub fn bypass(mut self, pattern: impl Into<String>) -> Self {
191        self.config.bypass.push(pattern.into());
192        self
193    }
194
195    /// Enable or disable upstream server certificate verification.
196    pub fn verify_upstream(mut self, verify: bool) -> Self {
197        self.config.verify_upstream = verify;
198        self
199    }
200
201    /// Set the ports to intercept.
202    pub fn intercepted_ports(mut self, ports: Vec<u16>) -> Self {
203        self.config.intercepted_ports = ports;
204        self
205    }
206
207    /// Enable or disable QUIC blocking on intercepted ports.
208    pub fn block_quic(mut self, block: bool) -> Self {
209        self.config.block_quic_on_intercept = block;
210        self
211    }
212
213    /// Set a custom CA certificate PEM file path.
214    pub fn ca_cert(mut self, path: impl Into<PathBuf>) -> Self {
215        self.config.ca.cert_path = Some(path.into());
216        self
217    }
218
219    /// Set a custom CA private key PEM file path.
220    pub fn ca_key(mut self, path: impl Into<PathBuf>) -> Self {
221        self.config.ca.key_path = Some(path.into());
222        self
223    }
224
225    /// Consume the builder and return the configuration.
226    pub fn build(self) -> TlsConfig {
227        self.config
228    }
229}
230
231impl SecretBuilder {
232    /// Start building a secret.
233    pub fn new() -> Self {
234        Self {
235            env_var: None,
236            value: None,
237            placeholder: None,
238            allowed_hosts: Vec::new(),
239            injection: SecretInjection::default(),
240            require_tls_identity: true,
241        }
242    }
243
244    /// Set the environment variable to expose the placeholder as (required).
245    pub fn env(mut self, var: impl Into<String>) -> Self {
246        self.env_var = Some(var.into());
247        self
248    }
249
250    /// Set the secret value (required).
251    pub fn value(mut self, value: impl Into<String>) -> Self {
252        self.value = Some(value.into());
253        self
254    }
255
256    /// Set a custom placeholder string.
257    /// If not set, auto-generated as `$MSB_<env_var>`.
258    pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
259        self.placeholder = Some(placeholder.into());
260        self
261    }
262
263    /// Add an allowed host (exact match).
264    pub fn allow_host(mut self, host: impl Into<String>) -> Self {
265        self.allowed_hosts.push(HostPattern::Exact(host.into()));
266        self
267    }
268
269    /// Add an allowed host with wildcard pattern (e.g., `*.openai.com`).
270    pub fn allow_host_pattern(mut self, pattern: impl Into<String>) -> Self {
271        self.allowed_hosts
272            .push(HostPattern::Wildcard(pattern.into()));
273        self
274    }
275
276    /// Allow for any host. **Dangerous**: secret can be exfiltrated to any
277    /// destination. Requires explicit acknowledgment.
278    pub fn allow_any_host_dangerous(mut self, i_understand_the_risk: bool) -> Self {
279        if i_understand_the_risk {
280            self.allowed_hosts.push(HostPattern::Any);
281        }
282        self
283    }
284
285    /// Require verified TLS identity before substituting (default: true).
286    pub fn require_tls_identity(mut self, enabled: bool) -> Self {
287        self.require_tls_identity = enabled;
288        self
289    }
290
291    /// Configure header injection (default: true).
292    pub fn inject_headers(mut self, enabled: bool) -> Self {
293        self.injection.headers = enabled;
294        self
295    }
296
297    /// Configure Basic Auth injection (default: true).
298    pub fn inject_basic_auth(mut self, enabled: bool) -> Self {
299        self.injection.basic_auth = enabled;
300        self
301    }
302
303    /// Configure query parameter injection (default: false).
304    pub fn inject_query(mut self, enabled: bool) -> Self {
305        self.injection.query_params = enabled;
306        self
307    }
308
309    /// Configure body injection (default: false).
310    pub fn inject_body(mut self, enabled: bool) -> Self {
311        self.injection.body = enabled;
312        self
313    }
314
315    /// Consume the builder and return a [`SecretEntry`].
316    ///
317    /// # Panics
318    /// Panics if `env` or `value` was not set.
319    pub fn build(self) -> SecretEntry {
320        let env_var = self.env_var.expect("SecretBuilder: .env() is required");
321        let value = self.value.expect("SecretBuilder: .value() is required");
322        let placeholder = self
323            .placeholder
324            .unwrap_or_else(|| format!("$MSB_{env_var}"));
325
326        SecretEntry {
327            env_var,
328            value,
329            placeholder,
330            allowed_hosts: self.allowed_hosts,
331            injection: self.injection,
332            require_tls_identity: self.require_tls_identity,
333        }
334    }
335}
336
337//--------------------------------------------------------------------------------------------------
338// Trait Implementations
339//--------------------------------------------------------------------------------------------------
340
341impl Default for NetworkBuilder {
342    fn default() -> Self {
343        Self::new()
344    }
345}
346
347impl Default for TlsBuilder {
348    fn default() -> Self {
349        Self::new()
350    }
351}
352
353impl Default for SecretBuilder {
354    fn default() -> Self {
355        Self::new()
356    }
357}