diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 5c25064..bb08cc2 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -1,24 +1,14 @@ -name: Test and publish development image +name: Test and publish production image on: push: branches: - main - paths: - - .github/workflows/docker-image.yml - - Dockerfile - - go.mod - - src/** pull_request: - paths: - - .github/workflows/docker-image.yml - - Dockerfile - - go.mod - - src/** workflow_dispatch: concurrency: - group: docker-dev-${{ github.ref }} + group: docker-production-${{ github.ref }} cancel-in-progress: true env: @@ -73,16 +63,16 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - - name: Build and push development image + - name: Build and push production image uses: docker/build-push-action@v7 with: context: . file: ./Dockerfile push: true platforms: linux/amd64,linux/arm64 - tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest build-args: | - APP_VERSION=dev + APP_VERSION=latest APP_GIT_SHA=${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max diff --git a/README.md b/README.md index 1745a17..7fa0644 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,220 @@ # docker-socket-proxy -Proxy HTTP minimaliste devant le socket Docker. Les clients sont associés à un profil par leur adresse IP et les droits Docker sont refusés par défaut. +Proxy HTTP minimaliste et sécurisé devant le socket Docker. Les clients sont associés à un profil par leur adresse IP, découverte depuis leurs labels Docker ; tout accès qui n'est pas explicitement accordé est refusé. -## Image de développement +Le projet permet de limiter les familles d'API Docker, puis de restreindre les opérations à certains conteneurs. Il évite ainsi de monter directement `/var/run/docker.sock` dans une application. + +## Image de production ```text -ghcr.io/cerede2000/docker-socket-proxy:dev +ghcr.io/cerede2000/docker-socket-proxy:latest +``` + +L'image est multi-architecture (`linux/amd64` et `linux/arm64`), construite avec Go et exécutée sans privilèges dans Distroless Debian 13. Chaque mise à jour de `main` validée par la CI publie cette image. + +## Principes de sécurité + +- Aucun droit n'est accordé implicitement. +- Seuls les conteneurs portant `socketproxy.role` (ou l'alias `socketproxy.service`) et correspondant à un profil sont autorisés. +- Le proxy ne retient que les IP partagées avec ses propres réseaux Docker. +- Les listes, les événements et les opérations ciblant un conteneur respectent la même portée. +- Le cache interne nom / ID de conteneur évite une requête Docker supplémentaire pour les vérifications usuelles. + +## Démarrage rapide + +Créez un fichier `profiles.yml`, puis lancez le proxy. Le montage du socket est en lecture seule : les requêtes Docker restent possibles via l'API Unix, mais le fichier socket ne peut pas être remplacé depuis le conteneur. + +```yaml +services: + docker-socket-proxy: + image: ghcr.io/cerede2000/docker-socket-proxy:latest + container_name: docker-socket-proxy + user: "1000:998" # adapter à l'UID:GID pouvant lire le socket sur l'hôte + read_only: true + cap_drop: [ALL] + security_opt: + - no-new-privileges:true + tmpfs: + - /tmp + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./profiles.yml:/config/profiles.yml:ro + networks: + - socketproxy + restart: unless-stopped + +networks: + socketproxy: + internal: true +``` + +Le compte configuré avec `user` doit avoir accès au socket Docker de l'hôte. Vérifiez son UID et son GID avec `stat -c '%u:%g' /var/run/docker.sock` sous Linux, puis adaptez la valeur. Sur certaines installations, il est préférable de construire une image dérivée qui crée un groupe portant le GID réel du socket. + +## Associer un client à un profil + +Ajoutez l'un des deux labels suivants au conteneur client : + +```yaml +labels: + socketproxy.role: mon-profil + # ou : socketproxy.service: mon-profil +``` + +Le profil est rechargé automatiquement lorsque `profiles.yml` est modifié. La découverte des IP intervient au démarrage, lors des événements Docker pertinents, et périodiquement. + +## Exemple complet : Traefik et Traefik Manager + +Cet exemple sépare les rôles : Traefik peut lire les informations nécessaires au provider Docker ; Traefik Manager peut seulement consulter et redémarrer le conteneur `traefik`. Il ne peut ni agir sur les autres conteneurs ni lancer d'exec. + +`compose.yml` : + +```yaml +services: + docker-socket-proxy: + image: ghcr.io/cerede2000/docker-socket-proxy:latest + container_name: docker-socket-proxy + user: "1000:998" # à adapter à l'hôte + read_only: true + cap_drop: [ALL] + security_opt: + - no-new-privileges:true + tmpfs: + - /tmp + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./profiles.yml:/config/profiles.yml:ro + networks: [socketproxy] + restart: unless-stopped + + traefik: + image: traefik:v3 + container_name: traefik + command: + - --providers.docker=true + - --providers.docker.endpoint=tcp://docker-socket-proxy:2375 + - --providers.docker.exposedbydefault=false + labels: + socketproxy.role: traefik + networks: [socketproxy, frontend] + restart: unless-stopped + + traefik-manager: + image: ghcr.io/chr0nzz/traefik-manager:latest + container_name: traefik-manager + environment: + DOCKER_HOST: tcp://docker-socket-proxy:2375 + RESTART_METHOD: proxy + TRAEFIK_CONTAINER: traefik + labels: + socketproxy.role: traefik-manager + networks: [socketproxy, frontend] + restart: unless-stopped + +networks: + socketproxy: + internal: true + frontend: + external: true +``` + +`profiles.yml` : + +```yaml +traefik: + ping: true + version: true + containers: true + networks: true + events: true + session: true + +traefik-manager: + ping: true + version: true + containers: true + post: true + allow_restart: true + container_scope: allowlist + allowed_containers: + - traefik ``` -L'image est publiée pour `linux/amd64` et `linux/arm64` après validation des tests. Elle utilise un binaire Go statique dans Distroless Debian 13 et s'exécute sans privilèges par défaut. +`container_name: traefik` et `allowed_containers: [traefik]` doivent correspondre exactement. Le profil `traefik-manager` ne permet pas `start` ou `stop` : seul `POST /containers/traefik/restart` est admis. -## Configuration +## Configuration du proxy | Variable | Défaut | Description | | --- | --- | --- | -| `DOCKER_SOCKET_PATH` | `/var/run/docker.sock` | Chemin du socket Docker | -| `PROXY_PORT` | `2375` | Port d'écoute et port utilisé par le healthcheck | -| `PROXY_LISTEN` | vide | Adresse d'écoute complète, prioritaire sur `PROXY_PORT` | -| `SOCKETPROXY_PROFILE_FILE` | `/config/profiles.yml` | Fichier de profils | -| `DISCOVER_INTERVAL` | `30s` | Intervalle de redécouverte | -| `EVENT_DEBOUNCE_DELAY` | `100ms` | Temporisation des événements Docker | +| `DOCKER_SOCKET_PATH` | `/var/run/docker.sock` | Chemin du socket Docker à joindre | +| `PROXY_PORT` | `2375` | Port d'écoute et port du healthcheck intégré | +| `PROXY_LISTEN` | — | Adresse d'écoute complète ; prioritaire sur `PROXY_PORT` | +| `SOCKETPROXY_PROFILE_FILE` | `/config/profiles.yml` | Fichier YAML des profils | +| `DISCOVER_INTERVAL` | `30s` | Période de redécouverte des conteneurs ; durée Go (`15s`) ou nombre de secondes (`15`) | +| `EVENT_DEBOUNCE_DELAY` | `100ms` | Délai de regroupement des événements Docker ; durée Go ou nombre de millisecondes | + +Les arguments suivants sont disponibles et prioritaires sur les variables correspondantes : `--listen`, `--socket`, `--profiles`, `--discover-interval` et `--debounce-delay`. + +Le healthcheck appelle `http://127.0.0.1:$PROXY_PORT/version`. Si `--listen` ou `PROXY_LISTEN` utilise un autre port, renseignez `PROXY_PORT` avec ce même port. + +### Options de ligne de commande par profil + +Les profils peuvent aussi être définis dans la commande du conteneur. Le format est `--.