socket-proxy
socket-proxy copied to clipboard
A secure unix socket proxy. Similar to tecnativa/docker-socket-proxy, but more flexible and written in Go with no dependencies
socket-proxy
About
socket-proxy
is a lightweight, secure-by-default unix socket proxy. Although it was created to proxy the docker socket to Traefik, it can also be used for other purposes.
It is heavily inspired by tecnativa/docker-socket-proxy.
As an additional benefit, socket-proxy can be used to examine the API calls of the client application.
The advantage over other solutions is the very slim container image (from-scratch-image) without any external dependencies (no OS, no packages, just the Go standard library). It is designed with security in mind, so there are secure defaults and an additional security layer (IP address-based access control) compared to most other solutions.
The allowlist is configured for each HTTP method separately using the Go regexp syntax, allowing fine-grained control over the allowed HTTP methods.
The source code is available on GitHub: wollomatic/socket-proxy.
Getting Started
Some examples can be found in the wiki and in the examples
directory of the repo.
Warning
You should know what you are doing. Never expose socket-proxy to a public network. It is meant to be used in a secure environment only.
Installing
The container image is available on Docker Hub: wollomatic/socket-proxy.
To pin one specific version, use the version tag (for example, wollomatic/socket-proxy:1.0.1
).
To always use the most recent version, use the 1
tag (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.
Every socket-proxy release image is signed with Cosign. The public key is available on GitHub: wollomatic/socket-proxy/main/cosign.pub and https://wollomatic.de/socket-proxy/cosign.pub. For more information, please refer to the Security Policy.
Allowing access
Because of the secure-by-default design, you need to allow every access explicitly.
This is meant to be an additional layer of security. It does not replace other security measures, such as firewalls, network segmentation, etc. Do not expose socket-proxy to a public network.
Setting up the TCP listener
Socket-proxy listens per default only on 127.0.0.1
. Depending on what you need, you may want to set another listener address with the -listenip
parameter. In almost every use case, -listenip=0.0.0.0
will be the correct configuration when using socket-proxy in a docker image.
Setting up the IP address or hostname allowlist
Per default, only 127.0.0.1/32
is allowed to connect to socket-proxy. You may want to set another allowlist with the -allowfrom
parameter, depending on your needs.
Alternatively, not only IP networks but also hostnames can be configured. So it is now possible to explicitly allow one or more specific hostnames to connect to the proxy, for example, -allowfrom=traefik
, or -allowfrom=traefik,dozzle
.
Using the hostname is an easy-to-configure way to have more security. Access to the socket proxy will not even be permitted from the host system.
Setting up the allowlist for requests
You must set up regular expressions for each HTTP method the client application needs access to.
The name of a parameter should be "-allow", followed by the HTTP method name (for example, -allowGET
). The request will be allowed if that parameter is set and the incoming request matches the method and path matching the regexp. If it is not set, then the corresponding HTTP method will not be allowed.
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.
Examples:
-
'-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.
For more information, refer to the Go regexp documentation.
An excellent online regexp tester is regex101.com.
To determine which HTTP requests your client application uses, you could switch socket-proxy to debug log level and look at the log output while allowing all requests in a secure environment.
Container health check
Health checks are disabled by default. As the socket-proxy container may not be exposed to a public network, a separate health check binary is included in the container image. To activate the health check, the -allowhealthcheck
parameter must be set. Then, a health check is possible for example with the following docker-compose snippet:
# [...]
healthcheck:
test: ["CMD", "./healthcheck"]
interval: 10s
timeout: 5s
retries: 2
# [...]
Socket watchdog
In certain circumstances (for example, after a Docker engine update), the socket connection may break, causing the client application to fail. To prevent this, the socket-proxy can be configured to check the socket availability at regular intervals. If the socket is not available, the socket-proxy will be stopped so the container orchestrator can restart it. This feature is disabled by default. To enable it, set the -watchdoginterval
parameter to the desired interval in seconds and set the -stoponwatchdog
parameter. If -stoponwatchdog
is not set, the watchdog will only log an error message and continue to run (the problem would still exist in that case).
Example for proxying the docker socket to Traefik
You need to know how to install Traefik in this environment. See wollomatic/traefik2-hardened for an example.
The image can be deployed with docker compose:
services:
dockerproxy:
image: wollomatic/socket-proxy:<<version>> # choose most recent image
restart: unless-stopped
user: "65534:<<your docker group id>>"
mem_limit: 64M
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges
command:
- '-loglevel=info'
- '-listenip=0.0.0.0'
- '-allowfrom=traefik' # allow only hostname "traefik" to connect
- '-allowGET=/v1\..{1,2}/(version|containers/.*|events.*)'
- '-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
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- docker-proxynet # NEVER EVER expose this to the public internet!
# this is a private network only for traefik and socket-proxy
# it is not the same as the traefik-servicenet
traefik:
# [...] see github.com/wollomatic/traefik2-hardened for a full example
depends_on:
- dockerproxy
networks:
- traefik-servicenet # this is the common traefik network
- docker-proxynet # this should be only restricted to traefik and socket-proxy
networks:
traefik-servicenet:
external: true
docker-proxynet:
driver: bridge
internal: true
Examining the API calls of the client application
To log the API calls of the client application, set the log level to DEBUG
and allow all requests. Then, you can examine the log output to determine which requests the client application makes. Allowing all requests can be done by setting the following parameters:
- '-loglevel=debug'
- '-allowGET=.*'
- '-allowHEAD=.*'
- '-allowPOST=.*'
- '-allowPUT=.*'
- '-allowPATCH=.*'
- '-allowDELETE=.*'
- '-allowCONNECT=.*'
- '-allowTRACE=.*'
- '-allowOPTIONS=.*'
Parameters
Parameter | Default Value | Description |
---|---|---|
-allowfrom |
127.0.0.1/32 |
Specifies the IP addresses 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 (comma-separated) 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. |
-allowhealthcheck |
(not set) | 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 |
127.0.0.1 |
Specifies the IP address the server will bind on. Default is only the internal network. |
-logjson |
(not set) | If set, it enables logging in JSON format. If unset, docker-proxy logs in plain text format. |
-loglevel |
INFO |
Sets the log level. Accepted values are: DEBUG , INFO , WARN , ERROR . |
-proxyport |
2375 |
Defines the TCP port the proxy listens to. |
-shutdowngracetime |
10 |
Defines the time in seconds to wait before forcing the shutdown after sigtern or sigint (socket-proxy first tries to graceful shut down the TCP server) |
-socketpath |
/var/run/docker.sock |
Specifies the UNIX socket path to connect to. By default, it connects to the Docker daemon socket. |
-stoponwatchdog |
(not set) | If set, socket-proxy will be stopped if the watchdog detects that the unix socket is not available. |
-watchdoginterval |
0 |
Check for socket availabibity every x seconds (disable checks, if not set or value is 0) |
Changelog
1.0 - initial release
1.1 - add hostname support for -allowfrom
parameter
1.2 - reformat logging of allowlist on program start
1.3 - allow multiple, comma-separated hostnames in -allowfrom
parameter
License
This project is licensed under the MIT License - see the LICENSE file for details.