The Varnish Controller UI is served by the UI server
(varnish-controller-ui), a Varnish Controller component. It does the following:
/api/ to one or more API-GW instances and keeps the authentication
tokens in HttpOnly cookies instead of handing them to the browserX-Frame-Options)brainzUI servers are not registered individually in the Controller and have no per-instance configuration. As many as needed can be run behind a load balancer, and they all share one set of configuration values.
The following components of Varnish Controller must be installed and running before installation of the GUI.
sudo apt install varnish-controller-ui
sudo dnf -y install varnish-controller-ui
The UI server can be started with various configurations. Since the ui-server is running as
varnish-controller user it will not be able to start on a privileged port(< 1024), such as port
80. This can be instead handled by iptables, authbind or setcap.
Example using setcap: setcap 'cap_net_bind_service=+ep' /usr/bin/varnish-controller-ui
All arguments can also be set as environment variables (VARNISH_CONTROLLER_<ARGUMENT>, e.g.
VARNISH_CONTROLLER_API_HOSTS) or in the [ui-server] section of a configuration file (-config).
A skeleton configuration file can be printed with -generate-config. Order of precedence, where
1 has the highest precedence:
Arguments that takes a duration value, such as -read-timeout, uses
Go time Duration format.
Valid time units are “ns”, “us” (or “µs”), “ms”, “s”, “m”, “h”. A combination of units can be used
to express a duration, e.g. 1h10m30s.
The Runtime column states whether the setting can be changed on a running UI server from the
Controller. Booleans are given as -flag=true or -flag=false. A bare -flag means true. A full
list of arguments is also printed by varnish-controller-ui --help.
| Argument | Default | Runtime | Description |
|---|---|---|---|
-http-host |
0.0.0.0 |
no | IP/host to listen on, for both the HTTP and the HTTPS listener. |
-http-port |
8080 |
no | Plain HTTP port. The UI is reachable here. |
-https-port |
8443 |
no | HTTPS port. The listener runs whenever at least one certificate is available, either pushed from the Controller or from -cert/-key, and stops again when the last one is removed. |
-tls |
false |
no | Load the local certificate files given by -cert and -key. This does not by itself decide whether HTTPS runs, see HTTPS. |
-cert |
server.crt |
no | Local TLS certificate (public key) for the HTTPS listener. |
-key |
server.key |
no | Local TLS private key for the HTTPS listener. |
-certificates |
yes | Comma-separated list of Controller TLS certificate IDs to serve on -https-port, selected per request via SNI. The first ID is the fallback when SNI matches none of them. Requires -enable-nats. |
|
-read-timeout |
1m0s |
no | Maximum duration for reading an entire HTTP request, including the body. 0s disables the timeout. |
-write-timeout |
1m0s |
no | Maximum duration before timing out writes of the HTTP response. 0s disables the timeout. See Timeouts. |
-trusted-proxy-cidrs |
no | Comma-separated list of trusted proxy CIDRs (e.g. 10.0.0.0/8,192.168.0.0/16). When a request arrives from an address in this list, the client IP is taken from X-Forwarded-For. Empty (default) makes X-Forwarded-For fully untrusted. |
| Argument | Default | Runtime | Description |
|---|---|---|---|
-static-path |
/usr/share/varnish-controller-ui/www/ |
no | Directory containing the UI assets. |
-index-file |
index.html |
no | Name of the single page application entry point within -static-path. Unknown paths fall back to this file. |
-compress |
true |
yes | Serve precompressed .br/.gz siblings of an asset when the client accepts them. |
-cache |
168h0m0s |
yes | Browser cache TTL for static assets (Cache-Control: max-age). 0s disables caching. The index file is always served with no-store. |
| Argument | Default | Runtime | Description |
|---|---|---|---|
-api-hosts |
yes | Comma-separated list of API-GW base URLs to proxy /api/ to, without trailing slash (e.g. https://api.example.com). Several hosts are used round-robin. |
|
-api-tls-allow-insecure |
false |
yes | Accept unverified TLS certificates when proxying to the API-GW. |
-compile-timeout |
1m0s |
no | Timeout for compilation of VCL, also used to derive the proxy and browser timeouts. Set it to the same value as on brainz and the API-GW. See Timeouts. |
| Argument | Default | Runtime | Description |
|---|---|---|---|
-trusted-origins |
yes | Comma-separated list of trusted cross-origins (e.g. https://foo.example.com). Used both to allow unsafe (state-changing) requests past the CSRF check and as the CORS allowed-origins list. |
|
-secure-cookies |
true |
yes | Set the Secure flag on the cookies the UI server issues. Keep enabled whenever the browser reaches the UI over HTTPS, including when TLS is terminated in front of the UI server. |
-cookie-domain |
yes | Domain attribute for the authentication cookies. Empty means host-only. |
|
-hsts |
false |
yes | Send Strict-Transport-Security: max-age=31536000. Only honored by browsers over HTTPS. |
-csp |
see below | yes | Content-Security-Policy response header value. An empty value disables the header. |
-x-frame-options |
deny |
yes | X-Frame-Options response header value, either deny or sameorigin. An empty value disables the header and leaves framing to the CSP frame-ancestors directive. |
-browser-security-exclusions |
yes | Comma-separated request paths that bypass the browser protections entirely: the cross-origin check, CORS, the security response headers, and the requirement for an authentication cookie on /api/ (the request is proxied with whatever credentials it carries itself). Exact paths, not prefixes. For non-browser clients, webhooks and public endpoints. |
The default -csp value is:
default-src 'self'; font-src 'self' data:; script-src 'self' 'unsafe-eval'; connect-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline';base-uri 'self';form-action 'self'
| Argument | Default | Runtime | Description |
|---|---|---|---|
-sso-org-hosts |
yes | Comma-separated list of <host>=<organization> mappings, used by /sso to find the organization from the host the request came in on when no org query parameter is given (e.g. acme.example.com=acme,foo.example.com=foo). Ports are ignored and hosts are matched case-insensitively. |
NATS is optional for the UI server. These settings only take effect with -enable-nats.
| Argument | Default | Runtime | Description |
|---|---|---|---|
-enable-nats |
false |
no | Connect to NATS to receive TLS certificates, configuration and ACME HTTP-01 challenges from the Controller. Without it the UI server runs standalone. |
-nats-server |
localhost:4222 |
no | host:port of the NATS server (user:pass@host:port). |
-nats-token |
no | NATS token for authentication. | |
-nats-ca |
no | Own CA certificate for the NATS TLS connection. | |
-nats-client-cert |
no | NATS client TLS certificate. | |
-nats-client-key |
no | NATS client TLS key. | |
-nats-insecure-tls |
false |
no | Do not verify the NATS server certificate. Not for production. |
-nats-timeout |
10s |
no | Timeout for sending messages over NATS. |
-nats-reconnect-time |
5s |
no | Maximum time between NATS connection retries. |
-nats-ping-interval |
2s |
no | Time between pings towards the NATS servers. |
-nats-ping-outs |
2 |
no | Number of missed pings before reconnecting. |
| Argument | Default | Runtime | Description |
|---|---|---|---|
-base-dir |
/var/lib/varnish-controller/varnish-controller-ui-server/ |
no | Directory for files the UI server generates, such as the persisted configuration override. Must be writable by the user the UI server runs as. |
-user |
varnish-controller |
no | User to run as when started as root. |
-group |
varnish-controller |
no | Group to run as when started as root. |
-log |
warning |
yes | Loglevel, valid: debug, info, warning, error, quiet. |
-log-timestamp |
true |
yes | Add a timestamp to log lines. |
-request-log |
false |
yes | Log every incoming HTTP request (method, host, URL and headers). |
-config |
no | Path to a configuration file. | |
-generate-config |
false |
no | Print a configuration file with all parameters and exit. |
-version |
false |
no | Show version and exit. |
The -http-host and -http-port is used to define where the UI will be reachable, the -api-hosts
is used to define to which API-GW the UI talks to.
Minimal setup to run the UI server, the UI server will run with all default values. The UI will be reachable at the server’s IP port 8080. For a plain-HTTP setup without any TLS in front, disable the Secure flag on the cookies:
varnish-controller-ui -api-hosts=http://api-gw:8002 -secure-cookies=false
To run the UI server on a different host and port (host: 192.0.0.1 and port: 5050) run the following. Your application can now be reached at 192.0.0.1:5050
varnish-controller-ui -api-hosts=http://api-gw:8002 -http-port 5050 -http-host 192.0.0.1 -secure-cookies=false
If you want to run your own UI you can run the service and point to your single page application.
The -index-file is used to define which file to load within the -static-path.
varnish-controller-ui -api-hosts=http://api-gw:8002 -static-path=/path/to/your/ui -index-file=app.html
With -enable-nats, the UI server connects to NATS and receives from the controller:
Configuration: the settings marked Runtime above are stored under the global ui-server
component in the Controller and broadcast to all UI servers on change, so they can be changed
without a restart. Since UI servers are nameless, the configuration is addressed by type rather
than per instance, and only a system administrator may change it:
vcli configs list -f type=ui-server
vcli configs update -f type=ui-server -f flag=log -v debug
vcli configs delete -f type=ui-server -f flag=log
A setting given on the command line, in the environment or in the configuration file cannot be
overridden this way. Clear it there first, and the override takes effect. The last applied
configuration is persisted under -base-dir, so a UI server keeps it across a restart and while
NATS is unavailable. brainz re-sends it periodically, so a UI server that was unreachable when
it changed heals by itself. See
Config/ConfigSets for the general mechanism.
TLS certificates: controller-managed certificates listed in -certificates are pushed by
brainz and served on -https-port, see HTTPS.
ACME challenges: HTTP-01 challenges for the UI server’s own domains are answered by the UI
server itself on /.well-known/acme-challenge/, so no VCL and no agent is involved. Certificate
authorities always request the challenge on port 80, so that port on the UI’s hostname has to
reach the UI server’s -http-port, through a port mapping or a proxy that forwards the path.
NATS is optional: without it the UI server still serves the UI and proxies the API, using only the
-cert/-key certificate. Single sign-on requires NATS.
The plain HTTP listener on -http-port always runs. The HTTPS listener on -https-port runs for as
long as at least one certificate is available, from either of two sources:
-tls together with -cert and -key.-certificates, pushed over NATS.Certificates from the Controller take precedence over the local files, and the local certificate serves again if the pushed list is cleared. A missing or unusable local certificate file is not fatal. It is logged, and the UI server keeps serving, since the Controller may still supply a certificate.
-tls only asks for the local certificate files to be loaded, it does not move the UI to HTTPS by itself. HTTPS has its own port (-https-port), it does not replace the plain listener on -http-port.
-certificates is a comma-separated list of
TLS certificate IDs
as known to the Controller, set by a system administrator:
vcli certificates ls
vcli configs update -f type=ui-server -f flag=certificates -v 3,7
The certificate matching the SNI name of the connection is served, with the first ID in the list as the fallback for a connection that matches none of them. A wildcard certificate matches one label, as it does in TLS. A non-existent ID is rejected when the configuration is set, a certificate that has not been obtained yet is skipped until it is, and a renewal is pushed to the fleet automatically. The certificates are held in memory only, so their private keys never touch the UI server’s disk. This is what makes ACME certificates usable for the UI itself.
Since version 7.6.0, the UI server can log a user in directly through the organization’s IDP. The user only sees the IDP’s own login screen, and no login screen at all when they already have a session with the IDP.
Browsing to https://<ui-host>/sso?org=<organization> starts the login. The org parameter is
optional: without it, the organization is looked up from the host the request came in on through
the -sso-org-hosts mapping (e.g. -sso-org-hosts acme.example.com=acme). If no organization can
be determined, the user is sent to the regular login page.
The browser is then redirected to the IDP and returns to /sso/callback, which the UI server
forwards to the API-GW. Session handling, account provisioning and the authentication cookies are
identical to an interactive IDP login, and the client secret never leaves brainz.
Requirements:
-enable-nats).https://<ui-host>/sso/callback as an allowed redirect URI to the organization’s existing
OIDC client at the IDP.offline_access (or the IDP’s equivalent) must be enabled for
the client.The UI must be reached by the same hostname as the registered redirect URI; reaching it by another hostname breaks the token exchange. A failed or cancelled SSO attempt ends on the regular login page, and a retry guard limits SSO to one automatic attempt per browser per 5 minutes.
The UI server rejects unsafe (state-changing) cross-origin requests using the Sec-Fetch-Site and
Origin request headers. There is no CSRF token. The same-origin case needs no configuration,
neither over HTTP nor over HTTPS. Two cases do:
-secure-cookies=false, otherwise the browser drops the
Secure cookies the UI server sets and the login does not complete.Host header. Add the origin to -trusted-origins.The security settings for the Controller-UI examples cover these, the security response headers, and what a reverse proxy has to forward so that client addresses and origins survive the hop.
The timeouts along the chain of browser, UI server, API-GW and brainz must each outlast the layer
they wrap. Otherwise a timeout is reported by the outer layer, which knows nothing about what
actually went wrong, and the API-GW’s answer is lost, including the trace ID needed to find the
request in the API-GW and brainz logs.
The UI server derives its upstream and browser timeouts from -compile-timeout, adding a margin per
boundary, so setting -compile-timeout to the same value as on the API-GW and brainz is normally
all that is needed. -write-timeout is raised automatically if the configured value would expire
before the UI server stops waiting for the API-GW, and that is logged as a warning at startup.
The UI server identifies itself with a Server: VarnishControllerUI/<version> response header.
Since the UI server proxies /api/ requests, it is possible to point vcli at it by mistake;
vcli detects this and refuses with a message to use an API-GW endpoint instead (default port
8002). It also refuses an API-GW whose major or minor version differs from its own. See
Errors.
If a reverse proxy rewrites the Server header, the check is skipped, and vcli instead reports a
login response that carries no token.
The arguments of the previous standalone UI server still work. A renamed one is translated and logs
a warning, and one whose feature is gone is accepted and ignored. The same applies to the
VARNISH_UI_SERVER_-prefixed environment variables, which are read as a fallback for the
VARNISH_CONTROLLER_ ones. Both will be removed in a future release, so update your configuration.
For the full mapping, and the behavior changes that come with it, see Upgrading.
It is possible to run the UI behind Varnish. Just make sure that you do not cache the API requests or just do not cache the UI entirely. Here is an example VCL file to run the UI with Varnish:
vcl 4.1;
import std;
backend controller-ui {
.host = "127.0.0.1";
.port = "8080";
}
sub vcl_recv {
# Force https
if (std.port(local.ip) == 80) {
set req.http.Location = "https://" + req.http.host + req.url;
return (synth(301));
}
if (req.http.host == "controller.varnish-software.com") {
set req.backend_hint = controller-ui;
return (pass);
} else {
return(synth(404));
}
}
sub vcl_synth {
# Redirects
if (req.http.location) {
if (resp.status == 301) {
set resp.http.Location = req.http.location;
return (deliver);
}
if (resp.status == 302) {
set resp.http.Location = req.http.location;
return (deliver);
}
}
}