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
4 changes: 2 additions & 2 deletions docs/content/docs/cis-docker-benchmark.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ the deny reason naming the specific knob:

(The preset uses `deny_verbosity: minimal` so the response never echoes
the violating field; the structured access log in
[Observability](/docs/observability) records the full deny reason.)
[Observability](/observability) records the full deny reason.)

## What sockguard enforces

Expand Down Expand Up @@ -75,7 +75,7 @@ from the benchmark.

| CIS control | Description | How sockguard contributes |
|---|---|---|
| **2.6** | TLS authentication for Docker daemon | mTLS on the sockguard TCP listener (see [Configuration β†’ mTLS](/docs/configuration#mtls)) replaces direct daemon TLS for in-cluster consumers |
| **2.6** | TLS authentication for Docker daemon | mTLS on the sockguard TCP listener (see [Configuration β†’ mTLS](/configuration#mtls-client-selectors)) replaces direct daemon TLS for in-cluster consumers |
| **4.5** | Content trust for Docker | `request_body.container_create.image_trust.mode: enforce` verifies cosign signatures on image references before the create reaches the daemon |
| **5.22/5.23** | Exec hardening | When a rule permits exec, the `request_body.exec` block applies the same admission gates documented above |
| **7.x** | Swarm controls (operations) | This preset denies the entire `/swarm/**`, `/services/**`, `/nodes/**`, `/configs/**`, `/secrets/**` surface β€” meeting 7.1–7.10 by exclusion. Re-enable per rule for clusters that actually use Swarm |
Expand Down
8 changes: 4 additions & 4 deletions docs/content/docs/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ The default listener is loopback TCP `127.0.0.1:2375`, which keeps the Docker AP
- `admin.enabled` is opt-in and exposes a single `POST <admin.path>` endpoint (default `/admin/validate`) that runs the same parse + validate + compile pipeline as the offline `sockguard validate` command against a YAML body in the request payload. Useful as a CI gate before promoting a candidate config to production. Running policy is never mutated. The endpoint rides the main listener, so the listener's CIDR allowlist, mTLS posture, and per-profile rate-limit / concurrency caps all apply. Bodies are hard-capped at `admin.max_request_bytes` (default 512 KiB) via `http.MaxBytesReader` and return `413` on overflow. Non-POST methods return `405` with `Allow: POST`. The response body is a structured JSON report: `{"ok": bool, "rules": int, "profiles": int, "compat_active": bool, "errors": [...]?}`. A failing candidate returns `422` with the validator's per-issue error list; a passing candidate returns `200`. `admin.path` must start with `/` and must not collide with `health.path` or `metrics.path` when those endpoints are also enabled.
- `reload.enabled` is opt-in and turns on hot reload of policy at runtime. When on, sockguard watches the loaded config file via `fsnotify` (Linux inotify / macOS kqueue) and also reloads on `SIGHUP`. A burst of editor events (vim's chmod + write + rename + create save dance, for example) is debounced into a single reload by `reload.debounce` (default `"250ms"`). The reload pipeline parses the new file, applies the same Tecnativa-compat env expansion the startup path uses, runs the full validator + rule compiler, and **atomically swaps** the running handler chain on success. In-flight requests at the moment of the swap complete on the previous chain; new requests immediately route through the new one β€” no connections dropped. On any failure (file unreadable, YAML malformed, validator rejects, compile error) the running policy is preserved untouched. Hot reload is restricted to a reloadable subset of the config. The **immutable** fields β€” `listen.*`, `upstream.socket`, `log.*`, `health.*`, `metrics.*`, `admin.*`, and `policy_bundle` trust material β€” are bound at startup to long-lived sockets and goroutines that cannot be replaced from within a running process. A reload that would mutate any of those is refused (the running config stays in place, and the failure is logged with `changed_fields=...`); operators must restart sockguard to apply listener, upstream socket, log sink, health, metrics, or admin changes. Everything else β€” `rules`, `clients.*`, `response.*`, `request_body.*`, `ownership.*`, `insecure_allow_*` β€” is rebuilt and atomically applied on every successful reload. Reload outcomes are surfaced as Prometheus metrics: `sockguard_config_reload_total{result="ok|reject_load|reject_validation|reject_immutable|reject_signature"}` counter and a `sockguard_config_reload_last_success_timestamp_seconds` gauge (omitted from scrape output until the first successful reload). **SIGHUP semantics change** when hot reload is on: previously SIGHUP terminated sockguard (Go's default action for unhandled SIGHUP); with `reload.enabled: true` it triggers a reload and never terminates the process. Default is `reload.enabled: false` for backward compatibility β€” operators who script around SIGHUP-as-shutdown must update their tooling before enabling reload.
- W3C trace/log correlation is always on and has no config knob. Sockguard preserves valid incoming `traceparent` trace IDs and sampled flags, replaces the parent span with a proxy-local span ID for the forwarded request, and generates fresh local context when the caller does not send valid trace context.
- `POST /containers/create` bodies are inspected by default. Sockguard blocks `HostConfig.Privileged=true`, `HostConfig.NetworkMode=host`, `HostConfig.PidMode=host`, `HostConfig.IpcMode=host`, `HostConfig.UsernsMode=host`, a non-empty `HostConfig.Sysctls` map (unless `allow_sysctls: true`), bind mount sources outside `request_body.container_create.allowed_bind_mounts`, `HostConfig.Devices` host paths outside `request_body.container_create.allowed_devices`, `HostConfig.DeviceRequests` (unless explicitly allowed via `allow_device_requests` or `allowed_device_requests`), `HostConfig.DeviceCgroupRules` (unless explicitly allowed via `allow_device_cgroup_rules` or `allowed_device_cgroup_rules`), and any `HostConfig.CapAdd` entry that isn't covered by `allow_all_capabilities` or `allowed_capabilities`. Named volumes still work without allowlist entries because they are not host bind mounts. `allowed_device_requests` is the structured opt-in for GPU passthrough and similar device request policy β€” each entry must specify a `driver` (exact match, case-insensitive), an `allowed_capabilities` list of capability sets (each request capability set must be a subset of at least one allowlisted set), and an optional `max_count` bound (`-1` means all devices; request `Count: -1` is only allowed when `max_count` is also `-1`); set `allow_device_requests: true` only when you need unrestricted device request access. `allowed_device_cgroup_rules` is the structured opt-in for cgroup device policy β€” it accepts Docker cgroup rule strings (`<type> <major>:<minor> <perms>`) with `*` wildcards for major or minor, and denies request wildcards unless the matching allowlist entry also uses a wildcard at that position; set `allow_device_cgroup_rules: true` only when you need unrestricted cgroup device access. Optional opt-in rails enforce `no-new-privileges`, non-root `Config.User`, `HostConfig.ReadonlyRootfs=true`, `HostConfig.CapDrop=["ALL"]`, memory / CPU / PIDs limits, allowlisted seccomp and AppArmor profiles, and required `Config.Labels` keys β€” all default to off so an existing 0.5.x configuration keeps its behavior except for the CapAdd allowlist and host-userns default-deny noted above. `image_trust` adds cosign-backed signature verification: set `mode: enforce` to deny containers whose image lacks a valid signature from one of your `allowed_signing_keys` (PEM public keys) or `allowed_keyless` identities (Fulcio cert chain matched by issuer URL and SAN regex); set `mode: warn` to log failures and allow the request through instead. `require_rekor_inclusion: true` additionally requires a Rekor transparency log entry for keyless bundles. `verify_timeout` controls the per-verification network timeout (default 10s); set to a low value in air-gapped environments to fail fast. Either mode is a no-op when the image reference is empty β€” Docker refuses to create a container without an image anyway.
- `POST /containers/create` bodies are inspected by default. Sockguard blocks `HostConfig.Privileged=true`, `HostConfig.NetworkMode=host`, `HostConfig.PidMode=host`, `HostConfig.IpcMode=host`, `HostConfig.UsernsMode=host`, a non-empty `HostConfig.Sysctls` map (unless `allow_sysctls: true`), bind mount sources outside `request_body.container_create.allowed_bind_mounts`, `HostConfig.Devices` host paths outside `request_body.container_create.allowed_devices`, `HostConfig.DeviceRequests` (unless explicitly allowed via `allow_device_requests` or `allowed_device_requests`), `HostConfig.DeviceCgroupRules` (unless explicitly allowed via `allow_device_cgroup_rules` or `allowed_device_cgroup_rules`), and any `HostConfig.CapAdd` entry that isn't covered by `allow_all_capabilities` or `allowed_capabilities`. Named volumes still work without allowlist entries because they are not host bind mounts. `allowed_device_requests` is the structured opt-in for GPU passthrough and similar device request policy β€” each entry must specify a `driver` (exact match, case-insensitive), an `allowed_capabilities` list of capability sets (each request capability set must be a subset of at least one allowlisted set), and an optional `max_count` bound (`-1` means all devices; request `Count: -1` is only allowed when `max_count` is also `-1`); set `allow_device_requests: true` only when you need unrestricted device request access. `allowed_device_cgroup_rules` is the structured opt-in for cgroup device policy β€” it accepts Docker cgroup rule strings (`<type> <major>:<minor> <perms>`) with `*` wildcards for major or minor, and denies request wildcards unless the matching allowlist entry also uses a wildcard at that position; set `allow_device_cgroup_rules: true` only when you need unrestricted cgroup device access. Optional opt-in rails enforce `no-new-privileges`, non-root `Config.User`, `HostConfig.ReadonlyRootfs=true`, `HostConfig.CapDrop=["ALL"]`, memory / CPU / PIDs limits, allowlisted seccomp and AppArmor profiles, and required `Config.Labels` keys β€” all default to off, so a configuration that does not set them keeps prior behavior except for the CapAdd allowlist and host-userns default-deny noted above. `image_trust` adds cosign-backed signature verification: set `mode: enforce` to deny containers whose image lacks a valid signature from one of your `allowed_signing_keys` (PEM public keys) or `allowed_keyless` identities (Fulcio cert chain matched by issuer URL and SAN regex); set `mode: warn` to log failures and allow the request through instead. `require_rekor_inclusion: true` additionally requires a Rekor transparency log entry for keyless bundles. `verify_timeout` controls the per-verification network timeout (default 10s); set to a low value in air-gapped environments to fail fast. Either mode is a no-op when the image reference is empty β€” Docker refuses to create a container without an image anyway.
- `POST /containers/create` also denies five `HostConfig` fields **unconditionally** β€” no `request_body` setting opts back in: `VolumesFrom`, `UTSMode=host`, a non-empty `CgroupParent`, `GroupAdd`, and `ExtraHosts`. Each one opens a namespace-escape or privilege-escalation path, so it is blocked regardless of policy.
- `POST /containers/*/exec` and `POST /exec/*/start` are inspected when `request_body.exec.allowed_commands` is non-empty. Sockguard denies argv vectors that match no allowlist entry β€” each entry is an argv template whose tokens are sockguard globs (`*` matches a run of non-slash characters, `**` matches any sequence), and a command matches when its token count equals an entry's and every token matches the glob at that position, so an exec carrying a variable argument can be allowlisted without enumerating every literal form. It also denies privileged exec unless `allow_privileged: true`, denies root-user exec unless `allow_root_user: true`, and re-checks `POST /exec/*/start` against Docker's stored exec metadata before execution. Docker exposes exec inspect and exec start as separate API calls, so this start-time check has an unavoidable time-of-check/time-of-use window; keep exec allowlists and client profile assignments narrow.
- `POST /images/create` is inspected by default. Sockguard blocks `fromSrc` imports unless `request_body.image_pull.allow_imports: true` and only allows Docker Hub official images unless you set `allow_all_registries: true` or list explicit `allowed_registries`.
Expand Down Expand Up @@ -257,7 +257,7 @@ com.sockguard.allow.post=/containers/*/restart

If you are migrating from wollomatic, set `clients.container_labels.label_prefix: socket-proxy.allow.` to reuse existing labels. Callers that cannot be resolved by IP (for example, because they share the host network) fall through to the global rule set unchanged.

> **Security note β€” IP-based identity is soft isolation.** IP-keyed admission (`clients.allowed_cidrs`, `clients.container_labels.enabled`, and profile `match.source_cidrs`) is adequate against configuration drift but not hard isolation against an attacker who can influence container-to-IP mapping on a shared bridge. For caller identity in the security boundary, prefer a unix socket with `clients.unix_peer_profiles` and `uids`/`gids` β€” `SO_PEERCRED` cannot be spoofed. See [the Known Limitations section in the Security guide](/docs/security#known-limitations) for the full caveat.
> **Security note β€” IP-based identity is soft isolation.** IP-keyed admission (`clients.allowed_cidrs`, `clients.container_labels.enabled`, and profile `match.source_cidrs`) is adequate against configuration drift but not hard isolation against an attacker who can influence container-to-IP mapping on a shared bridge. For caller identity in the security boundary, prefer a unix socket with `clients.unix_peer_profiles` and `uids`/`gids` β€” `SO_PEERCRED` cannot be spoofed. See [the Known Limitations section in the Security guide](/security#known-limitations) for the full caveat.

Named client profiles turn one Sockguard instance into a shared control plane for multiple consumers. Root-level `rules` and `request_body` remain the fallback policy unless `clients.default_profile` points at a named profile:

Expand Down Expand Up @@ -347,7 +347,7 @@ clients:

Each named profile can carry a `limits` block that enforces two independent
mechanisms. Both are per-profile and disabled by default β€” omitting `limits`
entirely (or omitting either sub-block) preserves pre-v0.7.0 behavior.
entirely (or omitting either sub-block) leaves that profile unthrottled.

### Token-bucket rate limiting

Expand Down Expand Up @@ -1007,7 +1007,7 @@ multiple entries via the env var; e.g.
- `redact_network_topology` (default `true`): redacts container, network, task, service, node, swarm, `/info`, and `/system/df` topology details such as network IDs, attached addresses, remote managers, and node reachability addresses.
- `redact_sensitive_data` (default `true`): redacts config payload material, service secret/config references, swarm join/unlock and CA material, and node/swarm TLS metadata.

> **Security note β€” the redaction toggles do not cover stream bodies.** These toggles operate only on structured JSON Docker responses. Hijacked / streaming endpoints (`GET /containers/*/logs`, `POST /containers/*/attach`, `GET /services/*/logs`, `GET /events`, exec attach, image-build progress) are forwarded byte-for-byte; secrets a workload writes to its own stdout will reach an allowed caller. Gate those paths by rule, not by redaction. See [the Known Limitations section in the Security guide](/docs/security#known-limitations) for the full caveat.
> **Security note β€” the redaction toggles do not cover stream bodies.** These toggles operate only on structured JSON Docker responses. Hijacked / streaming endpoints (`GET /containers/*/logs`, `POST /containers/*/attach`, `GET /services/*/logs`, `GET /events`, exec attach, image-build progress) are forwarded byte-for-byte; secrets a workload writes to its own stdout will reach an allowed caller. Gate those paths by rule, not by redaction. See [the Known Limitations section in the Security guide](/security#known-limitations) for the full caveat.

## Tecnativa Compatibility

Expand Down
4 changes: 2 additions & 2 deletions docs/content/docs/presets.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Presets
description: Ready-made sockguard configs for drydock, Traefik, Portainer, Watchtower, Homepage, Homarr, Diun, Autoheal, and read-only dashboards.
description: Ready-made sockguard configs for drydock, Traefik, Portainer, Watchtower, Homepage, Homarr, Diun, Autoheal, GitHub Actions and GitLab runners, the CIS Docker Benchmark, and read-only dashboards.
---

Sockguard ships with ready-made config presets for common Docker consumers. All presets are bundled in the container image at `/etc/sockguard/`.
Expand Down Expand Up @@ -167,7 +167,7 @@ before dockerd executes it.
# Denies: exec, build, swarm, secrets, plugins, raw archive/log/attach
```

See [the dedicated CIS Docker Benchmark guide](/docs/cis-docker-benchmark)
See [the dedicated CIS Docker Benchmark guide](/cis-docker-benchmark)
for the full control-by-control mapping, the negative-test recipe, and
notes on companion tools for the host/daemon/image controls sockguard
cannot inspect.
Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/verification.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ digest rather than by tag β€” a tag can be re-pointed, a digest cannot.
```bash
# Resolve the current digest for a tag
digest=$(docker buildx imagetools inspect \
ghcr.io/codeswhat/sockguard:0.5.1 --format '{{json .Manifest.Digest}}' \
ghcr.io/codeswhat/sockguard:<TAG> --format '{{json .Manifest.Digest}}' \
| tr -d '"')

# Verify against the digest
Expand Down
11 changes: 11 additions & 0 deletions website/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,17 @@ export default function Home() {
alt="Docker Hub pulls"
/>
</a>
<a
href="https://quay.io/repository/codeswhat/sockguard"
target="_blank"
rel="noopener noreferrer"
>
{/* biome-ignore lint/performance/noImgElement: external badge */}
<img
src="https://img.shields.io/badge/Quay.io-image-ee0000?logo=redhat&logoColor=white"
alt="Quay.io"
/>
</a>
<a
href="https://github.com/orgs/CodesWhat/packages/container/package/sockguard"
target="_blank"
Expand Down
Loading