Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- **`SecurityOpt` SELinux and system-paths directives are now policy-evaluable.** Three opt-in `request_body.container_create` knobs (all default off β€” zero behavior change): `deny_selinux_disable` denies `label=disable` and the legacy `label:disable` colon form (which turn off SELinux confinement); `deny_selinux_label_override` denies `label=user:`/`role:`/`type:`/`level:` SELinux context customization; `deny_unconfined_system_paths` denies `systempaths=unconfined` **and** requests that set `MaskedPaths`/`ReadonlyPaths` to an explicit empty array β€” the Docker CLI translates `--security-opt systempaths=unconfined` into `MaskedPaths: []` client-side, so direct API callers could otherwise clear the masked-path protections without ever sending the SecurityOpt string. Both vectors are covered.
- **Swarm services gained seccomp/AppArmor confinement-mode rails**, completing `ContainerSpec.Privileges` parity with container-create. Three opt-in `request_body.service` knobs (all default off): `deny_unconfined_seccomp` (denies `Privileges.Seccomp.Mode: "unconfined"`), `deny_custom_seccomp_profiles` (denies `Mode: "custom"`, and fail-closed denies a `Seccomp` object carrying a `Profile` blob with no `Mode` β€” an inline profile the proxy cannot vet can encode an allow-everything policy), and `deny_unconfined_apparmor` (denies `Privileges.AppArmor.Mode: "disabled"`, swarm's equivalent of unconfined).
- **Three bundled presets for the lookout Docker agent and drydock self-update (12 β†’ 15 presets).** `lookout.yaml` covers container lifecycle, image pull/remove, `GET /containers/*/logs` streaming, and event/network/volume/Swarm-service reads with exec denied; `lookout-with-exec.yaml` adds interactive exec (`/containers/*/exec`, `/exec/*/start`, `/exec/*/resize`, `/exec/*/json`). Both disable response redaction so container inspect data forwards intact through the tri-tool topology (sockguard β†’ lookout β†’ drydock) and set `insecure_allow_read_exfiltration: true` for the logs path. `drydock-with-selfupdate.yaml` extends the drydock preset with the exec paths drydock's self-update finalize callback needs, pinned to the finalize entrypoint argv via `allowed_commands`. A ready-to-run `examples/compose/lookout/` stack ships alongside.
- **Three bundled presets for the Portwing Docker agent and drydock self-update (12 β†’ 15 presets).** `portwing.yaml` covers container lifecycle, image pull/remove, `GET /containers/*/logs` streaming, and event/network/volume/Swarm-service reads with exec denied; `portwing-with-exec.yaml` adds interactive exec (`/containers/*/exec`, `/exec/*/start`, `/exec/*/resize`, `/exec/*/json`). Both Portwing presets disable response redaction so container inspect data forwards intact through the tri-tool topology (sockguard β†’ Portwing β†’ drydock) and set `insecure_allow_read_exfiltration: true` for the logs path. `drydock-with-selfupdate.yaml` extends the drydock preset with the exec paths drydock's self-update finalize callback needs, pinned to the finalize entrypoint argv via `allowed_commands`. A ready-to-run `examples/compose/portwing/` stack ships alongside.

### Fixed

Expand All @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

- **The rate-limit token bucket hot path is now allocation-free.** Bucket state (token count + refill timestamp) packs into a single `atomic.Uint64` (16.16 fixed-point tokens, millisecond timestamp), eliminating the per-admission heap allocation: the hot-path benchmark went from 1 alloc/16 B to 0 allocs/0 B per op (~47 β†’ ~36 ns/op). Two consequences: `limits.rate.burst` now has a validated upper bound of 65535 (configs above it are rejected at startup with a descriptive error; `tokens_per_second` is implicitly bounded the same way since burst β‰₯ tps), and refill granularity is milliseconds rather than nanoseconds β€” negligible for every supported rate.
- **Dependency refresh.** Bumped `sigstore/sigstore-go` v1.2.0 β†’ v1.2.1 (image-trust path; no behavior change) and refreshed the docs/website toolchain β€” Biome 2.4.16 β†’ 2.5.0, Tailwind 4.3.0 β†’ 4.3.1 (docs now in lockstep with website), fumadocs 16.10.0 β†’ 16.10.3, lucide-react 1.17 β†’ 1.18, `@radix-ui/react-slot` 1.2 β†’ 1.3. Lockfile regenerated from scratch so the existing `postcss` override resolves cleanly (`npm audit` reports 0 vulnerabilities).

## [1.3.0] - 2026-06-11

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ To run fully unprivileged with a unix socket, pre-create a host directory with t
- **v1.2.0 shipped on 2026-06-02** β€” operational resilience for a wedged daemon. An opt-in **readiness probe** (`health.readiness.*`, default `/ready`) issues a real `GET /containers/json` against the Docker API and returns `503` when the daemon accepts connections but no longer answers β€” the gap the raw-dial `/health` watchdog misses. An opt-in **`upstream.request_timeout`** bounds finite proxied requests with a total deadline, converting a hung body or heavy read into a fast `504` (`reason_code=upstream_request_timeout`) while exempting streaming and long-lived endpoints. New metrics `sockguard_upstream_api_up` + `sockguard_upstream_readiness_checks_total` mirror the watchdog. The bundled **drydock preset** now allowlists the stock `runc` runtime so drydock recreation stops getting 403'd out of the box. Dependency hygiene: the Go toolchain moves to `1.26.4` (clearing two *reachable* stdlib advisories, GO-2026-5037 / GO-2026-5039), plus the `go-minor` / `npm-minor` / `actions-minor` groups; `govulncheck` reports zero vulnerabilities.
- **v1.1.0 shipped on 2026-06-01** β€” image-trust verification wired end to end: registry digest resolution, cosign signature discovery (classic tag + OCI 1.1 referrers), digest-pinned forwarding, keyed (PEM) and keyless (Fulcio + Rekor) both enforced, swarm-service create/update now subject to the same image-trust policy as container create. A 21-finding security audit landed alongside: closed request-inspection bypasses (plugin multipart, BuildKit `# syntax=`, gzip bombs, swarm-service capability/sysctl/image-trust escapes), read-side sub-resource visibility gating, new `allowed_runtimes` allowlist, hardened config/admin paths (signed-bundle TOCTOU, PID-only peer rejection, admin-listener CIDR backstop), response redaction extended to `HostConfig.Mounts[].Source` and service `PreviousSpec`. CodeQL `actions` analysis and supply-chain dependency hygiene (`govulncheck` reports zero vulnerabilities) round out the release.
- **v1.0.0 shipped on 2026-05-20** with the public proxy contract locked: YAML schema, CLI flags, env vars, admin endpoints, and Prometheus metric names are now under the v1.x compatibility promise.
- **15 bundled presets** cover drydock, Traefik, Portainer, Watchtower, Homepage, Homarr, Diun, Autoheal, read-only, CIS Docker Benchmark, GitHub Actions self-hosted runner, GitLab Runner, lookout, lookout with exec, and drydock with self-update.
- **15 bundled presets** cover drydock, Traefik, Portainer, Watchtower, Homepage, Homarr, Diun, Autoheal, read-only, CIS Docker Benchmark, GitHub Actions self-hosted runner, GitLab Runner, Portwing, Portwing with exec, and drydock with self-update.
- **Expanded QA hardening** added proxy-vs-daemon differential tests, real-dockerd preset conformance, fuzz corpora for routing and visibility, weekly soak testing, and TLS edge-case coverage.
- **Supply-chain verification** covers release images across GHCR, Docker Hub, and Quay.io using the same cosign commands documented for operators.

Expand All @@ -241,7 +241,7 @@ Most existing socket proxies stop at method/path or regex filtering. Tecnativa a
|---|---|---|
| πŸ›‘οΈ | **Default-Deny Posture** | Everything blocked unless explicitly allowed. No match means deny. |
| πŸŽ›οΈ | **Granular Control** | Allow start/stop while blocking create/exec. Per-operation POST controls with glob matching. |
| πŸ“‹ | **YAML Configuration** | Declarative rules, glob path patterns, first-match-wins evaluation, and canonical path matching that strips API versions, collapses dot segments, and decodes escaped separators before policy evaluation. 15 bundled workload presets (including CIS Docker Benchmark, self-hosted GitHub Actions runners, GitLab Runner, and lookout) plus the default config. |
| πŸ“‹ | **YAML Configuration** | Declarative rules, glob path patterns, first-match-wins evaluation, and canonical path matching that strips API versions, collapses dot segments, and decodes escaped separators before policy evaluation. 15 bundled workload presets (including CIS Docker Benchmark, self-hosted GitHub Actions runners, GitLab Runner, and Portwing) plus the default config. |
| πŸ“Š | **Structured Access Logging** | JSON access logs with method, raw path, normalized path, decision, matched rule, latency, canonical request ID, W3C `traceparent` correlation fields, and client info. Use `normalized_path` for SIEM correlation and policy analysis; raw `path` is preserved for forensic replay. Canonical request IDs are generated from a buffered pool so request logging does not block on a fresh entropy read per request. |
| πŸ” | **mTLS for Remote TCP** | Non-loopback TCP listeners require mutual TLS by default. Plaintext TCP is explicit legacy mode only. |
| 🌐 | **Client ACL Primitives** | Optional source-CIDR admission checks, client-container label ACLs, listener certificate selectors (CN/DNS/IP/URI SAN/SPKI), profile certificate selectors (CN/DNS/IP/URI/SPIFFE/SPKI), and unix peer credentials let one proxy differentiate callers before the global rule set runs. When mTLS is enabled, certificate selectors follow the verified client leaf certificate rather than an unverified peer slice entry. |
Expand Down Expand Up @@ -269,11 +269,11 @@ Most existing socket proxies stop at method/path or regex filtering. Tecnativa a

### Bundled presets (15)

[drydock](app/configs/drydock.yaml) Β· [drydock with self-update](app/configs/drydock-with-selfupdate.yaml) Β· [lookout](app/configs/lookout.yaml) Β· [lookout with exec](app/configs/lookout-with-exec.yaml) Β· [Traefik](app/configs/traefik.yaml) Β· [Portainer](app/configs/portainer.yaml) Β· [Watchtower](app/configs/watchtower.yaml) Β· [Homepage](app/configs/homepage.yaml) Β· [Homarr](app/configs/homarr.yaml) Β· [Diun](app/configs/diun.yaml) Β· [Autoheal](app/configs/autoheal.yaml) Β· [read-only](app/configs/readonly.yaml) Β· [CIS Docker Benchmark](app/configs/cis-docker-benchmark.yaml) Β· [GitHub Actions self-hosted runner](app/configs/github-actions-runner.yaml) Β· [GitLab Runner](app/configs/gitlab-runner.yaml)
[drydock](app/configs/drydock.yaml) Β· [drydock with self-update](app/configs/drydock-with-selfupdate.yaml) Β· [Portwing](app/configs/portwing.yaml) Β· [Portwing with exec](app/configs/portwing-with-exec.yaml) Β· [Traefik](app/configs/traefik.yaml) Β· [Portainer](app/configs/portainer.yaml) Β· [Watchtower](app/configs/watchtower.yaml) Β· [Homepage](app/configs/homepage.yaml) Β· [Homarr](app/configs/homarr.yaml) Β· [Diun](app/configs/diun.yaml) Β· [Autoheal](app/configs/autoheal.yaml) Β· [read-only](app/configs/readonly.yaml) Β· [CIS Docker Benchmark](app/configs/cis-docker-benchmark.yaml) Β· [GitHub Actions self-hosted runner](app/configs/github-actions-runner.yaml) Β· [GitLab Runner](app/configs/gitlab-runner.yaml)

### Ready-to-run compose examples

[drydock](examples/compose/drydock/) Β· [lookout](examples/compose/lookout/) Β· [Traefik](examples/compose/traefik/) Β· [Portainer](examples/compose/portainer/) Β· [Watchtower](examples/compose/watchtower/) Β· [GitHub Actions self-hosted runner](examples/compose/github-actions-runner/) Β· [GitLab Runner](examples/compose/gitlab-runner/) Β· [CIS Docker Benchmark gate](examples/compose/cis-docker-benchmark/)
[drydock](examples/compose/drydock/) Β· [Portwing](examples/compose/portwing/) Β· [Traefik](examples/compose/traefik/) Β· [Portainer](examples/compose/portainer/) Β· [Watchtower](examples/compose/watchtower/) Β· [GitHub Actions self-hosted runner](examples/compose/github-actions-runner/) Β· [GitLab Runner](examples/compose/gitlab-runner/) Β· [CIS Docker Benchmark gate](examples/compose/cis-docker-benchmark/)

Each example pairs a downstream Docker API consumer with a `sockguard.yaml` overlay and a short README covering audience, exposed API surface, and security tradeoffs.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# Sockguard β€” Lookout with Exec Preset
# Sockguard β€” Portwing with Exec Preset
#
# Extends the lookout preset with exec support for interactive terminal access
# through the lookout agent (e.g. terminal-over-websocket or drydock-driven
# exec sessions in lookout's edge mode).
# Extends the portwing preset with exec support for interactive terminal access
# through the Portwing agent (e.g. terminal-over-websocket or drydock-driven
# exec sessions in Portwing's edge mode).
#
# Allows: everything in lookout.yaml plus:
# Allows: everything in portwing.yaml plus:
# POST /containers/{id}/exec β€” create an exec instance
# POST /exec/{id}/start β€” start the exec (raw HTTP/1.1 upgrade)
# POST /exec/{id}/resize β€” resize the PTY
# GET /exec/{id}/json β€” inspect exec state and exit code
#
# Exec body inspection: allow_privileged is disabled. allow_root_user is
# enabled because most container workloads run as root and the caller
# (lookout or drydock) needs unrestricted exec for interactive sessions.
# (Portwing or drydock) needs unrestricted exec for interactive sessions.
# If your deployment enforces non-root containers, set allow_root_user: false
# and optionally add allowed_commands to pin the permitted argv prefixes.
#
Expand All @@ -26,15 +26,15 @@
# allow_root_user with an allowed_commands list (see drydock-with-selfupdate.yaml
# for the pinned-command pattern) and drop insecure_allow_body_blind_writes.
#
# Use this preset instead of lookout.yaml when:
# - Lookout's terminal/exec feature is in use
# - Drydock drives exec calls through the lookout edge mode
# - You need exec for container debugging via the lookout UI
# Use this preset instead of portwing.yaml when:
# - Portwing's terminal/exec feature is in use
# - Drydock drives exec calls through the Portwing edge mode
# - You need exec for container debugging via the Portwing UI
#
# Security note: exec grants arbitrary command execution inside any container
# that lookout can reach. Keep the proxy socket access-controlled (unix peer
# credentials or client CIDR allowlist) so only the lookout process can call
# the exec paths. See lookout.yaml for the exec-disabled baseline.
# that Portwing can reach. Keep the proxy socket access-controlled (unix peer
# credentials or client CIDR allowlist) so only the Portwing process can call
# the exec paths. See portwing.yaml for the exec-disabled baseline.

upstream:
socket: /var/run/docker.sock
Expand All @@ -49,7 +49,7 @@ health:
path: /health

# Response redaction β€” all three disabled for the drydock passthrough topology.
# See lookout.yaml header for standalone-mode guidance.
# See portwing.yaml header for standalone-mode guidance.
response:
redact_mount_paths: false
redact_container_env: false
Expand All @@ -61,7 +61,7 @@ response:
insecure_allow_body_blind_writes: true
insecure_allow_read_exfiltration: true

# Container-create and image-pull inspection mirrors lookout.yaml.
# Container-create and image-pull inspection mirrors portwing.yaml.
# Exec body inspection: allow_privileged denied, root user allowed for
# interactive sessions. To pin allowed commands, replace allow_root_user
# with an allowed_commands list (argv prefix allowlist).
Expand Down Expand Up @@ -90,9 +90,9 @@ rules:
action: allow

# Container reads β€” list, inspect, stats, top, changes, and logs.
# /containers/*/logs is required by lookout's GetContainerLogs().
# /containers/*/logs is required by Portwing's GetContainerLogs().
# /containers/*/archive, /containers/*/export, and /containers/*/attach
# are intentionally omitted β€” bulk-data exfiltration paths lookout does not use.
# are intentionally omitted β€” bulk-data exfiltration paths Portwing does not use.
- match: { method: GET, path: "/containers/json" }
action: allow
- match: { method: GET, path: "/containers/*/json" }
Expand Down Expand Up @@ -178,4 +178,4 @@ rules:
# Deny everything else
- match: { method: "*", path: "/**" }
action: deny
reason: "not allowed by lookout-with-exec preset"
reason: "not allowed by portwing-with-exec preset"
Loading
Loading