From c30a4f56b5bb31ac1980e5aacea2e837eebb945e Mon Sep 17 00:00:00 2001 From: f-bn Date: Wed, 5 Feb 2025 18:57:39 +0100 Subject: [PATCH] feat(traefik-docker-proxy): add initial image --- .../workflows/build-traefik-docker-proxy.yml | 50 +++++++++++++++++++ README.md | 1 + traefik-docker-proxy/Caddyfile | 19 +++++++ traefik-docker-proxy/Dockerfile | 31 ++++++++++++ traefik-docker-proxy/README.md | 27 ++++++++++ .../examples/docker-compose.yml | 39 +++++++++++++++ 6 files changed, 167 insertions(+) create mode 100644 .github/workflows/build-traefik-docker-proxy.yml create mode 100644 traefik-docker-proxy/Caddyfile create mode 100644 traefik-docker-proxy/Dockerfile create mode 100644 traefik-docker-proxy/README.md create mode 100644 traefik-docker-proxy/examples/docker-compose.yml diff --git a/.github/workflows/build-traefik-docker-proxy.yml b/.github/workflows/build-traefik-docker-proxy.yml new file mode 100644 index 0000000..6a55f0e --- /dev/null +++ b/.github/workflows/build-traefik-docker-proxy.yml @@ -0,0 +1,50 @@ +--- +name: Build Traefik Docker Proxy image +on: + push: + branches: + - main + paths: + - 'traefik-docker-proxy/**' + - '!traefik-docker-proxy/README.md' + - '!traefik-docker-proxy/examples/**' + - '.github/workflows/build-traefik-docker-proxy.yml' + workflow_dispatch: + schedule: + - cron: '35 4 * * 6' # Every Saturday at 04:35am +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + strategy: + matrix: + version: ['0.1.0'] + caddy_version: ['2.9.1'] + steps: + - name: Check-out repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push image to registry + uses: docker/build-push-action@v5 + with: + platforms: linux/amd64 + context: ./traefik-docker-proxy + file: ./traefik-docker-proxy/Dockerfile + push: true + build-args: | + CADDY_VERSION=${{ matrix.caddy_version }} + tags: | + ghcr.io/f-bn/traefik-docker-proxy:${{ matrix.version }} + ghcr.io/f-bn/traefik-docker-proxy:latest \ No newline at end of file diff --git a/README.md b/README.md index baf7e2a..d1b8989 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ This repository contains my personal custom OCI containers images. - [pgbouncer](./pgbouncer/) - [postgresql](./postgresql/) - [traefik](./traefik/) +- [traefik-docker-proxy](./traefik-docker-proxy/) - [valkey](./valkey/) - [watchtower](./watchtower/) diff --git a/traefik-docker-proxy/Caddyfile b/traefik-docker-proxy/Caddyfile new file mode 100644 index 0000000..7536699 --- /dev/null +++ b/traefik-docker-proxy/Caddyfile @@ -0,0 +1,19 @@ +{ + admin off + auto_https off + persist_config off + skip_install_trust + grace_period 5s +} + +http://localhost:2375 { + route { + @allowed_paths { + method GET + protocol http + path_regexp ^(/v[0-9\.]+)?/(info|events|networks|containers/json|containers/[^/]+/json|version)$ + } + reverse_proxy @allowed_paths unix//var/run/docker.sock + respond "Forbidden" 403 + } +} diff --git a/traefik-docker-proxy/Dockerfile b/traefik-docker-proxy/Dockerfile new file mode 100644 index 0000000..d3d2cc1 --- /dev/null +++ b/traefik-docker-proxy/Dockerfile @@ -0,0 +1,31 @@ +# --- Build stage --- +FROM docker.io/golang:1.23.5 AS build + +ARG TARGETOS +ARG TARGETARCH +ARG CADDY_VERSION="2.9.1" + +WORKDIR /build + +RUN go install github.com/caddyserver/xcaddy/cmd/xcaddy@v0.4.4 + +RUN xcaddy build "v${CADDY_VERSION}" --output traefik-docker-proxy + +# --- Final stage --- +FROM cgr.dev/chainguard/wolfi-base:latest + +COPY --from=build --chown=0:0 --chmod=0755 \ + /build/traefik-docker-proxy /usr/bin/traefik-docker-proxy + +COPY Caddyfile /etc/Caddyfile + +ENTRYPOINT [ "/usr/bin/traefik-docker-proxy" ] + +CMD [ "run", "--config", "/etc/Caddyfile" ] + +LABEL \ + org.opencontainers.image.title="traefik-docker-proxy" \ + org.opencontainers.image.source="https://github.com/f-bn/containers-images/traefik" \ + org.opencontainers.image.description="Custom Caddy image for limiting Traefik access to the Docker API through the socket" \ + org.opencontainers.image.licenses="Apache-2.0" \ + org.opencontainers.image.authors="Florian Bobin " \ No newline at end of file diff --git a/traefik-docker-proxy/README.md b/traefik-docker-proxy/README.md new file mode 100644 index 0000000..76bf2b4 --- /dev/null +++ b/traefik-docker-proxy/README.md @@ -0,0 +1,27 @@ +## General informations + +Custom Caddy image built from sources with specific configuration for proxying Docker socket to limit access to Docker API. + +Built with Go 1.23.5 + +## Why creating this image ? + +The image goal is to provide the most simple solution for limiting Traefik access to the Docker API through the socket. Therefore, this allows to not running Traefik as root and not mounting the Docker socket inside the Traefik container (that could lead to a pretty bad privilege escalation if Traefik is compromised). + +For a more flexible solution for multiple other use-cases, prefer [docker-socket-proxy](https://github.com/Tecnativa/docker-socket-proxy) from Tecnativa. + +## Image configuration + +This image come with a specific Caddy configuration for the use-case described above. The reverse proxy server listens on `http://localhost:2375` and will forward request to the Docker UNIX domain socket mounted inside this container. + +However only `GET` method on a specific set of Docker API endpoints are allowed: +- `/info` +- `/events` +- `/networks` +- `/containers/json` +- `/containers//json` +- `/version` + +For any other methods and/or endpoints called, a 403 error is returned. Moreover, endpoints required for Docker Swarm are not considered. + +You can find a basic [docker-compose.yml](./examples/docker-compose.yml) in the `examples` folder showing a barebone configuration using this container image (i.e absolutely not production-ready). \ No newline at end of file diff --git a/traefik-docker-proxy/examples/docker-compose.yml b/traefik-docker-proxy/examples/docker-compose.yml new file mode 100644 index 0000000..098f195 --- /dev/null +++ b/traefik-docker-proxy/examples/docker-compose.yml @@ -0,0 +1,39 @@ +--- +services: + traefik: + container_name: traefik + image: ghcr.io/f-bn/traefik:3.3.3 + ports: + - 80:80 + - 443:443 + - 8080:8080 + command: + - '--log.level=DEBUG' + - '--entrypoints.web.address=:80' + - '--entrypoints.websecure.address=:443' + - '--api' + - '--api.dashboard' + - '--api.insecure' + - '--providers.docker' + - '--providers.docker.endpoint=http://localhost:2375' + - '--providers.docker.exposedbydefault=false' + cap_drop: ['ALL'] + + traefik-docker-proxy: + container_name: traefik-docker-proxy + image: ghcr.io/f-bn/traefik-docker-proxy:0.1.0 + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + network_mode: container:traefik # note: join Traefik netns to strictly limit access of the proxy to the Traefik container + depends_on: + - traefik + + demo: + container_name: demo + image: docker.io/nginx:latest + labels: + - traefik.enable=true + - traefik.http.routers.demo.rule=Host(`demo.domain.local`) + depends_on: + - traefik + - traefik-docker-proxy \ No newline at end of file