Reengineed the code. Generalized the package. Cloud configurators are modules.

This commit is contained in:
2026-08-10 09:02:45 +02:00
parent be4bb34bb0
commit 083ad9a596
40 changed files with 2367 additions and 650 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
cloud-router (1.0.0-1) unstable; urgency=medium
vpn-router (1.0.0-1) unstable; urgency=medium
* Initial release.
+23 -14
View File
@@ -2,22 +2,31 @@
set -e
. /usr/share/debconf/confmodule
db_input high cloud-router/local_addrs || true
db_input high cloud-router/local_fqdn || true
db_input high cloud-router/local_id_mode || true
db_input high cloud-router/local_cidrs || true
db_input high cloud-router/remote_addrs || true
db_input high cloud-router/remote_id || true
db_input high cloud-router/psk || true
db_input high cloud-router/remote_cidrs || true
db_input high cloud-router/router_int_gateway_ip || true
db_input high cloud-router/p2s_address_pool || true
db_input high cloud-router/wg_enabled || true
db_input high vpn-router/platform || true
db_input high vpn-router/external_interface || true
db_input high vpn-router/internal_interface || true
db_input high vpn-router/local_fqdn || true
db_input high vpn-router/local_id_mode || true
db_input high vpn-router/local_cidrs || true
db_input high vpn-router/int_gateway_ip || true
db_input high vpn-router/remote_addrs || true
db_input high vpn-router/remote_id || true
db_input high vpn-router/remote_cidrs || true
db_input high vpn-router/psk || true
db_input high vpn-router/p2s_enabled || true
db_input high vpn-router/wg_enabled || true
db_go || true
db_get cloud-router/wg_enabled
db_get vpn-router/p2s_enabled
if [ "$RET" = "true" ]; then
db_input high cloud-router/wg_address || true
db_input high cloud-router/wg_listen_port || true
db_input high vpn-router/p2s_address_pool || true
db_input high vpn-router/p2s_ca_name || true
db_go || true
fi
db_get vpn-router/wg_enabled
if [ "$RET" = "true" ]; then
db_input high vpn-router/wg_address || true
db_input high vpn-router/wg_listen_port || true
db_go || true
fi
+21 -11
View File
@@ -1,4 +1,4 @@
Source: cloud-router
Source: vpn-router
Section: net
Priority: optional
Maintainer: Sławomir Koszewski <slawek@koszewscy.waw.pl>
@@ -6,7 +6,7 @@ Build-Depends: debhelper-compat (= 13)
Standards-Version: 4.6.2
Rules-Requires-Root: no
Package: cloud-router
Package: vpn-router
Architecture: all
Depends: ${misc:Depends},
strongswan-swanctl,
@@ -17,13 +17,23 @@ Depends: ${misc:Depends},
ufw,
debconf,
openssl,
python3-jinja2
Description: Linux cloud router with IPSec and optional WireGuard
Configures a Linux host as a cloud router providing site-to-site IKEv2
IPSec (strongSwan swanctl) and road-warrior P2S VPN (EAP-TLS). WireGuard
is optionally enabled. Includes a PKI helper library (simple-ca.sh) for
managing the road-warrior certificate authority.
python3,
python3-jinja2,
iproute2,
systemd-resolved
Description: Linux VPN router with IPSec, road-warrior and WireGuard support
Configures a Linux host or virtual machine as a VPN router: site-to-site
IKEv2 IPSec (strongSwan swanctl), optional road-warrior access using EAP-TLS,
optional WireGuard, and the routing, NAT, MSS clamping and firewall rules a
router needs. Nothing in the package is specific to a cloud provider.
.
Site-specific values are collected via debconf at install time and written
to /etc/default/cloud-router. A one-shot systemd service (cloud-router-setup)
applies UFW rules and WireGuard keys on first boot.
Settings live in /etc/vpn-router/vpn-router.conf, which is created on first
install and owned by the administrator afterwards. Editing it and restarting
vpn-router-setup applies the change; the same service reconciles the system
at every boot. It is built on two inputs, the external and internal interface
names; the addresses on them are read from the system rather than configured
again, and nothing is auto-detected.
.
Platform-specific additions are provided by modules under
/usr/lib/vpn-router, selected explicitly by the platform setting. Modules for
Azure and GCP are included.
+1 -1
View File
@@ -1,5 +1,5 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: cloud-router
Upstream-Name: vpn-router
Upstream-Contact: Sławomir Koszewski <slawek@koszewscy.waw.pl>
Files: *
+4 -10
View File
@@ -1,10 +1,4 @@
etc/cloud-router
etc/cloud-router/pki
etc/wireguard
etc/swanctl/conf.d
etc/swanctl/x509ca
etc/swanctl/x509
etc/swanctl/private
etc/systemd/resolved.conf.d
usr/lib/cloud-router
usr/share/cloud-router/templates
etc/vpn-router
usr/lib/vpn-router
usr/lib/vpn-router/vpnrouter_platforms
usr/share/vpn-router/templates
+9 -4
View File
@@ -1,4 +1,9 @@
src/etc/sysctl.d/99-cloud-router.conf etc/sysctl.d/
src/opt/cloud-router/bin/simple-ca opt/cloud-router/bin/
src/usr/lib/cloud-router/configure usr/lib/cloud-router/
src/usr/share/cloud-router/templates/* usr/share/cloud-router/templates/
src/etc/sysctl.d/99-vpn-router.conf etc/sysctl.d/
src/usr/lib/vpn-router/vpnrouter.py usr/lib/vpn-router/
src/usr/lib/vpn-router/simple-ca usr/lib/vpn-router/
src/usr/lib/vpn-router/configure usr/lib/vpn-router/
src/usr/lib/vpn-router/setup usr/lib/vpn-router/
src/usr/lib/vpn-router/seed-config usr/lib/vpn-router/
src/usr/lib/vpn-router/vpnrouter_platforms/*.py usr/lib/vpn-router/vpnrouter_platforms/
src/usr/share/vpn-router/templates/* usr/share/vpn-router/templates/
src/usr/share/doc/vpn-router/vpn-router.conf.example usr/share/doc/vpn-router/
+37 -36
View File
@@ -4,47 +4,48 @@ set -e
case "$1" in
configure)
# ── Read debconf answers ──────────────────────────────────────────────
db_get cloud-router/local_addrs; CLOUD_ROUTER_LOCAL_ADDRS="$RET"
db_get cloud-router/local_fqdn; CLOUD_ROUTER_LOCAL_FQDN="$RET"
db_get cloud-router/local_id_mode; CLOUD_ROUTER_LOCAL_ID_MODE="$RET"
db_get cloud-router/local_cidrs; CLOUD_ROUTER_LOCAL_CIDRS="$RET"
db_get cloud-router/remote_addrs; CLOUD_ROUTER_REMOTE_ADDRS="$RET"
db_get cloud-router/remote_id; CLOUD_ROUTER_REMOTE_ID="$RET"
db_get cloud-router/psk; CLOUD_ROUTER_PSK="$RET"
db_get cloud-router/remote_cidrs; CLOUD_ROUTER_REMOTE_CIDRS="$RET"
db_get cloud-router/router_int_gateway_ip; CLOUD_ROUTER_ROUTER_INT_GATEWAY_IP="$RET"
db_get cloud-router/p2s_address_pool; CLOUD_ROUTER_P2S_ADDRESS_POOL="$RET"
db_get cloud-router/wg_enabled; CLOUD_ROUTER_WG_ENABLED="$RET"
db_get cloud-router/wg_address; CLOUD_ROUTER_WG_ADDRESS="$RET"
db_get cloud-router/wg_listen_port; CLOUD_ROUTER_WG_LISTEN_PORT="$RET"
# --- Read the installer's answers ---
db_get vpn-router/platform; VPN_ROUTER_PLATFORM="$RET"
db_get vpn-router/external_interface; VPN_ROUTER_EXTERNAL_INTERFACE="$RET"
db_get vpn-router/internal_interface; VPN_ROUTER_INTERNAL_INTERFACE="$RET"
db_get vpn-router/local_fqdn; VPN_ROUTER_LOCAL_FQDN="$RET"
db_get vpn-router/local_id_mode; VPN_ROUTER_LOCAL_ID_MODE="$RET"
db_get vpn-router/local_cidrs; VPN_ROUTER_LOCAL_CIDRS="$RET"
db_get vpn-router/int_gateway_ip; VPN_ROUTER_INT_GATEWAY_IP="$RET"
db_get vpn-router/remote_addrs; VPN_ROUTER_REMOTE_ADDRS="$RET"
db_get vpn-router/remote_id; VPN_ROUTER_REMOTE_ID="$RET"
db_get vpn-router/remote_cidrs; VPN_ROUTER_REMOTE_CIDRS="$RET"
db_get vpn-router/psk; VPN_ROUTER_PSK="$RET"
db_get vpn-router/p2s_enabled; VPN_ROUTER_P2S_ENABLED="$RET"
db_get vpn-router/p2s_address_pool; VPN_ROUTER_P2S_ADDRESS_POOL="$RET"
db_get vpn-router/p2s_ca_name; VPN_ROUTER_P2S_CA_NAME="$RET"
db_get vpn-router/wg_enabled; VPN_ROUTER_WG_ENABLED="$RET"
db_get vpn-router/wg_address; VPN_ROUTER_WG_ADDRESS="$RET"
db_get vpn-router/wg_listen_port; VPN_ROUTER_WG_LISTEN_PORT="$RET"
# ── Render configuration files via Jinja2 templates ─────────────────
export CLOUD_ROUTER_LOCAL_ADDRS CLOUD_ROUTER_LOCAL_FQDN \
CLOUD_ROUTER_LOCAL_ID_MODE CLOUD_ROUTER_LOCAL_CIDRS \
CLOUD_ROUTER_REMOTE_ADDRS CLOUD_ROUTER_REMOTE_ID \
CLOUD_ROUTER_PSK CLOUD_ROUTER_REMOTE_CIDRS \
CLOUD_ROUTER_ROUTER_INT_GATEWAY_IP CLOUD_ROUTER_P2S_ADDRESS_POOL \
CLOUD_ROUTER_WG_ENABLED CLOUD_ROUTER_WG_ADDRESS \
CLOUD_ROUTER_WG_LISTEN_PORT
export VPN_ROUTER_PLATFORM VPN_ROUTER_EXTERNAL_INTERFACE \
VPN_ROUTER_INTERNAL_INTERFACE VPN_ROUTER_LOCAL_FQDN \
VPN_ROUTER_LOCAL_ID_MODE VPN_ROUTER_LOCAL_CIDRS \
VPN_ROUTER_INT_GATEWAY_IP \
VPN_ROUTER_REMOTE_ADDRS VPN_ROUTER_REMOTE_ID \
VPN_ROUTER_REMOTE_CIDRS VPN_ROUTER_PSK \
VPN_ROUTER_P2S_ENABLED VPN_ROUTER_P2S_ADDRESS_POOL \
VPN_ROUTER_P2S_CA_NAME VPN_ROUTER_WG_ENABLED \
VPN_ROUTER_WG_ADDRESS VPN_ROUTER_WG_LISTEN_PORT
/usr/lib/cloud-router/configure
# --- Create the configuration file, only if it does not exist ---
/usr/lib/vpn-router/seed-config
db_set cloud-router/psk ""
# The key now lives in the configuration file; do not keep a copy.
db_set vpn-router/psk ""
# ── Apply system settings ─────────────────────────────────────────────
sysctl --system
netplan apply
systemctl daemon-reload
systemctl restart systemd-resolved
# Apply the sysctl drop-in shipped by this package so it takes effect
# without waiting for a reboot.
sysctl --system >/dev/null
# ── UFW: ensure SSH is allowed then enable ────────────────────────────
ufw allow 22/tcp
ufw --force enable
ufw reload
# ── strongSwan ────────────────────────────────────────────────────────
systemctl enable --now strongswan
# The firewall is not touched here. Enabling it, allowing SSH and
# setting the forward policy all belong to vpn-router-setup, which does
# them only once the configuration says this host is a router.
;;
esac
+49
View File
@@ -0,0 +1,49 @@
#!/bin/sh
set -e
BEFORE_RULES=/etc/ufw/before.rules
# Remove the rule blocks this package inserted, together with the single blank
# line that precedes each one, so before.rules returns to its original content.
strip_ufw_blocks() {
[ -f "$BEFORE_RULES" ] || return 0
grep -q '^# ROUTER MSS RULES START$\|^# IPSEC RULES START$' "$BEFORE_RULES" || return 0
tmp="$(mktemp)"
awk '
/^# (IPSEC|WIREGUARD|P2S DNS|ROUTER FORWARD|ROUTER NAT|ROUTER MSS) RULES START$/ {
skip = 1; pending = 0; next
}
/^# (IPSEC|WIREGUARD|P2S DNS|ROUTER FORWARD|ROUTER NAT|ROUTER MSS) RULES END$/ {
skip = 0; next
}
skip { next }
/^$/ { pending++; next }
{
while (pending > 0) { print ""; pending-- }
print
}
END { while (pending > 0) { print ""; pending-- } }
' "$BEFORE_RULES" > "$tmp"
cat "$tmp" > "$BEFORE_RULES"
rm -f "$tmp"
}
case "$1" in
purge)
strip_ufw_blocks
rm -f /etc/swanctl/conf.d/remote-site.conf \
/etc/swanctl/conf.d/road-warrior.conf \
/etc/systemd/resolved.conf.d/p2s-forwarder.conf \
/etc/wireguard/wg0.conf \
/etc/vpn-router/vpn-router.conf
# Key material is not package state: /etc/vpn-router/pki, the WireGuard
# key pair and everything under /etc/swanctl are left in place.
rmdir --ignore-fail-on-non-empty /etc/vpn-router 2>/dev/null || true
;;
esac
#DEBHELPER#
+9 -1
View File
@@ -3,7 +3,15 @@ set -e
case "$1" in
remove|deconfigure)
systemctl disable --now strongswan || true
# Stop offering the tunnels, but leave strongSwan itself enabled: it is
# a shared service and may be in use by something other than this
# package. The connection files are removed on purge.
if [ -d /run/systemd/system ]; then
swanctl --terminate --ike remote-site >/dev/null 2>&1 || true
swanctl --terminate --ike road-warrior >/dev/null 2>&1 || true
# This package enabled wg-quick@wg0, so it takes it down again.
systemctl disable --now wg-quick@wg0 >/dev/null 2>&1 || true
fi
;;
esac
+3
View File
@@ -1,3 +1,6 @@
#!/usr/bin/make -f
%:
dh $@
override_dh_installsystemd:
dh_installsystemd --name=vpn-router-setup
+98 -48
View File
@@ -1,80 +1,130 @@
Template: cloud-router/local_addrs
Type: string
Description: Local WAN IP address(es)
Comma-separated list of local WAN IP addresses that strongSwan binds on
for the site-to-site and road-warrior tunnels (e.g. 10.1.2.3).
Template: vpn-router/platform
Type: select
Choices: generic, azure, gcp
Default: generic
Description: Platform module
Platform-specific additions to load. The router works without any of them;
a module only adds what makes sense on its platform, such as the platform
DNS resolver for road-warrior clients.
Template: cloud-router/local_fqdn
Template: vpn-router/external_interface
Type: string
Default:
Description: External network interface
Name of the interface facing the untrusted network, for example eth0 or
ens4. The addresses strongSwan binds on are read from it, so they are never
configured separately.
.
Leave empty to configure the router later by editing
/etc/vpn-router/vpn-router.conf.
Template: vpn-router/internal_interface
Type: string
Default:
Description: Internal network interface
Name of the interface facing the protected network, for example eth1 or
ens5. Routes for the local subnets are applied to it.
Template: vpn-router/local_fqdn
Type: string
Default:
Description: Local router FQDN
Fully-qualified domain name of this router (e.g. router.example.com).
Used as the road-warrior server identity and certificate CN.
Fully-qualified domain name of this router (for example
router.example.com). Used as the road-warrior server identity and
certificate common name.
Template: cloud-router/local_id_mode
Template: vpn-router/local_id_mode
Type: select
Choices: fqdn, public_ip, internal_ip
Default: fqdn
Description: IKE local identity mode
How to derive the IKE identity advertised to the remote site:
fqdn — use the FQDN (default; requires matching on remote side)
public_ip — resolve the public IP from DNS at first boot
internal_ip — use the local WAN IP address
.
fqdn - use the FQDN, which must match what the peer expects.
.
public_ip - resolve the FQDN and use the address it returns.
.
internal_ip - use the internal NIC address.
Template: cloud-router/local_cidrs
Template: vpn-router/local_cidrs
Type: string
Default:
Description: Local subnet CIDR(s)
Comma-separated list of local subnet CIDRs to advertise into the
site-to-site tunnel (e.g. 10.0.0.0/24 or 10.0.0.0/24,10.0.1.0/24).
site-to-site tunnel (for example 10.0.0.0/24 or 10.0.0.0/24,10.0.1.0/24).
Template: cloud-router/remote_addrs
Template: vpn-router/int_gateway_ip
Type: string
Default:
Description: Internal network gateway IP
Address of the next-hop gateway on the internal side, used to route the
local subnets listed above.
Template: vpn-router/remote_addrs
Type: string
Default:
Description: Remote site WAN IP address(es)
Comma-separated list of remote site WAN IP addresses for the
Comma-separated list of remote gateway addresses or FQDNs for the
site-to-site IPSec tunnel.
Template: cloud-router/remote_id
Template: vpn-router/remote_id
Type: string
Default:
Description: Remote site IKE identity
IKE identity of the remote peer (FQDN, without leading @).
IKE identity of the remote peer, without a leading @.
Template: cloud-router/psk
Template: vpn-router/remote_cidrs
Type: string
Default:
Description: Remote subnet CIDR(s)
Comma-separated list of remote subnet CIDRs reachable through the
site-to-site tunnel (for example 192.168.0.0/24).
Template: vpn-router/psk
Type: password
Description: Pre-shared key (PSK)
Pre-shared key for the site-to-site IKEv2 tunnel. Must match the
value configured on the remote peer.
Pre-shared key for the site-to-site IKEv2 tunnel. Must match the value
configured on the remote peer. Stored base64-encoded in the configuration
file and cleared from the debconf database after installation.
Template: cloud-router/remote_cidrs
Type: string
Description: Remote subnet CIDR(s)
Comma-separated list of remote subnet CIDRs for the site-to-site
tunnel (e.g. 192.168.0.0/24).
Template: cloud-router/router_int_gateway_ip
Type: string
Description: Internal network gateway IP
IP address of the next-hop gateway on the internal NIC (eth1).
Used in the netplan route for the local subnet.
Template: cloud-router/p2s_address_pool
Type: string
Description: Road-warrior address pool
CIDR block assigned to road-warrior VPN clients (e.g. 172.16.0.0/24).
Template: cloud-router/wg_enabled
Template: vpn-router/p2s_enabled
Type: boolean
Default: false
Description: Enable WireGuard VPN?
If true, WireGuard is configured on wg0 and its UFW rules are installed.
Description: Enable road-warrior (P2S) access?
If enabled, this router accepts IKEv2 EAP-TLS connections from individual
clients. Certificates are taken from /etc/vpn-router/pki, and a local
certificate authority is created there if that directory is empty.
Template: cloud-router/wg_address
Template: vpn-router/p2s_address_pool
Type: string
Default: 10.0.1.1/24
Description: WireGuard interface address
IP address and prefix length for the wg0 interface (e.g. 10.0.1.1/24).
Only used when WireGuard is enabled.
Default:
Description: Road-warrior address pool
CIDR block assigned to road-warrior clients (for example 172.16.0.0/24).
Template: cloud-router/wg_listen_port
Template: vpn-router/p2s_ca_name
Type: string
Default: VPN Router CA
Description: Road-warrior CA name
Common name for the certificate authority created in /etc/vpn-router/pki
when that directory is empty. Ignored when certificates are supplied.
Template: vpn-router/wg_enabled
Type: boolean
Default: false
Description: Enable WireGuard?
If enabled, WireGuard is configured on wg0, a key pair is generated, and
the matching firewall rule is installed. Peers are added by editing
/etc/wireguard/wg0.conf.
Template: vpn-router/wg_address
Type: string
Default:
Description: WireGuard interface address
Address and prefix length for the wg0 interface (for example
192.168.200.1/24).
Template: vpn-router/wg_listen_port
Type: string
Default: 51820
Description: WireGuard listen port
UDP port that WireGuard listens on. Only used when WireGuard is enabled.
UDP port that WireGuard listens on.
@@ -0,0 +1,13 @@
[Unit]
Description=VPN Router Setup
Documentation=file:/usr/share/doc/vpn-router/README.md
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/lib/vpn-router/setup
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target