Compare commits

...

32 commits

Author SHA1 Message Date
Wyatt Gill
91bde63706 Add archive note 2024-03-16 13:17:27 -05:00
Wyatt Gill
ee61a9ecdb Merge branch 'rewrite' 2023-02-05 19:08:02 -06:00
Wyatt Gill
e511db1436 Update documentation for building 2023-02-05 19:05:27 -06:00
Wyatt Gill
99bb6571ae Update to Alpine 3.17 2023-02-05 19:04:34 -06:00
Wyatt Gill
d3ce6295ff Make insignificant changes 2022-10-29 16:33:32 -05:00
Wyatt Gill
333fcc682a Set defaults and update README.md 2022-10-29 16:33:32 -05:00
Wyatt Gill
045d47050e Remove Helm chart 2022-10-29 16:33:32 -05:00
Wyatt Gill
b5489ea7ce Image rewrite 2022-10-29 16:33:32 -05:00
Wyatt Gill
ac8a5ba3d4
Merge pull request #78 from choyri/master
fix(socks): user already exists
2022-08-22 20:33:23 -05:00
Wyatt Gill
994836b5f0
Update run-socks-proxy.sh 2022-08-22 20:32:49 -05:00
Chotow
e7c9e1ff6a
fix(socks): user already exists
avoid reporting an error after restart container and causing sockd not to start
2022-08-22 21:12:54 +08:00
Wyatt Gill
bc1648f04a Add note about SUBNETS 2022-07-09 07:56:15 -05:00
Wyatt Gill
51fa5f2e04 Re-add support for iptables
Add package for `useradd`
2022-06-30 09:00:10 -05:00
Wyatt Gill
650d55fe76 Add IP address getter updates to new wrapper 2022-06-15 08:08:06 -05:00
Wyatt Gill
6a1ad61a79 Merge branch 'jakopako/master' 2022-06-15 08:06:46 -05:00
Wyatt Gill
436e745c13 Fix spacing in generated nftables.conf 2022-06-15 07:59:12 -05:00
jakopako
0dec9be496 improved get_addr func 2022-06-15 14:04:50 +02:00
Wyatt Gill
ee14898805 Overhaul 2022-06-14 20:08:28 -05:00
jakopako
a457130638 fixed get_addr in case there are multiple ips 2022-06-14 17:39:35 +02:00
Wyatt Gill
ea0b4dd271
Merge pull request #63 from dokime7/patch-1
Fix env var
2022-06-05 08:21:37 -05:00
Dokime
574fec5212
Fix env var 2022-05-27 15:03:42 +00:00
Wyatt Gill
4cbfa8fb55
Delete sweep.yml 2022-04-09 17:14:37 -05:00
Wyatt Gill
283fe3305f
Merge pull request #59 from tilperion2/master
Fix problem with CRLF(Windows) openVpn config files.
2022-04-09 17:14:04 -05:00
Wyatt Gill
015ebab5ca
Merge pull request #58 from rsgilbert/master
documented on how to perform VPN authentication
2022-04-09 17:13:28 -05:00
Wyatt Gill
d1806f8aad
Merge pull request #57 from DavideWalder/keep-dns-unchanged
Added KEEP_DNS_UNCHANGED option to Helm chart
2022-04-09 17:13:15 -05:00
Wyatt Gill
ac66032038
Merge pull request #56 from azerum/feat-keep-dns-unchanged
Use on/off values for KEEP_DNS_UNCHANGED variable, update README
2022-04-09 17:13:05 -05:00
Merenkov, Andrii
de63b93ddb Fix problem with CRLF(Windows) openVpn config files. Convert line ending to LF(linux) format 2022-04-09 20:23:46 +02:00
ssg
6150534f86 documented on how to perform VPN authentication 2022-03-29 16:21:52 +03:00
Davide Walder
c3ca6fbaa5 Added KEEP_DNS_UNCHANGED option to Helm chart 2022-03-28 14:34:44 +02:00
azerum
fa2045ff68 Use on/off values for KEEP_DNS_UNCHANGED variable, update README
Use on/off intead of yes/no for consistency
2022-03-27 17:58:00 +03:00
Wyatt Gill
de244a77f8
Merge pull request #54 from azerum/feat-keep-dns-unchanged
Add KEEP_DNS_UNCHANGED environment variable that allows to keep /etc/resolv.conf unchanged
2022-03-26 21:38:49 -05:00
azerum
49fc0d7dfd Add KEEP_DNS_UNCHANGED environment variable
When KEEP_DNS_UNCHANGED is set to 'yes', running openvpn
won't modify DNS settings of the container. By default
containers use internal Docker DNS server, which allows
to resolve special names, such as 'host.docker.internal'
pointing to the host IP. If you need to use such domain names,
you need to persist the original DNS settings

