harden(compose): scope host-gateway route to the web app only (audit finding)
A multi-agent security audit of this branch flagged that the new `extra_hosts: host.docker.internal:host-gateway` sat on the shared x-app/x-image anchor, so reverb/queue/scheduler inherited it too. Only the web `app` container serves the (admin-gated) "Clusev host" Docker view; the workers run background code with no role gate. Combined with the image's ability to decrypt the root HostCredential and open root SSH via HostCredential::toServer()+FleetService, every worker held a network path to a root shell on the host — defeating the manage-fleet gate that only exists in the HTTP layer. Not reachable as shipped (no job/scheduled code touches the host credential), but a least-privilege regression. Move extra_hosts off the anchor onto ONLY the `app` service (dev + prod). Verified: app resolves host.docker.internal, queue/scheduler do not. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>feat/v1-foundation
parent
4c67b278bd
commit
096ba3ca98
|
|
@ -32,14 +32,16 @@ x-image: &image
|
||||||
security_opt:
|
security_opt:
|
||||||
- "no-new-privileges:true"
|
- "no-new-privileges:true"
|
||||||
pids_limit: 1024
|
pids_limit: 1024
|
||||||
# Reach the Docker host's sshd from the PHP layer so the "Clusev host" Docker view (and any other
|
|
||||||
# host-targeted SSH) can talk to the real machine — same mapping the terminal sidecar uses.
|
|
||||||
extra_hosts:
|
|
||||||
- "host.docker.internal:host-gateway"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
<<: *image
|
<<: *image
|
||||||
|
# ONLY the web container serves the (admin-gated) "Clusev host" Docker view, so ONLY it gets a
|
||||||
|
# route to the host gateway. Keeping it off the reverb/queue/scheduler workers (background code,
|
||||||
|
# not role-gated HTTP) denies them a network path to a root shell on the host if compromised —
|
||||||
|
# least privilege for the stored root HostCredential.
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,6 @@ x-app: &app
|
||||||
APP_GID: "${HOST_GID:-1002}"
|
APP_GID: "${HOST_GID:-1002}"
|
||||||
image: clusev-app:dev
|
image: clusev-app:dev
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
# Reach the Docker host's sshd from the PHP layer so the "Clusev host" Docker view (and any
|
|
||||||
# other host-targeted SSH) can talk to the real machine (host.docker.internal → host gateway),
|
|
||||||
# the same mapping the terminal sidecar already uses.
|
|
||||||
extra_hosts:
|
|
||||||
- "host.docker.internal:host-gateway"
|
|
||||||
volumes:
|
volumes:
|
||||||
- .:/var/www/html
|
- .:/var/www/html
|
||||||
- ./run:/var/www/html/storage/app/restart-signal
|
- ./run:/var/www/html/storage/app/restart-signal
|
||||||
|
|
@ -34,6 +29,12 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "${APP_PORT:-80}:80"
|
- "${APP_PORT:-80}:80"
|
||||||
- "${VITE_PORT:-5173}:5173"
|
- "${VITE_PORT:-5173}:5173"
|
||||||
|
# ONLY the web container serves the (admin-gated) "Clusev host" Docker view, so ONLY it gets a
|
||||||
|
# route to the host gateway. Keeping it off the reverb/queue/scheduler workers (which run
|
||||||
|
# background code, not role-gated HTTP) denies them a network path to a root shell on the host
|
||||||
|
# if one were ever compromised — least privilege for the stored root HostCredential.
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
depends_on:
|
depends_on:
|
||||||
mariadb:
|
mariadb:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue