feature: Added an internally only exposed status web service.
Release / release (push) Successful in 17s
Release / release (push) Successful in 17s
This commit is contained in:
1 parent
5402256ef6
commit
7163f167e0
10 files changed
+593
No files matched your search
@@ -26,3 +26,52 @@ because `vpn-router-docker` is already active. A published
|
||||
port is reachable from the external interface only when it is in `exposed_ports`. Other rules
|
||||
in `DOCKER-USER` are not touched, because the package owns only its own chain. Restarting Docker
|
||||
restores the rules. The rules cover IPv4 only.
|
||||
|
||||
## ADR-2: A status web service of the standard library, bound to the internal address
|
||||
|
||||
Status: Accepted
|
||||
|
||||
Context: The router has no way to see its state without a shell. A check from the protected
|
||||
network, by a person or by a monitor, needs a page or an endpoint. The router is an
|
||||
internet-facing host, so what the service shows and where it listens matter.
|
||||
|
||||
Decision: The `vpn-router-web` unit runs one Python script that uses only the standard library, in
|
||||
the way of a small test server. It binds to the address of the internal interface and to
|
||||
`web.port`, on by default at 8080. `/` is for people, in plain text for `curl` and `wget` and as
|
||||
a Tailwind CSS page for a browser, and `/status` is the same status as JSON. It shows the
|
||||
state of the services, the package version and the state of the site-to-site, road-warrior and
|
||||
WireGuard features, and nothing from the configuration. `vpn-router-setup` resolves the
|
||||
interface address, writes it with the port and the features to `/run/vpn-router/web.json`,
|
||||
opens the port on the internal interface in UFW and restarts the service, so the service does
|
||||
not need the package's configuration code, which needs Jinja2.
|
||||
|
||||
Consequences: Nothing outside the internal network can reach it, since it listens on the internal
|
||||
address only and the firewall opens the port on the internal interface only. It has no
|
||||
authentication, which is acceptable because it is read only and shows no configuration. It runs
|
||||
as root, because `swanctl` and `wg` need it, with `NoNewPrivileges`, `ProtectSystem=strict`,
|
||||
`ProtectHome` and `PrivateTmp`. The Tailwind style comes from a CDN, so a browser on a network
|
||||
without internet shows the unstyled page, and `curl` is unaffected. A change of the internal
|
||||
address is picked up at the next run of `vpn-router-setup`.
|
||||
|
||||
## ADR-3: /config is authenticated with a short-lived JWT signed with a shared secret
|
||||
|
||||
Status: Accepted
|
||||
|
||||
Context: A client such as manage-ui needs the configuration and the resolved state of the
|
||||
router, which `/` and `/status` must not show. The service has only the standard library, listens
|
||||
on plain HTTP on the internal network, and its configuration file is readable by root only.
|
||||
|
||||
Decision: `/config` needs a JWT in an `Authorization: Bearer` header. The service verifies HS256
|
||||
tokens with `hmac` and `hashlib`, with the secret `web.jwt_secret`, and accepts no other
|
||||
algorithm, because verifying RS256 or ES256 needs a library outside the standard library. The
|
||||
token must carry `exp`, not more than ten minutes ahead, so that the client makes a fresh token for
|
||||
each use and a leaked one expires. Without a secret the page is not served. The secrets in the
|
||||
configuration shown are masked by name (`psk`, and options with `secret`, `password`, `token` or a
|
||||
`_key` ending in the name).
|
||||
|
||||
Consequences: The client and the router share one secret, and whoever holds it can make tokens,
|
||||
so it is long and random; it is checked to have at least 32 characters. The secret is in the
|
||||
configuration file and in `/run/vpn-router/web.json`, both readable by root only. The token and
|
||||
the answer cross the network unencrypted, so the page is for a trusted internal network; HTTPS
|
||||
would need a certificate that the package does not have. The masking is by option name, so a new
|
||||
secret option needs a name that matches.
|
||||
Reference in new issue
Block a user