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:
@@ -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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user