Skip to content

docs(oseps): OSEP-0021 Multi-Sandbox Egress Control Plane - #1592

Open
Pangjiping wants to merge 8 commits into
opensandbox-group:mainfrom
Pangjiping:osep-0021-multi-sandbox-egress-control-plane
Open

docs(oseps): OSEP-0021 Multi-Sandbox Egress Control Plane#1592
Pangjiping wants to merge 8 commits into
opensandbox-group:mainfrom
Pangjiping:osep-0021-multi-sandbox-egress-control-plane

Conversation

@Pangjiping

Copy link
Copy Markdown
Collaborator

OSEP-0021: Multi-Sandbox Egress Control Plane

Related RFC: #1582

Summary

Introduces a Subject abstraction so a single egress control plane can serve N sandboxes sharing one host/network domain (fast-sandbox Fastlet Pod, bwrap sessions). The existing single-sandbox sidecar profile is unchanged; pod profile observes fastlet's existing network state store for identity, receives policies over fast-sandbox's own proxy-route mechanism, and loads credentials from Secret volumes (memory-only). A subject is deny-first from observation until its policy lands, so create-then-configure can never be fail-open.

Key design points

  • Three control paths (no socket, no egress-managed state): proxy route (policy, route-credential + UID header), slot store (identity, read-only), Secret volume (credentials)
  • Zero fast-sandbox API impact: CRD/RPC/fastlet process untouched; four internal additions (host delivery mode, proxy host upstream, UID propagation header, host-component route publication) + one deployment config
  • Fail-closed lifecycle: denying -> active state machine, atomic policy swaps, fencing-based reset protection
  • Scaling: point-to-point policy push (no watch storm / etcd amplification), 64 subjects/Pod target

Reviewer focus

  • Security boundaries (loopback-only listener, UID-header trust chain)
  • The four fast-sandbox internal additions (verified against source)
  • Create-then-configure semantics and race handling

Subject abstraction for a single egress control plane serving N sandboxes
sharing one host/network domain. pod profile observes fastlet's network
state store for identity, receives policies over fast-sandbox's proxy
route, and loads credentials from Secret volumes; deny-first lifecycle
keeps create-then-configure fail-closed. Zero fast-sandbox API impact.
@github-actions github-actions Bot added documentation Improvements or additions to documentation size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Aug 21, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 748d694960

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

SRV->>C: CreateSandbox (no policy fields)
F->>S: slot bound (slot.json written — existing logic)
Note over E: fsnotify: bound slot
E->>E: Register subject: deny-first rules, resolv.conf → gateway

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Block traffic before publishing a bound slot

In the creation sequence, the sandbox's slot becomes bound before the fsnotify handler installs its deny rules. Because the proposal also leaves Fastlet admission and lifecycle phases untouched, traffic can traverse the Pod network during the interval between lines 134 and 136, so the claimed create-then-configure fail-closed guarantee does not hold. Install a permanent default-deny rule for unregistered sandbox sources before slots can be bound, or gate network activation on egress registration.

AGENTS.md reference: AGENTS.md:L117-L120

Useful? React with 👍 / 👎.

Comment on lines +142 to +143
SRV->>SEC: create Secret (credentials)
SEC->>E: kubelet sync → load to memory

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use a Secret source that the running Pod can mount

Fastlet Pods are pre-created, but creating a per-sandbox Secret after slot allocation does not add that Secret to an already-running Pod's immutable volume sources. The proposal neither predeclares the Secret nor defines an aggregate Secret that is created with the Pod and subsequently updated, so SEC->>E cannot occur for dynamically created sandboxes and credential-proxy initialization will stall. Specify a mountable pre-provisioned source or another dynamic delivery mechanism.

AGENTS.md reference: AGENTS.md:L44-L46

Useful? React with 👍 / 👎.

Comment on lines +163 to +165
else Credential update
U->>SEC: update Secret
SEC->>E: kubelet sync → inotify

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep credential updates on the documented proxy API

When a user invokes the existing /credential-vault POST/PATCH API, the SDK only has the resolved egress endpoint and route headers; it has neither Kubernetes credentials nor a Secret identity to perform U->>SEC. This also contradicts the earlier control-path table, which says the proxy route carries runtime vault operations. As written, existing SDK credential updates cannot follow this sequence, so either the proxy endpoint must continue accepting and persisting those mutations or the public API and SDKs must change together.

AGENTS.md reference: AGENTS.md:L42-L47

Useful? React with 👍 / 👎.

