Files
linux-cloud-router/Plan.md
T
slawek 3c665c2b6c Add cloud-router configuration templates and scripts
- 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.
2026-05-27 00:33:07 +02:00

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.sh directly
  • 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-pki is not a dependency; openssl covers all PKI needs (including PKCS#12 which pki tool 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 debhelper version 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

  1. debian/control — package name cloud-router, short description, Build-Depends and Depends (see below)
  2. debian/changelog — initial 1.0.0-1 entry
  3. debian/rules — minimal dh $@ (debhelper)
  4. 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

  1. src/etc/sysctl.d/99-cloud-router.conf

    net.ipv4.ip_forward = 1
    net.ipv4.conf.all.rp_filter = 0
    net.ipv4.conf.default.rp_filter = 0
    

    (identical content to cloud-init)

  2. src/etc/xdg/nvim/init.vim — exact neovim settings from router-cloud-init.tpl

  3. src/etc/systemd/system/cloud-router-setup.service Renamed from azure-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

  1. Adapt azure-router-setup bash script from router-cloud-init.tpl:
    • Add source /etc/default/cloud-router at 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 to src/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" ]

Phase 4 — PKI Library

  1. src/usr/local/lib/simple-ca.sh — Bash function module providing make_ca, make_cert, make_pfx. Source it in any shell session with source /usr/local/lib/simple-ca.sh; set SIMPLE_CA_DIR=/etc/cloud-router/pki before use.

  2. 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_NAME in /etc/default/cloud-router (defaults to the hostname component of LOCAL_FQDN).


Phase 5 — Debconf Templates and Config Script

  1. debian/templates — one Template: block per configurable value:

    Question Type Default
    cloud-router/local_addrs string
    cloud-router/local_fqdn string
    cloud-router/local_id_mode select fqdn (choices: fqdn, public_ip, internal_ip)
    cloud-router/local_cidrs string
    cloud-router/remote_addrs string
    cloud-router/remote_id string
    cloud-router/psk password
    cloud-router/remote_cidrs string
    cloud-router/router_int_gateway_ip string
    cloud-router/p2s_address_pool string
    cloud-router/wg_enabled boolean false
    cloud-router/wg_address string 10.0.1.1/24 (shown only when wg_enabled=true)
    cloud-router/wg_listen_port string 51820 (shown only when wg_enabled=true)
  2. debian/config — pre-install shell script:

    • Sources /usr/share/debconf/confmodule
    • Calls db_input for each question in priority order
    • Shows wg_address and wg_listen_port conditionally: db_get cloud-router/wg_enabled; if [ "$RET" = "true" ]

Phase 6 — Maintainer Scripts

  1. debian/postinst (triggered on configure):

    1. db_get all debconf answers
    2. Derive LOCAL_SUBNET = first element of LOCAL_CIDRS
    3. Derive P2S_SERVER_NAME = hostname part of LOCAL_FQDN
    4. 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
      
    5. Generate /etc/swanctl/conf.d/remote-site.conf (mode 0600, root:root) via bash heredoc — includes both the connections {} block and a secrets { ike-remote-site { secret = "$PSK" } } block; the file must be 0600 because it contains the PSK
    6. Generate /etc/swanctl/conf.d/road-warrior.conf via heredoc
    7. Generate /etc/systemd/resolved.conf.d/p2s-forwarder.conf via heredoc
    8. If WG_ENABLED=true: generate /etc/wireguard/wg0.conf via heredoc
    9. Generate /etc/netplan/90-cloud-router.yaml via heredoc
    10. sysctl --system
    11. netplan apply
    12. systemctl daemon-reload
    13. systemctl enable --now cloud-router-setup.service
    14. ufw allow 22/tcp && ufw --force enable && ufw reload
    15. systemctl enable --now strongswan
    16. systemctl restart systemd-resolved
  2. debian/prerm (on remove):

    • systemctl disable --now cloud-router-setup.service
    • systemctl disable --now strongswan

Phase 7 — Install Manifest

  1. debian/install — maps src/ 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/
    
  2. 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_selections runs before packages in cloud-init's module order, so answers are in place before postinst executes
  • DEBIAN_FRONTEND does 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.tpl in the azure-router module

Verification

  1. dpkg-buildpackage -us -uc -b from linux-cloud-router/ completes without error; .deb produced
  2. dpkg -i cloud-router_1.0.0-1_all.deb on a fresh VM for each target Ubuntu release (22.04, 24.04, 26.04): debconf prompts appear; all deps satisfied
  3. cat /etc/default/cloud-router shows non-secret config; /etc/swanctl/conf.d/remote-site.conf and road-warrior.conf are present; remote-site.conf is mode 0600 and contains the secrets {} block with the PSK; /etc/wireguard/wg0.conf exists only when WG_ENABLED=true
  4. Source simple-ca.sh, then make_ca "My Router CA"/etc/cloud-router/pki/ca_cert.pem created; make_cert --type server router.example.com → cert/key produced; cloud-router-sync-pki → files appear in /etc/swanctl/
  5. make_pfx <cert-path>.pfx produced for a road-warrior client
  6. systemctl status cloud-router-setup.service is active (exited); UFW before.rules contains the IPsec/NAT/forwarding sections; when WG_ENABLED=true, WireGuard keys are present at /etc/wireguard/wg0.key and wg0.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-tools availability and kernel module inclusion
    • netplan API and YAML schema changes between releases
    • systemd-resolved DNS stub behaviour differences
  • dpkg-reconfigure behaviour: re-running dpkg-reconfigure cloud-router re-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 via simple-ca.sh) are not touched by postinst.

  • simple-ca.sh key algorithm support: currently RSA-4096 only. ECDSA/Ed25519 support is a planned future enhancement to simple-ca.sh itself and is independent of this package.

  • CRL / revocation: simple-ca.sh has no revocation support. If road-warrior client revocation is needed, strongswan-pki can be added as an optional dependency for pki --signcrl. Treat as a future enhancement.

  • Build tooling: container (Apple container CLI) is used to run dpkg-buildpackage inside Ubuntu containers. The source tree is bind-mounted into the container; the built .deb appears 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. container handles all build and install testing — run the same loop against 22.04, 24.04, and 26.04 containers once the compatibility matrix is resolved.