1use std::path::Path;
5
6use crate::backend::{default_backend_for_os, EnvOnlyEnforcer, SandboxBackend};
7use crate::{ConnectionPolicy, SecretMapping, StringMapping};
8
9pub trait Enforce: Send + Sync {
11 fn maybe_spawn_runner(
15 &self,
16 cmd: &str,
17 secret_mappings: &[SecretMapping],
18 string_mappings: &[StringMapping],
19 allow_private_connect: bool,
20 upstream_ca: &Option<std::path::PathBuf>,
21 connection_policies: &[ConnectionPolicy],
22 ) -> Result<Option<std::process::ExitStatus>, Box<dyn std::error::Error + Send + Sync>>;
23
24 fn run_child(
27 &self,
28 cmd: &str,
29 proxy_url: &str,
30 env_vars_with_masked: &[(String, String)],
31 ssl_cert_file: &Path,
32 force: bool,
33 ) -> Result<std::process::ExitStatus, Box<dyn std::error::Error + Send + Sync>>;
34}
35
36#[allow(dead_code)] pub struct NoOpEnforcer;
40
41impl Enforce for NoOpEnforcer {
42 fn maybe_spawn_runner(
43 &self,
44 _cmd: &str,
45 _secret_mappings: &[SecretMapping],
46 _string_mappings: &[StringMapping],
47 _allow_private_connect: bool,
48 _upstream_ca: &Option<std::path::PathBuf>,
49 _connection_policies: &[ConnectionPolicy],
50 ) -> Result<Option<std::process::ExitStatus>, Box<dyn std::error::Error + Send + Sync>> {
51 Ok(None)
52 }
53
54 fn run_child(
55 &self,
56 cmd: &str,
57 proxy_url: &str,
58 env_vars_with_masked: &[(String, String)],
59 ssl_cert_file: &Path,
60 _force: bool,
61 ) -> Result<std::process::ExitStatus, Box<dyn std::error::Error + Send + Sync>> {
62 run_child_duct(cmd, proxy_url, env_vars_with_masked, ssl_cert_file)
63 }
64}
65
66pub(crate) fn run_child_duct(
68 cmd: &str,
69 proxy_url: &str,
70 env_vars_with_masked: &[(String, String)],
71 ssl_cert_file: &Path,
72) -> Result<std::process::ExitStatus, Box<dyn std::error::Error + Send + Sync>> {
73 let mut env: Vec<(String, String)> = vec![
74 ("HTTP_PROXY".into(), proxy_url.to_string()),
75 ("HTTPS_PROXY".into(), proxy_url.to_string()),
76 ("NO_PROXY".into(), String::new()),
77 (
78 "SSL_CERT_FILE".into(),
79 ssl_cert_file.to_string_lossy().into_owned(),
80 ),
81 (
82 "NODE_EXTRA_CA_CERTS".into(),
83 ssl_cert_file.to_string_lossy().into_owned(),
84 ),
85 ];
86 env.extend(env_vars_with_masked.iter().cloned());
87
88 let mut run = duct_sh::sh_dangerous(cmd);
89 for (k, v) in &env {
90 run = run.env(k, v);
91 }
92 run.unchecked().run().map(|o| o.status).map_err(Into::into)
93}
94
95pub fn enforcer_for(
99 backend: Option<SandboxBackend>,
100 force_traffic_through_proxy: bool,
101) -> Result<&'static dyn Enforce, Box<dyn std::error::Error + Send + Sync>> {
102 if !force_traffic_through_proxy {
103 return Ok(&ENV_ONLY_ENFORCER);
104 }
105 let backend = backend.unwrap_or_else(default_backend_for_os);
106
107 #[cfg(target_os = "linux")]
108 {
109 match backend {
110 SandboxBackend::Firecracker => {
111 Err("Firecracker backend is not yet implemented; use --no-force-proxy".into())
112 }
113 SandboxBackend::Docker => Err(
114 "Docker backend is not available on Linux; use firecracker or --no-force-proxy"
115 .into(),
116 ),
117 }
118 }
119
120 #[cfg(target_os = "macos")]
121 {
122 match backend {
123 SandboxBackend::Docker => Ok(crate::enforce_docker::docker_enforcer()),
124 SandboxBackend::Firecracker => Err(
125 "Firecracker backend is not available on macOS; use docker or --no-force-proxy"
126 .into(),
127 ),
128 }
129 }
130
131 #[cfg(not(any(target_os = "linux", target_os = "macos")))]
132 {
133 let _ = backend;
134 Err("force_traffic_through_proxy requires a sandbox backend (Firecracker on Linux, Docker on macOS); this OS has no backend. Use --no-force-proxy.".into())
135 }
136}
137
138pub(crate) static ENV_ONLY_ENFORCER: EnvOnlyEnforcer = EnvOnlyEnforcer;