- **Update** on policy push (proxy route): DNS policy swap + one atomic nft batch (delete+add in a single `nft -f` transaction). Credential updates via Secret volume refresh (inotify), memory-only.
- **Unload** on slot-file deletion (fastlet's existing release path): detach → deny → free.
- **Race handling**: a push for an unknown UID is cached as pending (with TTL) until the slot appears; both sides idempotent. Fencing mismatch (same UID, new generation) discards old state — a reset can never carry old policy into a new sandbox.
- **Recovery**: egress restart → rescan slot store, every live subject re-enters `denying`; server reconciliation re-pushes policies. No platform replay, no fastlet involvement.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Persist policy for restart reconciliation

The recovery path requires the server to re-push each live subject's policy, but the design sends no policy to FastPath, introduces no cluster-side carrier, and specifies no durable server store. If the server restarts before or with egress, it can enumerate sandboxes but cannot reconstruct their policies, leaving every recovered subject permanently in denying. Define where the policy is durably stored and how reconciliation retrieves it.

AGENTS.md reference: AGENTS.md:L117-L121

Useful? React with 👍 / 👎.

- **Register** on observing a bound slot (identity + fencing from `Owner`); **denying** state installs deny-first rules immediately (nft sets empty, resolv.conf → gateway, REDIRECT + forward rules) — the subject is fully blocked until policy lands.
- **Update** on policy push (proxy route): DNS policy swap + one atomic nft batch (delete+add in a single `nft -f` transaction). Credential updates via Secret volume refresh (inotify), memory-only.
- **Unload** on slot-file deletion (fastlet's existing release path): detach → deny → free.
- **Race handling**: a push for an unknown UID is cached as pending (with TTL) until the slot appears; both sides idempotent. Fencing mismatch (same UID, new generation) discards old state — a reset can never carry old policy into a new sandbox.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Carry the slot generation on every policy push

Policy requests carry only X-Fast-Sandbox-Uid, while fencing information arrives independently from the slot store. Therefore, when the same UID is rebound with a new generation, egress cannot distinguish a delayed or pending old-generation push from a policy intended for the new subject, so it cannot implement the stated stale-policy discard guarantee. Include the generation in an authenticated proxy-supplied field and key pending updates by UID plus generation.

AGENTS.md reference: AGENTS.md:L117-L120

Useful? React with 👍 / 👎.

E->>E: Register subject: deny-first rules, resolv.conf → gateway
SRV->>C: ResolveEndpoint(component="egress")
C-->>SRV: route + route credential
SRV->>P: PUT /v2/sandboxes/{uid}/components/egress/policy

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Define the initial policy verb in the public contract

The server initialization sequence depends on PUT /policy, but specs/egress-api.yaml defines only GET, PATCH, and DELETE for /policy. Since PATCH accepts only a rule array and cannot transmit the complete creation policy including defaultAction, the documented contract provides no equivalent operation. Relying on the implementation-only PUT while declaring the spec unchanged creates contract drift; add the replace operation to the source-of-truth spec or define a separate internal initialization endpoint.

AGENTS.md reference: AGENTS.md:L42-L48

Useful? React with 👍 / 👎.

…s access

Replace the v2 component-route prefix with a dedicated sandboxfleets
route handled by fastlet-proxy (parseTarget branch, host upstream,
UID header); independent of the component Components map.
…ow protection

- Security boundary: policy operations stay usable while a subject is
  denying (management plane never traverses sandbox traffic paths)
- Creation window: OpenSandbox runtime driver probes egress healthz
  inside EnsureSandbox before container creation; existing drivers
  untouched
… Secret volume

Consistent with OSEP-0012: complete vault revisions are pushed by the
server over /v1/sandboxfleets/{sandboxId}/egress/credential-vault and
held memory-only in egress. Drops the Kubernetes Secret/kubelet sync
dependency (and the Secret volume from the egress container config).
Verified against containerd driver: slot store, pre-provisioning order,
no NET_ADMIN, resolv.conf mount, route credentials, FastletTemplate all
already present. NET_RAW is not dropped (runc default) - UDP spoofing
mitigated by iifname binding or a preferred additive NET_RAW drop in
the runtime driver.
…uestion

Egress consumes nine slot fields (identity, fencing, dispatch, netns,
veth, gateway, cidr, resolv path). Current source is fastlet's internal
file store - not a public contract. Records two stabilization options
(file contract vs kubelet-style read-only endpoint on fastlet's HTTP
RPC server) as an open question for fast-sandbox.
@Pangjiping

Copy link
Copy Markdown
Collaborator Author

关联:fast-sandbox#10(slot 数据稳定化契约归属的开放问题,待 fast-sandbox 表态)

…mantics

- TOC: Three -> Two Control Paths
- Security boundary: NET_RAW drop belongs to the new OpenSandbox driver
  (existing sandboxes keep runc defaults); iifname is defense in depth
- Sequence: add controller->fastlet create call
- Route parsing: credential target semantics must match ResolveEndpoint
- Creation window: healthz guarantees egress liveness; a fully
  deterministic guarantee needs subject-registered confirmation (recorded)
- bwrap endpoint: mark TBD instead of n/a
Semicolon in a message label breaks mermaid parsing; replaced with a comma.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant