Skip to content

feat: embed wireguard-go as a library, removing client-side external binary dependencies#477

Open
sechmann wants to merge 1 commit into
mainfrom
embed-wireguard-go
Open

feat: embed wireguard-go as a library, removing client-side external binary dependencies#477
sechmann wants to merge 1 commit into
mainfrom
embed-wireguard-go

Conversation

@sechmann
Copy link
Copy Markdown
Contributor

@sechmann sechmann commented Feb 25, 2026

Summary

Replaces all external WireGuard binary dependencies (wireguard-go, wg, wireguard.exe) with in-process wireguard-go and wgctrl on client platforms. The naisdevice-helper now manages WireGuard interfaces, configuration, and routes entirely as a Go library — no subprocesses needed.

Note — server-side components (apiserver, gateway-agent, prometheus-agent) still use the wg CLI and kernel WireGuard via network_configurer.go. That is out of scope for this PR.

Changes by platform

macOS

  • SetupInterface: tun.CreateTUN + device.NewDevice (userspace wireguard-go) + UAPI socket
  • SyncConf: wgctrl via new shared wgconfig.ApplyConfig()
  • SetupRoutes: BSD routing sockets (x/net/route) instead of route command
  • TeardownInterface: closes UAPI + wgDevice in-process

Linux

  • SetupInterface: netlink.LinkAdd(&netlink.Wireguard{}) + netlink.AddrAdd + netlink.LinkSetUp
  • SyncConf: wgctrl via wgconfig.ApplyConfig()
  • SetupRoutes: netlink.RouteAdd instead of ip route
  • TeardownInterface: netlink.LinkDel

Why kernel WireGuard on Linux, not embedded wireguard-go?

Linux uses the kernel WireGuard module (netlink.Wireguard{}) instead of an embedded userspace wireguard-go device. This is intentional:

  1. Performance: Kernel WireGuard processes packets in-kernel without context switches to userspace. For a VPN carrying all corporate traffic, this matters.
  2. Maturity: The kernel module has been in mainline Linux since 5.6 (March 2020) and is backported to all major LTS and enterprise kernels. Every distribution that naisdevice targets ships it.
  3. No external binaries removed on Linux regardless: The old code already used ip link add type wireguard (shelling out to ip) and wg syncconf (shelling out to wg). This PR replaces those CLI calls with the equivalent Go libraries (vishvananda/netlink for interface management, wgctrl for configuration via netlink), eliminating the dependency on iproute2 and wireguard-tools binaries. The kernel module itself was always required.
  4. Consistency with wgctrl: wgctrl auto-detects kernel WireGuard on Linux and uses the netlink configuration API, which is the same codepath whether you created the interface via ip link or netlink.LinkAdd. No UAPI socket needed.

If kernel WireGuard is unavailable (e.g., very old kernels or restricted containers), netlink.LinkAdd will fail with a clear error. A future fallback to userspace wireguard-go could be added if needed, but there is currently no evidence this is required for the target deployment environments.

Windows

  • SetupInterface: tun.CreateTUN (wintun) + device.NewDevice (userspace wireguard-go) + UAPI named pipe
  • SyncConf: wgctrl via wgconfig.ApplyConfig()
  • SetupRoutes: winipcfg.LUID.AddIPAddress + LUID.AddRoute instead of wireguard.exe AllowedIPs routing
  • Ships wintun.dll alongside binary instead of bundling WireGuard MSI

Shared / cleanup

  • New internal/wgconfig package — shared wgctrl-based config builder and applier (with tests)
  • Consolidated duplicate key generation code in internal/wireguard/keys.go
  • Removed all client-side INI config pipeline dead code (Marshal, MarshalHeader, writeConfigFile, WireGuardConfigPath)
  • Removed wireguard-go/wireguard-tools from Homebrew cask depends_on
  • Removed wireguard-tools/iproute2 from Nix client service PATH
  • NSIS installer: removed WireGuard MSI install, bundles wintun.dll instead
  • Fixed pre-existing bug in Configure() where wrong error variable was logged

Testing

  • mise run check — all passing (govet, golangci-lint, staticcheck, govulncheck, ratchet)
  • mise run test — all passing
  • Needs manual end-to-end testing on macOS, Linux, and Windows
  • Needs CI validation of Windows NSIS build with wintun.dll

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Feb 25, 2026

📝 Changelog preview

Below is a preview of the Changelog that will be added to the next release. Only commit messages that follow the Conventional Commits specification will be included in the Changelog.

