(
policy: &ProxyPolicy,
bind_addr: Option<SocketAddr>,
opa_engine: Arc<OpaEngine>,
identity_cache: Arc<BinaryIdentityCache>,
entrypoint_pid: Arc<AtomicU32>,
| 186 | /// via `/proc/net/tcp`. All connections are evaluated through OPA policy. |
| 187 | #[allow(clippy::too_many_arguments)] |
| 188 | pub(crate) async fn start_with_bind_addr( |
| 189 | policy: &ProxyPolicy, |
| 190 | bind_addr: Option<SocketAddr>, |
| 191 | opa_engine: Arc<OpaEngine>, |
| 192 | identity_cache: Arc<BinaryIdentityCache>, |
| 193 | entrypoint_pid: Arc<AtomicU32>, |
| 194 | tls_state: Option<Arc<ProxyTlsState>>, |
| 195 | inference_ctx: Option<Arc<InferenceContext>>, |
| 196 | provider_credentials: Option<ProviderCredentialState>, |
| 197 | policy_local_ctx: Option<Arc<PolicyLocalContext>>, |
| 198 | denial_tx: Option<mpsc::UnboundedSender<DenialEvent>>, |
| 199 | activity_tx: Option<ActivitySender>, |
| 200 | engine_ready: tokio::sync::watch::Receiver<bool>, |
| 201 | ) -> Result<Self> { |
| 202 | // Use override bind_addr, fall back to policy http_addr, then default |
| 203 | // to loopback:3128. The default allows the proxy to function when no |
| 204 | // network namespace is available (e.g. missing CAP_NET_ADMIN) and the |
| 205 | // policy doesn't specify an explicit address. |
| 206 | let default_addr: SocketAddr = ([127, 0, 0, 1], 3128).into(); |
| 207 | let http_addr = bind_addr.or(policy.http_addr).unwrap_or(default_addr); |
| 208 | |
| 209 | // Only enforce loopback restriction when not using network namespace override |
| 210 | if bind_addr.is_none() && !http_addr.ip().is_loopback() { |
| 211 | return Err(miette::miette!( |
| 212 | "Proxy http_addr must be loopback-only: {http_addr}" |
| 213 | )); |
| 214 | } |
| 215 | |
| 216 | let listener = TcpListener::bind(http_addr).await.into_diagnostic()?; |
| 217 | let local_addr = listener.local_addr().into_diagnostic()?; |
| 218 | { |
| 219 | let event = NetworkActivityBuilder::new(openshell_ocsf::ctx::ctx()) |
| 220 | .activity(ActivityId::Listen) |
| 221 | .severity(SeverityId::Informational) |
| 222 | .status(StatusId::Success) |
| 223 | .dst_endpoint(Endpoint::from_ip(local_addr.ip(), local_addr.port())) |
| 224 | .message(format!("Proxy listening on {local_addr}")) |
| 225 | .build(); |
| 226 | ocsf_emit!(event); |
| 227 | } |
| 228 | |
| 229 | // Detect the trusted host gateway IP from /etc/hosts before user code |
| 230 | // runs. This is read once at startup so later /etc/hosts modifications |
| 231 | // by sandbox workloads cannot influence the stored value. |
| 232 | let trusted_host_gateway: Arc<Option<IpAddr>> = Arc::new(detect_trusted_host_gateway()); |
| 233 | if let Some(ref ip) = *trusted_host_gateway { |
| 234 | tracing::info!( |
| 235 | %ip, |
| 236 | "Trusted host gateway detected from /etc/hosts; \ |
| 237 | host-gateway aliases exempt from SSRF always-blocked check" |
| 238 | ); |
| 239 | } |
| 240 | |
| 241 | let join = tokio::spawn(async move { |
| 242 | // Wait for the OPA engine's symlink resolution reload to complete |
| 243 | // before accepting connections. This prevents requests from |
| 244 | // observing a generation transition mid-flight, which would cause |
| 245 | // the generation guard to reject them with a 403. |
nothing calls this directly
no test coverage detected