Skip to content

wollomatic/socket-proxy

Repository files navigation

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 -stoponwatchdogis 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.

Aknowledgements