v1.15.0 - 2026-05-04

Full Changelog: v1.14.7...v1.15.0

🚀 Features

  • Embed wireguard-go as a library and manage tunnels directly (cb67e51)

⚙️ Miscellaneous Changes

  • (deps) Bump softprops/action-gh-release in /.github/workflows (a198fee)
  • (deps) Bump jdx/mise-action in /.github/workflows (dd5531d)
  • (deps) Bump docker/setup-buildx-action in /.github/workflows (5f44dab)
  • (deps) Bump Apple-Actions/import-codesign-certs (c39d591)
  • (deps) Bump actions/setup-go in /.github/workflows (82e7b89)

@sechmann sechmann changed the title feat: embed wireguard-go as a library, removing external binary dependencies feat: embed wireguard-go as a library, removing client-side external binary dependencies Feb 25, 2026
sechmann added a commit that referenced this pull request Feb 25, 2026
- Remove custom wireguard.PrivateKey type; use wgtypes.Key everywhere
- Fix ReadOrCreatePrivateKey to write/read base64 format (not raw bytes)
- Fix double base64 encoding in enrollgateway.go
- Replace fragile manual base64+copy with wgtypes.ParseKey in EnsurePrivateKey
- Add strings.TrimSpace when reading key files to handle trailing newlines
- Fix error wrapping: %v -> %w for proper errors.Is/As support
- Make Configure retry loop context-aware (select on ctx.Done vs time.Sleep)
- Clean up Linux interface on partial SetupInterface failure
- Make IPv6 address configuration conditional on all platforms
- Change GatewayRequest.WireGuardPublicKey from []byte to string
sechmann added a commit that referenced this pull request Mar 16, 2026
- Remove custom wireguard.PrivateKey type; use wgtypes.Key everywhere
- Fix ReadOrCreatePrivateKey to write/read base64 format (not raw bytes)
- Fix double base64 encoding in enrollgateway.go
- Replace fragile manual base64+copy with wgtypes.ParseKey in EnsurePrivateKey
- Add strings.TrimSpace when reading key files to handle trailing newlines
- Fix error wrapping: %v -> %w for proper errors.Is/As support
- Make Configure retry loop context-aware (select on ctx.Done vs time.Sleep)
- Clean up Linux interface on partial SetupInterface failure
- Make IPv6 address configuration conditional on all platforms
- Change GatewayRequest.WireGuardPublicKey from []byte to string
@sechmann sechmann force-pushed the embed-wireguard-go branch from cb6fb80 to 75eb38d Compare March 16, 2026 14:14
sechmann added a commit that referenced this pull request Mar 17, 2026
- Remove custom wireguard.PrivateKey type; use wgtypes.Key everywhere
- Fix ReadOrCreatePrivateKey to write/read base64 format (not raw bytes)
- Fix double base64 encoding in enrollgateway.go
- Replace fragile manual base64+copy with wgtypes.ParseKey in EnsurePrivateKey
- Add strings.TrimSpace when reading key files to handle trailing newlines
- Fix error wrapping: %v -> %w for proper errors.Is/As support
- Make Configure retry loop context-aware (select on ctx.Done vs time.Sleep)
- Clean up Linux interface on partial SetupInterface failure
- Make IPv6 address configuration conditional on all platforms
- Change GatewayRequest.WireGuardPublicKey from []byte to string
@sechmann sechmann force-pushed the embed-wireguard-go branch from 75eb38d to 5384124 Compare March 17, 2026 10:26
@sechmann sechmann force-pushed the embed-wireguard-go branch from 5384124 to e3c598b Compare March 27, 2026 12:36
@sechmann sechmann force-pushed the embed-wireguard-go branch from 7d746f0 to 74d7644 Compare April 9, 2026 10:04
Replace external wireguard-go/wg binaries and the Windows WireGuard MSI
with wireguard-go embedded as a Go library. The helper now creates TUN
devices, configures peers via wgctrl, and manages routes through native
OS APIs (BSD routing sockets, netlink, winipcfg) instead of shelling
out. Migrate key handling from custom crypto wrappers to wgtypes.Key
with automatic legacy key migration. Add WireGuard public key validation
at enrollment time, an iputil package for CIDR parsing, and cross-
platform post-install smoke tests. Remove wireguard-go/wireguard-tools
from all package dependencies (Homebrew, nix, deb).
@sechmann sechmann force-pushed the embed-wireguard-go branch from 74d7644 to cb67e51 Compare May 4, 2026 11:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant