#Network Policy

Control network access for sandboxes. Network policies allow you to restrict outbound traffic, block specific domains, or allow only certain endpoints.

Network Modes#

ModeDescriptionUse Case
allow-allAllow all outbound trafficDefault, unrestricted access
block-allBlock all outbound trafficMaximum security, air-gapped environments

Egress Policy#

Egress fields are interpreted by the selected mode:

FieldTypeDescription
allowed_domainsarrayWhitelist of allowed domains. Used only in block-all mode.
denied_domainsarrayBlacklist of blocked domains. Used only in allow-all mode.
allowed_cidrsarrayWhitelist of allowed IP CIDRs. Used only in block-all mode.
denied_cidrsarrayBlacklist of blocked IP CIDRs. Used only in allow-all mode.
allowed_portsarrayWhitelist of allowed destination ports. Used only in block-all mode.
denied_portsarrayBlacklist of blocked destination ports. Used only in allow-all mode.

In allow-all mode, traffic is permitted by default and only denied* fields are enforced. In block-all mode, traffic is denied by default and only allowed* fields are enforced.

Egress Credential Injection#

Credential injection now has two layers:

  • credentials.bindings defines sandbox-scoped bindings that map a stable ref to a manager-managed credential source.
  • egress.rules matches destinations and points to one binding with credentialRef.

This lets netd call cluster-local egress-broker and inject outbound credentials for selected destinations without handing the original source material to the sandbox.

Credential Sources#

Credential sources are managed through the manager API:

  • GET /api/v1/credential-sources
  • POST /api/v1/credential-sources
  • GET /api/v1/credential-sources/{name}
  • PUT /api/v1/credential-sources/{name}
  • DELETE /api/v1/credential-sources/{name}

The current public source kind is static_headers. It stores named values that bindings can project into outbound headers.

FieldTypeDescription
namestringStable source name used by sourceRef.
resolverKindstringResolver kind. Current public value: static_headers.
spec.staticHeaders.valuesobjectNamed source values consumed by binding templates.

Sandbox Bindings And Egress Rules#

FieldTypeDescription
credentials.bindingsarraySandbox-scoped credential bindings.
credentials.bindings[].refstringStable binding reference matched by egress.rules[].credentialRef.
credentials.bindings[].sourceRefstringCredential source name resolved by manager.
credentials.bindings[].projection.typestringProjection type. Current public value: http_headers.
credentials.bindings[].projection.httpHeaders.headers[].namestringHeader name to inject.
credentials.bindings[].projection.httpHeaders.headers[].valueTemplatestringGo template rendered against source values, for example Bearer {{.token}}.
credentials.bindings[].cachePolicy.ttlstringOptional broker-side cache TTL override, such as 5m.
egress.rulesarrayDestination-scoped egress credential rules.
egress.rules[].credentialRefstringRequired binding reference resolved against credentials.bindings[].ref.
egress.rules[].rolloutstringOptional rollout control: enabled or disabled. Empty defaults to enabled.
egress.rules[].protocolstringOptional protocol hint: http, https, or grpc.
egress.rules[].tlsModestringTLS handling mode for HTTPS/gRPC: passthrough or terminate-reoriginate.
egress.rules[].failurePolicystringOptional enforcement policy: fail-closed or fail-open.
egress.rules[].domainsarrayDomain match list for the rule.
egress.rules[].portsarrayDestination port constraints for the rule.

Supported cases:

  • plain HTTP header injection
  • HTTPS when netd terminates downstream TLS and re-establishes upstream TLS
  • gRPC over TLS when netd proxies HTTP/2 correctly and injects metadata

Not supported or not transparent:

  • certificate pinning
  • runtimes that do not trust the injected cluster CA
  • legacy applications that must read a static key from disk or env

Typical flow:

  1. Create or update a credential source.
  2. Bind that source into the sandbox with credentials.bindings.
  3. Match outbound destinations with egress.rules.

Example credential source payload:

yaml
name: example-api resolverKind: static_headers spec: staticHeaders: values: token: secret-token

Example network policy payload:

yaml
mode: block-all credentials: bindings: - ref: example-api sourceRef: example-api projection: type: http_headers httpHeaders: headers: - name: Authorization valueTemplate: Bearer {{.token}} cachePolicy: ttl: 5m egress: allowedDomains: - api.example.com rules: - name: example-api credentialRef: example-api protocol: https tlsMode: terminate-reoriginate failurePolicy: fail-closed domains: - api.example.com ports: - port: 443 protocol: tcp