(Usually DNS settings are modified by /etc/openvpn/up.sh script
which is runned once openvpn starts, as 'up <command>' option
is added to the vpn config file.
When KEEP_DNS_UNCHANGED is 'yes', 'up <command>' option is
simply not added to the config)
2022-03-16 21:19:57 +02:00
29 changed files with 206 additions and 965 deletions

View file

@ -1,2 +0,0 @@
*
!data/

46
.github/workflows/build.yaml vendored Normal file
View 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

View file

@ -1,59 +0,0 @@
name: Publish
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
env:
IMAGE_NAME: openvpn-client
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Log in to registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create tags
id: tags
uses: docker/metadata-action@v3
with:
images: ghcr.io/wfg/openvpn-client
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Create build args
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"
- name: Build and push
uses: docker/build-push-action@v2
with:
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
tags: ${{ steps.tags.outputs.tags }}
build-args: |
BUILD_DATE=${{ steps.build-args.outputs.date }}
IMAGE_VERSION=${{ steps.build-args.outputs.version }}
push: true

View file

@ -1,19 +0,0 @@
name: Mark issues as stale
on:
schedule:
- cron: '0 3 * * *'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v4
with:
stale-issue-label: no-issue-activity
stale-pr-label: no-pr-activity
days-before-stale: 30
days-before-close: -1
exempt-assignees: wfg
any-of-labels: needs-more-info

3
.gitignore vendored
View file

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

View file

@ -1,9 +0,0 @@
repos:
- repo: https://github.com/norwoodj/helm-docs
rev: v1.6.0
hooks:
- id: helm-docs
args:
- --chart-search-root=chart
- --template-files=./_templates.gotmpl
- --template-files=README.md.gotmpl

View file

@ -1,12 +0,0 @@
# Changelog
## Version 2.1.0 - 2022-03-06
### Added
- `VPN_CONFIG_PATTERN` environment variable.
## Version 2.0.0 - 2022-01-02
### Changed
- `OPENVPN_AUTH_SECRET` changed to `VPN_AUTH_SECRET` for consistency.
### Fixed
- Commented remotes are no longer processed.

View file

@ -1,28 +0,0 @@
FROM alpine:3.15
ARG IMAGE_VERSION
ARG BUILD_DATE
LABEL org.opencontainers.image.created="$BUILD_DATE"
LABEL org.opencontainers.image.source="github.com/wfg/docker-openvpn-client"
LABEL org.opencontainers.image.version="$IMAGE_VERSION"
ENV KILL_SWITCH=on \
VPN_LOG_LEVEL=3 \
HTTP_PROXY=off \
SOCKS_PROXY=off
RUN apk add --no-cache \
bash \
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"]

View file

