diff --git a/.github/workflows/docker-image-release.yaml b/.github/workflows/docker-image-release.yaml index 344cfaa..625e9c4 100644 --- a/.github/workflows/docker-image-release.yaml +++ b/.github/workflows/docker-image-release.yaml @@ -1,4 +1,7 @@ name: Build and Publish Release +permissions: + contents: read + packages: write on: push: diff --git a/.github/workflows/docker-image-testing.yaml b/.github/workflows/docker-image-testing.yaml index 19d4bc0..7b71480 100644 --- a/.github/workflows/docker-image-testing.yaml +++ b/.github/workflows/docker-image-testing.yaml @@ -1,4 +1,7 @@ name: Build and Publish Testing +permissions: + contents: read + packages: write on: push: diff --git a/Dockerfile b/Dockerfile index b7e74d6..8449c24 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1 -FROM --platform=$BUILDPLATFORM golang:1.24.5-alpine3.22 AS build +FROM --platform=$BUILDPLATFORM golang:1.25.0-alpine3.22 AS build WORKDIR /application COPY . ./ ARG TARGETOS diff --git a/README.md b/README.md index bcc0fa3..0ce9206 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # socket-proxy ## Latest image -- `wollomatic/socket-proxy:1.8.1` / `ghcr.io/wollomatic/socket-proxy:1.8.1` +- `wollomatic/socket-proxy:1.9.0` / `ghcr.io/wollomatic/socket-proxy:1.9.0` - `wollomatic/socket-proxy:1` / `ghcr.io/wollomatic/socket-proxy:1` ## About @@ -33,7 +33,7 @@ You should know what you are doing. Never expose socket-proxy to a public networ The container image is available on [Docker Hub (wollomatic/socket-proxy)](https://hub.docker.com/r/wollomatic/socket-proxy) and on the [GitHub Container Registry (ghcr.io/wollomatic/socket-proxy)](https://github.com/wollomatic/socket-proxy/pkgs/container/socket-proxy). -To pin one specific version, use the version tag (for example, `wollomatic/socket-proxy:1.6.0` or `ghcr.io/wollomatic/socket-proxy:1.6.0`). +To pin one specific version, use the version tag (for example, `wollomatic/socket-proxy:1.9.0` or `ghcr.io/wollomatic/socket-proxy:1.9.0`). To always use the most recent version, use the `1` tag (`wollomatic/socket-proxy:1` or `ghcr.io/wollomatic/socket-proxy:1`). This tag will be valid as long as there is no breaking change in the deployment. There may be an additional docker image with the `testing`-tag. This image is only for testing. Likely, documentation for the `testing` image could only be found in the GitHub commit messages. It is not recommended to use the `testing` image in production. @@ -75,7 +75,7 @@ The name of a parameter should be "-allow", followed by the HTTP method name (fo It is also possible to configure the allowlist via environment variables. The variables are called "SP_ALLOW_", followed by the HTTP method (for example, `SP_ALLLOW_GET`). -If both commandline parameter and environment variable is configured for a particular HTTP method, the environment variable is ignored. +If both commandline parameter and environment variable are configured for a particular HTTP method, the environment variable is ignored. Use Go's regexp syntax to create the patterns for these parameters. To avoid insecure configurations, the characters ^ at the beginning and $ at the end of the string are automatically added. Note: invalid regexp results in program termination. @@ -159,7 +159,7 @@ services: # it is not the same as the traefik-servicenet traefik: - # [...] see github.com/wollomatic/traefik2-hardened for a full example + # [...] see github.com/wollomatic/traefik-hardened for a full example depends_on: - dockerproxy networks: @@ -197,7 +197,7 @@ socket-proxy can be configured via command line parameters or via environment va | Parameter | Environment Variable | Default Value | Description | |--------------------------------|----------------------------------|------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `-allowfrom` | `SP_ALLOWFROM` | `127.0.0.1/32` | Specifies the IP addresses or hostnames (comma-separated) of the clients or the hostname of one specific client allowed to connect to the proxy. The default value is `127.0.0.1/32`, which means only localhost is allowed. This default configuration may not be useful in most cases, but it is because of a secure-by-default design. To allow all IPv4 addresses, set `-allowfrom=0.0.0.0/0`. Alternatively, hostnames can be set, for example `-allowfrom=traefik`, or `-allowfrom=traefik,dozzle`. Please remember that socket-proxy should never be exposed to a public network, regardless of this extra security layer. | -| `-allowbindmountfrom` | `SP_ALLOWBINDMOUNTFROM` | (not set) | Specifies the directories (comma-separated) that are allowed as bind mount sources. If not set, no bind mount restrictions are applied. When set, only bind mounts from the specified directories or their subdirectories are allowed. Each directory must start with `/`. For example, `-allowbindmountfrom=/home,/var/log` allows bind mounts from `/home`, `/var/log`, and any subdirectories. | +| `-allowbindmountfrom` | `SP_ALLOWBINDMOUNTFROM` | (not set) | Specifies the directories (comma-separated) that are allowed as bind mount sources. If not set, no bind mount restrictions are applied. When set, only bind mounts from the specified directories or their subdirectories are allowed. Each directory must start with `/`. For example, `-allowbindmountfrom=/home,/var/log` allows bind mounts from `/home`, `/var/log`, and any subdirectories. | | `-allowhealthcheck` | `SP_ALLOWHEALTHCHECK` | (not set/false) | If set, it allows the included health check binary to check the socket connection via TCP port 55555 (socket-proxy then listens on `127.0.0.1:55555/health`) | | `-listenip` | `SP_LISTENIP` | `127.0.0.1` | Specifies the IP address the server will bind on. Default is only the internal network. | | `-logjson` | `SP_LOGJSON` | (not set/false) | If set, it enables logging in JSON format. If unset, docker-proxy logs in plain text format. | @@ -228,7 +228,9 @@ socket-proxy can be configured via command line parameters or via environment va 1.7 - also allow comma-separated CIDRs in `-allowfrom` (not only hostnames as in versions > 1.3) -1.8 - add optional bind mount restrictions (thanks [@powerman](https://github.com/powerman)) +1.8 - add optional bind mount restrictions (thanks [@powerman](https://github.com/powerman), [@C4tWithShell](https://github.com/C4tWithShell)) + +1.9 - add IPv6 support to `-listenip` (thanks [@op3](https://github.com/op3)) ## License This project is licensed under the MIT License – see the [LICENSE](LICENSE) file for details. @@ -241,3 +243,8 @@ See the comments in this file and the LICENSE file for more information. + [Chris Wiegman: Protecting Your Docker Socket With Traefik 2](https://chriswiegman.com/2019/11/protecting-your-docker-socket-with-traefik-2/) [@ChrisWiegman](https://github.com/ChrisWiegman) + [tecnativa/docker-socket-proxy](https://github.com/Tecnativa/docker-socket-proxy) + [@justsomescripts](https://github.com/justsomescripts) fix parsing environment variable to configure unix socket + +## Alternatives + ++ [hectorm/cetusguard](https://github.com/hectorm/cetusguard) ++ [11notes/docker-socket-proxy](https://github.com/11notes/docker-socket-proxy) \ No newline at end of file diff --git a/cmd/socket-proxy/main.go b/cmd/socket-proxy/main.go index 0f3d7bf..77837cc 100644 --- a/cmd/socket-proxy/main.go +++ b/cmd/socket-proxy/main.go @@ -12,6 +12,7 @@ import ( "os" "os/signal" "runtime" + "strings" "syscall" "time" @@ -58,9 +59,11 @@ func main() { // print configuration slog.Info("starting socket-proxy", "version", version, "os", runtime.GOOS, "arch", runtime.GOARCH, "runtime", runtime.Version(), "URL", programURL) if cfg.ProxySocketEndpoint == "" { - slog.Info("configuration info", "socketpath", cfg.SocketPath, "listenaddress", cfg.ListenAddress, "loglevel", cfg.LogLevel, "logjson", cfg.LogJSON, "allowfrom", cfg.AllowFrom, "shutdowngracetime", cfg.ShutdownGraceTime, "allowbindmountfrom", cfg.AllowBindMountFrom) + // join the cfg.AllowFrom slice to a string to avoid the brackets in the logging (avoid confusion with IPv6 addresses) + allowFromString := strings.Join(cfg.AllowFrom, ",") + slog.Info("configuration info", "socketpath", cfg.SocketPath, "listenaddress", cfg.ListenAddress, "loglevel", cfg.LogLevel, "logjson", cfg.LogJSON, "allowfrom", allowFromString, "shutdowngracetime", cfg.ShutdownGraceTime) } else { - slog.Info("configuration info", "socketpath", cfg.SocketPath, "proxysocketendpoint", cfg.ProxySocketEndpoint, "proxysocketendpointfilemode", cfg.ProxySocketEndpointFileMode, "loglevel", cfg.LogLevel, "logjson", cfg.LogJSON, "allowfrom", cfg.AllowFrom, "shutdowngracetime", cfg.ShutdownGraceTime, "allowbindmountfrom", cfg.AllowBindMountFrom) + slog.Info("configuration info", "socketpath", cfg.SocketPath, "proxysocketendpoint", cfg.ProxySocketEndpoint, "proxysocketendpointfilemode", cfg.ProxySocketEndpointFileMode, "loglevel", cfg.LogLevel, "logjson", cfg.LogJSON, "shutdowngracetime", cfg.ShutdownGraceTime) slog.Info("proxysocketendpoint is set, so the TCP listener is deactivated") } if cfg.WatchdogInterval > 0 { @@ -68,8 +71,14 @@ func main() { } else { slog.Info("watchdog disabled") } + if len(cfg.AllowBindMountFrom) > 0 { + slog.Info("Docker bind mount restrictions enabled", "allowbindmountfrom", cfg.AllowBindMountFrom) + } else { + // we only log this on DEBUG level because bind mount restrictions are a very special use case + slog.Debug("no Docker bind mount restrictions") + } - // print request allow list + // print request allowlist if cfg.LogJSON { for method, regex := range cfg.AllowedRequests { slog.Info("configured allowed request", "method", method, "regex", regex) diff --git a/internal/config/config.go b/internal/config/config.go index bd99114..c7a73cb 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -180,13 +180,20 @@ func InitConfig() (*Config, error) { } // check listenIP and proxyPort - if net.ParseIP(listenIP) == nil { - return nil, fmt.Errorf("invalid IP \"%s\" for listenip", listenIP) - } if proxyPort < 1 || proxyPort > 65535 { - return nil, errors.New("port number has to be between 1 and 65535") + return nil, errors.New("port number has to be between 1 and 65535") + } + ip := net.ParseIP(listenIP) + if ip == nil { + return nil, fmt.Errorf("invalid IP \"%s\" for listenip", listenIP) + } + + // Properly format address for both IPv4 and IPv6 + if ip.To4() == nil { + cfg.ListenAddress = fmt.Sprintf("[%s]:%d", listenIP, proxyPort) + } else { + cfg.ListenAddress = fmt.Sprintf("%s:%d", listenIP, proxyPort) } - cfg.ListenAddress = fmt.Sprintf("%s:%d", listenIP, proxyPort) // parse defaultLogLevel and setup logging handler depending on defaultLogJSON switch strings.ToUpper(logLevel) {