HTTPS and gRPC interception require cluster-level support in self-hosted deployments: egress-broker must be enabled, netd egress auth must be turned on, and netd needs a cluster-local MITM CA. infra-operator manages that CA by default, or you can override it with services.netd.mitmCaSecretName.


Get Network Policy#

Retrieve the current network policy for a sandbox.

GET

/api/v1/sandboxes/{id}/network

go
// Get current network policy policy, err := sandbox.GetNetworkPolicy(ctx) if err != nil { log.Fatal(err) } fmt.Printf("Mode: %s\n", policy.Mode) if egress, ok := policy.Egress.Get(); ok { fmt.Printf("Allowed domains: %v\n", egress.AllowedDomains) }

Update Network Policy#

Update the network policy for a sandbox.

PUT

/api/v1/sandboxes/{id}/network

Request Body#

FieldTypeDescription
modestringNetwork mode: allow-all or block-all
credentialsobjectSandbox credential bindings resolved by egress-broker (optional)
egressobjectEgress policy rules (optional)

Allow All Traffic#

Allow all outbound network access (default behavior).

go
// Allow all traffic _, err = sandbox.UpdateNetworkPolicy(ctx, apispec.TplSandboxNetworkPolicy{ Mode: apispec.TplSandboxNetworkPolicyModeAllowAll, }) if err != nil { log.Fatal(err) } fmt.Println("Network policy updated: allow-all")

Block All Traffic#

Block all outbound network access.

go
// Block all traffic _, err = sandbox.UpdateNetworkPolicy(ctx, apispec.TplSandboxNetworkPolicy{ Mode: apispec.TplSandboxNetworkPolicyModeBlockAll, }) if err != nil { log.Fatal(err) } fmt.Println("Network policy updated: block-all")

Allow Specific Domains#

Block all traffic except for specific allowed domains.

go
// Block all except specific domains _, err = sandbox.UpdateNetworkPolicy(ctx, apispec.TplSandboxNetworkPolicy{ Mode: apispec.TplSandboxNetworkPolicyModeBlockAll, Egress: apispec.NewOptNetworkEgressPolicy(apispec.NetworkEgressPolicy{ AllowedDomains: []string{"github.com", "pypi.org", "api.openai.com"}, }), }) if err != nil { log.Fatal(err) } fmt.Println("Network policy updated: only github.com, pypi.org, api.openai.com allowed")

Block Specific Domains#

Allow all traffic except for specific blocked domains.

go
// Block specific domains (allow all others) _, err = sandbox.UpdateNetworkPolicy(ctx, apispec.TplSandboxNetworkPolicy{ Mode: apispec.TplSandboxNetworkPolicyModeAllowAll, Egress: apispec.NewOptNetworkEgressPolicy(apispec.NetworkEgressPolicy{ DeniedDomains: []string{"facebook.com", "twitter.com"}, }), }) if err != nil { log.Fatal(err) } fmt.Println("Network policy updated: block facebook.com, twitter.com")

Set Network Policy at Creation#

Configure network policy when claiming a sandbox.

go
// Claim sandbox with network policy sandbox, err = client.ClaimSandbox(ctx, "default", sandbox0.WithSandboxHardTTL(600), sandbox0.WithSandboxNetworkPolicy(apispec.TplSandboxNetworkPolicy{ Mode: apispec.TplSandboxNetworkPolicyModeAllowAll, }), ) if err != nil { log.Fatal(err) }

Test Network Connectivity#

Verify network policy by making requests from the sandbox.

go
// Test network connectivity const shell = `/bin/curl -s -o /dev/null -w "%{http_code}\n" --max-time 3 https://github.com` resp, err := sandbox.Cmd(ctx, shell) if err != nil { log.Fatal(err) } fmt.Printf("GitHub response before blocking: %s\n", resp.OutputRaw) // Block all traffic _, err = sandbox.UpdateNetworkPolicy(ctx, apispec.TplSandboxNetworkPolicy{ Mode: apispec.TplSandboxNetworkPolicyModeBlockAll, }) if err != nil { log.Fatal(err) } // Test again (should fail) resp, err = sandbox.Cmd(ctx, shell) if err != nil { fmt.Println("Request blocked as expected") } fmt.Printf("GitHub response after blocking: %s\n", resp.OutputRaw)

Next Steps#

Port Exposure

Expose sandbox ports publicly

Webhooks

Receive event notifications

Volumes

Persistent storage for sandboxes