The Ur Project

Documenting the craft of stacking things so they remain stacked. For a very long time.

dispatch-web(1)

NAME

dispatch-web - web UI and HTTP proxy for the dispatcher build host

SYNOPSIS

dispatch-web serve [--root DIR] [--listen ADDR:PORT] [--allow-host NAME]... [--designer-dir DIR] [--stage-repo DIR] [--scratch-root DIR] [--auth-verify-key-file PATH]

dispatch-web serve [--config PATH] [--socket PATH] [--listen ADDR:PORT] [--allow-host NAME]...

dispatch-web check-config PATH

dispatch-web version

DESCRIPTION

The dispatcher, designer-api, license-api, cve-api and malware-api all listen on Unix sockets, and nothing else. None of them opens a TCP port. But you still want to look at your build host from a browser.

dispatch-web is the one piece that does listen on TCP. It serves the build host's dashboard, and it proxies the browser's requests through to those sockets. It runs as dispatch-web.service, under the dispatch user.

Who can do what?

Looking is free. Changing things isn't.

dispatch-web holds a Viewer-role token of its own, and adds it to any request it proxies to the dispatcher that doesn't already carry an Authorization header. The browser never sees that token. So anyone who can reach the dashboard can read it, and that's the whole point of a dashboard.

Every write needs the caller's own token. Requests to designer-api, license-api, cve-api and malware-api get no Viewer token at all: the caller's token is forwarded unchanged, or there is none. Accepting a proposal is a decision somebody has to put their name on. The one exception is designer's read-only review diffs, which do get the Viewer token. A Viewer token can't satisfy a decision endpoint, so that grants nothing.

Where does the Viewer token come from?

In layout mode, from /var/lib/dispatcher/keys/viewer.token if that file exists. It's used as-is, and it can't be renewed, since there's nothing behind it but the token itself.

Otherwise dispatch-web mints one in-process from the auth seed, valid for 24 hours, and renews it at half its life. A request never carries a token with less than half its life left.

With neither, the server still starts, and says so on stderr. Every proxied dispatcher read then gets a 401.

LISTENER

Who can reach it?

By default, only this machine. The listener is 127.0.0.1:8081 until you widen it.

On a deployed host, widen it in the [web] table of dispatch.toml(5), not in a unit drop-in:

[web] listen = "0.0.0.0:8081" allowed_hosts = ["ur-build.lan"]

then run systemctl restart dispatch-web. --listen overrides listen, and --allow-host adds to allowed_hosts.

Anyone who can reach a widened listener reads the dashboard. Widen it only on a network you trust.

Why is my browser getting 421?

Because of DNS rebinding. A web page on some other site, open in a browser on your LAN, can re-resolve its own name to your build host and read the dashboard through that browser. The reads need no credentials, so the only thing standing in the way is the Host header.

So a request whose Host names anything other than the following is refused with 421 Misdirected Request:

  • localhost
  • this machine's hostname, or its first label
  • an IP literal
  • a name in allowed_hosts or --allow-host

Names are compared without case and without a trailing dot. If you reach the UI by a DNS name the machine doesn't call itself, put that name in allowed_hosts. A request with no Host header at all passes, since a rebinding attack needs a browser, and a browser always sends one.

COMMANDS

serve (layout mode)

The default, and what a deployed host runs. Every path comes from the deployment layout: the dispatcher socket under /run/dispatcher/, keys, journals and repos under /var/lib/dispatcher/, caches under /var/cache/dispatcher/, and the [web] table of /etc/dispatcher/dispatch.toml.

A missing dispatch.toml, or one without a [web] table, means the defaults. A file that can't be read or parsed, an unknown key in [web], or an address that doesn't parse fails startup. A typo should never quietly fall back to loopback.

serve takes no positional arguments; the paths are fixed by the layout.

--root DIR
Re-root the whole layout under DIR, for a test tree.
--listen ADDR:PORT
Bind address, overriding [web] listen.
--allow-host NAME
Accept NAME as a Host header, on top of [web] allowed_hosts. Can be repeated.
--designer-dir DIR
Read the designer journal from DIR/journal.sqlite.
--stage-repo DIR
Pin the trust and packages views to the repo at DIR.
--scratch-root DIR
Read scratch build output from DIR.
--auth-verify-key-file PATH
The token verify key that gates upstream curation writes, in place of the one in /var/lib/dispatcher/keys/. A key named here has to load, or startup fails. Without this flag, a missing or bad key in the layout only disables curation writes, and stderr says why.

serve (explicit-config mode)

For development. A deployed host uses layout mode.

Passing --config or --socket selects it. It reads a standalone TOML config, by default /etc/dispatch/web.toml (missing means defaults), and proxies only the dispatcher socket.

--config PATH
The config file.
--socket PATH
The dispatcher socket.
--listen, --allow-host
Same as in layout mode.

check-config

Parses and validates an explicit-mode config file without starting the server. Prints ok and exits 0, or prints the error.

version

Prints the version.

EXIT STATUS

0
Success.
1
A runtime or configuration error.
2
A usage error: a missing or unknown subcommand or flag.

FILES

/etc/dispatcher/dispatch.toml
The [web] table.
/var/lib/dispatcher/keys/viewer.token
A pre-minted Viewer token. When it exists, it wins over minting.
/var/lib/dispatcher/keys/auth.seed
The auth seed a Viewer token is minted from. auth.seed.hex and auth-dev.seed.hex in the same directory are tried after it.
/var/lib/dispatcher/keys/auth.pub.hex
The verify key for upstream curation writes, unless --auth-verify-key-file names another.
/run/dispatcher/api.sock
The dispatcher socket.

EXAMPLES

You don't have to widen the listener just to use the UI from a laptop. An SSH tunnel does it:

ssh -L 8081:127.0.0.1:8081 build-host

then open http://localhost:8081/.

To try a wider bind once, without editing dispatch.toml:

sudo -u dispatch dispatch-web serve --listen 0.0.0.0:8082 \ --allow-host build-host.example.org

SEE ALSO

dispatch.toml(5)

docs/components/dispatch-web.md, docs/design/deployment-layout.md