CodesWhat/sockguard

GitHub: CodesWhat/sockguard

一个基于 Go 的安全 Docker 套接字代理,通过默认拒绝与细粒度策略解决容器入口安全与审计问题。

Stars: 4 | Forks: 0

sockguard

sockguard

**Control what gets through. A security-first Docker socket proxy built in Go.**

Release GHCR Docker Hub pulls Quay.io
Multi-arch Image size License Apache-2.0

Stars Forks Issues Last commit Commit activity
Discussions Repo size

CI Go Report Card Go Reference
OpenSSF Scorecard


📑 Contents

- [📖 Documentation](https://getsockguard.com/docs) - [🌐 Website](https://getsockguard.com) - [🚀 Quick Start](#quick-start) - [🤔 Why Sockguard](#why-sockguard) - [✨ Features](#features) - [⚖️ Comparison](#comparison) - [⚙️ Configuration](#configuration) - [🔧 CLI](#cli) - [🔄 Migrating from Tecnativa](#migrating-from-tecnativa) - [🗺️ Roadmap](#roadmap) - [🛠️ Built With](#built-with) - [🤝 Contributing](#contributing) - [🔒 Security](#security)

🚀 Quick Start

Drop sockguard in front of any Docker API consumer. The proxy filters requests, your app stays unchanged. ``` # docker-compose.yml services: sockguard: image: codeswhat/sockguard:latest restart: unless-stopped read_only: true cap_drop: - ALL security_opt: - no-new-privileges:true volumes: - /var/run/docker.sock:/var/run/docker.sock:ro environment: - SOCKGUARD_LISTEN_ADDRESS=:2375 - SOCKGUARD_LISTEN_INSECURE_ALLOW_PLAIN_TCP=true - CONTAINERS=1 - IMAGES=1 - EVENTS=1 # Your app talks to tcp://sockguard:2375 over the compose network # instead of mounting /var/run/docker.sock. drydock: image: codeswhat/drydock:latest depends_on: - sockguard environment: - DD_WATCHER_LOCAL_SOCKET=tcp://sockguard:2375 ``` By default sockguard listens on loopback TCP `127.0.0.1:2375`, not on all interfaces. Non-loopback TCP now requires mutual TLS via `listen.tls` by default. The compose example above opts into **legacy plaintext TCP** with `SOCKGUARD_LISTEN_INSECURE_ALLOW_PLAIN_TCP=true` so migration from `tecnativa/docker-socket-proxy` and `linuxserver/socket-proxy` still works on a private Docker network. Do not publish that plaintext listener to the host or Internet. If you run sockguard directly on a host, keep `SOCKGUARD_LISTEN_ADDRESS=127.0.0.1:2375`, configure `listen.tls` for remote TCP, or switch to `SOCKGUARD_LISTEN_SOCKET` to avoid a network listener entirely.
Container runtime hardening Sockguard runs as root inside the container by default so it can open `/var/run/docker.sock` on stock Docker hosts without `user` or `group_add` overrides. For this class of tool, the meaningful hardening levers are the proxy policy, a read-only root filesystem, dropped capabilities, `no-new-privileges`, and the host runtime's seccomp/AppArmor/SELinux confinement. The examples in this README already opt into the container-level controls sockguard actually benefits from: - `read_only: true` - `cap_drop: [ALL]` - `security_opt: ["no-new-privileges:true"]` Keep Docker's default seccomp profile or replace it with a stricter custom profile via `security_opt`. On AppArmor or SELinux hosts, keep the runtime's default confinement enabled or replace it with a stricter host policy. If the host runs rootless dockerd, a compromised Docker API client inherits the daemon's reduced authority instead of full host root.
mTLS TCP mode (recommended for remote TCP) ``` services: sockguard: image: codeswhat/sockguard:latest restart: unless-stopped read_only: true cap_drop: - ALL security_opt: - no-new-privileges:true volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./certs:/certs:ro environment: - SOCKGUARD_LISTEN_ADDRESS=:2376 - SOCKGUARD_LISTEN_TLS_CERT_FILE=/certs/server-cert.pem - SOCKGUARD_LISTEN_TLS_KEY_FILE=/certs/server-key.pem - SOCKGUARD_LISTEN_TLS_CLIENT_CA_FILE=/certs/client-ca.pem - CONTAINERS=1 ``` Non-loopback TCP without `listen.tls` fails startup unless you explicitly set `SOCKGUARD_LISTEN_INSECURE_ALLOW_PLAIN_TCP=true`. Sockguard's server-side TLS minimum for `listen.tls` is TLS 1.3, so remote clients must support TLS 1.3.
Unix socket mode (filesystem-bounded access) If you prefer to expose sockguard as a unix socket (no network surface at all), opt in by setting `SOCKGUARD_LISTEN_SOCKET` and sharing the socket via a named volume: ``` services: sockguard: image: codeswhat/sockguard:latest read_only: true cap_drop: - ALL security_opt: - no-new-privileges:true volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - sockguard-socket:/var/run/sockguard environment: - SOCKGUARD_LISTEN_SOCKET=/var/run/sockguard/sockguard.sock - CONTAINERS=1 drydock: image: codeswhat/drydock:latest depends_on: - sockguard volumes: - sockguard-socket:/var/run/sockguard:ro environment: - DD_WATCHER_LOCAL_SOCKET=/var/run/sockguard/sockguard.sock volumes: sockguard-socket: ``` To run fully unprivileged with a unix socket, pre-create a host directory with the uid/gid you want and bind-mount it in place of the named volume.

🤔 Why Sockguard

The Docker socket is **root access to your host**. Every container with socket access can escape containment, mount the host filesystem, and pivot to other containers. Yet tools like Traefik, Portainer, and drydock need socket access to function. Existing socket proxies (Tecnativa, LinuxServer) filter by URL path only. Sockguard goes further: granular operation control, structured audit logging, and a default-deny posture out of the box.

✨ Features

| | Feature | Description | |---|---|---| | 🛡️ | **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. 10 bundled presets. | | 📊 | **Structured Logging** | JSON access logs with method, path, decision, matched rule, latency, client info. | | 🔐 | **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 and client-container label ACLs let one proxy differentiate TCP callers before the global rule set runs. | | 🔍 | **Request Body Inspection** | `POST /containers/create` bodies are inspected to block privileged containers, host networking, and non-allowlisted bind mounts before Docker sees the request. | | 🏷️ | **Owner Label Isolation** | A proxy instance can stamp containers, networks, volumes, and build-produced images with an owner label, auto-filter list/prune/events calls, and deny cross-owner access to labeled resources. | | 🫥 | **Visibility-Controlled Reads** | Redacts env, mount, and network-topology metadata by default and can hide list/events/inspect results behind per-client label visibility rules. | | 🧱 | **Body-Blind Write Guardrail** | Remaining body-sensitive write endpoints such as `exec`, `build`, and Swarm writes still require explicit unsafe opt-in until their request bodies are inspected. | | 🔄 | **Tecnativa Compatible** | Drop-in replacement using the same env vars. `CONTAINERS=1`, `POST=0`, `ALLOW_START=1` all work. | | 🪶 | **Minimal Attack Surface** | Wolfi-based image, ~12MB. Cosign-signed with SBOM and build provenance. | | ⚡ | **Streaming-Safe** | Preserves Docker streaming endpoints (logs, attach, events) without breaking timeouts, while reaping idle TCP keep-alive connections after 120s. | | 🩺 | **Health Check** | `/health` endpoint with cached upstream reachability probes. | | 🧪 | **Battle-Tested** | ~99% statement coverage, race-detector clean, fuzz testing on filter, config, proxy, and hijack paths. |

⚖️ Comparison

How sockguard stacks up against other Docker socket proxies: | Feature | Tecnativa | LinuxServer | wollomatic | **Sockguard** | |---------|:---------:|:-----------:|:----------:|:-------------:| | Method + path filtering | ✅ | ✅ | ✅ | ✅ | | Granular POST ops | ❌ | Partial | Via regex | ✅ | | Request body inspection | ❌ | ❌ | ❌ | ✅ (`/containers/create`) | | Per-client policies | ❌ | ❌ | CIDR + client labels | ✅ (CIDR + client labels) | | Response filtering | ❌ | ❌ | ❌ | ✅ (visibility + redaction) | | Structured audit log ❌ | ❌ | ❌ | ✅ | | YAML config | ❌ | ❌ | ❌ | ✅ | | Tecnativa env compat | N/A | ✅ | ❌ | ✅ |

⚙️ Configuration

### 环境变量(Tecnativa兼容) ``` CONTAINERS=1 # Allow GET /containers/** IMAGES=0 # Deny /images/** EVENTS=1 # Allow GET /events POST=0 # Read-only mode # 粒度(需要 POST=1) ALLOW_START=1 ALLOW_STOP=1 ALLOW_CREATE=0 ALLOW_EXEC=0 ``` Compat env vars only generate rules when no explicit `rules:` are configured. If you provide `rules:` in YAML, those rules win even when they happen to match the built-in defaults exactly. ### YAML 配置(推荐) ``` listen: address: 127.0.0.1:2375 insecure_allow_plain_tcp: false tls: cert_file: /run/secrets/sockguard/server-cert.pem key_file: /run/secrets/sockguard/server-key.pem client_ca_file: /run/secrets/sockguard/client-ca.pem insecure_allow_body_blind_writes: false response: deny_verbosity: minimal # recommended for production; verbose adds method/path/reason for debugging redact_container_env: true redact_mount_paths: true redact_network_topology: true request_body: container_create: allowed_bind_mounts: - /srv/containers - /var/lib/app-data exec: allowed_commands: - ["/usr/local/bin/pre-update", "--check"] image_pull: allow_official: true allowed_registries: - ghcr.io build: allow_remote_context: false allow_host_network: false allow_run_instructions: false clients: allowed_cidrs: - 172.18.0.0/16 container_labels: enabled: true label_prefix: com.sockguard.allow. ownership: owner: ci-job-123 label_key: com.sockguard.owner rules: - match: { method: GET, path: "/_ping" } action: allow - match: { method: GET, path: "/containers/**" } action: allow - match: { method: POST, path: "/containers/*/start" } action: allow - match: { method: "*", path: "/**" } action: deny ``` Trailing `/**` matches both the base path and any deeper path. For example, `/containers/**` matches `/containers` and `/containers/abc/json`. `listen.tls` is only needed when you expose Sockguard on non-loopback TCP. Plaintext non-loopback TCP is rejected unless you set `listen.insecure_allow_plain_tcp: true`, which is intended only for legacy compatibility on a private, trusted network. Allowed `POST /containers/create` requests are inspected by default. Unless you opt out, Sockguard blocks `HostConfig.Privileged=true`, `HostConfig.NetworkMode=host`, and any bind mount source outside `request_body.container_create.allowed_bind_mounts`. Named volumes still work without allowlist entries because they are not host bind mounts. Allowed `POST /containers/*/exec` and `POST /exec/*/start` requests are inspected when `request_body.exec.allowed_commands` is non-empty. Sockguard denies non-allowlisted argv vectors, denies privileged execs unless `request_body.exec.allow_privileged: true`, denies root-user execs unless `request_body.exec.allow_root_user: true`, and re-inspects `POST /exec/*/start` against Docker's stored exec metadata before letting it run. Allowed `POST /images/create` requests are inspected by default. Sockguard denies `fromSrc` image imports unless `request_body.image_pull.allow_imports: true` and only allows Docker Hub official images unless you set `request_body.image_pull.allow_all_registries: true` or list explicit `request_body.image_pull.allowed_registries`. Allowed `POST /build` requests are inspected by default. Sockguard denies remote build contexts, `networkmode=host`, and Dockerfiles containing `RUN` instructions unless you explicitly allow those behaviors under `request_body.build.*`. `clients.allowed_cidrs` is a coarse TCP-client gate. Requests whose source IP falls outside every configured CIDR are denied before `/health` or the global rule set runs. When `clients.container_labels.enabled` is true, Sockguard resolves bridge-network callers by source IP through the Docker API and looks for per-client allow labels on the calling container. Each `clients.container_labels.label_prefix + ` label is interpreted as a comma-separated Sockguard glob allowlist for that HTTP method. For example, `com.sockguard.allow.get=/containers/**,/events` allows only `GET /containers/**` and `GET /events` for that client. If you are migrating from wollomatic, set `clients.container_labels.label_prefix: socket-proxy.allow.` to reuse existing labels. For multi-consumer setups, define named client profiles and assign them by source IP or mTLS client certificate common name. Root-level `rules` and `request_body` remain the fallback policy unless `clients.default_profile` points at one of the named profiles: ``` clients: default_profile: readonly source_ip_profiles: - profile: watchtower cidrs: - 172.18.0.0/16 client_certificate_profiles: - profile: portainer common_names: - portainer-admin profiles: - name: readonly response: visible_resource_labels: - com.sockguard.visible=true rules: - match: { method: GET, path: "/containers/**" } action: allow - match: { method: GET, path: "/events" } action: allow - match: { method: "*", path: "/**" } action: deny - name: watchtower response: visible_resource_labels: - com.sockguard.client=watchtower request_body: image_pull: allow_all_registries: true exec: allowed_commands: - ["/usr/local/bin/pre-update"] rules: - match: { method: GET, path: "/containers/**" } action: allow - match: { method: POST, path: "/containers/*/exec" } action: allow - match: { method: POST, path: "/exec/*/start" } action: allow - match: { method: POST, path: "/images/create" } action: allow - match: { method: "*", path: "/**" } action: deny ``` Client-certificate profile assignment requires `listen.tls` mutual TLS. Profile rules and request-body policies are compiled and validated at startup just like the root policy, and `sockguard validate` now prints the configured client-profile sections too. `response.visible_resource_labels` and `clients.profiles[].response.visible_resource_labels` add read-side visibility control on top of allow rules. Sockguard injects those label selectors into `GET /containers/json`, `/images/json`, `/networks`, `/volumes`, and `/events`, and returns `404` for hidden targets on inspect-style reads such as `GET /containers/*/json`, `/images/*/json`, `/networks/*`, `/volumes/*`, and `GET /exec/*/json`. Selectors use Docker label syntax (`key` or `key=value`), are ANDed together, and profile selectors are additive with the root response selectors. Set `ownership.owner` to turn on per-proxy resource ownership isolation. Sockguard will add `ownership.label_key=ownership.owner` labels to container, network, and volume creates, add the same label to `POST /build`, inject owner label filters into list/prune/events calls, and deny direct access to labeled resources owned by some other proxy instance. Unowned images are still readable by default so shared base images can be pulled and inspected without relabeling. `insecure_allow_body_blind_writes` is off by default. Validation still fails unless you explicitly set it to `true` when your rules allow body-sensitive writes Sockguard cannot safely constrain yet, such as arbitrary `POST /containers/*/exec` / `POST /exec/*/start` without a `request_body.exec.allowed_commands` allowlist, `POST /services/create`, `POST /services/*/update`, or `POST /swarm/init`. `response.deny_verbosity` defaults to `minimal` so `403` responses carry only a generic deny message and never leak the request method, path, or matched rule reason back to the caller. Set it to `verbose` explicitly during rule authoring if you need to see which rule denied a request — verbose is still useful in dev but should never run in production because it echoes request details in the response body. Even in `verbose` mode, Sockguard redacts denied `/secrets/*` and `/swarm/unlockkey` paths before returning them. `response.redact_container_env`, `response.redact_mount_paths`, and `response.redact_network_topology` also default to `true`. Sockguard scrubs `Config.Env` on `GET /containers/*/json`, redacts `HostConfig.Binds` host paths plus `Mounts[*].Source` on container list/inspect responses, redacts volume `Mountpoint` on volume list/inspect responses, and strips container/network address topology from `GET /containers/json`, `GET /containers/*/json`, `GET /networks`, and `GET /networks/*`. Disable those toggles only for trusted admin clients that genuinely need raw Docker metadata. Preset configs included for [drydock](app/configs/drydock.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), and [read-only](app/configs/readonly.yaml).

🔧 CLI

``` sockguard serve # Start proxy (default) sockguard validate -c sockguard.yaml # Validate + print compiled rule table sockguard match -c sockguard.yaml -X GET --path /v1.45/containers/json # Dry-run a single request through the rules sockguard version # Print version ``` `sockguard match` is the offline rule-evaluation probe — point it at a config and a `` and it prints which rule fires, what the normalized path looks like, and the reason (if any), so you can sanity-check a ruleset before any traffic hits the proxy. Output is text by default or JSON via `-o json`.

🔄 Migrating from Tecnativa

Replace the image — your env vars work as-is: ``` services: socket-proxy: - image: tecnativa/docker-socket-proxy + image: codeswhat/sockguard volumes: - /var/run/docker.sock:/var/run/docker.sock:ro environment: - SOCKGUARD_LISTEN_ADDRESS=:2375 - SOCKGUARD_LISTEN_INSECURE_ALLOW_PLAIN_TCP=true - CONTAINERS=1 - POST=0 ```

🗺️ Roadmap

| Version | Theme | Status | |---------|-------|--------| | **0.1.0** | MVP — drop-in replacement with granular control, YAML config, structured logging | ✅ shipped | | **0.2.0** | mTLS for remote TCP, TLS 1.3 minimum, loopback-by-default listener, body-blind write guardrail | ✅ shipped | | **0.3.0** | Request-body inspection for `/containers/create`, per-proxy owner labels, per-client CIDR + container-label ACLs | ✅ shipped | | **0.4.0** | Profile inheritance, unix peer creds, container/image pattern visibility | 🕒 planned | | **0.5.0** | Operator auditability Prometheus metrics, dedicated audit log schema, stable request IDs, explicit deny reason codes | 🕒 planned | | **0.6.0** | Secure container enforcement — readonly rootfs, resource limits, approved seccomp/AppArmor/SELinux, restricted CapAdd/Devices, image signature verification | 🕒 planned | | **0.7.0** | Abuse controls — per-client rate limits, burst controls, concurrency caps | 🕒 planned | | **0.8.0** | Dynamic configuration — hot reload, admin API, config validation, policy versioning | 🕒 planned |

🛠️ Built With

Go Cobra Viper Wolfi Cosign
Next.js Nextra Tailwind Turborepo Biome


🔒 Security

- **Responsible disclosure** — see [SECURITY.md](SECURITY.md) for scope, supported versions, and how to report a vulnerability privately. - **Image verification** — every release is cosign-signed via GitHub Actions OIDC. Before running a sockguard image in production, verify it with the canonical invocation in the [image verification guide](docs/src/content/verification.mdx).
Built by CodesWhat · Licensed under Apache-2.0
标签:API代理, DevOps安全, Docker, Docker守护进程, EVTX分析, GitHub Advanced Security, Go语言, Hakrawler, Streamlit, Tecnativa, Web截图, 兼容, 安全代理, 安全加固, 安全合规, 安全防御评估, 审计日志, 容器安全, 容器防护, 方法过滤, 日志审计, 流量过滤, 程序破解, 结构化日志, 网络代理, 访问控制, 请求响应过滤, 请求拦截, 路径过滤, 默认拒绝