mirror of
https://github.com/wfg/docker-openvpn-client.git
synced 2025-04-21 18:57:06 -04:00
Compare commits
61 commits
Author | SHA1 | Date | |
---|---|---|---|
|
91bde63706 | ||
|
ee61a9ecdb | ||
|
e511db1436 | ||
|
99bb6571ae | ||
|
d3ce6295ff | ||
|
333fcc682a | ||
|
045d47050e | ||
|
b5489ea7ce | ||
|
ac8a5ba3d4 | ||
|
994836b5f0 | ||
|
e7c9e1ff6a | ||
|
bc1648f04a | ||
|
51fa5f2e04 | ||
|
650d55fe76 | ||
|
6a1ad61a79 | ||
|
436e745c13 | ||
|
0dec9be496 | ||
|
ee14898805 | ||
|
a457130638 | ||
|
ea0b4dd271 | ||
|
574fec5212 | ||
|
4cbfa8fb55 | ||
|
283fe3305f | ||
|
015ebab5ca | ||
|
d1806f8aad | ||
|
ac66032038 | ||
|
de63b93ddb | ||
|
6150534f86 | ||
|
c3ca6fbaa5 | ||
|
fa2045ff68 | ||
|
de244a77f8 | ||
|
49fc0d7dfd | ||
|
6b7edce24e | ||
|
62bf2463c6 | ||
|
a68336d184 | ||
|
cb8302f95b | ||
|
88ff3c1f3f | ||
|
f45886c0b4 | ||
|
558e52558b | ||
|
51a0d4705a | ||
|
b9e57a87e8 | ||
|
b11a502a1c | ||
|
6a83e31c95 | ||
|
f9eb4afdb1 | ||
|
2461375dab | ||
|
ee54af88fe | ||
|
3dfa177d81 | ||
|
525c0ec841 | ||
|
b1d897ed32 | ||
|
ca0e1c0a91 | ||
|
bffa3688bf | ||
|
dc5d4563dc | ||
|
6bf21cd18b | ||
|
368b17f687 | ||
|
9352d4c993 | ||
|
ffddfb0459 | ||
|
8eaa66c338 | ||
|
6b35bb71a2 | ||
|
5f62e9de21 | ||
|
8f5d3cd53d | ||
|
f2a5eef2b3 |
16 changed files with 205 additions and 464 deletions
|
@ -1,2 +0,0 @@
|
|||
*
|
||||
!data/
|
46
.github/workflows/build.yaml
vendored
Normal file
46
.github/workflows/build.yaml
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: docker/setup-qemu-action@v2
|
||||
- uses: docker/setup-buildx-action@v2
|
||||
|
||||
- uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- id: tags
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ghcr.io/wfg/openvpn-client
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
|
||||
|
||||
- id: build-args
|
||||
run: |
|
||||
ref=${{ github.ref }}
|
||||
vpatch=${ref##refs/*/}
|
||||
patch=${vpatch#v}
|
||||
echo "::set-output name=date::$(date --utc --iso-8601=seconds)"
|
||||
echo "::set-output name=version::$patch"
|
||||
|
||||
- uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: "{{defaultContext}}:build"
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
|
||||
build-args: |
|
||||
BUILD_DATE=${{ steps.build-args.outputs.date }}
|
||||
IMAGE_VERSION=${{ steps.build-args.outputs.version }}
|
||||
tags: ${{ steps.tags.outputs.tags }}
|
||||
push: true
|
36
.github/workflows/publish.yml
vendored
36
.github/workflows/publish.yml
vendored
|
@ -1,36 +0,0 @@
|
|||
name: Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
env:
|
||||
IMAGE_NAME: openvpn-client
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Get the version
|
||||
id: get_version
|
||||
run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v}
|
||||
|
||||
- name: Log in to registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
tags: |
|
||||
ghcr.io/wfg/openvpn-client:${{ steps.get_version.outputs.VERSION }}
|
||||
ghcr.io/wfg/openvpn-client:latest
|
||||
push: true
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
local/
|
||||
# Anything used during development should be put in local/ to prevent accidental committing.
|
||||
local/
|
||||
|
|
27
Dockerfile
27
Dockerfile
|
@ -1,27 +0,0 @@
|
|||
FROM alpine:3.14
|
||||
|
||||
ARG IMAGE_VERSION
|
||||
ARG BUILD_DATE
|
||||
|
||||
LABEL source="github.com/wfg/docker-openvpn-client"
|
||||
LABEL version="$IMAGE_VERSION"
|
||||
LABEL created="$BUILD_DATE"
|
||||
|
||||
ENV KILL_SWITCH=on \
|
||||
VPN_LOG_LEVEL=3 \
|
||||
HTTP_PROXY=off \
|
||||
SOCKS_PROXY=off
|
||||
|
||||
RUN apk add --no-cache \
|
||||
bind-tools \
|
||||
dante-server \
|
||||
openvpn \
|
||||
tinyproxy
|
||||
|
||||
RUN mkdir -p /data/vpn
|
||||
|
||||
COPY data/ /data
|
||||
|
||||
HEALTHCHECK CMD ping -c 3 1.1.1.1 || exit 1
|
||||
|
||||
ENTRYPOINT ["/data/scripts/entry.sh"]
|
87
README.md
87
README.md
|
@ -1,50 +1,46 @@
|
|||
# OpenVPN Client for Docker
|
||||
|
||||
Archived in favor of [a WireGuard version](https://github.com/wfg/docker-wireguard).
|
||||
|
||||
## What is this and what does it do?
|
||||
[`ghcr.io/wfg/openvpn-client`](https://github.com/users/wfg/packages/container/package/openvpn-client) is a containerized OpenVPN client.
|
||||
It has a kill switch built with `iptables` that kills Internet connectivity to the container if the VPN tunnel goes down for any reason.
|
||||
It also includes an HTTP proxy server ([Tinyproxy](https://tinyproxy.github.io/)) and a SOCKS proxy server ([Dante](https://www.inet.no/dante/index.html)).
|
||||
This allows hosts and non-containerized applications to use the VPN without having to run VPN clients on those hosts.
|
||||
|
||||
This image requires you to supply the necessary OpenVPN configuration file(s).
|
||||
Because of this, any VPN provider should work.
|
||||
|
||||
If you find something that doesn't work or have an idea for a new feature, issues and **pull requests are welcome**.
|
||||
If you find something that doesn't work or have an idea for a new feature, issues and **pull requests are welcome** (however, I'm not promising they will be merged).
|
||||
|
||||
## 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.
|
||||
It also keeps you from having to install an OpenVPN client on the underlying host.
|
||||
|
||||
The idea for this image came from a similar project by [qdm12](https://github.com/qdm12) that has since evolved into something bigger and more complex than I wanted to use.
|
||||
I decided to dissect it and take it in my own direction.
|
||||
I plan to keep everything here well-documented so this is not only a learning experience for me, but also anyone else that uses it.
|
||||
|
||||
## 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
|
||||
```bash
|
||||
```
|
||||
docker pull ghcr.io/wfg/openvpn-client
|
||||
```
|
||||
|
||||
To build it yourself, run
|
||||
```bash
|
||||
docker build -t ghcr.io/wfg/openvpn-client https://github.com/wfg/docker-openvpn-client.git
|
||||
```
|
||||
docker build -t ghcr.io/wfg/openvpn-client https://github.com/wfg/docker-openvpn-client.git#:build
|
||||
```
|
||||
|
||||
### Creating and running a container
|
||||
The image requires the container be created with the `NET_ADMIN` capability and `/dev/net/tun` accessible.
|
||||
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 use the [proxies](#http_proxy-and-socks_proxy) and have [other containers use `openvpn-client`'s network stack](#using-with-other-containers).
|
||||
See the below to learn how to have [other containers use `openvpn-client`'s network stack](#using-with-other-containers).
|
||||
|
||||
#### `docker run`
|
||||
```bash
|
||||
```
|
||||
docker run --detach \
|
||||
--name=openvpn-client \
|
||||
--cap-add=NET_ADMIN \
|
||||
--device=/dev/net/tun \
|
||||
--env KILL_SWITCH=off \
|
||||
--volume <path/to/config/dir>:/data/vpn \
|
||||
--volume <path/to/config/dir>:/config \
|
||||
ghcr.io/wfg/openvpn-client
|
||||
```
|
||||
|
||||
|
@ -58,48 +54,27 @@ services:
|
|||
- NET_ADMIN
|
||||
devices:
|
||||
- /dev/net/tun
|
||||
environment:
|
||||
- KILL_SWITCH=off
|
||||
volumes:
|
||||
- <path/to/config/dir>:/data/vpn
|
||||
- <path/to/config/dir>:/config
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
#### Environment variables
|
||||
| Variable | Default (blank is unset) | Description |
|
||||
| --- | --- | --- |
|
||||
| `KILL_SWITCH` | `on` | The on/off status of the network kill switch. |
|
||||
| `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. See important note about this [below](#subnets). |
|
||||
| `VPN_CONFIG_FILE` | | The OpenVPN config file to use. If this is unset, the first file with the extension .conf will be used. |
|
||||
| `VPN_LOG_LEVEL` | `3` | OpenVPN verbosity (`1`-`11`) |
|
||||
| `HTTP_PROXY` | `off` | The on/off status of Tinyproxy, the built-in HTTP proxy server. To enable, set to `on`. Any other value (including unset) will cause the proxy server to not start. It listens on port 8080. |
|
||||
| `SOCKS_PROXY` | `off` | The on/off status of Dante, the built-in SOCKS proxy server. To enable, set to `on`. Any other value (including unset) will cause the proxy server to not start. It listens on port 1080. |
|
||||
| `PROXY_USERNAME` | | Credentials for accessing the proxies. If `PROXY_USERNAME` is specified, you must also specify `PROXY_PASSWORD`. |
|
||||
| `PROXY_PASSWORD` | | Credentials for accessing the proxies. If `PROXY_PASSWORD` is specified, you must also specify `PROXY_USERNAME`. |
|
||||
| `PROXY_USERNAME_SECRET` | | Docker secrets that contain the credentials for accessing the proxies. If `PROXY_USERNAME_SECRET` is specified, you must also specify `PROXY_PASSWORD_SECRET`. |
|
||||
| `PROXY_PASSWORD_SECRET` | | Docker secrets that contain the credentials for accessing the proxies. If `PROXY_PASSWORD_SECRET` is specified, you must also specify `PROXY_USERNAME_SECRET`. |
|
||||
| `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. |
|
||||
| `AUTH_SECRET` | | Docker secret that contains the credentials for accessing the VPN. |
|
||||
| `CONFIG_FILE` | | The OpenVPN configuration file or search pattern. If unset, a random `.conf` or `.ovpn` file will be selected. |
|
||||
| `KILL_SWITCH` | `on` | Whether or not to enable the kill switch. Set to any "truthy" value[1] to enable. |
|
||||
|
||||
[1] "Truthy" values in this context are the following: `true`, `t`, `yes`, `y`, `1`, `on`, `enable`, or `enabled`.
|
||||
|
||||
##### Environment variable considerations
|
||||
###### `SUBNETS`
|
||||
**Important**:
|
||||
If the kill switch is enabled, the DNS server used by this container prior to VPN connection must be included in the value specified.
|
||||
For example, if your container is using 192.168.1.1 as a DNS server, then this address or an appropriate CIDR block must be included in `SUBNETS`.
|
||||
This is necessary because the kill switch blocks traffic outside of the VPN tunnel before it's actually established.
|
||||
If the DNS server is not allowed, the server addresses in the VPN configuration will not resolve.
|
||||
###### `ALLOWED_SUBNETS`
|
||||
If you intend on connecting to containers that use the OpenVPN container's network stack (which you probably do), **you will probably want to use this variable**.
|
||||
Regardless of whether or not you're using the kill switch, the entrypoint script also adds routes to each of the `ALLOWED_SUBNETS` to allow network connectivity from outside of Docker.
|
||||
|
||||
The subnets specified will be allowed through the firewall which allows for connectivity to and from hosts on the subnets.
|
||||
|
||||
###### `HTTP_PROXY` and `SOCKS_PROXY`
|
||||
If enabling the the proxy server(s), you'll want to publish the appropriate port(s) in order to access the server(s).
|
||||
To do that using `docker run`, add `-p <host_port>:8080` and/or `-p <host_port>:1080` where `<host_port>` is whatever port you want to use on the host.
|
||||
If you're using `docker-compose`, add the relevant port specification(s) from the snippet below to the `openvpn-client` service definition in your Compose file.
|
||||
```yaml
|
||||
ports:
|
||||
- <host_port>:8080
|
||||
- <host_port>:1080
|
||||
```
|
||||
|
||||
###### `PROXY_USERNAME_SECRET` and `PROXY_PASSWORD_SECRET`
|
||||
##### `AUTH_SECRET`
|
||||
Compose has support for [Docker secrets](https://docs.docker.com/engine/swarm/secrets/#use-secrets-in-compose).
|
||||
See the [Compose file](docker-compose.yml) in this repository for example usage of passing proxy credentials as Docker secrets.
|
||||
|
||||
|
@ -128,6 +103,24 @@ In both cases, replace `<host_port>` and `<container_port>` with the port used b
|
|||
Once you have container running `ghcr.io/wfg/openvpn-client`, run the following command to spin up a temporary container using `openvpn-client` for networking.
|
||||
The `wget -qO - ifconfig.me` bit will return the public IP of the container (and anything else using `openvpn-client` for networking).
|
||||
You should see an IP address owned by your VPN provider.
|
||||
```bash
|
||||
```
|
||||
docker run --rm -it --network=container:openvpn-client alpine wget -qO - ifconfig.me
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
#### VPN authentication
|
||||
Your OpenVPN configuration file may not come with authentication baked in.
|
||||
To provide OpenVPN the necessary credentials, create a file (any name will work, but this example will use `credentials.txt`) next to the OpenVPN configuration file with your username on the first line and your password on the second line.
|
||||
|
||||
For example:
|
||||
```
|
||||
vpn_username
|
||||
vpn_password
|
||||
```
|
||||
|
||||
In the OpenVPN configuration file, add the following line:
|
||||
```
|
||||
auth-user-pass credentials.txt
|
||||
```
|
||||
|
||||
This will tell OpenVPN to read `credentials.txt` whenever it needs credentials.
|
||||
|
|
1
build/.dockerignore
Normal file
1
build/.dockerignore
Normal file
|
@ -0,0 +1 @@
|
|||
Dockerfile
|
14
build/Dockerfile
Normal file
14
build/Dockerfile
Normal file
|
@ -0,0 +1,14 @@
|
|||
FROM alpine:3.17
|
||||
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
bind-tools \
|
||||
iptables \
|
||||
ip6tables \
|
||||
openvpn
|
||||
|
||||
COPY . /usr/local/bin
|
||||
|
||||
ENV KILL_SWITCH=on
|
||||
|
||||
ENTRYPOINT [ "entry.sh" ]
|
50
build/entry.sh
Executable file
50
build/entry.sh
Executable file
|
@ -0,0 +1,50 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
cleanup() {
|
||||
kill TERM "$openvpn_pid"
|
||||
exit 0
|
||||
}
|
||||
|
||||
is_enabled() {
|
||||
[[ ${1,,} =~ ^(true|t|yes|y|1|on|enable|enabled)$ ]]
|
||||
}
|
||||
|
||||
# Either a specific file name or a pattern.
|
||||
if [[ $CONFIG_FILE ]]; then
|
||||
config_file=$(find /config -name "$CONFIG_FILE" 2> /dev/null | sort | shuf -n 1)
|
||||
else
|
||||
config_file=$(find /config -name '*.conf' -o -name '*.ovpn' 2> /dev/null | sort | shuf -n 1)
|
||||
fi
|
||||
|
||||
if [[ -z $config_file ]]; then
|
||||
echo "no openvpn configuration file found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "using openvpn configuration file: $config_file"
|
||||
|
||||
|
||||
openvpn_args=(
|
||||
"--config" "$config_file"
|
||||
"--cd" "/config"
|
||||
)
|
||||
|
||||
if is_enabled "$KILL_SWITCH"; then
|
||||
openvpn_args+=("--route-up" "/usr/local/bin/killswitch.sh $ALLOWED_SUBNETS")
|
||||
fi
|
||||
|
||||
# Docker secret that contains the credentials for accessing the VPN.
|
||||
if [[ $AUTH_SECRET ]]; then
|
||||
openvpn_args+=("--auth-user-pass" "/run/secrets/$AUTH_SECRET")
|
||||
fi
|
||||
|
||||
openvpn "${openvpn_args[@]}" &
|
||||
openvpn_pid=$!
|
||||
|
||||
trap cleanup TERM
|
||||
|
||||
wait $openvpn_pid
|
44
build/killswitch.sh
Executable file
44
build/killswitch.sh
Executable file
|
@ -0,0 +1,44 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
iptables --insert OUTPUT \
|
||||
! --out-interface tun0 \
|
||||
--match addrtype ! --dst-type LOCAL \
|
||||
! --destination "$(ip -4 -oneline addr show dev eth0 | awk 'NR == 1 { print $4 }')" \
|
||||
--jump REJECT
|
||||
|
||||
# Create static routes for any ALLOWED_SUBNETS and punch holes in the firewall
|
||||
# (ALLOWED_SUBNETS is passed as $1 from entry.sh)
|
||||
default_gateway=$(ip -4 route | awk '$1 == "default" { print $3 }')
|
||||
for subnet in ${1//,/ }; do
|
||||
ip route add "$subnet" via "$default_gateway"
|
||||
iptables --insert OUTPUT --destination "$subnet" --jump ACCEPT
|
||||
done
|
||||
|
||||
# Punch holes in the firewall for the OpenVPN server addresses
|
||||
# $config is set by OpenVPN:
|
||||
# "Name of first --config file. Set on program initiation and reset on SIGHUP."
|
||||
global_port=$(awk '$1 == "port" { print $2 }' "${config:?"config file not found by kill switch"}")
|
||||
global_protocol=$(awk '$1 == "proto" { print $2 }' "${config:?"config file not found by kill switch"}")
|
||||
remotes=$(awk '$1 == "remote" { print $2, $3, $4 }' "${config:?"config file not found by kill switch"}")
|
||||
ip_regex='^(([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))\.){3}([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))$'
|
||||
while IFS= read -r line; do
|
||||
# Read a comment-stripped version of the line
|
||||
# Fixes #84
|
||||
IFS=" " read -ra remote <<< "${line%%\#*}"
|
||||
address=${remote[0]}
|
||||
port=${remote[1]:-${global_port:-1194}}
|
||||
protocol=${remote[2]:-${global_protocol:-udp}}
|
||||
|
||||
if [[ $address =~ $ip_regex ]]; then
|
||||
iptables --insert OUTPUT --destination "$address" --protocol "$protocol" --destination-port "$port" --jump ACCEPT
|
||||
else
|
||||
for ip in $(dig -4 +short "$address"); do
|
||||
iptables --insert OUTPUT --destination "$ip" --protocol "$protocol" --destination-port "$port" --jump ACCEPT
|
||||
echo "$ip $address" >> /etc/hosts
|
||||
done
|
||||
fi
|
||||
done <<< "$remotes"
|
|
@ -1,11 +0,0 @@
|
|||
#!/bin/ash
|
||||
# shellcheck shell=ash
|
||||
# shellcheck disable=SC2169 # making up for lack of ash support
|
||||
|
||||
echo -e "Running Dante SOCKS proxy server.\n"
|
||||
|
||||
until ip link show tun0 2>&1 | grep -qv "does not exist"; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
sockd -f /data/sockd.conf
|
|
@ -1,211 +0,0 @@
|
|||
#!/bin/ash
|
||||
# shellcheck shell=ash
|
||||
# shellcheck disable=SC2169 # making up for lack of ash support
|
||||
|
||||
cleanup() {
|
||||
# When you run `docker stop` or any equivalent, a SIGTERM signal is sent to PID 1.
|
||||
# A process running as PID 1 inside a container is treated specially by Linux:
|
||||
# it ignores any signal with the default action. As a result, the process will
|
||||
# not terminate on SIGINT or SIGTERM unless it is coded to do so. Because of this,
|
||||
# I've defined behavior for when SIGINT and SIGTERM is received.
|
||||
if [ "$openvpn_child" ]; then
|
||||
echo "Stopping OpenVPN..."
|
||||
kill -TERM "$openvpn_child"
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
rm "$config_file_modified"
|
||||
echo "Exiting."
|
||||
exit 0
|
||||
}
|
||||
|
||||
is_ip() {
|
||||
echo "$1" | grep -Eq "[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*"
|
||||
}
|
||||
|
||||
# shellcheck disable=SC2153
|
||||
if ! (echo "$VPN_LOG_LEVEL" | grep -Eq '^([1-9]|1[0-1])$'); then
|
||||
echo "WARNING: Invalid log level $VPN_LOG_LEVEL. Setting to default."
|
||||
vpn_log_level=3
|
||||
else
|
||||
vpn_log_level=$VPN_LOG_LEVEL
|
||||
fi
|
||||
|
||||
echo "
|
||||
---- Running with the following variables ----
|
||||
Kill switch: ${KILL_SWITCH:-off}
|
||||
HTTP proxy: ${HTTP_PROXY:-off}
|
||||
SOCKS proxy: ${SOCKS_PROXY:-off}
|
||||
Proxy username secret: ${PROXY_PASSWORD_SECRET:-none}
|
||||
Proxy password secret: ${PROXY_USERNAME_SECRET:-none}
|
||||
Allowing subnets: ${SUBNETS:-none}
|
||||
Using OpenVPN log level: $vpn_log_level"
|
||||
|
||||
if [ -n "$VPN_CONFIG_FILE" ]; then
|
||||
config_file_original="/data/vpn/$VPN_CONFIG_FILE"
|
||||
else
|
||||
# Capture the filename of the first .conf file to use as the OpenVPN config.
|
||||
config_file_original=$(find /data/vpn -name "*.conf" 2> /dev/null | sort | head -1)
|
||||
if [ -z "$config_file_original" ]; then
|
||||
>&2 echo "ERROR: No configuration file found. Please check your mount and file permissions. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo "Using configuration file: $config_file_original"
|
||||
|
||||
# Create a new configuration file to modify so the original is left untouched.
|
||||
config_file_modified="${config_file_original}.modified"
|
||||
|
||||
echo "Creating $config_file_modified and making required changes to that file."
|
||||
cp "$config_file_original" "$config_file_modified"
|
||||
|
||||
# These configuration file changes are required by Alpine.
|
||||
sed -i \
|
||||
-e '/up /c up \/etc\/openvpn\/up.sh' \
|
||||
-e '/down /c down \/etc\/openvpn\/down.sh' \
|
||||
-e 's/^proto udp$/proto udp4/' \
|
||||
-e 's/^proto tcp$/proto tcp4/' \
|
||||
"$config_file_modified"
|
||||
|
||||
echo -e "Changes made.\n"
|
||||
|
||||
trap cleanup INT TERM
|
||||
|
||||
# NOTE: When testing with the kill switch enabled, don't forget to pass in the
|
||||
# local subnet. It will save a lot of headache.
|
||||
default_gateway=$(ip r | grep 'default via' | cut -d " " -f 3)
|
||||
if [ "$KILL_SWITCH" = "on" ]; then
|
||||
local_subnet=$(ip r | grep -v 'default via' | grep eth0 | tail -n 1 | cut -d " " -f 1)
|
||||
|
||||
echo "Creating VPN kill switch and local routes."
|
||||
|
||||
echo "Allowing established and related connections..."
|
||||
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
echo "Allowing loopback connections..."
|
||||
iptables -A INPUT -i lo -j ACCEPT
|
||||
iptables -A OUTPUT -o lo -j ACCEPT
|
||||
|
||||
echo "Allowing Docker network connections..."
|
||||
iptables -A INPUT -s "$local_subnet" -j ACCEPT
|
||||
iptables -A OUTPUT -d "$local_subnet" -j ACCEPT
|
||||
|
||||
echo "Allowing specified subnets..."
|
||||
# for every specified subnet...
|
||||
for subnet in ${SUBNETS//,/ }; do
|
||||
# create a route to it and...
|
||||
ip route add "$subnet" via "$default_gateway" dev eth0
|
||||
# allow connections
|
||||
iptables -A INPUT -s "$subnet" -j ACCEPT
|
||||
iptables -A OUTPUT -d "$subnet" -j ACCEPT
|
||||
done
|
||||
|
||||
echo "Allowing remote servers in configuration file..."
|
||||
remote_port=$(grep "port " "$config_file_modified" | cut -d " " -f 2)
|
||||
remote_proto=$(grep "proto " "$config_file_modified" | cut -d " " -f 2 | cut -c1-3)
|
||||
remotes=$(grep "remote " "$config_file_modified" | cut -d " " -f 2-4)
|
||||
|
||||
echo " Using:"
|
||||
echo "${remotes}" | while IFS= read -r line; do
|
||||
# strip any comments from line that could mess up cuts
|
||||
clean_line=${line%% #*}
|
||||
addr=$(echo "$clean_line" | cut -d " " -f 1)
|
||||
port=$(echo "$clean_line" | cut -d " " -f 2)
|
||||
proto=$(echo "$clean_line" | cut -d " " -f 3 | cut -c1-3)
|
||||
if is_ip "$addr"; then
|
||||
echo " IP: $addr PORT: $port"
|
||||
iptables -A OUTPUT -o eth0 -d "$addr" -p "${proto:-$remote_proto}" --dport "${port:-$remote_port}" -j ACCEPT
|
||||
else
|
||||
for ip in $(dig -4 +short "$addr"); do
|
||||
echo " $addr (IP: $ip PORT: $port)"
|
||||
iptables -A OUTPUT -o eth0 -d "$ip" -p "${proto:-$remote_proto}" --dport "${port:-$remote_port}" -j ACCEPT
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Allowing connections over VPN interface..."
|
||||
iptables -A INPUT -i tun0 -j ACCEPT
|
||||
iptables -A OUTPUT -o tun0 -j ACCEPT
|
||||
|
||||
echo "Preventing anything else..."
|
||||
iptables -P INPUT DROP
|
||||
iptables -P OUTPUT DROP
|
||||
iptables -P FORWARD DROP
|
||||
|
||||
echo -e "iptables rules created and routes configured.\n"
|
||||
else
|
||||
echo -e "WARNING: VPN kill switch is disabled. Traffic will be allowed outside of the tunnel if the connection is lost.\n"
|
||||
echo "Creating routes to specified subnets..."
|
||||
for subnet in ${SUBNETS//,/ }; do
|
||||
ip route add "$subnet" via "$default_gateway" dev eth0
|
||||
done
|
||||
echo -e "Routes created.\n"
|
||||
fi
|
||||
|
||||
if [ "$HTTP_PROXY" = "on" ]; then
|
||||
if [ "$PROXY_USERNAME" ]; then
|
||||
if [ "$PROXY_PASSWORD" ]; then
|
||||
echo "Configuring HTTP proxy authentication."
|
||||
echo -e "\nBasicAuth $PROXY_USERNAME $PROXY_PASSWORD" >> /data/tinyproxy.conf
|
||||
else
|
||||
echo "WARNING: Proxy username supplied without password. Starting HTTP proxy without credentials."
|
||||
fi
|
||||
elif [ -f "/run/secrets/$PROXY_USERNAME_SECRET" ]; then
|
||||
if [ -f "/run/secrets/$PROXY_PASSWORD_SECRET" ]; then
|
||||
echo "Configuring proxy authentication."
|
||||
echo -e "\nBasicAuth $(cat /run/secrets/$PROXY_USERNAME_SECRET) $(cat /run/secrets/$PROXY_PASSWORD_SECRET)" >> /data/tinyproxy.conf
|
||||
else
|
||||
echo "WARNING: Credentials secrets not read. Starting HTTP proxy without credentials."
|
||||
fi
|
||||
fi
|
||||
/data/scripts/tinyproxy_wrapper.sh &
|
||||
fi
|
||||
|
||||
if [ "$SOCKS_PROXY" = "on" ]; then
|
||||
if [ "$PROXY_USERNAME" ]; then
|
||||
if [ "$PROXY_PASSWORD" ]; then
|
||||
echo "Configuring SOCKS proxy authentication."
|
||||
adduser -S -D -g "$PROXY_USERNAME" -H -h /dev/null "$PROXY_USERNAME"
|
||||
echo "$PROXY_USERNAME:$PROXY_PASSWORD" | chpasswd 2> /dev/null
|
||||
sed -i 's/socksmethod: none/socksmethod: username/' /data/sockd.conf
|
||||
else
|
||||
echo "WARNING: Proxy username supplied without password. Starting SOCKS proxy without credentials."
|
||||
fi
|
||||
elif [ -f "/run/secrets/$PROXY_USERNAME_SECRET" ]; then
|
||||
if [ -f "/run/secrets/$PROXY_PASSWORD_SECRET" ]; then
|
||||
echo "Configuring proxy authentication."
|
||||
adduser -S -D -g "$(cat /run/secrets/$PROXY_USERNAME_SECRET)" -H -h /dev/null "$(cat /run/secrets/$PROXY_USERNAME_SECRET)"
|
||||
echo "$(cat /run/secrets/$PROXY_USERNAME_SECRET):$(cat /run/secrets/$PROXY_PASSWORD_SECRET)" | chpasswd 2> /dev/null
|
||||
sed -i 's/socksmethod: none/socksmethod: username/' /data/sockd.conf
|
||||
else
|
||||
echo "WARNING: Credentials secrets not read. Starting SOCKS proxy without credentials."
|
||||
fi
|
||||
fi
|
||||
/data/scripts/dante_wrapper.sh &
|
||||
fi
|
||||
|
||||
ovpn_auth_flag=''
|
||||
if [ -n "$OPENVPN_AUTH_SECRET" ]; then
|
||||
if [ -f "/run/secrets/$OPENVPN_AUTH_SECRET" ]; then
|
||||
echo "Configuring OpenVPN authentication."
|
||||
ovpn_auth_flag="--auth-user-pass /run/secrets/$OPENVPN_AUTH_SECRET"
|
||||
else
|
||||
echo "WARNING: OpenVPN Credentials secrets fail to read."
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e "Running OpenVPN client.\n"
|
||||
|
||||
openvpn --config "$config_file_modified" \
|
||||
$ovpn_auth_flag \
|
||||
--verb "$vpn_log_level" \
|
||||
--auth-nocache \
|
||||
--connect-retry-max 10 \
|
||||
--pull-filter ignore "route-ipv6" \
|
||||
--pull-filter ignore "ifconfig-ipv6" \
|
||||
--script-security 2 \
|
||||
--up-restart \
|
||||
--cd /data/vpn &
|
||||
openvpn_child=$!
|
||||
|
||||
wait $openvpn_child
|
|
@ -1,18 +0,0 @@
|
|||
#!/bin/ash
|
||||
# shellcheck shell=ash
|
||||
# shellcheck disable=SC2169 # making up for lack of ash support
|
||||
|
||||
echo -e "Running Tinyproxy HTTP proxy server.\n"
|
||||
|
||||
until ip link show tun0 2>&1 | grep -qv "does not exist"; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
addr_eth=$(ip a show dev eth0 | grep inet | cut -d " " -f 6 | cut -d "/" -f 1)
|
||||
addr_tun=$(ip a show dev tun0 | grep inet | cut -d " " -f 6 | cut -d "/" -f 1)
|
||||
sed -i \
|
||||
-e "/Listen/c Listen $addr_eth" \
|
||||
-e "/Bind/c Bind $addr_tun" \
|
||||
/data/tinyproxy.conf
|
||||
|
||||
tinyproxy -d -c /data/tinyproxy.conf
|
|
@ -1,65 +0,0 @@
|
|||
# Logging
|
||||
logoutput: /var/log/sockd.log
|
||||
errorlog: stderr
|
||||
|
||||
# Server address specification
|
||||
internal: eth0 port = 1080
|
||||
external: tun0
|
||||
|
||||
# Authentication methods
|
||||
clientmethod: none
|
||||
socksmethod: none
|
||||
|
||||
# Server identities
|
||||
user.unprivileged: sockd
|
||||
|
||||
##
|
||||
## SOCKS client access rules
|
||||
##
|
||||
# Rule processing stops at the first match; no match results in blocking
|
||||
|
||||
# Block access to socks server from 192.0.2.22
|
||||
# client block {
|
||||
# # Block connections from 192.0.2.22/32
|
||||
# from: 192.0.2.22/24 to: 0.0.0.0/0
|
||||
# log: error # connect disconnect
|
||||
# }
|
||||
|
||||
# Allow all connections
|
||||
client pass {
|
||||
from: 0.0.0.0/0 to: 0.0.0.0/0
|
||||
log: error connect disconnect
|
||||
}
|
||||
|
||||
##
|
||||
## SOCKS command rules
|
||||
##
|
||||
# Rule processing stops at the first match; no match results in blocking
|
||||
|
||||
# Block communication with www.example.org
|
||||
# socks block {
|
||||
# from: 0.0.0.0/0 to: www.example.org
|
||||
# command: bind connect udpassociate
|
||||
# log: error # connect disconnect iooperation
|
||||
# }
|
||||
|
||||
# Generic pass statement - bind/outgoing traffic
|
||||
socks pass {
|
||||
from: 0.0.0.0/0 to: 0.0.0.0/0
|
||||
command: bind connect udpassociate
|
||||
log: error connect disconnect # iooperation
|
||||
}
|
||||
|
||||
# Block incoming connections/packets from ftp.example.org
|
||||
# socks block {
|
||||
# from: ftp.example.org to: 0.0.0.0/0
|
||||
# command: bindreply udpreply
|
||||
# log: error # connect disconnect iooperation
|
||||
# }
|
||||
|
||||
# Generic pass statement for incoming connections/packets
|
||||
socks pass {
|
||||
from: 0.0.0.0/0 to: 0.0.0.0/0
|
||||
command: bindreply udpreply
|
||||
log: error connect disconnect # iooperation
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
User tinyproxy
|
||||
Group tinyproxy
|
||||
|
||||
Port 8080
|
||||
Listen
|
||||
Bind
|
||||
|
||||
Timeout 600
|
||||
|
||||
DefaultErrorFile "/usr/share/tinyproxy/default.html"
|
||||
StatFile "/usr/share/tinyproxy/stats.html"
|
||||
LogFile "/var/log/tinyproxy/tinyproxy.log"
|
||||
|
||||
LogLevel Info
|
||||
|
||||
MaxClients 100
|
||||
MinSpareServers 5
|
||||
MaxSpareServers 15
|
||||
StartServers 10
|
|
@ -1,32 +1,13 @@
|
|||
# Use this file as an example if you need help writing your Compose files.
|
||||
# The commented-out parts may or may not be relevant to your setup.
|
||||
|
||||
services:
|
||||
vpn:
|
||||
image: ghcr.io/wfg/openvpn-client
|
||||
# build: .
|
||||
openvpn-client:
|
||||
image: ghcr.io/wfg/openvpn-client:latest
|
||||
container_name: openvpn-client
|
||||
cap_add:
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
devices:
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
environment:
|
||||
# - SUBNETS=192.168.10.0/24
|
||||
- HTTP_PROXY=on
|
||||
- SOCKS_PROXY=on
|
||||
# - PROXY_USERNAME_SECRET=username # <-- If used, these must match the name of a
|
||||
# - PROXY_PASSWORD_SECRET=password # <-- secret (NOT the file used by the secret)
|
||||
# volumes:
|
||||
# - ~/local/vpn:/data/vpn
|
||||
ports:
|
||||
- 1080:1080
|
||||
- 8088:8080
|
||||
# secrets:
|
||||
# - username
|
||||
# - password
|
||||
|
||||
# secrets:
|
||||
# username:
|
||||
# file: ~/local/secrets/username
|
||||
# password:
|
||||
# file: ~/local/secrets/password
|
||||
- ALLOWED_SUBNETS=192.168.10.0/24
|
||||
volumes:
|
||||
- ./local:/config
|
||||
restart: unless-stopped
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue