Hosting: TLS, routing and the web files¶
One origin serves both the web interface and the API
(client contracts §10.1, decision D1 (a)).
The component that terminates TLS routes /api/v1/ and /livez to the server and serves every
other path from the web files. The server listens on plain HTTP, serves no files and sends no CORS
header, and the API never accepts a cookie. The reference terminator is Caddy, at the image
CADDY_IMAGE in packaging/versions.env, pinned by digest.
Host names on this page are synthetic: bronzeward.example.com for the site and
idp.example.com for the identity provider.
The web archive¶
A release's bronzeward-web_<version>.tar.gz holds:
| File | What it is |
|---|---|
dist/ |
The web interface's static files. |
headers.json |
The response headers every page must carry (§10.2). |
Caddyfile |
The reference configuration, rendered from headers.json. Render it again rather than edit it. |
bronzeward-client.example.json |
The client-configuration file to copy and fill in (below). |
LICENSE-STATUS.md, THIRD_PARTY_NOTICES.txt |
The licence status and the notices of what the files include. |
No release is published yet (ginsys/bronzeward#16
holds the licence). To build the same archive from a checkout, run
BW_VERSION=v0.0.0-dev mise run package, which writes it to dist/.
Caddy¶
The Caddyfile reads every deployment value from the environment, so one rendering serves every
deployment:
| Variable | Value |
|---|---|
BW_SITE_ADDRESS |
The site, as https://bronzeward.example.com. Caddy obtains and renews its certificate. |
BW_SERVER |
The server's listen address, as 127.0.0.1:8080. |
BW_WEB_ROOT |
The directory holding the archive's dist/ files. |
BW_ISSUER_ORIGIN |
The identity provider's issuer origin, as https://idp.example.com. |
BW_PROVIDER_ORIGINS |
The issuer's origin, plus the origins of the token and revocation endpoints its discovery document names, separated by spaces. |
tar -xzf bronzeward-web_<version>.tar.gz
cd bronzeward-web_<version>
install -m 644 bronzeward-client.example.json dist/bronzeward-client.json # then fill it in
BW_SITE_ADDRESS=https://bronzeward.example.com BW_SERVER=127.0.0.1:8080 BW_WEB_ROOT="$PWD/dist" \
BW_ISSUER_ORIGIN=https://idp.example.com BW_PROVIDER_ORIGINS=https://idp.example.com \
caddy run --config Caddyfile --adapter caddyfile
Read the provider origins from the issuer's discovery document
(https://idp.example.com/.well-known/openid-configuration, members issuer, token_endpoint
and revocation_endpoint). Where all three share one origin, that origin is the whole list. A
missing origin makes the browser refuse sign-in or sign-out under the page's connect-src.
mise run hosting serves a built archive through this Caddyfile in the pinned image
(scripts/test/hosting-test.py). It checks every page's
headers against headers.json, with and without a Range header. It checks that /livez and
/api/v1/ reach the server, and that a client route or a missing file answers with the
application. A deployment that runs another Caddy release, or edits the
file, runs a configuration this repository has not tested.
Another terminator¶
Any TLS terminator works if it does what the Caddyfile does:
- Terminate TLS for the site, with TLS 1.2 or later and a certificate the operators' browsers and
CLI trust (§6). A private CA is added per CLI
context with
--certificate-authority, never by turning verification off. No option turns it off. - Route
/api/v1/*and/livezto the server, unchanged, and send nothing else there. - Serve every other path from
dist/, answering a path with no file withdist/index.html. - Send every header in
headers.jsonon every response from the web files, with{issuerOrigin}and{providerOrigins}replaced as above. Serve whole files only: ignore aRangeheader, as theCaddyfiledoes, because Caddy sends a refused range's answer without these headers. - Add no cookie, CORS header or authentication of its own.
The client-configuration file¶
bronzeward-client.json at the web root names the identity provider for both clients
(§5 item 4). It holds
no secret: both clients are public, and the file is read before anyone signs in.
{
"issuer": "https://idp.example.com",
"audience": "<the server's auth.oidc.audience>",
"cliClientId": "<the CLI client's ID>",
"webClientId": "<the web client's ID>",
"scopes": "<optional: scopes requested after openid offline_access>"
}
Leave scopes out when the provider needs none, and never list openid or offline_access
in it. The web interface reads the file from its own origin. The CLI imports it with
bronzewardctl context add <name> --from https://bronzeward.example.com, which stores the CLI
client's ID. The identity-provider page gives Zitadel's values.
The issuer must be https, or http to a loopback address. The server's own rule for its
issuer also allows the hosts listed in auth.oidc.plainHTTPHosts
(examples/bronzeward.yaml). The clients have no such list.