@ -1,49 +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 \
--volume <path/to/config/dir>:/data/vpn \
--volume <path/to/config/dir>:/config \
ghcr.io/wfg/openvpn-client
```
@ -58,38 +55,26 @@ services:
devices:
- /dev/net/tun
volumes:
- <path/to/config/dir>:/data/vpn
- <path/to/config/dir>:/config
restart: unless-stopped
```
#### Environment variables (alphabetical)
#### Environment variables
| Variable | Default (blank is unset) | Description |
| --- | --- | --- |
| `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. |
| `KILL_SWITCH` | `on` | The on/off status of the network kill switch. |
| `LISTEN_ON` | | Address the proxies will be listening on. Set to `0.0.0.0` to listen on all IP addresses. |
| `PROXY_PASSWORD` | | Credentials for accessing the proxies. If `PROXY_PASSWORD` is specified, you must also specify `PROXY_USERNAME`. |
| `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`. |
| `PROXY_USERNAME` | | Credentials for accessing the proxies. If `PROXY_USERNAME` is specified, you must also specify `PROXY_PASSWORD`. |
| `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`. |
| `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. |
| `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. |
| `VPN_AUTH_SECRET` | | Docker secret that contain the credentials for accessing the VPN. |
| `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`) |
| `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
###### `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
```
###### `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.
###### `PROXY_USERNAME_SECRET`, `PROXY_PASSWORD_SECRET`, and `VPN_AUTH_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.
@ -118,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
View file

@ -0,0 +1 @@
Dockerfile

14
build/Dockerfile Normal file
View 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
View 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
View 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"

View file

@ -1,23 +0,0 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View file

@ -1,15 +0,0 @@
apiVersion: v2
name: openvpn-client
description: A Helm chart for an OpenVPN client with HTTP and SOCKS5 proxies
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "v1.2.1"

View file

