Clarify network policy and worktree scoping in architecture docs

This commit is contained in:
2026-05-23 23:59:06 +00:00
parent b4f30e9479
commit bdb4bd6174

View File

@@ -32,7 +32,7 @@ The worktree is prepared in two distinct container runs:
1. **Checkout container** — mounts the base worktree volume as writable. User credentials are injected here. Runs `git clone` / `git checkout` for the target revision, then exits. Keeps git operations and credential handling sandboxed away from the host. 1. **Checkout container** — mounts the base worktree volume as writable. User credentials are injected here. Runs `git clone` / `git checkout` for the target revision, then exits. Keeps git operations and credential handling sandboxed away from the host.
2. **Task container** — mounts base worktree read-only (overlayfs lower) and mutations volume writable (overlayfs upper). Runs the actual CI job. No credentials present. 2. **Task container** — mounts base worktree read-only (overlayfs lower) and mutations volume writable (overlayfs upper). Runs the actual CI job. Secrets are mounted only if explicitly declared in the task configuration.
The base worktree volume is writable only during step 1. For all subsequent runs it is mounted `:ro` — the kernel enforces this. Any writes from the task container go to the mutations volume via overlayfs and cannot affect the base worktree. The base worktree volume is writable only during step 1. For all subsequent runs it is mounted `:ro` — the kernel enforces this. Any writes from the task container go to the mutations volume via overlayfs and cannot affect the base worktree.
@@ -92,11 +92,9 @@ The private key is mounted into the checkout container for the duration of the c
--- ---
#### Access control and worktree scoping #### Worktree scoping
Base worktrees are scoped to `(owner, repo, revision)` — not just `(repo, revision)`. Sharing across owners would allow one user's job to read a repo checked out under another user's credentials. Even for public repos this special case is not worth the complexity. Base worktree volumes are scoped per owner. Access control is handled entirely by Gitea via SSH deploy keys — the checkout container only has the key for the specific repo it is cloning, so Gitea enforces what it can and cannot access. The per-owner scoping is about cache ownership and lifecycle: each owner manages and cleans up their own volumes independently.
Checkout is performed using credentials of the job owner (or a member of their org). If access is revoked, the owner's base worktree cache is invalidated and future runs re-checkout with fresh credential verification. Runs already in progress are unaffected — mid-run revocation is accepted as a limitation.
#### Cleanup #### Cleanup
@@ -207,14 +205,13 @@ Individual tools may need additional flags (e.g. `--color=always` for git, cargo
### Network isolation ### Network isolation
Runners need outbound WAN access (to fetch dependencies) but must not be able to reach internal LAN or host-local services. Two approaches under consideration: WAN access is both expected and fine for most tasks — containers need to fetch dependencies, push to registries, etc. The specific concern is containers reaching the host's localhost, which could expose internal services as an unintended back channel.
1. **Subnet filtering** — firewall rules block RFC-1918 ranges for container network namespaces while allowing WAN. The default network policy is therefore: WAN allowed, host localhost blocked. Network policy is configurable per task — tasks that need no network at all (e.g. the rsync publish experiment) can use `network_mode: none`.
2. **Custom egress VPS** — route all runner traffic through a separate VPS with egress filtering, reusable across services.
For tasks that only operate on local mounts (e.g. the rsync publish experiment), `network_mode: none` is used — no network at all. How to enforce the host localhost restriction is still open — likely a firewall rule on the Docker bridge interface blocking access to `127.0.0.1` from container network namespaces.
**Status: open. Approach 2 is preferred for reusability but adds infrastructure complexity.** **Status: default policy decided. Enforcement mechanism is open.**
--- ---
@@ -239,6 +236,6 @@ Builds targeting non-Linux platforms (Windows, macOS) would require QEMU or sepa
| Layered caches | Whether caches can use the same overlay approach as worktrees | | Layered caches | Whether caches can use the same overlay approach as worktrees |
| Deployment permissions | Which tasks are allowed to write which bind-mounted targets | | Deployment permissions | Which tasks are allowed to write which bind-mounted targets |
| SSH key registration | Automate public key registration via Gitea API (`POST /repos/{owner}/{repo}/keys`) — requires a Gitea user token with repo admin permissions, which is a separate credential. Only worth pursuing if the CI system already holds a user token for other reasons (status checks, repo metadata etc.). | | SSH key registration | Automate public key registration via Gitea API (`POST /repos/{owner}/{repo}/keys`) — requires a Gitea user token with repo admin permissions, which is a separate credential. Only worth pursuing if the CI system already holds a user token for other reasons (status checks, repo metadata etc.). |
| Network isolation | Subnet filtering vs egress VPS | | Network isolation | How to block host localhost from container network namespaces |
| Progress reporting | In-container callback channel (socket/HTTP) for async mid-run status | | Progress reporting | In-container callback channel (socket/HTTP) for async mid-run status |
| Multi-stage output passing | Exact format/protocol for stage-to-stage data handoff | | Multi-stage output passing | Exact format/protocol for stage-to-stage data handoff |