Skip to content

Network Egress Rules

Sortie supports per-application network egress policies to control what network destinations session pods can reach. This provides defense-in-depth by restricting outbound connections from containerized sessions.

Overview

Each application can have an egress policy that defines what outbound network traffic is allowed from its session pods. Two modes are supported:

  • Allowlist (recommended): Only explicitly listed destinations are permitted. All other egress traffic is blocked (except DNS).
  • Denylist: All egress traffic is permitted except explicitly listed destinations.

When no egress policy is configured, sessions inherit the cluster-level default NetworkPolicy (which typically allows DNS + HTTP/HTTPS to any destination).

Configuration

Egress policies are configured per-application via the API when creating or updating an application.

Data Model

json
{
  "egress_policy": {
    "mode": "allowlist",
    "rules": [
      {
        "cidr": "10.0.0.0/8",
        "port": 443,
        "protocol": "TCP"
      },
      {
        "cidr": "0.0.0.0/0",
        "port": 80
      }
    ]
  }
}

Fields

FieldTypeDescription
modestring"allowlist" or "denylist". Empty = inherit cluster default.
rulesarrayList of egress rules.
rules[].cidrstringDestination CIDR (e.g., "10.0.0.0/8", "0.0.0.0/0"). Required.
rules[].portintDestination port. 0 or omitted = all ports.
rules[].protocolstring"TCP", "UDP", or empty for both.

API Examples

Create an app with allowlist egress (only HTTPS to specific subnets):

bash
curl -X POST /api/apps \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "id": "secure-browser",
    "name": "Secure Browser",
    "category": "Tools",
    "visibility": "approved",
    "launch_type": "container",
    "container_image": "ghcr.io/example/browser:latest",
    "egress_policy": {
      "mode": "allowlist",
      "rules": [
        {"cidr": "10.0.0.0/8", "port": 443, "protocol": "TCP"},
        {"cidr": "10.0.0.0/8", "port": 80, "protocol": "TCP"}
      ]
    }
  }'

Create an app with denylist egress (block internal networks):

bash
curl -X POST /api/apps \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "id": "dev-workstation",
    "name": "Dev Workstation",
    "category": "Development",
    "visibility": "admin_only",
    "launch_type": "container",
    "container_image": "ghcr.io/example/devbox:latest",
    "egress_policy": {
      "mode": "denylist",
      "rules": [
        {"cidr": "10.0.0.0/8"},
        {"cidr": "172.16.0.0/12"},
        {"cidr": "192.168.0.0/16"}
      ]
    }
  }'

App with no egress policy (inherits cluster default):

bash
curl -X POST /api/apps \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "id": "basic-app",
    "name": "Basic App",
    "category": "General",
    "visibility": "public",
    "launch_type": "container",
    "container_image": "ghcr.io/example/app:latest"
  }'

Enforcement

Egress rules are enforced using Kubernetes NetworkPolicies. When a session is created for an app with an egress policy:

  1. A pod is created for the session (as usual).
  2. A per-session NetworkPolicy named sortie-egress-{session-id} is created.
  3. The policy targets only the session pod via label selectors.
  4. When the session is terminated, the NetworkPolicy is automatically cleaned up.

Allowlist Mode

Creates a NetworkPolicy that permits:

  • DNS traffic (UDP/TCP port 53) — always allowed
  • Each rule becomes an explicit egress allow entry

All other egress traffic is implicitly denied by the NetworkPolicy.

Denylist Mode

Creates a NetworkPolicy that permits:

  • DNS traffic (UDP/TCP port 53) — always allowed
  • All traffic to 0.0.0.0/0 except the listed CIDRs (using ipBlock.except)

Note: Kubernetes ipBlock.except works at the CIDR level only. Port-level denylist filtering is not supported by K8s NetworkPolicies. For port-level control, use allowlist mode.

Prerequisites

CNI Plugin

Your Kubernetes cluster must have a CNI plugin that supports NetworkPolicies. Common options:

  • Calico (recommended)
  • Cilium
  • Weave Net

Clusters using basic kubenet or flannel (without Calico) do not enforce NetworkPolicies — the policies will be created but have no effect.

RBAC

The Sortie service account needs permission to manage NetworkPolicies. This is included in the Helm chart and deploy manifests:

yaml
- apiGroups: ["networking.k8s.io"]
  resources: ["networkpolicies"]
  verbs: ["create", "delete", "get", "list"]

Cluster-Level Defaults

The Helm chart includes a default NetworkPolicy for session pods that allows:

  • Ingress: Only from the Sortie server on VNC/RDP/proxy ports
  • Egress: DNS + HTTP/HTTPS to any destination

Per-app egress policies create additional NetworkPolicies. Since Kubernetes NetworkPolicies are additive (union), the per-app policy will be the effective policy when it is more restrictive than the cluster default. For maximum control, configure the cluster-level session egress to be restrictive (e.g., DNS only) and use per-app allowlists to grant access.

Limitations

  • Denylist port filtering: K8s NetworkPolicies cannot deny specific ports while allowing others. Use allowlist mode for port-level control.
  • Domain-based rules: K8s NetworkPolicies work with IP CIDRs, not domain names. Use a network proxy or service mesh for domain-based filtering.
  • IPv6: Currently only IPv4 CIDRs are supported in egress rules.
  • CNI dependency: Enforcement requires a NetworkPolicy-capable CNI plugin.