From 7c99e30ced0c5f288519f06020a7c552054e5884 Mon Sep 17 00:00:00 2001 From: Alex Efros Date: Wed, 16 Jul 2025 12:10:09 +0300 Subject: [PATCH 01/14] add bind mount restrictions --- README.md | 16 ++ cmd/socket-proxy/bindmount.go | 184 +++++++++++++++++++++++ cmd/socket-proxy/bindmount_test.go | 206 ++++++++++++++++++++++++++ cmd/socket-proxy/handlehttprequest.go | 6 + cmd/socket-proxy/main.go | 4 +- internal/config/config.go | 31 +++- 6 files changed, 439 insertions(+), 8 deletions(-) create mode 100644 cmd/socket-proxy/bindmount.go create mode 100644 cmd/socket-proxy/bindmount_test.go diff --git a/README.md b/README.md index 8e780a5..443bfa4 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,20 @@ If both commandline parameter and environment variable is configured for a parti 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. +#### Setting up bind mount restrictions + +By default, socket-proxy does not restrict bind mounts. If you want to add an additional layer of security by restricting which directories can be used as bind mount sources, you can use the `-allowbindmountfrom` parameter or the `SP_ALLOWBINDMOUNTFROM` environment variable. + +When configured, only bind mounts from the specified directories or their subdirectories are allowed. Each directory must start with `/`. Multiple directories can be specified separated by commas. + +For example: ++ `-allowbindmountfrom=/home,/var/log` allows bind mounts from `/home`, `/var/log`, and any subdirectories like `/home/user/data` or `/var/log/app` ++ `SP_ALLOWBINDMOUNTFROM="/app/data,/tmp"` allows bind mounts from `/app/data` and `/tmp` directories + +Bind mount restrictions are applied to relevant Docker API endpoints and work with both legacy bind mount syntax (`-v /host/path:/container/path`) and modern mount syntax. + +**Note**: This feature only restricts bind mounts. Other mount types (volumes, tmpfs, etc.) are not affected by this restriction. + Examples (command line): + `'-allowGET=/v1\..{1,2}/(version|containers/.*|events.*)'` could be used for allowing access to the docker socket for Traefik v2. + `'-allowHEAD=.*` allows all HEAD requests. @@ -133,6 +147,7 @@ services: - '-listenip=0.0.0.0' - '-allowfrom=traefik' # allow only hostname "traefik" to connect - '-allowGET=/v1\..{1,2}/(version|containers/.*|events.*)' + - '-allowbindmountfrom=/var/log,/tmp' # restrict bind mounts to specific directories - '-watchdoginterval=3600' # check once per hour for socket availability - '-stoponwatchdog' # halt program on error and let compose restart it - '-shutdowngracetime=5' # wait 5 seconds before shutting down @@ -182,6 +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. | | `-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. | diff --git a/cmd/socket-proxy/bindmount.go b/cmd/socket-proxy/bindmount.go new file mode 100644 index 0000000..0c0dc93 --- /dev/null +++ b/cmd/socket-proxy/bindmount.go @@ -0,0 +1,184 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "log/slog" + "net/http" + "path/filepath" + "strings" +) + +// mountType is the subset of github.com/docker/docker/api/types/mount.Type. +type mountType string + +const ( + // mountTypeBind is the type for mounting host dir. + mountTypeBind mountType = "bind" +) + +type ( + // containerCreateRequest is the subset of github.com/docker/docker/api/types/container.CreateRequest. + containerCreateRequest struct { + HostConfig *containerHostConfig `json:"HostConfig,omitempty"` + } + // containerHostConfig is the subset of github.com/docker/docker/api/types/container.HostConfig. + containerHostConfig struct { + Binds []string // List of volume bindings for this container. + Mounts []mountMount `json:",omitempty"` // Mounts specs used by the container. + } + // swarmServiceSpec is the subset of github.com/docker/docker/api/types/swarm.ServiceSpec. + swarmServiceSpec struct { + TaskTemplate swarmTaskSpec `json:",omitempty"` + } + // swarmTaskSpec is the subset of github.com/docker/docker/api/types/swarm.TaskSpec. + swarmTaskSpec struct { + ContainerSpec *swarmContainerSpec `json:",omitempty"` + } + // swarmContainerSpec is the subset of github.com/docker/docker/api/types/swarm.ContainerSpec. + swarmContainerSpec struct { + Mounts []mountMount `json:",omitempty"` + } + // mountMount is the subset of github.com/docker/docker/api/types/mount.Mount. + mountMount struct { + Type mountType `json:",omitempty"` + // Source specifies the name of the mount. Depending on mount type, this + // may be a volume name or a host path, or even ignored. + // Source is not supported for tmpfs (must be an empty value) + Source string `json:",omitempty"` + Target string `json:",omitempty"` + } +) + +// checkBindMountRestrictions checks if bind mounts in the request are allowed. +func checkBindMountRestrictions(r *http.Request) error { + // Only check if bind mount restrictions are configured + if len(cfg.AllowBindMountFrom) == 0 { + return nil + } + + if r.Method != http.MethodPost { + return nil + } + + // Check different API endpoints that can use bind mounts + pathParts := strings.Split(r.URL.Path, "/") + switch { + case len(pathParts) >= 4 && pathParts[2] == "containers" && pathParts[3] == "create": + // Container creation: /vX.xx/containers/create + return checkContainer(r) + case len(pathParts) >= 5 && pathParts[2] == "containers" && pathParts[4] == "update": + // Container update: /vX.xx/containers/{id}/update + return checkContainer(r) + case len(pathParts) >= 4 && pathParts[2] == "services" && pathParts[3] == "create": + // Service creation: /vX.xx/services/create + return checkService(r) + case len(pathParts) >= 5 && pathParts[2] == "services" && pathParts[4] == "update": + // Service update: /vX.xx/services/{id}/update + return checkService(r) + default: + return nil + } +} + +// checkContainer checks bind mounts in container creation requests. +func checkContainer(r *http.Request) error { + body, err := readAndRestoreBody(r) + if err != nil { + return err + } + + var req containerCreateRequest + if err := json.Unmarshal(body, &req); err != nil { + slog.Debug("failed to parse container request", "error", err) + return nil // Don't block if we can't parse. + } + + return checkHostConfigBindMounts(req.HostConfig) +} + +// checkService checks bind mounts in service creation requests. +func checkService(r *http.Request) error { + body, err := readAndRestoreBody(r) + if err != nil { + return err + } + + var req swarmServiceSpec + if err := json.Unmarshal(body, &req); err != nil { + slog.Debug("failed to parse service request", "error", err) + return nil // Don't block if we can't parse. + } + + if req.TaskTemplate.ContainerSpec == nil { + return nil // No container spec, nothing to check. + } + return checkHostConfigBindMounts(&containerHostConfig{ + Mounts: req.TaskTemplate.ContainerSpec.Mounts, + }) +} + +// checkHostConfigBindMounts checks bind mounts in HostConfig. +func checkHostConfigBindMounts(hostConfig *containerHostConfig) error { + if hostConfig == nil { + return nil // No HostConfig, nothing to check + } + + // Check legacy Binds field + for _, bind := range hostConfig.Binds { + if err := validateBindMount(bind); err != nil { + return err + } + } + + // Check modern Mounts field + for _, mountItem := range hostConfig.Mounts { + if mountItem.Type == mountTypeBind { + if err := validateBindMountSource(mountItem.Source); err != nil { + return err + } + } + } + + return nil +} + +// validateBindMount validates a bind mount string in the format "source:target:options". +func validateBindMount(bind string) error { + parts := strings.Split(bind, ":") + if len(parts) < 2 { + return fmt.Errorf("invalid bind mount format: %s", bind) + } + return validateBindMountSource(parts[0]) +} + +// validateBindMountSource checks if the source directory is allowed. +func validateBindMountSource(source string) error { + // Skip if source is not an absolute path (i.e. bind mount). + if !strings.HasPrefix(source, "/") { + return nil + } + + source = filepath.Clean(source) // Clean the path to resolve .. and . components. + for _, allowedDir := range cfg.AllowBindMountFrom { + if allowedDir == "/" || source == allowedDir || strings.HasPrefix(source, allowedDir+"/") { + return nil + } + } + + return fmt.Errorf("bind mount source directory not allowed: %s", source) +} + +// readAndRestoreBody reads the request body and restores it for further processing. +func readAndRestoreBody(r *http.Request) ([]byte, error) { + body, err := io.ReadAll(r.Body) + if err != nil { + return nil, fmt.Errorf("failed to read request body: %w", err) + } + + // Restore the body for further processing + r.Body = io.NopCloser(bytes.NewBuffer(body)) + return body, nil +} diff --git a/cmd/socket-proxy/bindmount_test.go b/cmd/socket-proxy/bindmount_test.go new file mode 100644 index 0000000..fe3c597 --- /dev/null +++ b/cmd/socket-proxy/bindmount_test.go @@ -0,0 +1,206 @@ +package main + +import ( + "bytes" + "net/http" + "testing" + + "github.com/wollomatic/socket-proxy/internal/config" +) + +func TestValidateBindMountSource(t *testing.T) { + cfg = &config.Config{ + AllowBindMountFrom: []string{"/home", "/var/log"}, + } + + tests := []struct { + name string + source string + shouldPass bool + }{ + {"exact match", "/home", true}, + {"subdirectory", "/home/user", true}, + {"deep subdirectory", "/home/user/data", true}, + {"not allowed", "/etc", false}, + {"empty source", "", true}, // empty sources are skipped + {"relative path", "home", true}, // relative paths are skipped + {"var log exact", "/var/log", true}, + {"var log subdir", "/var/log/app", true}, + {"similar but different", "/home2", false}, + {"prefix but not subdir", "/home2/user", false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateBindMountSource(tt.source) + if tt.shouldPass && err != nil { + t.Errorf("expected %s to pass, but got error: %v", tt.source, err) + } + if !tt.shouldPass && err == nil { + t.Errorf("expected %s to fail, but it passed", tt.source) + } + }) + } +} + +func TestIsPathAllowed(t *testing.T) { + tests := []struct { + name string + path string + allowedDir string + expected bool + }{ + {"exact match", "/home", "/home", true}, + {"subdirectory", "/home/user", "/home", true}, + {"deep subdirectory", "/home/user/data", "/home", true}, + {"not subdirectory", "/etc", "/home", false}, + {"similar prefix", "/home2", "/home", false}, + {"parent directory", "/", "/home", false}, + {"path traversal with ..", "/home/user/../..", "/home", false}, + {"path traversal to allowed", "/home/user/..", "/home", true}, + {"path traversal outside", "/home/../etc", "/home", false}, + {"complex path traversal", "/home/user/../../etc", "/home", false}, + {"path with dots in name", "/home/user.name", "/home", true}, + {"path with current dir", "/home/./user", "/home", true}, + {"root directory exact match", "/", "/", true}, + {"any path should be allowed when root is allowed", "/etc", "/", true}, + {"deep path should be allowed when root is allowed", "/var/log/app", "/", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg = &config.Config{ + AllowBindMountFrom: []string{tt.allowedDir}, + } + err := validateBindMountSource(tt.path) + if (err == nil) != tt.expected { + t.Errorf("isPathAllowed(%s, %s) = %v, expected %v", tt.path, tt.allowedDir, err, tt.expected) + } + }) + } +} + +func TestValidateBindMount(t *testing.T) { + cfg = &config.Config{ + AllowBindMountFrom: []string{"/home", "/var/log"}, + } + + tests := []struct { + name string + bind string + shouldPass bool + }{ + {"valid bind", "/home/user:/app", true}, + {"invalid format", "/home/user", false}, + {"not allowed source", "/etc:/app", false}, + {"allowed with options", "/home/user:/app:ro", true}, + {"var log bind", "/var/log:/logs:ro", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateBindMount(tt.bind) + if tt.shouldPass && err != nil { + t.Errorf("expected %s to pass, but got error: %v", tt.bind, err) + } + if !tt.shouldPass && err == nil { + t.Errorf("expected %s to fail, but it passed", tt.bind) + } + }) + } +} + +func TestCheckBindMountRestrictions(t *testing.T) { + cfg = &config.Config{ + AllowBindMountFrom: []string{"/home"}, + } + + tests := []struct { + name string + method string + path string + body string + shouldPass bool + }{ + { + name: "GET request should pass", + method: "GET", + path: "/v1.40/containers/json", + body: "", + shouldPass: true, + }, + { + name: "POST to non-container endpoint should pass", + method: "POST", + path: "/v1.40/images/create", + body: "", + shouldPass: true, + }, + { + name: "container create with allowed bind", + method: "POST", + path: "/v1.40/containers/create", + body: `{"HostConfig":{"Binds":["/home/user:/app"]}}`, + shouldPass: true, + }, + { + name: "container create with disallowed bind", + method: "POST", + path: "/v1.40/containers/create", + body: `{"HostConfig":{"Binds":["/etc:/app"]}}`, + shouldPass: false, + }, + { + name: "path traversal attack", + method: "POST", + path: "/v1.40/containers/create", + body: `{"HostConfig":{"Binds":["/home/user/../../etc:/app"]}}`, + shouldPass: false, + }, + { + name: "container create with no binds", + method: "POST", + path: "/v1.40/containers/create", + body: `{"HostConfig":{}}`, + shouldPass: true, + }, + { + name: "container update with bind mount", + method: "POST", + path: "/v1.40/containers/abc123/update", + body: `{"HostConfig":{"Binds":["/home/user:/app"]}}`, + shouldPass: true, + }, + { + name: "service create with bind mount", + method: "POST", + path: "/v1.40/services/create", + body: `{"TaskTemplate":{"ContainerSpec":{"Mounts":[{"Type":"bind","Source":"/etc","Target":"/app"}]}}}`, + shouldPass: false, + }, + { + name: "v2 API should work too", + method: "POST", + path: "/v2.0/containers/create", + body: `{"HostConfig":{"Binds":["/etc:/app"]}}`, + shouldPass: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req, err := http.NewRequest(tt.method, tt.path, bytes.NewBufferString(tt.body)) + if err != nil { + t.Fatalf("failed to create request: %v", err) + } + + err = checkBindMountRestrictions(req) + if tt.shouldPass && err != nil { + t.Errorf("expected request to pass, but got error: %v", err) + } + if !tt.shouldPass && err == nil { + t.Errorf("expected request to fail, but it passed") + } + }) + } +} diff --git a/cmd/socket-proxy/handlehttprequest.go b/cmd/socket-proxy/handlehttprequest.go index 5c03507..8d748b4 100644 --- a/cmd/socket-proxy/handlehttprequest.go +++ b/cmd/socket-proxy/handlehttprequest.go @@ -33,6 +33,12 @@ func handleHTTPRequest(w http.ResponseWriter, r *http.Request) { return } + // check bind mount restrictions + if err := checkBindMountRestrictions(r); err != nil { + communicateBlockedRequest(w, r, "bind mount restriction: "+err.Error(), http.StatusForbidden) + return + } + // finally, log and proxy the request slog.Debug("allowed request", "method", r.Method, "URL", r.URL, "client", r.RemoteAddr) socketProxy.ServeHTTP(w, r) // proxy the request diff --git a/cmd/socket-proxy/main.go b/cmd/socket-proxy/main.go index 09b6c2b..0f3d7bf 100644 --- a/cmd/socket-proxy/main.go +++ b/cmd/socket-proxy/main.go @@ -58,9 +58,9 @@ 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) + 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) } 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) + 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("proxysocketendpoint is set, so the TCP listener is deactivated") } if cfg.WatchdogInterval > 0 { diff --git a/internal/config/config.go b/internal/config/config.go index 3b6292e..decae12 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -9,6 +9,7 @@ import ( "net" "net/http" "os" + "path/filepath" "regexp" "strconv" "strings" @@ -27,6 +28,7 @@ var ( defaultStopOnWatchdog = false // set to true to stop the program when the socket gets unavailable (otherwise log only) defaultProxySocketEndpoint = "" // empty string means no socket listener, but regular TCP listener defaultProxySocketEndpointFileMode = uint(0o400) // set the file mode of the unix socket endpoint + defaultAllowBindMountFrom = "" // empty string means no bind mount restrictions ) type Config struct { @@ -42,6 +44,7 @@ type Config struct { SocketPath string ProxySocketEndpoint string ProxySocketEndpointFileMode os.FileMode + AllowBindMountFrom []string } // used for list of allowed requests @@ -69,12 +72,13 @@ var mr = []methodRegex{ func InitConfig() (*Config, error) { var ( - cfg Config - allowFromString string - listenIP string - proxyPort uint - logLevel string - endpointFileMode uint + cfg Config + allowFromString string + listenIP string + proxyPort uint + logLevel string + endpointFileMode uint + allowBindMountFromString string ) if val, ok := os.LookupEnv("SP_ALLOWFROM"); ok && val != "" { @@ -127,6 +131,9 @@ func InitConfig() (*Config, error) { defaultProxySocketEndpointFileMode = uint(parsedVal) } } + if val, ok := os.LookupEnv("SP_ALLOWBINDMOUNTFROM"); ok && val != "" { + defaultAllowBindMountFrom = val + } for i := range mr { if val, ok := os.LookupEnv("SP_ALLOW_" + mr[i].method); ok && val != "" { @@ -152,6 +159,7 @@ func InitConfig() (*Config, error) { } flag.StringVar(&cfg.ProxySocketEndpoint, "proxysocketendpoint", defaultProxySocketEndpoint, "unix socket endpoint (if set, used instead of the TCP listener)") flag.UintVar(&endpointFileMode, "proxysocketendpointfilemode", defaultProxySocketEndpointFileMode, "set the file mode of the unix socket endpoint") + flag.StringVar(&allowBindMountFromString, "allowbindmountfrom", defaultAllowBindMountFrom, "allowed directories for bind mounts (comma-separated)") for i := range mr { flag.StringVar(&mr[i].regexStringFromParam, "allow"+mr[i].method, "", "regex for "+mr[i].method+" requests (not set means method is not allowed)") } @@ -160,6 +168,17 @@ func InitConfig() (*Config, error) { // parse comma-separeted allowFromString into allowFrom slice cfg.AllowFrom = strings.Split(allowFromString, ",") + // parse allowBindMountFromString into AllowBindMountFrom slice and validate + if allowBindMountFromString != "" { + cfg.AllowBindMountFrom = strings.Split(allowBindMountFromString, ",") + for i, dir := range cfg.AllowBindMountFrom { + if !strings.HasPrefix(dir, "/") { + return nil, fmt.Errorf("bind mount directory must start with /: %q", dir) + } + cfg.AllowBindMountFrom[i] = filepath.Clean(dir) + } + } + // check listenIP and proxyPort if net.ParseIP(listenIP) == nil { return nil, fmt.Errorf("invalid IP \"%s\" for listenip", listenIP) From ea7947d8a0a8e1e412181385b52407c8701fe5d0 Mon Sep 17 00:00:00 2001 From: wollomatic Date: Wed, 16 Jul 2025 19:30:35 +0200 Subject: [PATCH 02/14] update license information, add to Changelog --- LICENSE | 197 ++++++++++++++++++++++++++++++++++ README.md | 8 +- cmd/socket-proxy/bindmount.go | 26 +++++ 3 files changed, 229 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 745db2f..d968fa0 100644 --- a/LICENSE +++ b/LICENSE @@ -19,3 +19,200 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- +Parts of this project, specifically the file cmd/internal/bindmount.go, +contain source code licensed under the Apache License 2.0. See the comments +in that file for details. +The rest of the project is licensed under the MIT License. + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2013-2018 Docker, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md index 443bfa4..321f0b5 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,7 @@ socket-proxy can be configured via command line parameters or via environment va 1.2 - reformat logging of allowlist on program start -1.3 - allow multiple, comma-separated hostnames in `-allowfrom` parameter +1.3 - allow multiple, comma-separated hostnames in `-allowfrom` parameter (thanks [@ildyria]) 1.4 - allow configuration from env variables @@ -228,9 +228,13 @@ 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]) + ## License +This project is licensed under the MIT License – see the [LICENSE](LICENSE) file for details. -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. +Parts of the file `cmd/internal/bindmount.go` are licensed under the Apache 2.0 License. +See the comments in this file and the LICENSE file for more information. ## Aknowledgements diff --git a/cmd/socket-proxy/bindmount.go b/cmd/socket-proxy/bindmount.go index 0c0dc93..5b6ba16 100644 --- a/cmd/socket-proxy/bindmount.go +++ b/cmd/socket-proxy/bindmount.go @@ -11,6 +11,32 @@ import ( "strings" ) +/* +The subsets of github.com/docker/docker/api/types/ are licensed under a Apache 2.0 license. + +NOTICE regarding this file only: + +Docker +Copyright 2012-2017 Docker, Inc. + +This product includes software developed at Docker, Inc. (https://www.docker.com). + +This product contains software (https://github.com/creack/pty) developed +by Keith Rarick, licensed under the MIT License. + +The following is courtesy of our legal counsel: + + +Use and transfer of Docker may be subject to certain restrictions by the +United States and other governments. +It is your responsibility to ensure that your use and/or transfer does not +violate applicable laws. + +For more information, please see https://www.bis.doc.gov + +See also https://www.apache.org/dev/crypto.html and/or seek legal counsel. +*/ + // mountType is the subset of github.com/docker/docker/api/types/mount.Type. type mountType string From 1494959f89d1c2e053f4485fa626fe11327edfec Mon Sep 17 00:00:00 2001 From: wollomatic Date: Wed, 16 Jul 2025 19:31:41 +0200 Subject: [PATCH 03/14] Go 1.24.5 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7877ef4..b7e74d6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1 -FROM --platform=$BUILDPLATFORM golang:1.24.4-alpine3.22 AS build +FROM --platform=$BUILDPLATFORM golang:1.24.5-alpine3.22 AS build WORKDIR /application COPY . ./ ARG TARGETOS From 9ed3cfdd93ad24b3476a099878e4b209386adb8d Mon Sep 17 00:00:00 2001 From: wollomatic Date: Wed, 16 Jul 2025 19:45:14 +0200 Subject: [PATCH 04/14] fix wrong fmt verb in error message --- internal/config/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index decae12..9f86691 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -149,13 +149,13 @@ func InitConfig() (*Config, error) { flag.UintVar(&proxyPort, "proxyport", defaultProxyPort, "tcp port to listen on") flag.UintVar(&cfg.ShutdownGraceTime, "shutdowngracetime", defaultShutdownGraceTime, "maximum time in seconds to wait for the server to shut down gracefully") if cfg.ShutdownGraceTime > math.MaxInt { - return nil, fmt.Errorf("shutdowngracetime has to be smaller than %i", math.MaxInt) // this maximum value has no practical significance + return nil, fmt.Errorf("shutdowngracetime has to be smaller than %d", math.MaxInt) // this maximum value has no practical significance } flag.StringVar(&cfg.SocketPath, "socketpath", defaultSocketPath, "unix socket path to connect to") flag.BoolVar(&cfg.StopOnWatchdog, "stoponwatchdog", defaultStopOnWatchdog, "stop the program when the socket gets unavailable (otherwise log only)") flag.UintVar(&cfg.WatchdogInterval, "watchdoginterval", defaultWatchdogInterval, "watchdog interval in seconds (0 to disable)") if cfg.WatchdogInterval > math.MaxInt { - return nil, fmt.Errorf("watchdoginterval has to be smaller than %i", math.MaxInt) // this maximum value has no practical significance + return nil, fmt.Errorf("watchdoginterval has to be smaller than %d", math.MaxInt) // this maximum value has no practical significance } flag.StringVar(&cfg.ProxySocketEndpoint, "proxysocketendpoint", defaultProxySocketEndpoint, "unix socket endpoint (if set, used instead of the TCP listener)") flag.UintVar(&endpointFileMode, "proxysocketendpointfilemode", defaultProxySocketEndpointFileMode, "set the file mode of the unix socket endpoint") From efdb2c2f33a75a0d1c3bfa575b399350ac1dc47d Mon Sep 17 00:00:00 2001 From: wollomatic Date: Wed, 16 Jul 2025 19:45:32 +0200 Subject: [PATCH 05/14] skip tests on non unix runtimes --- cmd/socket-proxy/bindmount_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cmd/socket-proxy/bindmount_test.go b/cmd/socket-proxy/bindmount_test.go index fe3c597..6fe8d10 100644 --- a/cmd/socket-proxy/bindmount_test.go +++ b/cmd/socket-proxy/bindmount_test.go @@ -3,12 +3,24 @@ package main import ( "bytes" "net/http" + "runtime" "testing" "github.com/wollomatic/socket-proxy/internal/config" ) +func skipIfNotUnix(t *testing.T) { + switch runtime.GOOS { + case "linux", "darwin", "freebsd", "openbsd", "netbsd", "dragonfly", "solaris", "aix": + // Supported Unix platforms + default: + t.Skip("skipping test: only runs on Unix-like systems") + } +} + func TestValidateBindMountSource(t *testing.T) { + skipIfNotUnix(t) + cfg = &config.Config{ AllowBindMountFrom: []string{"/home", "/var/log"}, } @@ -44,6 +56,8 @@ func TestValidateBindMountSource(t *testing.T) { } func TestIsPathAllowed(t *testing.T) { + skipIfNotUnix(t) + tests := []struct { name string path string @@ -81,6 +95,8 @@ func TestIsPathAllowed(t *testing.T) { } func TestValidateBindMount(t *testing.T) { + skipIfNotUnix(t) + cfg = &config.Config{ AllowBindMountFrom: []string{"/home", "/var/log"}, } @@ -111,6 +127,8 @@ func TestValidateBindMount(t *testing.T) { } func TestCheckBindMountRestrictions(t *testing.T) { + skipIfNotUnix(t) + cfg = &config.Config{ AllowBindMountFrom: []string{"/home"}, } From 144e3ce239da961b2591909a26501d8797e60cf5 Mon Sep 17 00:00:00 2001 From: wollomatic Date: Wed, 16 Jul 2025 19:56:57 +0200 Subject: [PATCH 06/14] execute tests in Dockerfile --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index b7e74d6..6a26636 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,7 @@ COPY . ./ ARG TARGETOS ARG TARGETARCH ARG VERSION +RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go test ./... RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ go build -tags=netgo -gcflags=all=-d=checkptr -ldflags="-w -s -X 'main.version=${VERSION}'" -trimpath \ -o / ./... From b0f42ff3e59aea9bf5926de5d83f45bee5ed4777 Mon Sep 17 00:00:00 2001 From: wollomatic Date: Wed, 16 Jul 2025 20:03:33 +0200 Subject: [PATCH 07/14] don't do cross-testing --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6a26636..11788df 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ COPY . ./ ARG TARGETOS ARG TARGETARCH ARG VERSION -RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go test ./... +RUN go test ./... RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ go build -tags=netgo -gcflags=all=-d=checkptr -ldflags="-w -s -X 'main.version=${VERSION}'" -trimpath \ -o / ./... From cf425e01840ab9414561eed5a82389127fc55ed4 Mon Sep 17 00:00:00 2001 From: wollomatic Date: Sat, 26 Jul 2025 12:42:18 +0200 Subject: [PATCH 08/14] update Cosign + Cosign-Installer in GH action. Build images for Docker and GHCR separately to avoid useless signatures --- .github/workflows/docker-image-release.yaml | 32 +++++++----- .github/workflows/docker-image-testing.yaml | 54 +++++++++++++++------ 2 files changed, 60 insertions(+), 26 deletions(-) diff --git a/.github/workflows/docker-image-release.yaml b/.github/workflows/docker-image-release.yaml index 083dcfd..fd62ce4 100644 --- a/.github/workflows/docker-image-release.yaml +++ b/.github/workflows/docker-image-release.yaml @@ -26,9 +26,9 @@ jobs: run: echo "::set-output name=VERSION::${GITHUB_REF#refs/tags/}" - name: Install Cosign - uses: sigstore/cosign-installer@v3.8.1 + uses: sigstore/cosign-installer@v3.9.2 with: - cosign-release: 'v2.4.3' + cosign-release: 'v2.5.3' - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -46,9 +46,9 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push Docker image + - name: Build and push Docker Hub image uses: docker/build-push-action@v5 - id: build-and-push + id: push-dockerhub with: context: . platforms: linux/amd64,linux/arm/v7,linux/arm64 @@ -57,17 +57,27 @@ jobs: tags: | docker.io/wollomatic/socket-proxy:${{ steps.get_tag.outputs.VERSION }} docker.io/wollomatic/socket-proxy:1 - ghcr.io/wollomatic/socket-proxy:${{ steps.get_tag.outputs.VERSION }} - ghcr.io/wollomatic/socket-proxy:1 - - name: Sign images for Docker - run: cosign sign --yes --recursive --key env://COSIGN_PRIVATE_KEY docker.io/wollomatic/socket-proxy:${{ steps.get_tag.outputs.VERSION }}@${{ steps.build-and-push.outputs.digest }} + - name: Sign Docker Hub image + run: cosign sign --yes --recursive --key env://COSIGN_PRIVATE_KEY docker.io/wollomatic/socket-proxy:${{ steps.get_tag.outputs.VERSION }}@${{ steps.push-dockerhub.outputs.digest }} env: COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} - - name: Sign images for GHCR - run: cosign sign --yes --recursive --key env://COSIGN_PRIVATE_KEY ghcr.io/wollomatic/socket-proxy:${{ steps.get_tag.outputs.VERSION }}@${{ steps.build-and-push.outputs.digest }} + - name: Build and push GHCR image + uses: docker/build-push-action@v5 + id: push-ghcr + with: + context: . + platforms: linux/amd64,linux/arm/v7,linux/arm64 + push: true + build-args: VERSION=${{ steps.get_tag.outputs.VERSION }} + tags: | + ghcr.io/wollomatic/socket-proxy:${{ steps.get_tag.outputs.VERSION }} + ghcr.io/wollomatic/socket-proxy:1 + + - name: Sign GHCR image + run: cosign sign --yes --recursive --key env://COSIGN_PRIVATE_KEY ghcr.io/wollomatic/socket-proxy:${{ steps.get_tag.outputs.VERSION }}@${{ steps.push-ghcr.outputs.digest }} env: COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} - COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} \ No newline at end of file diff --git a/.github/workflows/docker-image-testing.yaml b/.github/workflows/docker-image-testing.yaml index f3d34b4..10a4d16 100644 --- a/.github/workflows/docker-image-testing.yaml +++ b/.github/workflows/docker-image-testing.yaml @@ -22,10 +22,10 @@ jobs: with: args: ./... -# - name: Install Cosign -# uses: sigstore/cosign-installer@v3.8.1 -# with: -# cosign-release: 'v2.4.3' + - name: Install Cosign + uses: sigstore/cosign-installer@v3.9.2 + with: + cosign-release: 'v2.5.3' - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -57,14 +57,38 @@ jobs: ghcr.io/wollomatic/socket-proxy:testing ghcr.io/wollomatic/socket-proxy:testing-${{ github.sha }} -# - name: Sign Docker Hub image -# run: cosign sign --yes --recursive --key env://COSIGN_PRIVATE_KEY docker.io/wollomatic/socket-proxy:testing-${{ github.sha }}@${{ steps.build-and-push.outputs.digest }} -# env: -# COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} -# COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} -# -# - name: Sign GitHub Container Registry image -# run: cosign sign --yes --recursive --key env://COSIGN_PRIVATE_KEY ghcr.io/wollomatic/socket-proxy:testing-${{ github.sha }}@${{ steps.build-and-push.outputs.digest }} -# env: -# COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} -# COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + - name: Build and push Docker Hub image + uses: docker/build-push-action@v5 + id: push-dockerhub + with: + context: . + platforms: linux/amd64,linux/arm/v7,linux/arm64 + push: true + build-args: VERSION=testing-${{ github.sha }} + tags: | + docker.io/wollomatic/socket-proxy:testing + docker.io/wollomatic/socket-proxy:testing-${{ github.sha }} + + - name: Sign Docker Hub image + run: cosign sign --yes --recursive --key env://COSIGN_PRIVATE_KEY docker.io/wollomatic/socket-proxy:${{ steps.get_tag.outputs.VERSION }}@${{ steps.push-dockerhub.outputs.digest }} + env: + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + + - name: Build and push GHCR image + uses: docker/build-push-action@v5 + id: push-ghcr + with: + context: . + platforms: linux/amd64,linux/arm/v7,linux/arm64 + push: true + build-args: VERSION=testing-${{ github.sha }} + tags: | + ghcr.io/wollomatic/socket-proxy:testing + ghcr.io/wollomatic/socket-proxy:testing-${{ github.sha }} + + - name: Sign GHCR image + run: cosign sign --yes --recursive --key env://COSIGN_PRIVATE_KEY ghcr.io/wollomatic/socket-proxy:${{ steps.get_tag.outputs.VERSION }}@${{ steps.push-ghcr.outputs.digest }} + env: + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} \ No newline at end of file From 97cb357e01da7bc4126632fba1bdc9106052f8e6 Mon Sep 17 00:00:00 2001 From: wollomatic Date: Sat, 26 Jul 2025 12:44:34 +0200 Subject: [PATCH 09/14] remove redundant step --- .github/workflows/docker-image-testing.yaml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/.github/workflows/docker-image-testing.yaml b/.github/workflows/docker-image-testing.yaml index 10a4d16..d7f225b 100644 --- a/.github/workflows/docker-image-testing.yaml +++ b/.github/workflows/docker-image-testing.yaml @@ -43,20 +43,6 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push Docker image - uses: docker/build-push-action@v5 - id: build-and-push - with: - context: . - platforms: linux/amd64,linux/arm/v7,linux/arm64 - push: true - build-args: VERSION=testing-${{ github.sha }} - tags: | - docker.io/wollomatic/socket-proxy:testing - docker.io/wollomatic/socket-proxy:testing-${{ github.sha }} - ghcr.io/wollomatic/socket-proxy:testing - ghcr.io/wollomatic/socket-proxy:testing-${{ github.sha }} - - name: Build and push Docker Hub image uses: docker/build-push-action@v5 id: push-dockerhub From 416b936363d9a369b90ea23ddf17468fb8442c73 Mon Sep 17 00:00:00 2001 From: wollomatic Date: Sat, 26 Jul 2025 15:52:32 +0200 Subject: [PATCH 10/14] fix copy&paste error in test build action --- .github/workflows/docker-image-testing.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-image-testing.yaml b/.github/workflows/docker-image-testing.yaml index d7f225b..f1f3558 100644 --- a/.github/workflows/docker-image-testing.yaml +++ b/.github/workflows/docker-image-testing.yaml @@ -56,7 +56,7 @@ jobs: docker.io/wollomatic/socket-proxy:testing-${{ github.sha }} - name: Sign Docker Hub image - run: cosign sign --yes --recursive --key env://COSIGN_PRIVATE_KEY docker.io/wollomatic/socket-proxy:${{ steps.get_tag.outputs.VERSION }}@${{ steps.push-dockerhub.outputs.digest }} + run: cosign sign --yes --recursive --key env://COSIGN_PRIVATE_KEY docker.io/wollomatic/socket-proxy:testing-${{ github.sha }}@${{ steps.push-dockerhub.outputs.digest }} env: COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} @@ -74,7 +74,7 @@ jobs: ghcr.io/wollomatic/socket-proxy:testing-${{ github.sha }} - name: Sign GHCR image - run: cosign sign --yes --recursive --key env://COSIGN_PRIVATE_KEY ghcr.io/wollomatic/socket-proxy:${{ steps.get_tag.outputs.VERSION }}@${{ steps.push-ghcr.outputs.digest }} + run: cosign sign --yes --recursive --key env://COSIGN_PRIVATE_KEY ghcr.io/wollomatic/socket-proxy:testing-${{ github.sha }}@${{ steps.push-ghcr.outputs.digest }} env: COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} \ No newline at end of file From b4cd1be83381f46155b2ae98812a26e8fda94062 Mon Sep 17 00:00:00 2001 From: wollomatic Date: Sat, 26 Jul 2025 16:13:39 +0200 Subject: [PATCH 11/14] test in action instead of in Dockerfile --- .github/workflows/docker-image-testing.yaml | 113 +++++++++++--------- Dockerfile | 1 - 2 files changed, 61 insertions(+), 53 deletions(-) diff --git a/.github/workflows/docker-image-testing.yaml b/.github/workflows/docker-image-testing.yaml index f1f3558..7cac81e 100644 --- a/.github/workflows/docker-image-testing.yaml +++ b/.github/workflows/docker-image-testing.yaml @@ -22,59 +22,68 @@ jobs: with: args: ./... - - name: Install Cosign - uses: sigstore/cosign-installer@v3.9.2 + - name: Set up Go + uses: actions/setup-go@v5 with: - cosign-release: 'v2.5.3' + go-version: '1.24.5' - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: Run Go tests + run: go test ./... - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push Docker Hub image - uses: docker/build-push-action@v5 - id: push-dockerhub - with: - context: . - platforms: linux/amd64,linux/arm/v7,linux/arm64 - push: true - build-args: VERSION=testing-${{ github.sha }} - tags: | - docker.io/wollomatic/socket-proxy:testing - docker.io/wollomatic/socket-proxy:testing-${{ github.sha }} - - - name: Sign Docker Hub image - run: cosign sign --yes --recursive --key env://COSIGN_PRIVATE_KEY docker.io/wollomatic/socket-proxy:testing-${{ github.sha }}@${{ steps.push-dockerhub.outputs.digest }} - env: - COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} - COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} - - - name: Build and push GHCR image - uses: docker/build-push-action@v5 - id: push-ghcr - with: - context: . - platforms: linux/amd64,linux/arm/v7,linux/arm64 - push: true - build-args: VERSION=testing-${{ github.sha }} - tags: | - ghcr.io/wollomatic/socket-proxy:testing - ghcr.io/wollomatic/socket-proxy:testing-${{ github.sha }} - - name: Sign GHCR image - run: cosign sign --yes --recursive --key env://COSIGN_PRIVATE_KEY ghcr.io/wollomatic/socket-proxy:testing-${{ github.sha }}@${{ steps.push-ghcr.outputs.digest }} - env: - COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} - COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} \ No newline at end of file +# - name: Install Cosign +# uses: sigstore/cosign-installer@v3.9.2 +# with: +# cosign-release: 'v2.5.3' +# +# - name: Set up Docker Buildx +# uses: docker/setup-buildx-action@v3 +# +# - name: Login to Docker Hub +# uses: docker/login-action@v3 +# with: +# username: ${{ secrets.DOCKERHUB_USERNAME }} +# password: ${{ secrets.DOCKERHUB_TOKEN }} +# +# - name: Login to GitHub Container Registry +# uses: docker/login-action@v3 +# with: +# registry: ghcr.io +# username: ${{ github.actor }} +# password: ${{ secrets.GITHUB_TOKEN }} +# +# - name: Build and push Docker Hub image +# uses: docker/build-push-action@v5 +# id: push-dockerhub +# with: +# context: . +# platforms: linux/amd64,linux/arm/v7,linux/arm64 +# push: true +# build-args: VERSION=testing-${{ github.sha }} +# tags: | +# docker.io/wollomatic/socket-proxy:testing +# docker.io/wollomatic/socket-proxy:testing-${{ github.sha }} +# +# - name: Sign Docker Hub image +# run: cosign sign --yes --recursive --key env://COSIGN_PRIVATE_KEY docker.io/wollomatic/socket-proxy:testing-${{ github.sha }}@${{ steps.push-dockerhub.outputs.digest }} +# env: +# COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} +# COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} +# +# - name: Build and push GHCR image +# uses: docker/build-push-action@v5 +# id: push-ghcr +# with: +# context: . +# platforms: linux/amd64,linux/arm/v7,linux/arm64 +# push: true +# build-args: VERSION=testing-${{ github.sha }} +# tags: | +# ghcr.io/wollomatic/socket-proxy:testing +# ghcr.io/wollomatic/socket-proxy:testing-${{ github.sha }} +# +# - name: Sign GHCR image +# run: cosign sign --yes --recursive --key env://COSIGN_PRIVATE_KEY ghcr.io/wollomatic/socket-proxy:testing-${{ github.sha }}@${{ steps.push-ghcr.outputs.digest }} +# env: +# COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} +# COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 11788df..b7e74d6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,6 @@ COPY . ./ ARG TARGETOS ARG TARGETARCH ARG VERSION -RUN go test ./... RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ go build -tags=netgo -gcflags=all=-d=checkptr -ldflags="-w -s -X 'main.version=${VERSION}'" -trimpath \ -o / ./... From 626b561f357a84423fc513fc89a83866b20e91c3 Mon Sep 17 00:00:00 2001 From: wollomatic Date: Sat, 26 Jul 2025 16:52:04 +0200 Subject: [PATCH 12/14] update buildx action --- .github/workflows/docker-image-testing.yaml | 49 +++++++++++++-------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/.github/workflows/docker-image-testing.yaml b/.github/workflows/docker-image-testing.yaml index 7cac81e..19d4bc0 100644 --- a/.github/workflows/docker-image-testing.yaml +++ b/.github/workflows/docker-image-testing.yaml @@ -30,28 +30,41 @@ jobs: - name: Run Go tests run: go test ./... - # - name: Install Cosign # uses: sigstore/cosign-installer@v3.9.2 # with: # cosign-release: 'v2.5.3' -# -# - name: Set up Docker Buildx -# uses: docker/setup-buildx-action@v3 -# -# - name: Login to Docker Hub -# uses: docker/login-action@v3 -# with: -# username: ${{ secrets.DOCKERHUB_USERNAME }} -# password: ${{ secrets.DOCKERHUB_TOKEN }} -# -# - name: Login to GitHub Container Registry -# uses: docker/login-action@v3 -# with: -# registry: ghcr.io -# username: ${{ github.actor }} -# password: ${{ secrets.GITHUB_TOKEN }} -# + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push image to Docker Hub and GHCR + uses: docker/build-push-action@v6 + id: push-all + with: + context: . + platforms: linux/amd64,linux/arm/v7,linux/arm64 + push: true + build-args: VERSION=testing-${{ github.sha }} + tags: | + docker.io/wollomatic/socket-proxy:testing + docker.io/wollomatic/socket-proxy:testing-${{ github.sha }} + ghcr.io/wollomatic/socket-proxy:testing + ghcr.io/wollomatic/socket-proxy:testing-${{ github.sha }} + # - name: Build and push Docker Hub image # uses: docker/build-push-action@v5 # id: push-dockerhub From a84301a08449789329836c5393e6de5c7ab031bf Mon Sep 17 00:00:00 2001 From: wollomatic Date: Sat, 26 Jul 2025 17:02:04 +0200 Subject: [PATCH 13/14] add Go tests as action step, replace deprecated syntax, update buildx step [skip ci] --- .github/workflows/docker-image-release.yaml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-image-release.yaml b/.github/workflows/docker-image-release.yaml index fd62ce4..344cfaa 100644 --- a/.github/workflows/docker-image-release.yaml +++ b/.github/workflows/docker-image-release.yaml @@ -21,9 +21,17 @@ jobs: with: args: ./... + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24.5' + + - name: Run Go tests + run: go test ./... + - name: Extract tag name id: get_tag - run: echo "::set-output name=VERSION::${GITHUB_REF#refs/tags/}" + run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" - name: Install Cosign uses: sigstore/cosign-installer@v3.9.2 @@ -47,7 +55,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push Docker Hub image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 id: push-dockerhub with: context: . @@ -65,7 +73,7 @@ jobs: COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} - name: Build and push GHCR image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 id: push-ghcr with: context: . From b9077b63e69cb10c25837f203ab696a71b7a1126 Mon Sep 17 00:00:00 2001 From: wollomatic Date: Sat, 26 Jul 2025 17:14:58 +0200 Subject: [PATCH 14/14] fix/add aknowledgements --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 321f0b5..9424183 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,7 @@ socket-proxy can be configured via command line parameters or via environment va 1.2 - reformat logging of allowlist on program start -1.3 - allow multiple, comma-separated hostnames in `-allowfrom` parameter (thanks [@ildyria]) +1.3 - allow multiple, comma-separated hostnames in `-allowfrom` parameter (thanks [@ildyria](https://github.com/ildyria)) 1.4 - allow configuration from env variables @@ -228,7 +228,7 @@ 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]) +1.8 - add optional bind mount restrictions (thanks [@powerman](https://github.com/powerman)) ## License This project is licensed under the MIT License – see the [LICENSE](LICENSE) file for details. @@ -240,3 +240,4 @@ 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 \ No newline at end of file