From f36e2a3b045135864260378b923880e49ff290d5 Mon Sep 17 00:00:00 2001 From: Slawomir Koszewski Date: Fri, 30 Oct 2026 02:35:21 +0100 Subject: [PATCH] feature: https.acme_staging takes Caddy's certificates from Let's Encrypt's staging environment. --- ADR.md | 8 ++++++-- README.md | 13 ++++++++++++- debian-package/debian/changelog | 10 ++++++++++ debian-package/src/usr/lib/vpn-router/vpnrouter.py | 7 ++++++- .../src/usr/share/vpn-router/templates/Caddyfile.j2 | 3 +++ 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/ADR.md b/ADR.md index f1e8eeb..8983ecc 100644 --- a/ADR.md +++ b/ADR.md @@ -534,11 +534,15 @@ package's drop-in for `caddy.service` points it at that file and keeps it from s the file exists, so `/etc/caddy/Caddyfile`, a configuration file of the `caddy` package, is left unchanged. Both services always listen on `127.0.0.1` as well as on the interfaces their `listen` settings name, and trust `X-Forwarded-For` from it, which Caddy sets from the peer and -never takes from the client. +never takes from the client. Let's Encrypt issues at most 5 certificates a week for the same +names, which routers rebuilt for tests reach; `https.acme_staging` sets Caddy's global `acme_ca` +to Let's Encrypt's staging directory, which replaces Caddy's default CAs for every site, so no +test certificate comes from a production CA. Consequences: HTTPS needs `wan.local_fqdn` in public DNS pointing at the router and port 80 reachable from the internet; until then Caddy retries and the HTTPS ports do not answer. The -certificate and the ACME account are in `/var/lib/caddy`. Plain HTTP stays as the `listen` +certificate and the ACME account are in `/var/lib/caddy`. A router on the staging CA has a +certificate no client trusts. Plain HTTP stays as the `listen` settings set it, so `internal` keeps passwords off the internet. Caddy's admin endpoint listens on `localhost:2019`, which `caddy reload` uses. Port 80 and the HTTPS ports are not available to Docker containers while HTTPS is on. The Manager's HTTPS port is a setting of the router, not diff --git a/README.md b/README.md index 32a0d8d..6b673f9 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,7 @@ 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` | `acme_staging` | boolean | `false` | Caddy takes its certificates from Let's Encrypt's staging environment alone, whose certificates no browser trusts and whose rate limits are higher, for routers rebuilt often (see HTTPS) | | `https` | `caddy_source` | string | `ubuntu` | Where Caddy is installed from: `ubuntu`, the Ubuntu archive, or `upstream`, Caddy's own repository (see HTTPS) | | `oidc` | `enabled` | boolean | `false` | The OpenID Provider at the status web service's HTTPS site, which needs `web.enabled`, `https.enabled` and `wan.local_fqdn` (see OpenID Provider) | | `sites` | `enabled` | boolean | `false` | Caddy serves the web sites (see Web sites) | @@ -1471,7 +1472,17 @@ while metrics are enabled the metrics service on `metrics.https_port` (9465), al and renews the certificate from Let's Encrypt with the HTTP challenge, so `wan.local_fqdn` must resolve to the router's public address and port 80 must be reachable from the internet; the certificate and the ACME account are kept in `/var/lib/caddy`. Port 80 answers the challenge only; -it does not redirect to HTTPS. +it does not redirect to HTTPS. Caddy's default CAs are Let's Encrypt and ZeroSSL, which it takes +when Let's Encrypt refuses, such as for its rate limit of 5 certificates a week for the same name. + +While `https.acme_staging` is `true`, Caddy's only CA is Let's Encrypt's staging environment, for +routers that are rebuilt often. Its rate limits are far higher, but no browser or client trusts +its certificates, so they are for tests only. The setting applies to every certificate Caddy +obtains from an ACME CA, those of the web sites with `tls` `acme` included. + +```sh +vpn-config-edit set --restart https.acme_staging true +``` `vpn-router-setup` writes Caddy's configuration to `/etc/caddy/vpn-router.Caddyfile` (root, group `caddy`, the group Caddy runs as, mode 0640), which the diff --git a/debian-package/debian/changelog b/debian-package/debian/changelog index 3a2526c..96100e5 100644 --- a/debian-package/debian/changelog +++ b/debian-package/debian/changelog @@ -1,3 +1,13 @@ +vpn-router (0.38.0-1) unstable; urgency=medium + + feature: https.acme_staging takes Caddy's certificates from Let's Encrypt's staging environment. + + * While https.acme_staging is true, Caddy's only CA is Let's Encrypt's + staging environment, for the router's services and the web sites + with tls acme; its certificates are not trusted by browsers. + + -- Slawomir Koszewski Fri, 30 Oct 2026 02:34:01 +0100 + vpn-router (0.37.3-1) unstable; urgency=medium fix: an answer whose client went away ends quietly, and a shell whose client left before it opened is stopped. diff --git a/debian-package/src/usr/lib/vpn-router/vpnrouter.py b/debian-package/src/usr/lib/vpn-router/vpnrouter.py index 47d028c..dcbfdc3 100644 --- a/debian-package/src/usr/lib/vpn-router/vpnrouter.py +++ b/debian-package/src/usr/lib/vpn-router/vpnrouter.py @@ -66,7 +66,8 @@ SCHEMA = { 'status_content': 'basic', 'shell': False, 'shell_idle_minutes': 15, 'shell_auth_timeout': 15, 'log_files': []}, 'ssh': {'ldap_login': False}, - 'https': {'enabled': True, 'email': '', 'web_port': 7444, 'manager_port': 7443, 'caddy_source': 'ubuntu'}, + 'https': {'enabled': True, 'email': '', 'web_port': 7444, 'manager_port': 7443, 'caddy_source': 'ubuntu', + 'acme_staging': False}, 'oidc': {'enabled': False}, 'sites': {'enabled': False, 'upload_max_mb': 512, 'webdav': False, 'webdav_port': 7445}, 'dns': {'enabled': False, 'nameserver': '', 'addresses': [], 'listen': 'external', 'ttl': 300}, @@ -164,6 +165,9 @@ MANAGER_PORT = 7080 #: Where vpn-router-setup installs Caddy from, https.caddy_source: the Ubuntu archive or Caddy's own repository. CADDY_SOURCES = ('ubuntu', 'upstream') +#: The ACME directory of Let's Encrypt's staging environment, Caddy's only CA while https.acme_staging is true. +ACME_STAGING_CA = 'https://acme-staging-v02.api.letsencrypt.org/directory' + #: Caddy's own repository, which https.caddy_source upstream configures: the source, its signing key and their URLs. CADDY_APT_SOURCE = pathlib.Path('/etc/apt/sources.list.d/caddy-stable.sources') CADDY_KEYRING = pathlib.Path('/etc/apt/keyrings/caddy-stable.asc') @@ -1034,6 +1038,7 @@ def build_context(cfg, public_ip=None): 'web_port': _get(cfg, 'web', 'port'), 'https_enabled': _get(cfg, 'https', 'enabled'), 'https_email': _get(cfg, 'https', 'email'), + 'https_acme_ca': ACME_STAGING_CA if _get(cfg, 'https', 'acme_staging') else '', 'https_web_port': _get(cfg, 'https', 'web_port'), 'https_manager_port': _get(cfg, 'https', 'manager_port'), 'pki_publish': _get_list(cfg, 'pki', 'publish'), diff --git a/debian-package/src/usr/share/vpn-router/templates/Caddyfile.j2 b/debian-package/src/usr/share/vpn-router/templates/Caddyfile.j2 index d49b698..3663a63 100644 --- a/debian-package/src/usr/share/vpn-router/templates/Caddyfile.j2 +++ b/debian-package/src/usr/share/vpn-router/templates/Caddyfile.j2 @@ -4,6 +4,9 @@ {%- if https_email %} email {{ https_email }} {%- endif %} +{%- if https_acme_ca %} + acme_ca {{ https_acme_ca }} +{%- endif %} {%- if pki_cas %} skip_install_trust pki {