Gatekeeper

Run Gatekeeper headless or in a container

Headless mode, signals, the status socket, the audit log, metrics, and the container image for a pod.

How-toFor developers

Connect runs Gatekeeper with a dashboard in a terminal. This page is for a service, a script, or a pod.

Headless

sh
gatekeeper run --headless

Log lines go to stdout, which is what a container or a systemd unit wants. --log-format json is for a log shipper; --log-level takes debug, info, warn or error.

Signals:

  • SIGHUP reloads the configuration in place and re-attests every endpoint. A reload that fails to load or validate changes nothing.
  • SIGINT and SIGTERM drain the listeners (--drain-timeout, 30 seconds by default) and exit.

Status from another process

Add an admin section so gatekeeper status in one terminal can report on a gatekeeper run in another. Loopback or a Unix socket only: it carries verdicts.

yaml
admin:  listen: unix:/run/user/1000/gatekeeper.sock

Audit log and metrics

yaml
audit:  file: ~/.local/state/confidential-gatekeeper/audit.logmetrics:  listen: 127.0.0.1:9464

The audit log is one JSON object per line: every verdict and every blocked request, never a request or response body. Each verdict line carries rootAnchor, one of trustedRoots, attested (registry) or attested (operator-pinned), so a review can tell what admitted a cloud without reconstructing the configuration in force at the time. Metrics are Prometheus, off unless metrics.listen is set.

Every tuning knob has an environment override, globally (CR_GATEKEEPER_REATTEST_INTERVAL) or per endpoint (CR_GATEKEEPER_ENDPOINT_ROUTER_FAIL_MODE). Precedence, weakest first: built-in defaults, the file, CR_GATEKEEPER_*, command-line flags.

The container image

What a person installs is a binary from a GitHub release. The other packaging is a container image, for a pod that runs Gatekeeper beside the process it serves:

ghcr.io/super-protocol/confidential-router/gatekeeper:<version>

It holds the same static binary the archives do, runs as uid 65532 on a read-only root filesystem, and writes nothing. Pin it by digest and give it nothing it does not need:

sh
docker run --rm --read-only --user 65532 \  -v ./config.yaml:/etc/gatekeeper/config.yaml:ro \  -p 8443:8443 ghcr.io/super-protocol/confidential-router/gatekeeper@sha256:…

The entrypoint is a supervisor rather than Gatekeeper itself. It waits for $GATEKEEPER_CONFIG (default /etc/gatekeeper/config.yaml) to hold a configuration Gatekeeper will run, starts gatekeeper run --headless, and sends SIGHUP whenever the file's contents change, so a configuration rendered by whoever owns the file is applied in place. $GATEKEEPER_WATCH_INTERVAL (default 2s) tunes the poll. SIGTERM is passed through.

Two things it does not forward: identical bytes (a rewrite that changed nothing does not re-attest every endpoint), and a file that is not a configuration (a writer that truncates before it writes is briefly an empty file). Render to a temporary file and rename, and the second case never happens.

One-shot commands skip the supervisor: --entrypoint /usr/local/bin/gatekeeper.

Policies

User policies are Rego modules, ANDed with the built-in pin policy, so a policy can narrow trust but never widen it:

yaml
policies:  - name: images-from-our-registry    file: ./policies/images.rego

The policy most operators want, every image from one registry and pinned by digest:

rego
package user.imagesimport rego.v1default allow := falseallow if {  count(input.evidence.containerImages) > 0  every image in input.evidence.containerImages {    startswith(image, "ghcr.io/super-protocol/")    contains(image, "@sha256:")  }}

Test a policy offline against a bundle you saved with gatekeeper verify router --json:

sh
gatekeeper policy test ./bundle.json --endpoint router

The offline run is policy-only: no signature, chain, freshness or live channel is checked, and it says so. The input document is specified in the repository: docs/contracts/rego-input.md.