Files
dejima-guard/docker-compose.example.yml
branderson ed2a843406 Say what changed, hold alerts during expected changes, and shorten the commands
Alerts now name the kind of change and the watched area it falls under, from
a fixed list, and report lists each path as added, modified or removed.
ignore_paths keeps a change in the report without alerting on it.

dejima-guard expect opens a window that holds change and unreachable alerts,
then accepts at its end. Critical paths and containment still alert, and a
window never accepts a critical path.

accept now runs a check afterwards so the heartbeat resumes, status
summarises the guard's state, and bin/dejima-guard-docker runs any command in
the container from any directory.
2026-10-15 15:09:56 -07:00

120 lines
5.4 KiB
YAML

# The guard, as one service.
#
# AN EXAMPLE, not a compose file. Copy it to docker-compose.yml, edit it there,
# and start that - the copy is gitignored so your host and your URLs do not end
# up in a commit.
#
# Do not use it as an override. `docker compose up` reads docker-compose.yml
# and docker-compose.override.yml together and merges them, so an override
# leaves these example values in play rather than replacing them.
#
# As written it will not start: the host below is a placeholder the guard
# refuses, so this can never become a live config by being copied and
# forgotten.
services:
dejima-guard:
# Built here, not pulled: there is no registry for this. Which makes
# `--build` part of every command that starts it - `docker compose up -d`
# alone reuses the image it already has, so after a `git pull` it would run
# the old code and look like it had worked.
build: .
# image: gitbox.example.invalid/dejima/dejima-guard:latest
container_name: dejima-guard
# bin/dejima-guard-docker finds the container by this name. And an init,
# because `dejima-guard expect` leaves a process waiting for the window's
# end, which needs reaping once it is done.
init: true
restart: unless-stopped
environment:
# --- the machine being WATCHED, not the one this runs on ------------
#
# The devbox's hostname or address, as this container reaches it.
# Replace the placeholder: the guard refuses to start on CHANGE-ME.
DEJIMA_GUARD_DEVBOX_HOST: CHANGE-ME.example.invalid
# The account ON THE DEVBOX to log in as. Not root, and there is no
# default: the devbox runbook builds a machine with no root password
# and expects sudo from an ordinary account. The guard connects as that
# account and elevates for the two commands it runs.
DEJIMA_GUARD_DEVBOX_USER: your-devbox-account
# The private key, inside this container, to connect with. Mounted as a
# secret below rather than baked into the image.
DEJIMA_GUARD_SSH_KEY: /run/secrets/guard_key
# Where the devbox's host key is remembered. On the state volume,
# because a container filesystem is new every run - and without it,
# every collection fails host-key verification and reports the devbox
# unreachable. Already set in the image; here so it is visible.
DEJIMA_GUARD_SSH_KNOWN_HOSTS: /var/lib/dejima-guard/known_hosts
# --- where alerts go -----------------------------------------------
#
# Its OWN ntfy topic, not the one dejima pushes to. That URL lives on
# the devbox, so root there can publish to it: a compromised box could
# drown the topic or forge an all-clear. The token for this one goes in
# the mounted config, not here.
DEJIMA_GUARD_NTFY_URL: https://ntfy.example.invalid/devbox-alerts
# --- the dead-man's switch ------------------------------------------
#
# NOT HERE. heartbeat_url goes in the mounted config beside ntfy_token,
# because an Uptime Kuma push URL carries its token in the path: the URL
# is the credential rather than the destination. `docker inspect` shows
# everything in this block, and so does anything that can read /proc.
#
# What it grants is worth being plain about. Anyone holding that URL can
# tell Kuma this guard is healthy, which suppresses the one signal that
# reports a guard that has died - the single channel a compromised devbox
# cannot reach.
# --- this container --------------------------------------------------
#
# Seconds between collections. Sets the dead-man's switch granularity
# and how long an intruder has to notice the monitoring and scrub.
DEJIMA_GUARD_INTERVAL: "900"
# The name of the machine this container runs on, so an alert says
# where to go and run `dejima-guard report`. Optional.
# DEJIMA_GUARD_GUARD_NAME: your-docker-host
# Anything secret goes in the config file rather than here. Everything
# above is visible in `docker inspect` and to anything that can read
# /proc; a mounted secret is not. The guard reads the environment first
# and the file second, so this is the value that overrides.
DEJIMA_GUARD_CONF: /run/secrets/guard_config
# Mounted at /run/secrets/<name>, which is why CONF points there rather
# than at /etc. A `target:` naming an absolute path is a swarm feature and
# does not do what it looks like it does under plain `docker compose`.
secrets:
- guard_key
- guard_config
volumes:
# The baseline, the last report, and known_hosts. Losing this is not a
# clean slate: it is a guard that compares clean against everything and
# cannot verify the host it connects to.
- guard-state:/var/lib/dejima-guard
# It opens two outbound connections and writes one directory. Nothing here
# is a hardship, and this container holds an SSH key to a machine whose
# threat model says it may be owned.
read_only: true
tmpfs:
- /tmp
cap_drop: [ALL]
security_opt:
- no-new-privileges:true
# NO PORTS. Not "none needed today" - the reason a container is an
# acceptable home for this is that nothing can connect to it.
volumes:
guard-state:
secrets:
guard_key:
file: ./secrets/guard_key # mode 0600, the private key
guard_config:
file: ./secrets/config # ntfy_token, and anything else secret