OpenSandbox Egress Sidecar
The Egress is a core component of OpenSandbox that provides FQDN-based egress control.
It runs alongside the sandbox application container (sharing the same network namespace) and enforces declared network policies.
Features
- FQDN-based Allowlist: Control outbound traffic by domain name (e.g.,
api.github.com). - IP / CIDR Targets: Egress rules can also target literal IP addresses or CIDR ranges (e.g.,
10.0.0.0/8). - Wildcard Support: Allow subdomains using wildcards (e.g.,
*.pypi.org). - Transparent Interception: Uses transparent DNS proxying; no application configuration required.
- Experimental: Transparent HTTPS MITM (mitmproxy): Optional transparent TLS interception for outbound
80/443traffic in the sidecar network namespace. - Dynamic DNS (dns+nft mode): When a domain is allowed and the proxy resolves it, the resolved A/AAAA IPs are added to nftables with TTL so that default-deny + domain-allow is enforced at the network layer.
- Credential Vault: Automatic credential injection (bearer, basic, API-key, custom headers, and scoped placeholder substitutions) for allowed hosts via transparent mitmproxy. See Credential Vault.
- Privilege Isolation: Requires
CAP_NET_ADMINonly for the sidecar; the application container runs unprivileged. - Fail-Closed Enforcement: DNS redirect setup is required through
iptablesor the native nft fallback; the sidecar exits if no enforced redirect can be installed. Optional subsystems (OpenTelemetry, startup hooks) degrade gracefully.
Architecture
The egress control is implemented as a Sidecar that shares the network namespace with the sandbox application.
DNS Proxy (Layer 1):
- Runs on
127.0.0.1:15353. iptablesrules redirect all port 53 (DNS) traffic to this proxy.- Filters queries based on the allowlist.
- Returns
NXDOMAINfor denied domains.
- Runs on
Network Filter (Layer 2) (when
OPENSANDBOX_EGRESS_MODE=dns+nft):- Uses
nftablesto enforce IP-level allow/deny. Resolved IPs for allowed domains are added to dynamic allow sets with TTL (dynamic DNS). - At startup, the sidecar whitelists 127.0.0.1 (redirect target for the proxy) and nameserver IPs from
/etc/resolv.confso DNS resolution and proxy upstream work (including private DNS). Nameserver count is capped and invalid IPs are filtered.
- Uses
Kubernetes Service Access Under defaultAction: deny
In Kubernetes deployments that use defaultAction: deny, reaching an in-cluster Service usually needs two separate allowances:
- allow the Service DNS name so the DNS proxy resolves it
- allow the Service CIDR (or a narrower ClusterIP range) so
dns+nftdoes not drop the TCP connection after resolution
Allowing only postgres.opensandbox.svc.cluster.local is not sufficient if the resolved ClusterIP still belongs to a denied range such as 10.96.0.0/12. Likewise, allowing only the CIDR is not sufficient if the DNS proxy still denies the hostname.
See Network Isolation for operator guidance and examples.
Requirements
- Runtime: Docker or Kubernetes.
- Capabilities:
CAP_NET_ADMIN(for the sidecar container only). - Kernel: Linux kernel with
iptablessupport. - Service mesh: OpenSandbox egress is not currently supported inside pods that already have a transparent service-mesh sidecar (for example Istio/Envoy injection). Both layers rewrite outbound traffic in the same network namespace and can conflict.
Configuration
Most deployments only need these settings:
- Mode:
OPENSANDBOX_EGRESS_MODEdns(default): DNS filtering onlydns+nft: DNS + nftables IP/CIDR enforcement (recommended for strict default-deny)
- Initial policy:
OPENSANDBOX_EGRESS_RULES(JSON, same shape asPOST /policy)- or
OPENSANDBOX_EGRESS_POLICY_FILE(if valid file exists, it takes precedence at startup)
- HTTP API:
OPENSANDBOX_EGRESS_HTTP_ADDR(default:18080)OPENSANDBOX_EGRESS_TOKEN(optional auth viaOPENSANDBOX-EGRESS-AUTH)
- Rule limit:
OPENSANDBOX_EGRESS_MAX_RULESforPOST/PATCH /policy(default4096,0disables cap)
Optional advanced features:
- Nameserver bypass:
OPENSANDBOX_EGRESS_NAMESERVER_EXEMPT - Denied hostname webhook:
OPENSANDBOX_EGRESS_DENY_WEBHOOK,OPENSANDBOX_EGRESS_SANDBOX_ID - DoH/DoT controls:
OPENSANDBOX_EGRESS_BLOCK_DOH_443,OPENSANDBOX_EGRESS_DOH_BLOCKLIST - Custom DNS upstream:
OPENSANDBOX_EGRESS_DNS_UPSTREAM(comma-separated IPs, optional:port),OPENSANDBOX_EGRESS_DNS_UPSTREAM_TIMEOUT(default5seconds) - DNS upstream health probe:
OPENSANDBOX_EGRESS_DNS_UPSTREAM_PROBE(enable),OPENSANDBOX_EGRESS_DNS_UPSTREAM_PROBE_INTERVAL_SEC - Credential vault:
OPENSANDBOX_EGRESS_CREDENTIAL_VAULT_REQUIRE_TLS,OPENSANDBOX_EGRESS_CREDENTIAL_VAULT_TRUSTED_PROXY_CIDRS,OPENSANDBOX_CREDENTIAL_PROXY_SOCKET(default/run/opensandbox/credential-proxy/active.sock) - Metrics:
OPENSANDBOX_EGRESS_METRICS_EXTRA_ATTRS(extra key=value attributes for OTLP metrics and structured log fields)
Always-Rules Files
Static rule files under /var/egress/rules/ are loaded at startup and take priority over dynamic API rules:
| File | Purpose |
|---|---|
/var/egress/rules/deny.always | Domains always denied, overrides user and allow rules |
/var/egress/rules/allow.always | Domains always allowed, overrides user rules |
/var/egress/rules/log_skip.always | Domain patterns whose DNS blocks are not logged (noise reduction) |
Format: one domain per line (supports wildcards like *.example.com). Lines starting with # are comments. Missing files are silently ignored.
Rule precedence: deny.always > allow.always > user policy (API/env).
Always-rules are hot-reloaded: the sidecar polls the files once per minute and applies changes without restart.
Service Mesh Compatibility
Not Supported with Transparent Mesh Sidecars
OpenSandbox egress is designed to be the only transparent outbound interception layer inside the sandbox pod. Deployments that automatically inject a service-mesh sidecar such as Istio/Envoy into the same pod are not currently supported for egress-sidecar features.
Why this conflicts today:
- OpenSandbox egress installs
iptables/nftredirect rules in the shared pod network namespace so DNS and optional HTTPS MITM traffic flow through the egress sidecar. - Service meshes such as Istio also redirect outbound traffic in that same namespace, usually to Envoy.
- When both are present, the redirect order becomes deployment-dependent and can produce double interception, broken TLS, or traffic that bypasses the expected Credential Vault / egress-policy path.
This matters for:
- per-sandbox
networkPolicy/network_policyenforcement - transparent mitmproxy mode
- Credential Vault / Credential Proxy
Recommended operator choices today:
- Exclude OpenSandbox sandbox pods from automatic mesh sidecar injection when they need the egress sidecar.
- If mesh injection is mandatory, do not rely on the OpenSandbox egress sidecar for outbound control in those pods; instead use a platform-level mechanism such as a CNI/network-policy solution.
- Treat mesh-injected sandboxes as a separate runtime profile and document that Credential Vault and transparent egress interception are unavailable there until first-class coexistence support is implemented.
See also Credential Vault and Network Isolation.
Runtime HTTP API
| Method | Path | Description |
|---|---|---|
GET | /policy | Get current policy and enforcement mode |
POST | /policy | Replace policy ({}, null, empty body => reset to deny-all) |
PUT | /policy | Alias for POST |
PATCH | /policy | Merge/append rules (body is JSON array of egress rules) |
DELETE | /policy | Remove specific targets (body is JSON string array, e.g. ["*.example.com"]) |
GET/POST/PATCH/DELETE | /credential-vault | Manage the credential vault (create, update, delete) |
GET | /credential-vault/credentials | List credential metadata |
GET | /credential-vault/credentials/{name} | Get single credential metadata |
GET | /credential-vault/bindings | List binding metadata |
GET | /credential-vault/bindings/{name} | Get single binding metadata |
GET | /healthz | Health check; returns 200 ok or 503 mitmproxy not ready (when transparent MITM is enabled but not yet initialized) |
Quick example:
# Replace policy
curl -XPOST http://127.0.0.1:18080/policy \
-d '{"defaultAction":"deny","egress":[{"action":"allow","target":"*.example.com"}]}'
# Remove specific targets
curl -XDELETE http://127.0.0.1:18080/policy \
-d '["*.example.com"]'Experimental: Transparent MITM (mitmproxy)
Experimental
APIs, environment variables, and behavior may change.
Optional transparent HTTPS interception for outbound 80/443 traffic in the sidecar network namespace.
Credential Vault
The credential vault provides automatic credential injection for outbound requests to allowed hosts. Credentials are stored in-memory and injected into matching requests by the transparent mitmproxy layer.
Prerequisites: transparent mitmproxy enabled (OPENSANDBOX_EGRESS_MITMPROXY_TRANSPARENT=true), egress API auth token set (OPENSANDBOX_EGRESS_TOKEN).
Supported auth types: bearer, basic, apiKey, customHeaders.
See Credential Vault for full API usage, binding rules, and security model.
Observability (OpenTelemetry)
Egress can export OTLP metrics; application logs use the native zap logger (JSON to stdout by default, configurable via OPENSANDBOX_LOG_OUTPUT / OPENSANDBOX_EGRESS_LOG_LEVEL). OTLP log export is not used.
Build & Run
Build Docker Image
cd components/egress
# Build locally
docker build -t opensandbox/egress:local .
# Or use the build script (multi-arch)
./build.shRun Locally
- Start sidecar:
docker run -d --name sandbox-egress \
--cap-add=NET_ADMIN \
opensandbox/egress:local- Apply policy:
curl -XPOST http://127.0.0.1:18080/policy \
-d '{"defaultAction":"deny","egress":[{"action":"allow","target":"*.google.com"}]}'- Run app container in the same network namespace:
docker run --rm -it \
--network container:sandbox-egress \
curlimages/curl sh- Verify from app container:
curl -I https://google.com
curl -I https://github.comDevelopment
- Language: Go 1.25+
- Key Packages:
pkg/dnsproxy: DNS server and policy matching logic.pkg/iptables:iptablesrule management.pkg/nftables: nftables static/dynamic rules and DNS-resolved IP sets.pkg/policy: Policy parsing and definition.pkg/credentialvault: Credential vault store and binding validation.pkg/startup: Post-startup hook registry (Register/RunPost).hooks/: Side-effect import target;init()functions register startup hooks that run after iptables/MITM setup.
cd components/egress
go test ./...Process Supervisor
The egress container runs under opensandbox-supervisor, a lightweight process wrapper that restarts the egress worker on crash with exponential backoff, a crashloop circuit breaker, and structured JSONL event logging.
ENTRYPOINT: supervisor --pre-start=cleanup.sh --name=egress --grace-period=20s -- /opt/opensandbox-egress/egressEgress-specific configuration:
--grace-period=20s: Egress needs extra time to drain DNS connections and tear down iptables/nft rules on shutdown (default is 10 s).- Pre-start hook (
cleanup.sh): Reaps orphanedmitmdumpprocesses from a previous crash and removes stale DNS redirect iptables/native nft state that would otherwise point port 53 at a dead proxy. It does not manage theinet opensandboxpolicy table; the nftables manager deletes and recreates that table when policy enforcement starts.
Troubleshooting
- "iptables setup failed": ensure sidecar has
--cap-add=NET_ADMIN. - DNS fails for all domains: check sidecar upstream DNS reachability and logs.
- Traffic not blocked as expected: in
dns+nft, verify nft applied (nft list table inet opensandbox) and check sidecar logs for fallback.