Files
wehub-resource-sync e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:39:33 +08:00

12 KiB

title, description
title description
Network Isolation Why Kubernetes NetworkPolicy is insufficient for sandbox isolation and how OpenSandbox uses egress sidecar controls instead.

Network Isolation on Kubernetes

Problem

On a Kubernetes cluster, each OpenSandbox sandbox runs as an independent Pod with a dedicated Pod IP assigned by the CNI plugin. By default, any sandbox can reach other sandboxes in the same cluster directly via Pod IP. This introduces several security risks:

  • Internal network scanning: malicious code inside a sandbox can scan the cluster Pod CIDR range to discover other sandboxes.
  • Unauthorized access: an attacker can connect to other sandboxes' listening ports directly, bypassing OpenSandbox's authentication and authorization mechanisms.
  • Data leakage: when sandboxes from different tenants co-exist in the same cluster, the lack of isolation may lead to unauthorized cross-tenant data access.

A mechanism is needed to prevent direct IP-based communication between sandboxes while preserving legitimate external access paths (via OpenSandbox Ingress).

Native Kubernetes NetworkPolicy

Kubernetes NetworkPolicy controls traffic through Pod label selectors. It faces fundamental limitations in the sandbox isolation scenario:

  • Unpredictable labels: sandbox Pod labels are injected automatically by the platform and are not user-controllable. Sandboxes from different tenants or security levels may share the same label set, making it impossible to define precise isolation boundaries with label selectors.
  • Dynamic lifecycle: sandboxes are created and destroyed frequently. NetworkPolicy is a static declaration and cannot track the expected isolation relationships of each sandbox in real time.
  • Granularity mismatch: NetworkPolicy operates on Pod sets (namespace + label selector), while sandbox isolation requires each sandbox to be an independent security domain that denies access from all other sandboxes by default. Expressing "deny access from all other Pods to me" with NetworkPolicy would require a separate rule per sandbox, and cannot cover sandboxes created in the future.
  • No outbound control: NetworkPolicy Ingress rules can block inbound traffic, but cannot prevent sandbox processes from initiating outbound connections (e.g., curling another sandbox's Pod IP). Blocking outbound direct connections requires bidirectional (Ingress + Egress) policies per sandbox, which runs into the same label unpredictability and dynamicity problems.

Therefore, OpenSandbox does not rely on native Kubernetes NetworkPolicy. Instead, it uses sandbox-level egress control to enforce sandbox-to-sandbox isolation. The control point lives inside the sandbox (egress sidecar), not at the cluster network layer.

Approach 1: Global Enforced Isolation via Egress Sidecar deny.always

How It Works

The OpenSandbox egress sidecar provides a deny.always mechanism: place a file at /var/egress/rules/deny.always inside the sidecar container, and the rules declared in it take effect unconditionally, with higher priority than any allow rules set by users via the SDK or API.

The implementation is in components/egress/pkg/policy/always_rules.go:

func MergeAlwaysOverlay(user *NetworkPolicy, alwaysDeny, alwaysAllow []EgressRule) *NetworkPolicy {
    // alwaysDeny is prepended so it matches first, achieving unconditional denial
    merged = append(merged, alwaysDeny...)
    merged = append(merged, alwaysAllow...)
    merged = append(merged, out.Egress...)
}

MergeAlwaysOverlay is called on every policy change (both initial startup and runtime updates), merging always-deny, always-allow, and user policies by priority. Since rules match in order, deny.always rules have the highest priority and cannot be overridden by users through the SDK or API.

Additionally, the deny.always file is automatically reloaded every minute, so updates take effect without restarting the container.

Configuration Steps

1. Determine Cluster Pod CIDR and Service CIDR

Obtain the Pod and Service CIDR ranges of the Kubernetes cluster:

# Service CIDR: usually set by kube-controller-manager's --service-cluster-ip-range
# Pod CIDR: usually set by the CNI plugin or kube-controller-manager's --pod-network-cidr

Common example values:

  • Pod CIDR: 10.244.0.0/16
  • Service CIDR: 10.96.0.0/12

2. Create the deny.always Rule File

10.244.0.0/16
10.96.0.0/12

Supported target types: IP addresses (e.g., 10.0.0.5), CIDR ranges (e.g., 10.0.0.0/8), or domain names (e.g., internal.service.local, with *. wildcard prefix support).

3. Build a Custom Image Based on the Official Egress Image

Create a Dockerfile that embeds the deny.always file into the image:

FROM opensandbox/egress:latest

COPY deny.always /var/egress/rules/deny.always

Build and push to a registry accessible by the cluster:

docker build -t registry.example.com/opensandbox/egress:hardened .
docker push registry.example.com/opensandbox/egress:hardened

4. Update the Server Configuration

In the OpenSandbox server configuration, point the [egress] image to the custom image:

[egress]
image = "registry.example.com/opensandbox/egress:hardened"
mode = "dns+nft"

After this configuration is applied, all newly created sandboxes will use the egress sidecar image that includes the deny.always rules. No changes to individual sandbox creation requests are needed.

Note: the deny.always file is hot-reloaded every minute. To change the rules (e.g., after a cluster CIDR change), update the deny.always file, rebuild the image, and perform a rolling update of the server configuration.

Effects

  • Pod IP blocked: sandboxes cannot directly reach other Pods in the same cluster via Pod IP.
  • Service ClusterIP blocked: sandboxes cannot reach in-cluster services via Service ClusterIP.
  • Forced access path: legitimate cross-sandbox communication must go through the GetEndpoint() API, which returns an external access endpoint proxied by OpenSandbox Ingress with authentication and authorization.
  • Transparent to users: users do not need to declare any additional parameters in SDK calls. Isolation is enforced automatically at the platform level.

Allowing Legitimate In-Cluster Services

When defaultAction: deny is enabled, allowing a sandbox to reach a Kubernetes Service requires both layers of the egress sidecar to agree:

  1. DNS layer: allow the service hostname so the DNS proxy does not return NXDOMAIN.
  2. Network layer (dns+nft mode): allow the Service CIDR (or a narrower ClusterIP range) so nftables does not drop the resolved TCP connection.

For example, if a sandbox should reach postgres.opensandbox.svc.cluster.local, this is not enough by itself:

{ "action": "allow", "target": "postgres.opensandbox.svc.cluster.local" }

The request can still fail after DNS resolution if the Service ClusterIP falls inside a denied CIDR such as 10.96.0.0/12. In dns+nft mode you must also allow the Service CIDR (or the specific ClusterIP range you want exposed):

{ "action": "allow", "target": "10.96.0.0/12" }
  • Platform-managed allowlist: if many sandboxes need the same internal services, keep the Service CIDR allowance in allow.always and let users request only the DNS names they should reach.
  • Per-sandbox allowlist: if access should be tightly scoped, include both the service FQDN and the required Service CIDR in the sandbox's network_policy / networkPolicy.
  • Avoid assuming CoreDNS exemptions are enough: the sidecar automatically allows nameserver IPs so DNS forwarding works, but it does not automatically allow arbitrary Service ClusterIPs for application traffic.

Approach 2: On-Demand Isolation via Per-Sandbox NetworkPolicy

If global enforced isolation is not desired, or if specific sandboxes need more permissive access, you can explicitly deny internal network access at sandbox creation time via the network_policy parameter:

from opensandbox import Sandbox, NetworkPolicy, EgressRule

sandbox = await Sandbox.create(
    image="python:3.11",
    network_policy=NetworkPolicy(
        default_action="deny",
        egress=[
            EgressRule(action="deny", target="10.244.0.0/16"),   # Deny Pod CIDR
            EgressRule(action="deny", target="10.96.0.0/12"),   # Deny Service CIDR
            EgressRule(action="allow", target="*.example.com"),  # Allow external domains
        ],
    ),
)

Limitations

  • Per-sandbox declaration: each sandbox must explicitly pass the network_policy parameter at creation. The platform cannot enforce it by default.
  • CIDR exposure: users need to know the cluster's Pod CIDR and Service CIDR. In multi-tenant scenarios, this is sensitive information that should not be exposed to users.
  • Overridable: users can declare higher-priority allow rules within the same network_policy to override their own deny rules, making this less secure than the deny.always enforcement in Approach 1.

Comparison

Dimension Global deny.always Per-Sandbox NetworkPolicy
Enforcement Yes (user cannot override) No (user can modify policy)
User awareness Transparent (platform-level) Requires explicit declaration
Operational cost Low (one-time image build, global effect) High (declare per sandbox)
Cluster CIDR exposure Not exposed to users Must be exposed to users
Use case Platform-wide default isolation, recommended Whitelist mode, fine-grained control

Runtime Compatibility

Both Approach 1 (deny.always via egress sidecar) and Approach 2 (per-sandbox network_policy) depend on the egress sidecar, which uses an iptables nat table REDIRECT rule for DNS interception. This works with runc (default) and all Kata Containers variants (kata-qemu, kata-clh, kata-fc), but not with gVisor — gVisor's netstack does not implement the nat table.

If you need both gVisor's syscall isolation and FQDN egress control:

  • Use kata-qemu instead — it provides comparable security isolation and supports the egress sidecar.
  • Alternatively, use a CNI-level FQDN policy (e.g., Cilium toFQDNs) for network isolation alongside gVisor.

The same architectural constraint applies to transparent service meshes such as Istio/Envoy sidecar injection: OpenSandbox egress expects to own outbound interception inside the pod network namespace. If a mesh sidecar also rewrites outbound traffic in that namespace, egress-sidecar features such as per-sandbox network policy, transparent MITM, and Credential Vault are not currently supported together. Prefer excluding sandbox pods from mesh injection or enforcing outbound policy at the platform network layer instead.

See the Compatibility Matrix in the Secure Container Runtime Guide for the full feature support table.

Recommendations

  1. Default full isolation: use deny.always to block the cluster's internal CIDR ranges as the platform's default security baseline.
  2. Whitelist with allow.always: for scenarios requiring cross-sandbox communication (e.g., an agent dispatching tasks to sandboxes), use the allow.always file (/var/egress/rules/allow.always) to open specific Pod IPs or Service DNS names. allow.always has higher priority than user policies but lower than deny.always, giving the platform precise control over the open scope.
  3. External access entry: the only way for a sandbox to expose services externally should be the GetEndpoint() API, proxied through OpenSandbox Ingress. Pod IPs should not be used as external service endpoints.