@ -1,41 +0,0 @@
# openvpn-client
![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v1.2.1](https://img.shields.io/badge/AppVersion-v1.2.1-informational?style=flat-square)
A Helm chart for an OpenVPN client with HTTP and SOCKS5 proxies
## Values
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| affinity | object | `{}` | |
| auth.enabled | bool | `false` | Whether to turn on authentication for the proxies |
| auth.existingSecret | string | `""` | Existing secret containing the credentials for accessing the proxies. |
| auth.proxyPassword | string | `""` | |
| auth.proxyUsername | string | `""` | |
| autoscaling.enabled | bool | `false` | |
| autoscaling.maxReplicas | int | `100` | |
| autoscaling.minReplicas | int | `1` | |
| autoscaling.targetCPUUtilizationPercentage | int | `80` | |
| configFiles.files | object | `{}` | OpenVPN config files |
| configFiles.openVPNConfig | string | `""` | The OpenVPN config file to use. If this is unset, the first file with the extension `.conf` will be used. |
| fullnameOverride | string | `""` | |
| httpProxy.enabled | bool | `false` | The on/off status of Tinyproxy, the built-in HTTP proxy server. |
| image.pullPolicy | string | `"IfNotPresent"` | |
| image.repository | string | `"ghcr.io/wfg/openvpn-client"` | |
| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. |
| killSwitch.enabled | bool | `true` | The on/off status of the network kill switch. |
| listenOn | string | `""` | Address the proxies will be listening on. Set to `0.0.0.0` to allow all IP addresses. |
| nameOverride | string | `""` | |
| nodeSelector | object | `{}` | |
| podAnnotations | object | `{}` | |
| replicaCount | int | `1` | |
| resources | object | `{}` | |
| service.type | string | `"ClusterIP"` | |
| socksProxy.enabled | bool | `false` | The on/off status of Dante, the built-in SOCKS proxy server. |
| subnets | list | `[]` | A list of one or more subnets to allow outside of the VPN tunnel. |
| tolerations | list | `[]` | |
| vpnLogLevel | int | `3` | OpenVPN verbosity (`1`-`11`) |
----------------------------------------------
Autogenerated from chart metadata using [helm-docs v1.5.0](https://github.com/norwoodj/helm-docs/releases/v1.5.0)

View file

@ -1,76 +0,0 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "openvpn-client.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "openvpn-client.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "openvpn-client.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "openvpn-client.labels" -}}
helm.sh/chart: {{ include "openvpn-client.chart" . }}
{{ include "openvpn-client.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "openvpn-client.selectorLabels" -}}
app.kubernetes.io/name: {{ include "openvpn-client.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Convert boolean to on/off
*/}}
{{- define "openvpn-client.boolean" -}}
{{- if .enabled }} "on" {{- else }} "off" {{- end }}
{{- end }}
{{/*
Define auth secret name
*/}}
{{- define "openvpn-client.authSecretName" -}}
{{- if .Values.auth.existingSecret -}}
{{- .Values.auth.existingSecret -}}
{{- else -}}
{{- include "openvpn-client.fullname" . | printf "%s-auth" }}
{{- end -}}
{{- end -}}
{{/*
Define config secret name
*/}}
{{- define "openvpn-client.configSecretName" -}}
{{- include "openvpn-client.fullname" . | printf "%s-config" }}
{{- end -}}

View file

@ -1,12 +0,0 @@
{{- if and .Values.auth.enabled (not .Values.auth.existingSecret) (or .Values.httpProxy.enabled .Values.socksProxy.enabled) -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "openvpn-client.authSecretName" . }}
labels:
{{- include "openvpn-client.labels" . | nindent 4 }}
type: kubernetes.io/basic-auth
data:
username: {{ .Values.auth.proxyUsername | b64enc | quote }}
password: {{ .Values.auth.proxyPassword | b64enc | quote }}
{{- end -}}

View file

@ -1,12 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: {{ include "openvpn-client.configSecretName" . }}
labels:
{{- include "openvpn-client.labels" . | nindent 4 }}
type: Opaque
data:
{{- range $fileName, $fileContent := $.Values.configFiles.files }}
{{ $fileName }}: |-
{{- $fileContent | b64enc | nindent 4 }}
{{- end }}

View file

@ -1,106 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "openvpn-client.fullname" . }}
labels:
{{- include "openvpn-client.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "openvpn-client.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "openvpn-client.selectorLabels" . | nindent 8 }}
spec:
initContainers:
- name: copy
image: busybox
command: ["/bin/sh", "-c", "cp -r /from/. /to"]
volumeMounts:
- name: openvpn-client
mountPath: /from
- name: configs
mountPath: /to
containers:
- name: {{ .Chart.Name }}
securityContext:
capabilities:
add:
- NET_ADMIN
privileged: true
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
{{- if .Values.socksProxy.enabled }}
- containerPort: 1080
{{- end }}
{{- if .Values.httpProxy.enabled }}
- containerPort: 8080
{{- end }}
readinessProbe:
exec:
command: ["ping", "-c", "3", "1.1.1.1"]
env:
- name: VPN_LOG_LEVEL
value: {{ .Values.vpnLogLevel | quote }}
- name: SOCKS_PROXY
value: {{- include "openvpn-client.boolean" .Values.socksProxy }}
- name: HTTP_PROXY
value: {{- include "openvpn-client.boolean" .Values.httpProxy }}
- name: KILL_SWITCH
value: {{- include "openvpn-client.boolean" .Values.killSwitch }}
{{- if .Values.listenOn }}
- name: LISTEN_ON
value: {{ .Values.listenOn }}
{{- end }}
{{- if .Values.subnets }}
- name: SUBNETS
value: {{ join "," .Values.subnets | quote }}
{{- end }}
{{- if .Values.auth.enabled }}
- name: PROXY_USERNAME
valueFrom:
secretKeyRef:
name: {{ include "openvpn-client.authSecretName" . }}
key: username
- name: PROXY_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "openvpn-client.authSecretName" . }}
key: password
{{- end }}
{{- with .Values.configFiles.openVPNConfig }}
- name: VPN_CONFIG_FILE
value: {{ . }}
{{- end }}
volumeMounts:
- mountPath: /data/vpn
name: configs
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
- name: openvpn-client
secret:
secretName: {{ include "openvpn-client.configSecretName" . }}
- name: configs
emptyDir: {}

View file

