WireGuard Integration
Overview
OpenCHAMI’s cloud-init server supports WireGuard to encrypt and isolate cloud-init traffic between compute nodes and the cloud-init server. This ensures that sensitive metadata, user data, and vendor data exchanged during node provisioning are transmitted over an encrypted tunnel.
The cloud-init server embeds a WireGuard server that dynamically assigns VPN IPs to compute nodes, manages peer configurations, and can optionally restrict cloud-init data access to only those nodes connected via the WireGuard tunnel. This protects against users making HTTPS requests directly to the API endpoints to obtain secrets delivered by the cloud-init-server.
Architecture
The WireGuard integration works in two phases:
- Tunnel establishment β At boot, the compute node calls the unprotected
/cloud-init/wg-initendpoint to request a WireGuard IP and peer configuration. This happens before cloud-init itself runs. - Secure data fetch β Once the tunnel is up, cloud-init fetches
/meta-data,/user-data,/vendor-data, and group YAML files over the encrypted WireGuard link.
ββββββββββββββββ WireGuard Tunnel ββββββββββββββββββββ
β Compute β ββββββββββββββββββββββββββΊ β cloud-init β
β Node β wg0: 100.97.0.x/32 β Server β
β β β wg0: 100.97.0.1 β
ββββββββββββββββ ββββββββββββββββββββ
β β
β 1. POST /cloud-init/wg-init β
β (before cloud-init starts) β
β β
β 2. GET /meta-data, /vendor-data β
β (over WireGuard tunnel) β
βββββββββββββββββββββββββββββββββββββββββββββServer-Side Configuration
Environment Variables
| Variable | Description | Default |
|---|---|---|
WIREGUARD_SERVER | WireGuard server IP and network CIDR (e.g. 100.97.0.1/16) | (none) |
WIREGUARD_ONLY | Only serve cloud-init data to clients in the WireGuard subnet | false |
DEBUG | Enable debug logging | false |
WIREGUARD_ONLY Behavior
When WIREGUARD_ONLY=true is set, the cloud-init server restricts access to data-bearing endpoints (/meta-data, /user-data, /vendor-data, /{group}.yaml) to clients whose IP falls within the WireGuard subnet or whose request arrives on the server’s WireGuard interface.
Important
The /cloud-init/wg-init, /cloud-init/admin/*, and /cloud-init/phone-home/{id} endpoints are never restricted by the WireGuard middleware, even when WIREGUARD_ONLY=true. This allows nodes to establish their WireGuard tunnel before fetching cloud-init data.
Network Setup
The cloud-init container needs access to both the internal cluster network and an external network for WireGuard traffic. Configure this using Podman quadlet overrides.
Example quadlet override (10-override.conf)
# /etc/containers/systemd/cloud-init-server.container.d/10-override.conf
[Container]
Image=ghcr.io/openchami/cloud-init:v1.4.2
Environment=WIREGUARD_SERVER=172.16.1.1/24
Environment=WIREGUARD_ONLY=true
Environment=DEBUG=true
AddCapability=NET_ADMIN
AddDevice=/dev/net/tun
PublishPort=58036:58036/udp
Network=openchami-internal.network:alias=cloud-init-int
Network=openchami-external.network:alias=cloud-init-wgHAProxy Configuration
If using HAProxy in front of the cloud-init server, ensure the backend routes traffic to the internal network alias:
backend cloud-init
server cloud-init-server cloud-init-int:27777
http-request set-path %[path,regsub(^/cloud-init/,/)]Client-Side Configuration
Compute nodes require two pieces of setup to use WireGuard for cloud-init:
- A systemd override that runs a pre-exec script before cloud-init starts.
- A shell script that configures the WireGuard tunnel and requests a VPN IP from the server.
Systemd Override
# /etc/systemd/system/cloud-init.service.d/override.conf
[Service]
Environment=ochami_wg_ip=172.16.0.254
ExecStartPre=/usr/local/bin/ochami-ci-setup.sh
ExecStopPost=/bin/bash -c "ip link delete wg0"The ochami_wg_ip variable should point to the cloud-init server’s external IP address (the one accessible from the compute node before the tunnel is established).
WireGuard Setup Script
The pre-exec script performs the following steps:
- Generate a WireGuard key pair on the compute node.
- Request a VPN IP from the cloud-init server via the
/cloud-init/wg-initendpoint. - Create and configure the
wg0interface with the assigned IP and server peer. - Add a route to the WireGuard server IP over the tunnel.
Example ochami-ci-setup.sh
#!/bin/sh
set -e -o pipefail
if [ -z "${ochami_wg_ip}" ]; then
echo "ERROR: Failed to find the 'ochami_wg_ip' environment variable."
if [ -f "/etc/cloud/cloud.cfg.d/ochami.cfg" ]; then
echo "Removing ochami-specific cloud-config; cloud-init will use other defaults"
rm /etc/cloud/cloud.cfg.d/ochami.cfg
else
echo "Not writing ochami-specific cloud-config; cloud-init will use other defaults"
fi
exit 0
fi
echo "Loading WireGuard kernel module"
modprobe wireguard
echo "Generating WireGuard keys"
wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key
echo "Requesting WireGuard tunnel configuration"
PUBLIC_KEY=$(cat /etc/wireguard/public.key)
PAYLOAD="{ \"public_key\": \"${PUBLIC_KEY}\" }"
WG_PAYLOAD=$(curl -s -X POST -d "${PAYLOAD}" http://${ochami_wg_ip}:8081/cloud-init/wg-init)
CLIENT_IP=$(echo $WG_PAYLOAD | jq -r '."client-vpn-ip"')
SERVER_IP=$(echo $WG_PAYLOAD | jq -r '."server-ip"' | awk -F'/' '{print $1}')
SERVER_PORT=$(echo $WG_PAYLOAD | jq -r '."server-port"')
SERVER_KEY=$(echo $WG_PAYLOAD | jq -r '."server-public-key"')
echo "Setting up WireGuard interface"
ip link add dev wg0 type wireguard
ip address add dev wg0 ${CLIENT_IP}/32
wg set wg0 private-key /etc/wireguard/private.key
ip link set wg0 up
wg set wg0 peer ${SERVER_KEY} allowed-ips ${SERVER_IP}/32 endpoint ${ochami_wg_ip}:${SERVER_PORT}
echo "Adding route to WireGuard server"
ip route replace "${SERVER_IP}/32" dev wg0
rm /etc/wireguard/private.key
rm /etc/wireguard/public.keyImage Building
Inject these files into the compute node image using the OpenCHAMI image builder. Add them under copyfiles in the image build configuration:
copyfiles:
- src: '/opt/workdir/images/files/cloud-init-override.conf'
dest: '/etc/systemd/system/cloud-init.service.d/override.conf'
- src: '/opt/workdir/images/files/ochami-ci-setup.sh'
dest: '/usr/local/bin/ochami-ci-setup.sh'The image must also include the wireguard-tools package:
packages:
- wireguard-toolsBoot Parameters
Configure the node’s boot parameters in BSS to point to the cloud-init server using the WireGuard subnet address:
params: 'nomodeset ro root=live:http://172.16.0.254:7070/boot-images/... ip=dhcp ... cloud-init=enabled ds=nocloud-net;s=http://172.16.1.1:27777/cloud-init'The ds=nocloud-net URL should use the WireGuard server IP (e.g. 172.16.1.1:27777), which is the address the compute node will reach once the tunnel is established.
How the Middleware Works
The WireGuard middleware enforces access policy based on two criteria:
- Client IP in WireGuard subnet β If the connecting client’s IP falls within the configured WireGuard CIDR (e.g.
100.97.0.0/16), the request is allowed. - Request arrived on WireGuard interface β If the connection was received on the server’s
wg0interface, the request is allowed regardless of client IP.
Before the fix in PR
#113, the WireGuard middleware was applied globally to all routes when WIREGUARD_ONLY=true. This created a chicken-and-egg problem: compute nodes needed to call /cloud-init/wg-init to establish their WireGuard tunnel, but that endpoint was blocked by the middleware.
The fix scoped the middleware to only the data-bearing routes (/meta-data, /user-data, /vendor-data, /{group}.yaml), leaving the tunnel setup endpoint (/wg-init) and phone-home endpoint (/phone-home/{id}) accessible without restriction.
Verification
To verify the WireGuard tunnel is functioning correctly from a compute node:
# Check the WireGuard interface
wg show
# Verify the tunnel is resolving the cloud-init server
ping 100.97.0.1On the cloud-init server, check that the WireGuard interface is active:
# List WireGuard peers
podman exec cloud-init-server wg show wg0
# Check debug logs for connection metadata
journalctl -u cloud-init-serverIf WIREGUARD_ONLY=true is set and a non-WireGuard client tries to access a protected endpoint, they will receive:
Access denied: IP 10.89.2.63 not in WireGuard subnet or interface