- Introduced debian templates for cloud-router configuration parameters. - Added simple-ca.sh script for managing a minimal Certificate Authority (CA) for IKEv2 PKI. - Created sysctl configuration to enable IP forwarding and adjust rp_filter settings. - Implemented configure script to render configuration files using Jinja2 templates. - Added simple-ca script for generating CA and certificates. - Created Jinja2 templates for various configuration files including netplan, strongSwan, and WireGuard. - Implemented UFW rules setup for IPsec and WireGuard. - Added support for road-warrior and site-to-site VPN configurations.
15 KiB
Plan: Debian Package Source for linux-cloud-router
Context
The azure-router Terraform module provisions a Linux router VM using Cloud Init. Cloud Init
installs packages, writes config files (strongSwan swanctl, WireGuard, netplan, sysctl, UFW
rules), and runs a one-shot setup service. All site-specific values (IPs, FQDNs, CIDRs, PSK)
are injected at provision time via Terraform's templatefile().
The Debian package replicates the same outcome on Ubuntu 22.04, 24.04, and 26.04 hosts
(exact supported version list TBD — compatibility and feature availability must be verified
across all three; see Further Considerations). Site-specific values
are collected via debconf interactive prompts at dpkg install time. A postinst script
reads the debconf answers and generates all derived config files.
Decisions
- Site config: debconf interactive prompts at install time
- P2S certificates: managed with
simple-ca.shdirectly - Networking: Ubuntu only — netplan (not
/etc/network/interfaces.d/) - Build approach: full Debian source package (
dpkg-buildpackage+ debhelper ≥13) - PKI library:
simple-ca.sh(OpenSSL-based); currently RSA-4096, ECDSA/Ed25519 support is a planned future enhancement to the script itself — not a concern for this package strongswan-pkiis not a dependency;opensslcovers all PKI needs (including PKCS#12 whichpkitool cannot produce)
Source Tree Layout
linux-cloud-router/
├── debian/
│ ├── control
│ ├── changelog
│ ├── rules
│ ├── copyright
│ ├── install
│ ├── dirs
│ ├── templates ← debconf question definitions
│ ├── config ← pre-install debconf prompts
│ ├── postinst ← generates all config files, enables services
│ └── prerm ← stops/disables services
└── src/
├── etc/
│ ├── sysctl.d/
│ │ └── 99-cloud-router.conf
│ ├── xdg/nvim/
│ │ └── init.vim
│ └── systemd/system/
│ └── cloud-router-setup.service
└── usr/local/
├── lib/
│ └── simple-ca.sh
└── sbin/
└── cloud-router-setup adapted from azure-router-setup
Phase 0 — Package Availability Research
Use Apple's container CLI to launch one-shot Ubuntu containers for each target release:
PKGS="strongswan-swanctl charon-systemd libstrongswan-extra-plugins \
libcharon-extra-plugins wireguard-tools ufw openssl debconf \
debhelper netplan.io"
for RELEASE in 22.04 24.04 26.04; do
echo "=== ubuntu:$RELEASE ==="
container run --rm ubuntu:$RELEASE \
sh -c "apt-get update -qq && apt-cache policy $PKGS"
done
container is also the primary build environment: mount the source tree and run
dpkg-buildpackage inside the target Ubuntu container, keeping the macOS host clean.
Record the candidate version for each package per release. Use the results to:
- Confirm all packages exist in the default repos (no PPAs needed)
- Verify
debhelperversion supports compat 14 (requires ≥13.10); fall back to compat 13 if 22.04 support is required and its debhelper is too old - Confirm strongSwan package names are consistent across releases
- Finalise the supported Ubuntu version list
Phase 1 — Package Scaffold
debian/control— package namecloud-router, short description,Build-DependsandDepends(see below)debian/changelog— initial1.0.0-1entrydebian/rules— minimaldh $@(debhelper)debian/copyright— MIT (matches simple-ca.sh)
debian/control Build-Depends:
debhelper-compat (= 14)
debian/control Depends:
strongswan-swanctl, charon-systemd, libstrongswan-extra-plugins, libcharon-extra-plugins,
wireguard-tools, ufw, debconf, openssl
Phase 2 — Static Source Files
-
src/etc/sysctl.d/99-cloud-router.confnet.ipv4.ip_forward = 1 net.ipv4.conf.all.rp_filter = 0 net.ipv4.conf.default.rp_filter = 0(identical content to cloud-init)
-
src/etc/xdg/nvim/init.vim— exact neovim settings fromrouter-cloud-init.tpl -
src/etc/systemd/system/cloud-router-setup.serviceRenamed fromazure-router-setup.service. Same content:[Unit] Description=Linux Cloud Router Setup Wants=network-online.target After=network-online.target [Service] Type=oneshot ExecStart=/usr/local/sbin/cloud-router-setup RemainAfterExit=yes [Install] WantedBy=multi-user.target
Phase 3 — cloud-router-setup Script
- Adapt
azure-router-setupbash script fromrouter-cloud-init.tpl:- Add
source /etc/default/cloud-routerat the top - Replace all Terraform-interpolated values (
${local_subnet},${remote_cidrs},${wg_listen_port},${p2s_address_pool},${local_addrs},${fqdn}) with the corresponding variable names sourced from/etc/default/cloud-router - Rename script to
cloud-router-setup; install tosrc/usr/local/sbin/cloud-router-setup - The script remains idempotent (checks for existing UFW rule markers before inserting)
- WireGuard key generation and WireGuard UFW rules are guarded by
if [ "$WG_ENABLED" = "true" ]
- Add
Phase 4 — PKI Library
-
src/usr/local/lib/simple-ca.sh— Bash function module providingmake_ca,make_cert,make_pfx. Source it in any shell session withsource /usr/local/lib/simple-ca.sh; setSIMPLE_CA_DIR=/etc/cloud-router/pkibefore use. -
cloud-router-sync-pki— standalone script that copies PKI output to swanctl's expected locations:/etc/cloud-router/pki/ca_bundle.pem→/etc/swanctl/x509ca/ca.pem/etc/cloud-router/pki/{server}_cert.pem→/etc/swanctl/x509/server.pem/etc/cloud-router/pki/{server}_key.pem→/etc/swanctl/private/server.key
The server cert name is read from
P2S_SERVER_NAMEin/etc/default/cloud-router(defaults to the hostname component ofLOCAL_FQDN).
Phase 5 — Debconf Templates and Config Script
-
debian/templates— oneTemplate:block per configurable value:Question Type Default cloud-router/local_addrsstring cloud-router/local_fqdnstring cloud-router/local_id_modeselect fqdn(choices: fqdn, public_ip, internal_ip)cloud-router/local_cidrsstring cloud-router/remote_addrsstring cloud-router/remote_idstring cloud-router/pskpassword cloud-router/remote_cidrsstring cloud-router/router_int_gateway_ipstring cloud-router/p2s_address_poolstring cloud-router/wg_enabledboolean falsecloud-router/wg_addressstring 10.0.1.1/24(shown only when wg_enabled=true)cloud-router/wg_listen_portstring 51820(shown only when wg_enabled=true) -
debian/config— pre-install shell script:- Sources
/usr/share/debconf/confmodule - Calls
db_inputfor each question in priority order - Shows
wg_addressandwg_listen_portconditionally:db_get cloud-router/wg_enabled; if [ "$RET" = "true" ]
- Sources
Phase 6 — Maintainer Scripts
-
debian/postinst(triggered onconfigure):db_getall debconf answers- Derive
LOCAL_SUBNET= first element ofLOCAL_CIDRS - Derive
P2S_SERVER_NAME= hostname part ofLOCAL_FQDN - Write
/etc/default/cloud-router(mode 0644, root:root) — non-secret config:LOCAL_ADDRS="..." LOCAL_FQDN="..." LOCAL_ID_MODE="..." LOCAL_CIDRS="..." LOCAL_SUBNET="..." REMOTE_ADDRS="..." REMOTE_ID="..." REMOTE_CIDRS="..." ROUTER_INT_GATEWAY_IP="..." P2S_ADDRESS_POOL="..." P2S_SERVER_NAME="..." WG_ENABLED="..." WG_ADDRESS="..." # only relevant when WG_ENABLED=true WG_LISTEN_PORT="..." # only relevant when WG_ENABLED=true - Generate
/etc/swanctl/conf.d/remote-site.conf(mode 0600, root:root) via bash heredoc — includes both theconnections {}block and asecrets { ike-remote-site { secret = "$PSK" } }block; the file must be 0600 because it contains the PSK - Generate
/etc/swanctl/conf.d/road-warrior.confvia heredoc - Generate
/etc/systemd/resolved.conf.d/p2s-forwarder.confvia heredoc - If
WG_ENABLED=true: generate/etc/wireguard/wg0.confvia heredoc - Generate
/etc/netplan/90-cloud-router.yamlvia heredoc sysctl --systemnetplan applysystemctl daemon-reloadsystemctl enable --now cloud-router-setup.serviceufw allow 22/tcp && ufw --force enable && ufw reloadsystemctl enable --now strongswansystemctl restart systemd-resolved
-
debian/prerm(onremove):systemctl disable --now cloud-router-setup.servicesystemctl disable --now strongswan
Phase 7 — Install Manifest
-
debian/install— mapssrc/tree entries to absolute filesystem paths, e.g.:src/etc/sysctl.d/99-cloud-router.conf etc/sysctl.d/ src/etc/xdg/nvim/init.vim etc/xdg/nvim/ src/etc/systemd/system/cloud-router-setup.service etc/systemd/system/ src/usr/local/sbin/cloud-router-setup usr/local/sbin/ src/usr/local/lib/simple-ca.sh usr/local/lib/ -
debian/dirs— directories that must pre-exist: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
Phase 8 — Cloud-Init YAML
The .deb is delivered via a private apt repository (URL/GPG key TBD). The cloud-init
user-data pre-seeds all debconf answers before the package is installed, so postinst runs
fully unattended on first boot.
Template file cloud-router-cloud-init.yaml.tpl (rendered by templatefile() at provision
time, Terraform variable names shown as ${…}):
#cloud-config
apt:
sources:
cloud-router:
source: "deb [signed-by=/etc/apt/keyrings/cloud-router.gpg] ${repo_url} ${ubuntu_codename} main"
key: |
${repo_gpg_key}
debconf_selections: |
cloud-router cloud-router/local_addrs string ${local_addrs}
cloud-router cloud-router/local_fqdn string ${fqdn}
cloud-router cloud-router/local_id_mode select ${local_id_mode}
cloud-router cloud-router/local_cidrs string ${local_cidrs}
cloud-router cloud-router/remote_addrs string ${remote_addrs}
cloud-router cloud-router/remote_id string ${remote_id}
cloud-router cloud-router/psk password ${psk}
cloud-router cloud-router/remote_cidrs string ${remote_cidrs}
cloud-router cloud-router/router_int_gateway_ip string ${router_int_gateway_ip}
cloud-router cloud-router/p2s_address_pool string ${p2s_address_pool}
cloud-router cloud-router/wg_enabled boolean ${wg_enabled}
cloud-router cloud-router/wg_address string ${wg_address}
cloud-router cloud-router/wg_listen_port string ${wg_listen_port}
package_update: true
packages:
- cloud-router
Notes:
debconf_selectionsruns beforepackagesin cloud-init's module order, so answers are in place beforepostinstexecutesDEBIAN_FRONTENDdoes not need to be set explicitly; debconf detects the pre-seeded answers and skips interactive prompts automatically${ubuntu_codename}(e.g.noble) must be passed as a Terraform variable or derived from the VM image;${repo_gpg_key}is the ASCII-armoured public key of the signing key- The template replaces the existing
router-cloud-init.tplin theazure-routermodule
Verification
dpkg-buildpackage -us -uc -bfromlinux-cloud-router/completes without error;.debproduceddpkg -i cloud-router_1.0.0-1_all.debon a fresh VM for each target Ubuntu release (22.04, 24.04, 26.04): debconf prompts appear; all deps satisfiedcat /etc/default/cloud-routershows non-secret config;/etc/swanctl/conf.d/remote-site.confandroad-warrior.confare present;remote-site.confis mode 0600 and contains thesecrets {}block with the PSK;/etc/wireguard/wg0.confexists only whenWG_ENABLED=true- Source
simple-ca.sh, thenmake_ca "My Router CA"→/etc/cloud-router/pki/ca_cert.pemcreated;make_cert --type server router.example.com→ cert/key produced;cloud-router-sync-pki→ files appear in/etc/swanctl/ make_pfx <cert-path>→.pfxproduced for a road-warrior clientsystemctl status cloud-router-setup.serviceisactive (exited); UFWbefore.rulescontains the IPsec/NAT/forwarding sections; whenWG_ENABLED=true, WireGuard keys are present at/etc/wireguard/wg0.keyandwg0.pub
Further Considerations
-
Ubuntu version compatibility (must verify before finalising supported version list): items to check on each of 22.04, 24.04, 26.04:
debhelper-compat (= 14)— Ubuntu 22.04 ships debhelper 13.6 (max compat 13); 24.04 ships 13.11 (compat 14 supported); may need to drop to compat 13 for 22.04 support- strongSwan package names (
strongswan-swanctl,charon-systemd, plugin packages) — verify availability and correct names on each release wireguard-toolsavailability and kernel module inclusion- netplan API and YAML schema changes between releases
systemd-resolvedDNS stub behaviour differences
-
dpkg-reconfigurebehaviour: re-runningdpkg-reconfigure cloud-routerre-prompts all debconf questions and regenerates config files, overwriting any manual edits. Files "owned" by debconf (remote-site.conf, wg0.conf, netplan yaml, env) should be documented as managed; swanctl cert files (written viasimple-ca.sh) are not touched by postinst. -
simple-ca.shkey algorithm support: currently RSA-4096 only. ECDSA/Ed25519 support is a planned future enhancement tosimple-ca.shitself and is independent of this package. -
CRL / revocation:
simple-ca.shhas no revocation support. If road-warrior client revocation is needed,strongswan-pkican be added as an optional dependency forpki --signcrl. Treat as a future enhancement. -
Build tooling:
container(Apple container CLI) is used to rundpkg-buildpackageinside Ubuntu containers. The source tree is bind-mounted into the container; the built.debappears in the parent directory as usual. Inside the container:apt-get install -y build-essential debhelper. No Ubuntu host or VM needed for builds. -
Development environment: source files are authored on macOS.
containerhandles all build and install testing — run the same loop against 22.04, 24.04, and 26.04 containers once the compatibility matrix is resolved.