| 1234567891011121314151617181920212223242526272829303132333435363738394041 |
- services:
- scheduler:
- image: irdb-scheduler:latest
- build: { context: ./scheduler }
- environment:
- INTERNAL_JOB_TOKEN: ${INTERNAL_JOB_TOKEN}
- # SEC_REVIEW F25: share the api container's network namespace so the
- # baked crontab can hit /internal/jobs/tick over loopback. The api's
- # /internal/* gate is restricted to 127.0.0.1/::1 (Caddy + PHP), so
- # the previous `api:8081` URL from a sibling docker-bridge peer
- # would now 404 — and that's the goal: a malicious neighbour on the
- # same bridge can no longer reach /internal/*. Side effect: this
- # service has no DNS / its own ports, but it needs neither.
- network_mode: "service:api"
- # SEC_REVIEW F22: dependencies (curl, tini, ca-certificates) are now
- # baked into the image at build time with pinned versions, against a
- # digest-pinned alpine base. The previous `image: alpine:3` +
- # `apk add` at container start trusted the apk mirror on every
- # restart and would have given a mirror compromise a foothold in the
- # container that holds INTERNAL_JOB_TOKEN.
- read_only: true
- # busybox crond writes a tiny tempfile when the schedule fires; /run
- # has to be writable for that. Everything else stays read-only.
- tmpfs:
- - /run:mode=0755
- - /tmp:mode=1777
- # busybox crond calls initgroups() before each exec, which needs
- # CAP_SETGID even when the target user is the same root it is
- # already running as — full cap_drop crashes it with
- # "can't set groups: Operation not permitted". Hardening the
- # process to non-root would mean shipping a custom cron binary;
- # not worth the maintenance cost given the container has no
- # persistent volume, no exposed port, and only INTERNAL_JOB_TOKEN
- # in env. `no-new-privileges` is still useful: there is no setuid
- # binary in the image and we want to keep it that way.
- security_opt:
- - no-new-privileges:true
- depends_on:
- api:
- condition: service_healthy
- restart: unless-stopped
|