feature: the router has a two-level CA with an optional offline root, issuing CAs for P2S, site-to-site and general use, templates, published certificates and CRLs and an ACME server, site-to-site connections authenticate with certificates, and setup installs Caddy from Ubuntu or Caddy's repository.
Release / release (push) Successful in 13s

This commit is contained in:
slawek committed 2026-10-25 22:05:39 +01:00
1 parent fe45272f7c
commit 8d3469d10a
37 files changed
+2499 -921

No files matched your search

+290 -47
View File
@@ -2,8 +2,11 @@
A Debian package that configures a Linux host or virtual machine as a VPN router:
- site-to-site IKEv2 IPSec with pre-shared key authentication (strongSwan swanctl)
- road-warrior (P2S) access using IKEv2 EAP-TLS, with a bundled certificate authority
- site-to-site IKEv2 IPSec with pre-shared key or certificate authentication (strongSwan swanctl)
- road-warrior (P2S) access using IKEv2 EAP-TLS
- a two-level certificate authority: a root, which may be kept offline, and issuing CAs for the VPN
clients, for site-to-site and for general use, with certificate templates, published certificates
and CRLs, and an optional ACME server
- OpenVPN (P2S) access with a certificate and a password per user, the users kept in a local
LDAP directory and managed with `vpn-router-user` or the web service
- optional WireGuard endpoint
@@ -110,7 +113,6 @@ must have the type shown below; an unknown section or setting is an error.
| `site_to_site` | | object | | The site-to-site connections, each a name and an object of the settings below |
| `p2s` | `enabled` | boolean | `false` | Road-warrior access |
| `p2s` | `address_pool` | string | | CIDR assigned to road-warrior clients |
| `p2s` | `ca_name` | string | `VPN Router CA` | Common name for a locally created CA |
| `wireguard` | `enabled` | boolean | `false` | WireGuard endpoint |
| `wireguard` | `address` | string | | Address and prefix for `wg0`, for example `192.168.200.1/24` |
| `wireguard` | `listen_port` | integer | `51820` | UDP port |
@@ -130,6 +132,18 @@ must have the type shown below; an unknown section or setting is an error.
| `https` | `email` | string | | The e-mail address of the Let's Encrypt account |
| `https` | `web_port` | integer | `7444` | HTTPS port of the status web service |
| `https` | `manager_port` | integer | `7443` | HTTPS port of the Router Manager |
| `https` | `caddy_source` | string | `ubuntu` | Where Caddy is installed from: `ubuntu`, the Ubuntu archive, or `upstream`, Caddy's own repository (see HTTPS) |
| `pki` | `root_name` | string | `VPN Router Root CA` | Common name of the root CA, when the router creates it |
| `pki` | `root_days` | integer | `7300` | Days the root CA's certificate is valid |
| `pki` | `ca_days` | integer | `3650` | Days an issuing CA's certificate is valid |
| `pki` | `key` | string | `rsa-4096` | Key of the root and the issuing CAs: `rsa-2048`, `rsa-3072`, `rsa-4096`, `ec-p256` or `ec-p384` |
| `pki` | `crl_days` | integer | `7` | Days the CRLs of the issuing CAs are valid |
| `pki` | `root_crl_days` | integer | `180` | Days the root CRL is valid, when the router signs it |
| `pki` | `publish` | list | `http` | Where the CA certificates and CRLs are published: `http`, `http-80`, `https`, `https-default` (see Certificates) |
| `pki` | `p2s_name` | string | `VPN Router P2S CA` | Common name of the P2S issuing CA |
| `pki` | `s2s_name` | string | `VPN Router Site-to-Site CA` | Common name of the site-to-site issuing CA |
| `pki` | `issuing_cas` | object | | The general-purpose issuing CAs, each a name and `{name, templates, acme}` (see Certificates) |
| `pki` | `templates` | object | | Certificate templates, each a name and the options it changes or defines (see Certificates) |
| `metrics` | `enabled` | boolean | `true` | The metrics service: collection, history and `/metrics` |
| `metrics` | `listen` | string | `internal` | The interfaces `/metrics` is served on: `internal`, `external` or `both` |
| `metrics` | `port` | integer | `9464` | TCP port of `/metrics` |
@@ -176,13 +190,22 @@ of the status web service, which the Router Manager uses.
| `cidrs` | list | | Remote subnets reachable through the tunnel |
| `local_cidrs` | list | | Local subnets advertised into the tunnel |
| `local_id_mode` | string | `fqdn` | The router's IKE identity: `fqdn` is `wan.local_fqdn`, `public_ip` the address it resolves to, `internal_ip` the internal address |
| `psk` | string | | Pre-shared key |
| `auth` | string | `psk` | How both sides authenticate: `psk`, a pre-shared key, or `certificate` |
| `psk` | string | | Pre-shared key, with `auth` `psk` |
| `psk_file` | string | | Path to a file holding the key; wins over `psk` |
| `remote_ca` | string | | With `auth` `certificate`, the PEM certificate of the CA that issued the peer's certificate; without it the router's site-to-site CA |
A connection is set up once all of `addrs`, `id`, `cidrs`, `local_cidrs`, the key and its
local identity are known; all such connections are written to
`/etc/swanctl/conf.d/site-to-site.conf`. Traffic from a connection's `local_cidrs` to its
`cidrs` is not masqueraded.
A connection is set up once all of `addrs`, `id`, `cidrs`, `local_cidrs` and its local
identity are known, with `psk` the key, and with `certificate` the router's site-to-site
certificate; all such connections are written to `/etc/swanctl/conf.d/site-to-site.conf`.
Traffic from a connection's `local_cidrs` to its `cidrs` is not masqueraded.
With `auth` `certificate`, the router authenticates with its site-to-site certificate, which the
site-to-site issuing CA signs for `wan.local_fqdn`, the public address and the internal
address, so it fits every `local_id_mode`. The peer's certificate must name `id` and be issued
by the router's site-to-site CA, or by the CA of `remote_ca`. A peer with no PKI of its own gets
its certificate from the router (template `site-to-site`, see Certificates), with the root and
the site-to-site CA certificates as its trust anchors.
A minimal site-to-site configuration in `auto` mode:
@@ -341,57 +364,215 @@ anything:
```
This does still write to the system: it renders into `/etc/swanctl/`,
`/etc/systemd/resolved.conf.d/` and `/etc/wireguard/`, and if road-warrior access is
enabled with an empty PKI directory it creates the certificate authority and adds it
to the host trust store. What it leaves alone is routes, firewall rules and services -
that is what `systemctl restart vpn-router-setup` adds.
`/etc/systemd/resolved.conf.d/` and `/etc/wireguard/`, creates what the PKI is missing and
adds the root CA to the host trust store. What it leaves alone is routes, firewall rules and
services - that is what `systemctl restart vpn-router-setup` adds.
## Certificates
`/etc/vpn-router/pki` is the PKI directory, laid out the way `simple-ca` expects:
The router has a two-level certificate authority in `/etc/vpn-router/pki` (mode 0700), kept by
`vpn-router-setup` and managed with `vpn-router-ca`, `vpn-router-user` and `/ca` of the status
web service, which the Router Manager uses:
```
ca_cert.pem ca_key.pem ca_bundle.pem <label>_cert.pem <label>_key.pem crl.pem
root/cert.pem root/key.pem root/crl.pem the root CA; key.pem is absent while the root is offline
ca/<name>/cert.pem key.pem crl.pem csr.pem an issuing CA; csr.pem while it waits for the offline root
router/vpn-server_cert.pem _key.pem the router's VPN server certificate
router/site-to-site_cert.pem _key.pem the router's site-to-site certificate
users/<user>_cert.pem _key.pem the users' certificates
retired/<name>-<time>/ the issuing CAs taken out of use
state.json the certificates issued, revocations and CRL numbers
```
`<label>` is the first component of the router FQDN, so `router.example.com` gives
`router_cert.pem`.
### The CAs
Once a platform is chosen and while this directory is empty, a certificate authority and a CRL
valid for ten years are created there automatically, whether or not road-warrior or OpenVPN
access is enabled. A CA of the router's own (with `ca_key.pem`) without a CRL gets one, and
without a server certificate gets one once `wan.local_fqdn` is set. User certificates are issued
by `vpn-router-user` into `users/`. To use your own CA instead, place the files in that layout
before choosing the platform; nothing present is overwritten.
The root signs the issuing CAs only (path length 1), and the issuing CAs sign the certificates
(path length 0):
Either way, the package copies what each consumer needs into place: the CA into
`/etc/swanctl/x509ca/`, the server certificate and key into `/etc/swanctl/x509/` and
`/etc/swanctl/private/`, a CRL into `/etc/swanctl/x509crl/`, and the CA into the system
- `p2s`, named `pki.p2s_name`, signs the router's VPN server certificate, which IKEv2 and OpenVPN
present, and the users' certificates. Road-warrior and OpenVPN accept client certificates of
this CA only.
- `s2s`, named `pki.s2s_name`, signs the router's site-to-site certificate and the certificates
of its peers.
- The general-purpose CAs of `pki.issuing_cas`, each a name and `{"name": common name,
"templates": [...], "acme": false}`. The name starts with a lowercase letter, has at most 31
lowercase letters, digits and hyphens, and is not `root`, `p2s` or `s2s`; `templates` are the
templates the CA issues.
Once a platform is chosen, `vpn-router-setup` creates what is missing: the root when it has
neither certificate nor key, each issuing CA, the router's certificates once `wan.local_fqdn` is
set, and the CRLs. The names, lifetimes and key type of `pki` apply when something is created. An
issuing CA removed from `pki.issuing_cas` is retired: its directory moves to `retired/` and its
certificate is revoked on the root CRL. The router renews its own certificates when less than a
third of their lifetime is left, when the names they need change, or when their CA changes; a
user's certificate is renewed by removing and adding the user.
The package copies what each consumer needs: the root, `p2s` and `s2s` into
`/etc/swanctl/x509ca/` as `root.pem`, `p2s.pem` and `s2s.pem` (strongSwan reads one certificate
per file), the remote CA of each site-to-site connection that has one as `s2s-<name>.pem`, the
router's certificates into `/etc/swanctl/x509/` and their keys into `/etc/swanctl/private/`, the
CRLs into `/etc/swanctl/x509crl/`, the root and `p2s` with their CRLs into
`/etc/openvpn/server/vpn-router-ca.pem` and `vpn-router-crl.pem`, and the root into the system
trust store.
Issue a client certificate with the bundled tool:
`vpn-router-ca status` shows the root, the issuing CAs and what needs attention:
```sh
/usr/lib/vpn-router/simple-ca make-cert --ca-dir /etc/vpn-router/pki alice
/usr/lib/vpn-router/simple-ca make-pfx --ca-dir /etc/vpn-router/pki \
--password s3cr3t /etc/vpn-router/pki/alice_cert.pem
vpn-router-ca status
vpn-router-ca list --ca p2s
```
`simple-ca` comes from a separate project, <https://gitea.koszewscy.waw.pl/slawek/simple-ca>,
and also supports `make-crl` and `revoke-cert`.
### Templates
A template says what a certificate holds:
| Option | Type | Default | Meaning |
|---|---|---|---|
| `key` | string | `rsa-3072` | `rsa-2048`, `rsa-3072`, `rsa-4096`, `ec-p256` or `ec-p384`, for a key the router makes |
| `days` | integer | `365` | Days the certificate is valid, at most until its CA's certificate expires |
| `key_usage` | list | `digital_signature` | Of `digital_signature`, `content_commitment`, `key_encipherment`, `data_encipherment`, `key_agreement` |
| `extended_key_usage` | list | | Of `server_auth`, `client_auth`, `code_signing`, `email_protection`, `time_stamping`, `ocsp_signing`, `ipsec_ike`, `ike_intermediate` |
| `subject` | string | `name` | The common name: `dns`, a DNS name, which is also a subject alternative name; `name`, any name; `email`, an e-mail address |
| `sans` | list | | The subject alternative names allowed: of `dns`, `ip`, `email` |
The built-in templates:
| Template | Key usage | Extended key usage | Subject | Names | Issued by |
|---|---|---|---|---|---|
| `vpn-server` | digital signature, key encipherment | server auth, IKE intermediate | `dns` | `dns`, `ip` | `p2s`, for the router only |
| `vpn-user` | digital signature | client auth | `name` | `dns`, `email` | `p2s`, for the users only |
| `site-to-site` | digital signature, key encipherment | server auth, client auth | `dns` | `dns`, `ip` | `s2s` |
| `server` | digital signature, key encipherment | server auth | `dns` | `dns`, `ip` | general-purpose CAs |
| `client` | digital signature | client auth | `name` | `dns`, `email` | general-purpose CAs |
`pki.templates` changes options of the built-in templates, which cannot be removed, and adds
templates of its own, named like the issuing CAs:
```json
"pki": {
"issuing_cas": { "servers": { "name": "Example Server CA", "templates": ["server", "web"] } },
"templates": {
"server": { "days": 397 },
"web": { "key": "ec-p256", "days": 90, "key_usage": ["digital_signature"],
"extended_key_usage": ["server_auth"], "subject": "dns", "sans": ["dns"] }
}
}
```
### Issuing and revoking
```sh
vpn-router-ca issue --ca servers --template server --csr web.csr > web.pem
vpn-router-ca issue --ca servers --template server --subject web.example.com --san 10.0.0.5 --pfx web.pfx
vpn-router-ca revoke 5F0A12C3D4 --reason key_compromise
```
With a CSR, the certificate is for the CSR's key, common name and subject alternative names,
checked against the template, and the certificate and the chain (issuing CA, root) are printed.
Without one, the router makes a key as the template says and writes a PKCS#12 file with the key,
the certificate and the chain, protected by the password read from the first line of standard
input; the router keeps the certificate and not the key. `vpn-server` and `vpn-user` are issued by
the router only.
`revoke` takes the serial number `list` shows and a reason: `unspecified`, `key_compromise`,
`affiliation_changed`, `superseded`, `cessation_of_operation` or `privilege_withdrawn`. A user's
certificate is revoked by removing the user, and an issuing CA by removing it from
`pki.issuing_cas`; a revoked router certificate is issued again by `vpn-router-setup`.
### Published certificates and CRLs
Every certificate names its issuer's certificate (AIA) and CRL (CRL distribution point) at each
endpoint of `pki.publish`, in that order. They are served, without authentication, as DER at
`/pki/<name>.crt` and `/pki/<name>.crl`, the root as `root`, from copies in
`/var/lib/vpn-router/pki-publish`:
| Endpoint | URL | Served by |
|---|---|---|
| `http` | `http://<wan.local_fqdn>:<web.port>/pki/` | the status web service; needs `web.enabled` |
| `http-80` | `http://<wan.local_fqdn>/pki/` | Caddy, on port 80, open on both interfaces; needs `https.enabled` |
| `https` | `https://<wan.local_fqdn>:<https.web_port>/pki/` | Caddy; needs `https.enabled` |
| `https-default` | `https://<wan.local_fqdn>/pki/` | Caddy, on port 443, open on both interfaces; needs `https.enabled` |
The URLs are written into a certificate when it is signed, so a change of `pki.publish` or of the
names applies to the certificates signed afterwards. An empty `pki.publish` leaves the extensions
out.
The CRLs of the issuing CAs are valid for `pki.crl_days`, and the root CRL, when the router signs
it, for `pki.root_crl_days`. A CRL is signed again at every revocation and, once half of its
validity has passed, by `vpn-router-pki.timer`, which runs `vpn-router-ca maintain` daily; it also
has `vpn-router-setup` renew the router's certificates when they are due. OpenVPN checks the CRL
of every CA of a client's chain, the root's included, and refuses every client while one has
expired.
### Offline root
Keeping the root key off the router is recommended, not required. Export it, encrypted with a
passphrase read from the first line of standard input, keep the file in a safe place, and remove
the key from the router:
```sh
vpn-router-ca root export-key --out /root/vpn-router-root-key.pem
vpn-router-ca root remove-key
```
While the root key is not on the router, a new issuing CA gets its key and a CSR and waits;
`vpn-router-ca status` says so. Print the CSR and the extensions to sign it with, sign it with the
root on the offline machine, and import the certificate:
```sh
vpn-router-ca ca csr servers # the CSR, then the [ issuing_ca ] extensions
# on the offline machine, with the CSR in servers.csr and the extensions in issuing_ca.cnf:
openssl x509 -req -in servers.csr -CA root-cert.pem -CAkey vpn-router-root-key.pem -CAcreateserial \
-days 3650 -sha256 -extfile issuing_ca.cnf -extensions issuing_ca -out servers.pem
vpn-router-ca ca import servers servers.pem
```
The router cannot sign the root CRL without the key. Sign it where the key is, or put the key back
for a moment, and take the CRL before the current one expires:
```sh
vpn-router-ca root import-key /root/vpn-router-root-key.pem # the passphrase on standard input
vpn-router-ca maintain
vpn-router-ca root remove-key
# or, with a CRL signed elsewhere:
vpn-router-ca root import-crl root.crl
```
An existing root CA of your own is used by placing its certificate, and its key if the router may
use it, before choosing the platform: `vpn-router-ca root import-cert root-cert.pem`, or the files
`root/cert.pem` and `root/key.pem`. Without the key, the issuing CAs wait for their certificates
as above.
### ACME server
A general-purpose CA with `"acme": true` backs an ACME server of Caddy, while `https.enabled`, at
`https://<wan.local_fqdn>:<https.web_port>/acme/<name>/directory`, so servers get and renew
certificates with any ACME client:
```sh
certbot certonly --standalone -d web.example.com \
--server https://router.example.com:7444/acme/servers/directory
```
Caddy proves the client's control of the name as Let's Encrypt does, so it must reach the client.
The certificates are valid for 12 hours, carry Caddy's extensions, are not listed by
`vpn-router-ca list`, cannot be revoked, and no template applies to them. Caddy reads the CA's
certificate and key from `/etc/caddy/vpn-router-pki/<name>/`, readable by its group.
## OpenVPN and users
With `openvpn.enabled`, an `address_pool` and `wan.local_fqdn` set, `vpn-router-setup` runs
`openvpn-server@vpn-router` with `/etc/openvpn/server/vpn-router.conf`. The server uses the
router's CA, certificate and CRL (the copies under `/etc/swanctl`), pushes routes to
`local.cidrs` and the router as DNS server, and opens `openvpn.port` on the external interface.
router's VPN server certificate, the root and the P2S CA and their CRLs (see Certificates),
pushes routes to `local.cidrs` and the router as DNS server, and opens `openvpn.port` on the
external interface.
A client needs both a certificate of the router CA and the password of the user the
A client needs both a certificate of the P2S CA and the password of the user the
certificate names:
- the certificate is checked against the CA and the CRL (`crl-verify`, read at every
connection);
- the certificate is checked against the root and the P2S CA and their CRLs (`crl-verify`, read
again when the file changes), and `/usr/lib/vpn-router/openvpn-issuer` (`tls-verify`) refuses
a certificate another issuing CA signed;
- the password is checked by `openvpn-auth-ldap`, which binds to the user directory as the
user; a user enrolled for TOTP types the password followed by the current 6-digit code;
- `/usr/lib/vpn-router/openvpn-same-user` refuses a login whose user name is not the common
@@ -723,6 +904,7 @@ curl -H "Authorization: Bearer $TOKEN" http://10.0.0.4:7081/config
| `wireguard` | The public key and port of `wg0`, and each peer's public key, endpoint, allowed IPs, handshake age and traffic; no private or preshared key |
| `docker` | `docker.exposed_ports` and the rules of the `VPN-ROUTER-DOCKER` chain |
| `postgresql` | `clusters`: each PostgreSQL cluster of `pg_lsclusters` with its version, name, port, status and data directory, and for one that is online its `settings`: `server_version`, `listen_addresses`, `port`, `max_connections`, `shared_buffers`, `effective_cache_size`, `work_mem`, `password_encryption`, `ssl` and `timezone`; `null` when `pg_lsclusters` fails |
| `caddy` | The installed Caddy: `version`, and `source`, `upstream` once Caddy's repository is configured and `ubuntu` otherwise; `null` when Caddy is not installed |
### The /postgresql page
@@ -737,7 +919,7 @@ router:
`/users` manages users with the same token as `/config`, with the module `vpn-router-user` uses
too, `/usr/lib/vpn-router/vpnrouter_users.py`, so both apply the same rules. Request
and answer bodies are JSON; a request body is at most 4096 bytes.
and answer bodies are JSON; a request body is at most 65536 bytes.
| Request | Body | Answer |
| --- | --- | --- |
@@ -830,8 +1012,8 @@ group.
| Request | Body | Answer |
| --- | --- | --- |
| `GET /site-to-site` | | `{"connections": [{"name", "addrs", "id", "cidrs", "local_cidrs", "local_id_mode", "psk_file", "psk_set", "established"}, ...]}` |
| `POST /site-to-site` | `{"name", "addrs", "id", "cidrs", "local_cidrs", "local_id_mode", "psk"}`; `psk` is required | 201 and the connection |
| `GET /site-to-site` | | `{"connections": [{"name", "addrs", "id", "cidrs", "local_cidrs", "local_id_mode", "auth", "psk_file", "remote_ca", "psk_set", "established"}, ...]}` |
| `POST /site-to-site` | `{"name", "addrs", "id", "cidrs", "local_cidrs", "local_id_mode", "auth", "psk", "remote_ca"}`; `psk` is required with `auth` `psk`, the default | 201 and the connection |
| `PUT /site-to-site/<name>` | any of the settings of `POST` but `name`; an empty or absent `psk` keeps the key, a `psk` given replaces `psk_file` | the connection |
| `DELETE /site-to-site/<name>` | | `{"name", "removed": true}` |
@@ -865,6 +1047,36 @@ The answer of `GET /backup` holds:
A change is made, checked and applied as for `/site-to-site`.
### The /ca API
`/ca` manages the PKI (see Certificates) with the same token as `/users`, with the module
`vpn-router-ca` uses too, `/usr/lib/vpn-router/vpnrouter_pki.py`.
| Request | Body | Answer |
| --- | --- | --- |
| `GET /ca` | | `{"settings", "templates", "status", "certificates"}` |
| `PUT /ca` | any of the settings of the `pki` section | `{"settings", "templates"}` |
| `POST /ca/certificates` | `{"ca", "template", "csr"}` | 201 and `{"serial", "certificate", "chain"}`, PEM |
| `POST /ca/certificates` | `{"ca", "template", "subject", "sans", "password"}` | 201 and `{"serial", "pfx"}`, a PKCS#12 file in base64 |
| `POST /ca/certificates/<serial>/revoke` | `{"reason"}`, optional | the certificate, with `reissue` for a router certificate |
| `GET /ca/issuing/<name>/csr` | | `{"name", "csr", "extensions"}` of an issuing CA waiting for the offline root |
| `PUT /ca/issuing/<name>/certificate` | `{"certificate"}`, PEM, signed by the root | `{"name", "subject", "expires"}` |
| `PUT /ca/root/crl` | `{"crl"}`, PEM, signed by the root | `{"next_update"}` |
The answer of `GET /ca` holds:
- **`settings`:** the `pki` section.
- **`templates`:** every template, the built-in ones included, with all its options.
- **`status`:** `root` (`subject`, `expires`, `key`, whether the root key is on the router, `crl_next_update` and `crl_source`, `signed` or `uploaded`), `authorities`, one per issuing CA (`name`, `common_name`, `state`: `active`, `pending` or `missing`, `builtin`, `templates`, `acme`, `subject`, `expires`, `key`, `crl_next_update`, `acme_url`), `retired` and `warnings`.
- **`certificates`:** the certificates issued, newest first, each `{"serial", "ca", "kind", "template", "subject", "sans", "created", "expires", "revoked", "reason", "state"}`; `kind` is `ca`, `router`, `user` or `external`, and `state` `valid`, `expired` or `revoked`.
A change of the settings is made, checked and applied as for `/site-to-site`; an imported issuing
CA certificate and a revoked router certificate restart `vpn-router-setup` too. A failure is
`{"error": ...}` with 400 for an invalid request, 404 for something that does not exist, 409 when
the state does not allow it, as for a revoked certificate or an issuing CA that has its
certificate, and 500 otherwise. The root key never crosses the API; `vpn-router-ca` exports,
removes and imports it.
The service starts on its own, without waiting for `vpn-router-setup`, and keeps running. Every
30 seconds it reads `web.listen`, `web.port` and `web.enabled` from `vpn-router.json` and works
out the addresses of the interfaces `web.listen` names as `general.mode` directs; it listens on
@@ -877,7 +1089,7 @@ and the facts for `/config` from `/run/vpn-router/web.json` (readable by root on
## HTTPS
Caddy, from Ubuntu's `caddy` package, serves the status web service on `https.web_port` (7444 by
Caddy serves the status web service on `https.web_port` (7444 by
default), while the Router Manager is installed the manager on `https.manager_port` (7443), and
while metrics are enabled the metrics service on `metrics.https_port` (9465), all for `wan.local_fqdn`, while `https.enabled` is `true` and `wan.local_fqdn` is set. It obtains
and renews the certificate from Let's Encrypt with the HTTP challenge, so `wan.local_fqdn` must
@@ -891,7 +1103,8 @@ package's drop-in `/usr/lib/systemd/system/caddy.service.d/vpn-router.conf` make
read; `caddy.service` does not start before the file exists, and `/etc/caddy/Caddyfile` is not
used. Setup starts Caddy, reloads it when the file changes, and stops it and removes the file when
HTTPS is off. In the firewall it opens port 80 on the external interface and the HTTPS ports on the
internal and the external interface. Caddy calls both services on `127.0.0.1`, where they listen
internal and the external interface, and ports 80 and 443 on both when `pki.publish` has `http-80`
and `https-default`, on which Caddy serves only the CA files (see Certificates). Caddy calls both services on `127.0.0.1`, where they listen
whatever `web.listen` and the manager's `listen` say, and passes the client's address in
`X-Forwarded-For`, which the manager trusts from `127.0.0.1` and the web service by default, as
the address the call came in on; a `web.trusted_proxies` that is set must list `127.0.0.1`. The plain HTTP ports stay as
@@ -902,6 +1115,34 @@ the HTTPS ports while `https.enabled` is `true`.
curl https://router.example.com:7444/status
```
### Installing Caddy
The package does not depend on Caddy; `vpn-router-setup` has it installed, while HTTPS is on,
from the source `https.caddy_source` names:
- `ubuntu`, the default: the `caddy` package of the Ubuntu archive.
- `upstream`: Caddy's own repository, `https://dl.cloudsmith.io/public/caddy/stable/deb/debian`,
whose source the package writes to `/etc/apt/sources.list.d/caddy-stable.sources` with its key,
downloaded over HTTPS, in `/etc/apt/keyrings/caddy-stable.asc`.
apt runs in `vpn-router-caddy.service`, which waits for dpkg's lock, so setup run by a package
installation does not wait for itself; the unit runs `vpn-router-caddy apply` and then setup
again, which serves with the new Caddy. `vpn-router-caddy status` shows the configured source, the
source in effect and the versions.
A router installed with `ubuntu` is switched to upstream's Caddy with:
```sh
vpn-router-caddy use-upstream
```
It sets `https.caddy_source` to `upstream`, configures the repository and replaces Caddy, printing
each step. Upstream versions sort above Ubuntu's, so once the repository is configured apt keeps
upstream's Caddy, and `ubuntu` has no effect; setup says so. Going back is a downgrade nothing
does on its own: remove the source and the key, run `apt-get update`, and install Ubuntu's version
with `apt-get install --allow-downgrades caddy=<version>`, the version `apt-cache policy caddy`
lists for the archive.
## Metrics
While `metrics.enabled` is `true`, `vpn-router-setup` runs `vpn-router-metrics`, which every minute
@@ -1023,7 +1264,7 @@ A backup holds the state that `vpn-router-setup` cannot make again:
| Item | Content |
|---|---|
| `/etc/vpn-router` | `vpn-router.json`, the secrets, `rclone.conf`, the PKI and the certificates of the users |
| `/etc/vpn-router` | `vpn-router.json`, the secrets, `rclone.conf`, and the PKI: the CAs, the root key while it is on the router, and the certificates of the router and the users |
| `/etc/wireguard` | The WireGuard keys and the peers |
| `/etc/ufw`, `/etc/default/ufw` | The firewall rules |
| `/etc/vpn-router-manager` | The Router Manager's settings, when it is installed |
@@ -1107,7 +1348,7 @@ What it replaces is kept in `/var/lib/vpn-router/backup/before-restore/<UTC time
The `vpn-router-manager` package, built in the repository `linux-cloud-router-manager`, is a web
application that shows the state of the router and manages its users, site-to-site
connections and backups through the status web service. It can be installed on the router or on another Linux system. While it is installed on
connections, backups and certificates through the status web service. It can be installed on the router or on another Linux system. While it is installed on
the router, `vpn-router-setup` opens its port (`port` in `/etc/vpn-router-manager/manager.json`,
7080 by default, HTTP; HTTPS is Caddy's, on `https.manager_port`) on the internal and the external
interface; restart `vpn-router-setup` after changing it. The manager's `listen` chooses the
@@ -1116,8 +1357,8 @@ interfaces it listens on.
## Removal
`apt-get remove` stops `vpn-router-docker`, which removes the Docker rules. It terminates the
tunnels and takes `wg-quick@wg0`, `openvpn-server@vpn-router`, `caddy`, `vpn-router-metrics` and
`vpn-router-backup.timer` down, but leaves the
tunnels and takes `wg-quick@wg0`, `openvpn-server@vpn-router`, `caddy`, `vpn-router-metrics`,
`vpn-router-backup.timer` and `vpn-router-pki.timer` down, but leaves the
configuration in place. `strongswan` is left enabled, since it is a shared service that
may be in use by something else.
@@ -1127,8 +1368,10 @@ before deleting it so a later reinstall starts clean. It also removes the web se
signing key, the password file of the metrics service account, and the database of the refresh
tokens and the metrics, its role and their password
file when the package created them, and Caddy's configuration `/etc/caddy/vpn-router.Caddyfile`; the certificate in
`/var/lib/caddy` belongs to the `caddy` package. It removes the backup timer's schedule and the
record of the last backup. Key material, users and backups are never deleted: `/etc/vpn-router/pki`,
`/var/lib/caddy` belongs to the `caddy` package. It removes the backup timer's schedule, the
record of the last backup, the published CA files, the copies of the ACME CAs in
`/etc/caddy/vpn-router-pki` and OpenVPN's CA and CRL files. Caddy stays installed, and with it the
source of Caddy's repository. Key material, users and backups are never deleted: `/etc/vpn-router/pki`,
`/etc/vpn-router/ldap`, `/etc/vpn-router/rclone.conf`, the user directory in slapd, the WireGuard
key pair, everything under `/etc/swanctl`, the archives in `backup.path` and
`/var/lib/vpn-router/backup/before-restore` are left alone.