actions/connect-wireguard brings a WireGuard tunnel up for a deployment job and takes it down again, with a guide and offline tests. The runs on a real runner (MIL-012 task 3) are still to do. Refs #184 Task: MIL-012#3
4.2 KiB
Reaching a private network with WireGuard
The composite action actions/connect-wireguard brings a WireGuard tunnel up on
a Gitea Actions runner, so the next steps of the same job (a deployment over
SSH, a database migration) can reach hosts that are not on the internet. It is
the WireGuard counterpart of the Tailscale action. It is experimental: it
lives on a branch until a trial shows it is wanted.
A tunnel belongs to one job: every job that deploys brings its own tunnel up and takes it down. It cannot be a reusable workflow, because a called workflow runs as a separate job.
Use it
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.2.2
- name: Connect the VPN
uses: TirSystem/github-action/actions/connect-wireguard@v2
with:
config: ${{ secrets.WG_CONFIG }}
health-check-host: 10.8.0.1
- name: Deploy
run: ssh deploy@10.8.0.10 ./deploy.sh
- name: Disconnect the VPN
if: always()
uses: TirSystem/github-action/actions/connect-wireguard@v2
with:
action: down
If the short form does not resolve on your instance (Gitea looks for the action
on the host set in DEFAULT_ACTIONS_URL), use the full address:
https://git.tirsystem.com/TirSystem/github-action/actions/connect-wireguard@v2.
To try the branch before it is released, replace @v2 with
@wireguard-vpn-action.
Inputs
| Input | Default | Meaning |
|---|---|---|
action |
up |
up or down |
config |
A complete wg-quick file, as a secret. When set, the fields below are ignored | |
private-key, address, peer-public-key, endpoint, allowed-ips |
The fields of a configuration, all five needed without config |
|
preshared-key, dns |
Optional | |
persistent-keepalive |
25 |
Seconds |
interface |
wg0 |
Interface name |
health-check-host |
A host behind the tunnel; the step fails when it does not answer a ping |
Set it up
- On the WireGuard server add one peer for CI, with its own key pair and an
address inside the tunnel (
10.8.0.2/32). Let it reach only the hosts the deployment needs. - Store the configuration (or the private key) as a secret of the
organization, never in the repository. Give the secret a name that does not
start with
GITEA_orGITHUB_. - Set
allowed-ipsto the private network only.0.0.0.0/0sends all of the job's traffic, including the checkout, through the tunnel. - Rotate the key when a person with access to the secret leaves.
The runner must allow it
A tunnel needs a network interface, so the runner needs more than a plain job container:
- the job runs as root or can use
sudo(the action installswireguard-toolswithaptwhen it is missing, so a Debian or Ubuntu image); - the container has the capability
NET_ADMINand the device/dev/net/tun. In theact_runnerconfiguration set undercontainer:options: "--cap-add=NET_ADMIN --device=/dev/net/tun"; - without the kernel module the action uses
wireguard-go(user space), which needs the same capability and device.
Use a dedicated runner with a label of its own (for example vpn) and put
runs-on: vpn on the jobs that need it, so the other jobs do not get the extra
privilege.
What the action does
- masks the keys in the log before anything else;
- writes
/etc/wireguard/<interface>.confwith mode 600 and brings it up withwg-quick; - prints errors as
::error::[category] message, with the categoriesconfiguration,environmentandconnection; - on
downtakes the interface down and deletes the file.
It never prints a key. Do not turn on shell tracing (set -x) in the steps
that follow.
Troubleshooting
| Message | Cause |
|---|---|
[configuration] ... is empty |
Neither config nor all five fields were given |
[environment] ... NET_ADMIN and /dev/net/tun |
The runner container lacks the capability or the device |
[connection] wg-quick could not bring wg0 up |
A wrong key, endpoint or a firewall; run wg show on the server |
[connection] health-check-host ... does not answer |
The tunnel is up but the host is not in allowed-ips, or is blocked |