@ -1,28 +0,0 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "openvpn-client.fullname" . }}
labels:
{{- include "openvpn-client.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "openvpn-client.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View file

@ -1,23 +0,0 @@
{{- if or .Values.socksProxy.enabled .Values.httpProxy.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "openvpn-client.fullname" . }}
labels:
{{- include "openvpn-client.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
{{- if .Values.socksProxy.enabled }}
- port: 1080
protocol: TCP
name: socks5
{{- end }}
{{- if .Values.httpProxy.enabled }}
- port: 8080
protocol: TCP
name: http
{{- end }}
selector:
{{- include "openvpn-client.selectorLabels" . | nindent 4 }}
{{- end }}

View file

@ -1,84 +0,0 @@
# Default values for openvpn-client.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
image:
repository: ghcr.io/wfg/openvpn-client
pullPolicy: IfNotPresent
# -- Overrides the image tag whose default is the chart appVersion.
tag: ""
nameOverride: ""
fullnameOverride: ""
podAnnotations: {}
service:
type: ClusterIP
resources:
{}
# limits:
# cpu: 10m
# memory: 64Mi
# requests:
# cpu: 10m
# memory: 64Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
nodeSelector: {}
tolerations: []
affinity: {}
httpProxy:
# -- The on/off status of Tinyproxy, the built-in HTTP proxy server.
enabled: false
socksProxy:
# -- The on/off status of Dante, the built-in SOCKS proxy server.
enabled: false
# -- Address the proxies will be listening on. Set to `0.0.0.0` to allow all IP addresses.
listenOn: ""
auth:
# -- Whether to turn on authentication for the proxies
enabled: false
# -- Existing secret containing the credentials for accessing the proxies.
existingSecret: ""
proxyUsername: ""
proxyPassword: ""
# -- OpenVPN verbosity (`1`-`11`)
vpnLogLevel: 3
# -- A list of one or more subnets to allow outside of the VPN tunnel.
subnets:
[]
# - "192.168.0.0/24"
# - "192.168.1.0/24"
killSwitch:
# -- The on/off status of the network kill switch.
enabled: true
configFiles:
# -- The OpenVPN config file to use. If this is unset, the first file with the extension `.conf` will be used.
openVPNConfig: ""
# -- OpenVPN config files
files:
{}
# example.conf: |-
# # Your OpenVPN configuration file
# Line 1
# Line 2

View file

@ -1,9 +0,0 @@
#!/bin/bash
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

View file

@ -1,237 +0,0 @@
#!/usr/bin/env bash
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 [[ -n "$openvpn_child" ]]; then
echo "Stopping OpenVPN..."
kill -TERM "$openvpn_child"
fi
sleep 1
rm "$config_file_modified"
echo "Exiting."
exit 0
}
# OpenVPN log levels are 1-11.
# shellcheck disable=SC2153
if [[ "$VPN_LOG_LEVEL" -lt 1 || "$VPN_LOG_LEVEL" -gt 11 ]]; 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
Listening on: ${LISTEN_ON:-none}"
if [[ -n "$VPN_CONFIG_FILE" ]]; then
config_file_original="/data/vpn/$VPN_CONFIG_FILE"
elif [[ -n "$VPN_CONFIG_PATTERN" ]]; then
# Capture the filename of the random .conf file according to the pattern to use as OpenVPN config.
config_file_original=$(find /data/vpn -name "$VPN_CONFIG_PATTERN" 2> /dev/null | sort | shuf -n 1)
else
# Capture the filename of the random .conf file to use as the OpenVPN config.
config_file_original=$(find /data/vpn -name "*.conf" 2> /dev/null | sort | shuf -n 1)
fi
if [[ -z "$config_file_original" ]]; then
>&2 echo "ERROR: No configuration file found. Please check your mount and file permissions. Exiting."
exit 1
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."
grep -Ev "(^up\s|^down\s)" "$config_file_original" > "$config_file_modified"
# These configuration file changes are required by Alpine.
sed -i \
-e 's/^proto udp$/proto udp4/' \
-e 's/^proto tcp$/proto tcp4/' \
"$config_file_modified"
echo "up /etc/openvpn/up.sh" >> "$config_file_modified"
echo "down /etc/openvpn/down.sh" >> "$config_file_modified"
echo -e "Changes made.\n"
trap cleanup INT TERM
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..."
global_port=$(grep "port " "$config_file_modified" | cut -d " " -f 2)
global_protocol=$(grep "proto " "$config_file_modified" | cut -d " " -f 2 | cut -c1-3)
remotes=$(grep "remote " "$config_file_modified")
echo " Using:"
comment_regex='^[[:space:]]*[#;]'
echo "$remotes" | while IFS= read -r line; do
# Ignore comments.
if ! [[ "$line" =~ $comment_regex ]]; then
# Remove the line prefix 'remote '.
line=${line#remote }
# Remove any trailing comments.
line=${line%%#*}
# Split the line into an array.
# The first element is an address (IP or domain), the second is a port,
# and the fourth is a protocol.
IFS=' ' read -r -a remote <<< "$line"
address=${remote[0]}
# Use port from 'remote' line, then 'port' line, then '1194'.
port=${remote[1]:-${global_port:-1194}}
# Use protocol from 'remote' line, then 'proto' line, then 'udp'.
protocol=${remote[2]:-${global_protocol:-udp}}
# Map from OpenVPN tcp-client config option to tcp for iptables
if [[ $protocol == "tcp-client" ]]; then
protocol='tcp'
fi
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]))$'
if [[ "$address" =~ $ip_regex ]]; then
echo " IP: $address PORT: $port PROTOCOL: $protocol"
iptables -A OUTPUT -o eth0 -d "$address" -p "$protocol" --dport "$port" -j ACCEPT
else
for ip in $(dig -4 +short "$address"); do
echo " $address (IP: $ip PORT: $port PROTOCOL: $protocol)"
iptables -A OUTPUT -o eth0 -d "$ip" -p "$protocol" --dport "$port" -j ACCEPT
echo "$ip $address" >> /etc/hosts
done
fi
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 [[ -n "$PROXY_USERNAME" ]]; then
if [[ -n "$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 [[ -n "$LISTEN_ON" ]]; then
sed -i "s/internal: eth0/internal: $LISTEN_ON/" /data/sockd.conf
fi
if [[ -n "$PROXY_USERNAME" ]]; then
if [[ -n "$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 present. Starting SOCKS proxy without credentials."
fi
fi
/data/scripts/dante_wrapper.sh &
fi
openvpn_args=(
"--config" "$config_file_modified"
"--auth-nocache"
"--cd" "/data/vpn"
"--pull-filter" "ignore" "ifconfig-ipv6"
"--pull-filter" "ignore" "route-ipv6"
"--script-security" "2"
"--up-restart"
"--verb" "$vpn_log_level"
)
if [[ -n "$VPN_AUTH_SECRET" ]]; then
if [[ -f "/run/secrets/$VPN_AUTH_SECRET" ]]; then
echo "Configuring OpenVPN authentication."
openvpn_args+=("--auth-user-pass" "/run/secrets/$VPN_AUTH_SECRET")
else
echo "WARNING: OpenVPN credentials secrets not present."
fi
fi
echo -e "Running OpenVPN client.\n"
openvpn "${openvpn_args[@]}" &
openvpn_child=$!
wait $openvpn_child

View file

@ -1,20 +0,0 @@
#!/bin/bash
echo -e "Running Tinyproxy HTTP proxy server.\n"
until ip link show tun0 2>&1 | grep -qv "does not exist"; do
sleep 1
done
get_addr() {
ip a show dev "$1" | grep inet | cut -d " " -f 6 | cut -d "/" -f 1
}
addr_eth=${LISTEN_ON:-$(get_addr eth0)}
addr_tun=$(get_addr tun0)
sed -i \
-e "/Listen/c Listen $addr_eth" \
-e "/Bind/c Bind $addr_tun" \
/data/tinyproxy.conf
tinyproxy -d -c /data/tinyproxy.conf

View file

@ -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
}

View file

@ -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

View file

@ -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