Initial commit

This commit is contained in:
Wyatt Gill
2022-09-22 17:39:55 -05:00
commit 8fd12580d3
6 changed files with 181 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
# Anything used during development should be put in local/ to prevent accidental committing.
local/

88
README.md Normal file
View File

@@ -0,0 +1,88 @@
# WireGuard client for Docker
## What is this and what does it do?
`wireguard` is exactly what it says on the tin (a containerized WireGuard client).
It has a built-in kill switch to stop Internet connectivity if the VPN goes down for any reason.
## Why?
Having a containerized VPN client lets you use container networking to easily choose which applications you want using the VPN instead of having to set up split tunnelling.
## How do I use it?
### Getting the image
You can either pull it from GitHub Container Registry or build it yourself.
To pull it from GitHub Container Registry, run
```
docker pull ghcr.io/wfg/wireguard
```
To build it yourself, run
```
docker build -t ghcr.io/wfg/wireguard https://github.com/wfg/docker-wireguard.git#:build
```
### Creating and running a container
Below are bare-bones examples for `docker run` and Compose; however, you'll probably want to do more than just run the VPN client.
See the sections below to learn how to have [other containers use `wireguard`'s network stack](#using-with-other-containers).
#### `docker run`
```
docker run --detach \
--name wireguard \
--cap-add NET_ADMIN \
--sysctl net.ipv4.conf.all.src_valid_mark=1 \
--volume <path/to/config>:/etc/wireguard/wg0.conf \
ghcr.io/wfg/wireguard
```
#### `docker-compose`
```yaml
services:
wireguard:
image: ghcr.io/wfg/wireguard
container_name: wireguard
cap_add:
- NET_ADMIN
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
volumes:
- <path/to/config>:/etc/wireguard/wg0.conf
restart: unless-stopped
```
#### Environment variables
| Variable | Default (blank is unset) | Description |
| --- | --- | --- |
| `ALLOWED_SUBNETS` | | A list of one or more comma-separated subnets (e.g. `192.168.0.0/24,192.168.1.0/24`) to allow outside of the VPN tunnel. |
##### Environment variable considerations
###### `ALLOWED_SUBNETS`
If you intend on connecting to containers that use the WireGuard container's network stack (which you probably do), **you will want to use this variable**.
### Using with other containers
Once you have your `wireguard` container up and running, you can tell other containers to use `wireguard`'s network stack which gives them the ability to utilize the VPN tunnel.
There are a few ways to accomplish this depending how how your container is created.
If your container is being created with
1. the same Compose YAML file as `wireguard`, add `network_mode: service:wireguard` to the container's service definition.
2. a different Compose YAML file than `wireguard`, add `network_mode: container:wireguard` to the container's service definition.
3. `docker run`, add `--network=container:wireguard` as an option to `docker run`.
Once running and provided your container has `wget` or `curl`, you can run `docker exec <container_name> wget -qO - ifconfig.me` or `docker exec <container_name> curl -s ifconfig.me` to get the public IP of the container and make sure everything is working as expected.
This IP should match the one of `wireguard`.
#### Handling ports intended for connected containers
If you have a connected container and you need to access a port that container, you'll want to publish that port on the `wireguard` container instead of the connected container.
To do that, add `-p <host_port>:<container_port>` if you're using `docker run`, or add the below snippet to the `wireguard` service definition in your Compose file if using `docker-compose`.
```yaml
ports:
- <host_port>:<container_port>
```
In both cases, replace `<host_port>` and `<container_port>` with the port used by your connected container.
### Verifying functionality
Once you have container running `ghcr.io/wfg/wireguard`, run the following command to spin up a temporary container using `wireguard` for networking.
The `wget -qO - ifconfig.me` bit will return the public IP of the container.
You should see an IP address owned by your VPN provider.
```bash
docker run --rm -it --network=container:wireguard alpine wget -qO - ifconfig.me
```

16
build.py Executable file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env python3
import datetime
import subprocess
image_tag = 'latest'
docker_build_cmd = [
'docker', 'build',
'--build-arg', f'BUILD_DATE={str(datetime.datetime.utcnow())}',
'--build-arg', f'IMAGE_VERSION={image_tag}',
'--tag', f'ghcr.io/wfg/wireguard:{image_tag}',
'./build',
]
subprocess.run(docker_build_cmd)

17
build/Dockerfile Normal file
View File

@@ -0,0 +1,17 @@
FROM alpine:3.16
RUN apk add --no-cache \
wireguard-tools
# Make wg-quick docker-friendly
RUN sed -i '/sysctl -q net.ipv4.conf.all.src_valid_mark=1/d' \
"$(which wg-quick)"
COPY entry.sh /usr/local/bin
ENTRYPOINT [ "entry.sh" ]
ARG BUILD_DATE
ARG IMAGE_VERSION
LABEL build-date=$BUILD_DATE
LABEL image-version=$IMAGE_VERSION

45
build/entry.sh Executable file
View File

@@ -0,0 +1,45 @@
#!/usr/bin/env bash
set -e
cleanup() {
wg-quick down "$1"
exit 0
}
# Find a config file and isolate the interface name
config_file=$(find /etc/wireguard -type f -name '*.conf' | shuf -n 1)
if [[ -z $config_file ]]; then
echo "config file not found"
exit 1
fi
interface=$(basename "${config_file%.*}")
# Bring up the WireGuard interface
wg-quick up "$interface"
# > ...when used with interfaces that have a peer that specifies 0.0.0.0/0 as part of the AllowedIPs,
# > [this iptables command] works together with wg-quicks fwmark usage in order to drop all packets
# > that are either not coming out of the tunnel encrypted or not going through the tunnel itself
# Source: https://git.zx2c4.com/wireguard-tools/about/src/man/wg-quick.8
#
# This also allows packets to the Docker network
iptables --insert OUTPUT \
! --out-interface "$interface" \
--match mark ! --mark "$(wg show "$interface" fwmark)" \
--match addrtype ! --dst-type LOCAL \
! --destination "$(ip -o addr show dev eth0 | awk '$3 == "inet" {print $4}')" \
--jump REJECT
# Create static routes for any ALLOWED_SUBNETS and punch holes in the firewall
default_gateway=$(ip -4 route | grep default | awk '{print $3}')
for subnet in ${ALLOWED_SUBNETS//,/ }; do
ip route add "$subnet" via "$default_gateway"
iptables --insert OUTPUT --destination "$subnet" --jump ACCEPT
done
# Gracefully exit when signalled
trap 'cleanup $interface' TERM INT QUIT
sleep infinity &
wait

13
docker-compose.yaml Normal file
View File

@@ -0,0 +1,13 @@
services:
wireguard:
image: ghcr.io/wfg/wireguard:latest
container_name: wireguard
cap_add:
- NET_ADMIN
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
environment:
- ALLOWED_SUBNETS=192.168.10.0/24
volumes:
- ./wg0.conf:/etc/wireguard/wg0.conf
restart: unless-stopped