Defer configuration by default and manage vpn-router.conf with ucf

Add platform = none as the shipped default so installing the package
changes nothing on the machine until a platform is chosen. Add a mode
setting (manual/interfaces/auto) controlling how much of the network
configuration is supplied versus detected from the system. Manage
/etc/vpn-router/vpn-router.conf with ucf instead of writing it once,
so dpkg-reconfigure can safely reapply debconf answers without
clobbering local edits. Extend NAT/forward rules to all local subnets,
not just the first.
This commit is contained in:
2026-08-23 17:15:49 +02:00
parent 083ad9a596
commit 7cb2f1b8dd
14 changed files with 439 additions and 124 deletions
+68 -25
View File
@@ -7,12 +7,16 @@ A Debian package that configures a Linux host or virtual machine as a VPN router
- optional WireGuard endpoint
- routing and NAT for a protected subnet, TCP MSS clamping, and UFW firewall rules
Nothing in the package is specific to a cloud provider. It is built on two inputs, the
name of the external NIC and the name of the internal one; the addresses on them are read
from the system rather than configured again. Nothing is auto-detected, because a router
that guesses which interface to NAT out of is a router that can guess wrong silently.
Nothing in the package is specific to a cloud provider. It is built on the external and
internal NIC names, and a `mode` setting says how much of the rest you are supplying and
how much is read from the system. Detection happens only in the mode that asks for it, so
a router never quietly guesses which interface to NAT out of unless you told it to.
Platform-specific additions live in separate modules and are selected explicitly.
Installing the package configures nothing by default. The shipped platform is `none`,
which means the files are laid down and the machine is left alone until you say
otherwise.
## Installing
The package can be installed by hand or as a step in automated provisioning. Both are
@@ -27,27 +31,32 @@ systemctl restart vpn-router-setup apply it
### By hand
```sh
apt-get install vpn-router
apt-get install vpn-router # answer "none" to the platform question
$EDITOR /etc/vpn-router/vpn-router.conf
systemctl restart vpn-router-setup
```
Accepting every default at install time is safe. Installing the package enables IP
forwarding and relaxes `rp_filter` through its own drop-in in `/etc/sysctl.d/`, which a
router needs, but it configures no tunnels, adds no firewall rules and does not enable the
firewall. Fill the file in when you are ready.
Answering `none` to the platform question defers everything: no further questions are
asked and nothing on the machine is configured. The only change an install makes is the
package's own `/etc/sysctl.d/` drop-in, which enables IP forwarding and relaxes
`rp_filter` as a router needs. Set `platform` in the file when you are ready.
### Automated
Preseed the answers and the router comes up configured, with no follow-up command:
Preseed the answers and the router comes up configured, with no follow-up command. Note
that `platform` must be something other than `none`, and `mode` says how much you are
supplying:
```sh
```shell
debconf-set-selections <<'EOF'
vpn-router vpn-router/platform select generic
vpn-router vpn-router/mode select manual
vpn-router vpn-router/external_interface string eth0
vpn-router vpn-router/internal_interface string eth1
vpn-router vpn-router/int_addr string 10.1.1.4
vpn-router vpn-router/int_gateway_ip string 10.1.1.1
vpn-router vpn-router/local_fqdn string router.example.com
vpn-router vpn-router/local_cidrs string 10.0.0.0/24
vpn-router vpn-router/int_gateway_ip string 10.1.1.1
vpn-router vpn-router/remote_addrs string peer.example.net
vpn-router vpn-router/remote_id string peer.example.net
vpn-router vpn-router/remote_cidrs string 192.168.0.0/24
@@ -56,6 +65,9 @@ EOF
DEBIAN_FRONTEND=noninteractive apt-get install -y vpn-router
```
With `mode = interfaces` the `int_addr` and `int_gateway_ip` lines are dropped and read
from the internal NIC instead; with `mode = auto` the interface names go too.
Alternatively write `/etc/vpn-router/vpn-router.conf` directly before or after installing.
A configuration-management tool should do that and then restart `vpn-router-setup`.
@@ -63,22 +75,24 @@ See [examples/](examples/) for a cloud-init template and a shell installer.
## Configuration
`/etc/vpn-router/vpn-router.conf` is INI, mode 0600, and is created once on first install.
The package never rewrites it, so it is safe to edit, keep in version control or template.
A fully commented reference is installed at
`/usr/share/doc/vpn-router/vpn-router.conf.example`.
`/etc/vpn-router/vpn-router.conf` is INI, mode 0600, and is created on first install. It
is managed with `ucf`, so it is yours to edit: a later `dpkg-reconfigure` applies what
changed and asks before touching anything you altered by hand. A fully commented
reference is installed at `/usr/share/doc/vpn-router/vpn-router.conf.example`.
Lists are comma-separated, booleans are `true`/`false`, an empty value means unset, and a
`_b64` suffix means the value is base64-encoded.
| Section | Setting | Meaning |
|---|---|---|
| `general` | `platform` | Platform module: `generic`, `azure` or `gcp` |
| `general` | `platform` | `none`, `generic`, `azure` or `gcp`. `none` defers configuration entirely |
| `general` | `mode` | How much you supply: `manual`, `interfaces` or `auto` |
| `interfaces` | `external` | Name of the NIC facing the untrusted network |
| `interfaces` | `internal` | Name of the NIC facing the protected network |
| `wan` | `local_fqdn` | This router's FQDN |
| `wan` | `local_id_mode` | IKE identity source: `fqdn`, `public_ip` or `internal_ip` |
| `local` | `cidrs` | Local subnets advertised into the tunnel |
| `local` | `int_addr` | This host's address on the internal network |
| `local` | `int_gateway_ip` | Next hop on the internal side |
| `remote` | `addrs` | Remote gateway address(es) or FQDN |
| `remote` | `id` | Remote IKE identity, without a leading `@` |
@@ -105,10 +119,26 @@ printf %s 'the key' | base64 -w0
Every feature is independent. Leaving a group empty simply means that feature is not
configured, and its generated files are removed.
The two interface names are the base the rest is built on. The addresses strongSwan binds
on come from the external NIC, and the address used for `local_id_mode = internal_ip`
comes from the internal one, so neither is configured twice and neither can drift out of
step with the host. If a named interface does not exist the service stops and says so.
### What `mode` decides
`mode` states how much of the network configuration you are supplying, and therefore how
much the package reads from the system:
| `mode` | You provide | The package works out |
|---|---|---|
| `manual` | both interface names, `int_addr`, `int_gateway_ip` | nothing |
| `interfaces` | both interface names | `int_addr` and `int_gateway_ip`, from those interfaces |
| `auto` | nothing | the interface names too, from the routing table |
`manual` is the only mode in which no detection code runs at all. `auto` is the
convenient one and can pick the wrong interface, which is a trade you make deliberately
by choosing it rather than something the package does behind your back.
The addresses strongSwan binds on always come from the external NIC, in every mode, so
they are never configured twice and cannot drift out of step with the host. If a named
interface does not exist the service stops and says so. In `interfaces` and `auto` mode
the internal gateway is taken from the routing table, falling back to the first host
address of the connected subnet with a warning when the routing table has nothing to say.
## Applying changes
@@ -116,20 +146,28 @@ step with the host. If a named interface does not exist the service stops and sa
systemctl restart vpn-router-setup
```
The service also runs at boot. It regenerates the configuration files, reads the current
addresses of the two interfaces, applies routes, generates WireGuard keys and injects
The service also runs at boot. With `platform = none` it does nothing and says so.
Otherwise it regenerates the configuration files, resolves the interfaces and their
addresses as `mode` directs, applies routes, generates WireGuard keys and injects
firewall rules. It then starts `strongswan` and `wg-quick@wg0` if the corresponding
feature is configured, reloading or restarting them only when their inputs changed, and
restarts `systemd-resolved` only when the road-warrior DNS drop-in changed. Turning a
feature off in the configuration stops its service and removes its files. Every step is
idempotent, so restarting with no changes does nothing.
To regenerate the configuration files without touching the running system:
To regenerate the configuration files and PKI without starting, stopping or reloading
anything:
```sh
/usr/lib/vpn-router/configure
```
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.
## Certificates
`/etc/vpn-router/pki` is the PKI directory, laid out the way `simple-ca` expects:
@@ -169,6 +207,10 @@ Platform-specific values do not belong in the core, so they are Python modules i
`general.platform`. The platform is never probed: the caller already knows where it is
deploying.
`none` is not a module. It is the shipped default and means "configure nothing at all",
which is how a deferred install is expressed. `generic` is the module to choose when the
router should be configured but the platform adds nothing.
The shipped modules currently add nothing. Road-warrior clients are given the router
itself as their DNS server and the router forwards through its own `systemd-resolved`, so
no provider resolver has to be advertised or routed through the tunnel on any platform.
@@ -206,7 +248,8 @@ configuration in place. `strongswan` is left enabled, since it is a shared servi
may be in use by something else.
`apt-get purge` additionally removes the generated files and the firewall rules, restoring
`/etc/ufw/before.rules` to its original content. Key material is never deleted:
`/etc/ufw/before.rules` to its original content, and hands `vpn-router.conf` back to `ucf`
before deleting it so a later reinstall starts clean. Key material is never deleted:
`/etc/vpn-router/pki`, the WireGuard key pair and everything under `/etc/swanctl` are left
alone.