OpenVPN 19.0 uses Debian 13 Trixie packages for OpenVPN Community Edition, Easy-RSA, iptables and their supporting libraries. Debian supplies the complete application stack, so this appliance adds no third-party application repository or downloaded application artifact. The inherited TurnKey Core and Webmin packages continue to use the signed TurnKey Trixie repository.
The OpenVPN, Easy-RSA and iptables update path is normal authenticated APT.
tests/v19.sh refreshes signed metadata, confirms an eligible candidate for
each identity-defining package, and proves that the nonmutating check did not
change installed versions. No separate compatible apply fixture exists or is
needed for the Debian package route.
The acceptance command is:
TKLDEV_CONTAINER=tkldev19-jeremy-qa \
TKL_HARNESS_STATE_DIR=/home/agent/.local/state/jeremy-v19-pr-qa/openvpn \
TKL_HARNESS_LOCK_FILE=/home/agent/.local/state/jeremy-v19-pr-qa/build.lock \
/home/agent/turnkey/tools/test-v19-appliance openvpn \
--source /home/agent/.local/state/jeremy-v19-pr-qa/worktrees/openvpn-candidateIt builds a configured Trixie root in TKLDev, imports it into a disposable
privileged Docker container, follows normal systemd init and firstboot, and
runs tests/v19.sh. The retained harness report records the exact source and
harness commits, source archive/tree hashes, configured-root driver, runtime
evidence, updater evidence, cleanup status and verdict.
- Firstboot administrator inputs cross into Easy-RSA variables and OpenVPN configuration. The Python hook validates email, DNS/IP address and strict IPv4 networks before changing PKI; the shell initializer independently rejects unsafe email/address spellings. Failed initialization or service restart is fatal rather than silently leaving partial success.
- Client names cross into certificate and filesystem paths. The helper scripts
accept only a bounded portable name, validate email, subnet and port values,
and create embedded-private-key profiles and keys with mode
0600under a process umask of0077. - The kernel routing table selects the current IPv4 default interface. The
dedicated oneshot helper validates that kernel-provided name, installs one
idempotent, appliance-labelled rule limited to the configured VPN subnet,
records it below
/run, and removes that exact rule on stop. No interface name such aseth0is assumed. - OpenVPN starts as root only for device, socket and key setup, then follows
the shipped configuration's
user nobody,group nogroupand CRL chroot. The NAT service is bounded toCAP_NET_ADMINand theCAP_NET_RAWsocket capability required by iptables-legacy, a read-only system tree and its one/runstate file. - The OpenVPN web root is global to the appliance and inherits Core's hardened TLS listeners and HTTP-to-HTTPS redirect; the application does not create a competing SSL socket or duplicate the shared cipher policy.
| README contract | Focused command | Required result | Evidence |
|---|---|---|---|
| Server/gateway firstboot creates a working OpenVPN deployment | Check inithook completion, server config, PKI and active openvpn@server after normal init |
Firstboot succeeds and the service has a CA, server key/cert, modern data ciphers and subnet topology | tests/v19.sh; retained child/runtime result |
| Clients authenticate with certificates and keys | Generate a disposable profile with openvpn-addclient, start a client in its own network namespace, and wait for tun0 |
The TLS client establishes a tunnel using the generated embedded profile | tests/v19.sh; OpenVPN client log on failure |
| A private subnet behind a client can join the VPN | Generate a client with an RFC 5737 test subnet, restart OpenVPN, and inspect its route and chrooted client-config access | The scoped route is installed, the dropped OpenVPN process reads the root-owned CCD file, and revocation removes both route and CCD state | tests/v19.sh; service journal on failure |
| Gateway profile routes client traffic through the VPN | Resolve the Debian HTTPS endpoint outside the namespace, prove its route uses client tun0, and fetch it through that interface |
A nonempty authenticated HTTPS response traverses tunnel, server forwarding and default-interface NAT | tests/v19.sh; retained runtime result |
| The tunnel carries application data | Request the appliance HTTPS endpoint and a published profile through the server tunnel address | The client receives a nonempty HTTPS response, the profile listing, and the exact generated profile over tun0 |
tests/v19.sh |
| Service configuration persists across restart | Restart OpenVPN and the NAT service, wait for client reconnection and repeat the tunnel request | Both services remain active, exactly one NAT rule remains, and tunnel data still passes | tests/v19.sh |
| Client profiles can be revoked | Stop the disposable client and run openvpn-removeclient |
The profile is removed and the Easy-RSA index records revocation | tests/v19.sh |
| Embedded private keys are protected | Inspect client profile and private-key modes and search the private directory for group/world access | Profile and client key are 0600; no private key grants group/world permissions |
tests/v19.sh |
| Administrator input cannot append OpenVPN directives or escape client paths | Submit a newline-bearing public address, a host-address CIDR and a path-bearing client name | Each input fails and the existing server configuration digest is unchanged | tests/v19.sh |
| OpenVPN is maintained from Debian | Refresh APT and inspect OpenVPN, Easy-RSA and iptables candidates | Signed Trixie metadata is accepted, eligible candidates exist, installed versions do not change and no Bookworm source remains | tests/v19.sh; harness updater fields |
| Expiring HTTPS profile links are available | Publish the disposable client under a random 192-bit URL token, download the page and profile through the tunnel, then run the expiry helper as www-data |
Lighttpd serves the profile with bounded permissions and the observed download is deleted | tests/v19.sh; retained runtime result |
| Webmin and SSH use the inherited root credential | Covered by the unchanged Core implementation | Passes in the accepted Core 19 baseline | Core 19 baseline evidence |
The server profile's optional routed private LAN and the client profile's site-to-site routing use the same validated CIDR and authenticated tunnel primitives. Acceptance exercises the higher-value gateway path end to end; it does not create a second physical LAN solely to duplicate those primitives.
The Docker path does not exercise the installer, host kernel variants, physical interfaces, cloud security groups or mobile QR scanners. The test does exercise privileged TUN creation, a separate client network namespace, certificate authentication, real encrypted data transfer, forwarding, NAT and service restart. The inherited Core 19 baseline covers unchanged platform behavior.
IPv6 enablement remains explicitly documented as future, untested work in
docs/ipv6.md; it is not a v19.0 runtime claim. Performance, multi-client load,
optional TLS 1.3-only configuration and exhaustive site-to-site topology
coverage are outside the functional 20/80 gate.