Feature: Docker secrets, refers to #306
This commit is contained in:
@@ -51,6 +51,8 @@ ENV VPNSP=pia \
|
||||
# PIA, Windscribe, Surfshark, Cyberghost, Vyprvpn, NordVPN, PureVPN only
|
||||
OPENVPN_USER= \
|
||||
OPENVPN_PASSWORD= \
|
||||
USER_SECRETFILE=/run/secrets/openvpn_user \
|
||||
PASSWORD_SECRETFILE=/run/secrets/openvpn_password \
|
||||
REGION= \
|
||||
# PIA only
|
||||
PIA_ENCRYPTION=strong \
|
||||
@@ -69,6 +71,8 @@ ENV VPNSP=pia \
|
||||
PORT= \
|
||||
# Cyberghost only
|
||||
CYBERGHOST_GROUP="Premium UDP Europe" \
|
||||
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
|
||||
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
|
||||
# NordVPN only
|
||||
SERVER_NUMBER= \
|
||||
# Openvpn
|
||||
@@ -102,11 +106,14 @@ ENV VPNSP=pia \
|
||||
HTTPPROXY_PORT=8888 \
|
||||
HTTPPROXY_USER= \
|
||||
HTTPPROXY_PASSWORD= \
|
||||
HTTPPROXY_USER_SECRETFILE=/run/secrets/httpproxy_user \
|
||||
HTTPPROXY_PASSWORD_SECRETFILE=/run/secrets/httpproxy_password \
|
||||
# Shadowsocks
|
||||
SHADOWSOCKS=off \
|
||||
SHADOWSOCKS_LOG=off \
|
||||
SHADOWSOCKS_PORT=8388 \
|
||||
SHADOWSOCKS_PASSWORD= \
|
||||
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
|
||||
SHADOWSOCKS_METHOD=chacha20-ietf-poly1305 \
|
||||
UPDATER_PERIOD=0
|
||||
ENTRYPOINT ["/entrypoint"]
|
||||
|
||||
28
README.md
28
README.md
@@ -54,7 +54,7 @@ Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN, PureVPN and Privado
|
||||
```bash
|
||||
docker run -d --name gluetun --cap-add=NET_ADMIN \
|
||||
-e VPNSP="private internet access" -e REGION="CA Montreal" \
|
||||
-e OPENVPN_USER=js89ds7 -e PASSWORD=8fd9s239G \
|
||||
-e OPENVPN_USER=js89ds7 -e OPENVPN_PASSWORD=8fd9s239G \
|
||||
-v /yourpath:/gluetun \
|
||||
qmcgaw/private-internet-access
|
||||
```
|
||||
@@ -62,6 +62,8 @@ Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN, PureVPN and Privado
|
||||
or use [docker-compose.yml](https://github.com/qdm12/gluetun/blob/master/docker-compose.yml) with:
|
||||
|
||||
```bash
|
||||
echo "your openvpn username" > openvpn_user
|
||||
echo "your openvpn password" > openvpn_password
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
@@ -71,6 +73,7 @@ Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN, PureVPN and Privado
|
||||
- Use `-p 8888:8888/tcp` to access the HTTP web proxy
|
||||
- Use `-p 8388:8388/tcp -p 8388:8388/udp` to access the Shadowsocks proxy
|
||||
- Use `-p 8000:8000/tcp` to access the [HTTP control server](#HTTP-control-server) built-in
|
||||
- Use [Docker secrets](#Docker-secrets) to read your credentials instead of environment variables
|
||||
|
||||
**If you encounter an issue with the tun device not being available, see [the FAQ](https://github.com/qdm12/gluetun/blob/master/doc/faq.md#how-to-fix-openvpn-failing-to-start)**
|
||||
|
||||
@@ -163,7 +166,9 @@ docker run --rm --network=container:gluetun alpine:3.12 wget -qO- https://ipinfo
|
||||
| `REGION` | | One of the Cyberghost regions, [Wiki page](https://github.com/qdm12/gluetun/wiki/Cyberghost-Servers) | VPN server country |
|
||||
| `CYBERGHOST_GROUP` | `Premium UDP Europe` | One of the server groups (see above Wiki page) | Server group |
|
||||
|
||||
**Additional setup steps**: Bind mount your `client.key` file to `/gluetun/client.key` and your `client.crt` file to `/gluetun/client.crt`. For example, you can use with your `docker run` command:
|
||||
**Additional setup steps**: If you use docker Swarm or docker-compose, you should use the [Docker secrets](#Docker-secrets) `openvpn_clientkey` and `openvpn_clientcrt`.
|
||||
|
||||
Otherwise, bind mount your `client.key` and `client.crt` files with, for example:
|
||||
|
||||
```sh
|
||||
-v /yourpath/client.key:/gluetun/client.key:ro -v /yourpath/client.crt:/gluetun/client.crt:ro
|
||||
@@ -282,6 +287,25 @@ None of the following values are required.
|
||||
| `VERSION_INFORMATION` | `on` | `on`, `off` | Logs a message indicating if a newer version is available once the VPN is connected |
|
||||
| `UPDATER_PERIOD` | `0` | Valid duration string such as `24h` | Period to update all VPN servers information in memory and to /gluetun/servers.json. Set to `0` to disable. This does a burst of DNS over TLS requests, which may be blocked if you set `BLOCK_MALICIOUS=on` for example. |
|
||||
|
||||
## Docker secrets
|
||||
|
||||
If you use Docker Compose or Docker Swarm, you can optionally use [Docker secret files](https://docs.docker.com/engine/swarm/secrets/) for all sensitive values such as your Openvpn credentials, instead of using environment variables.
|
||||
|
||||
The following secrets can be used:
|
||||
|
||||
- `openvpn_user`
|
||||
- `openvpn_password`
|
||||
- `openvpn_clientkey`
|
||||
- `openvpn_clientcrt`
|
||||
- `httpproxy_username`
|
||||
- `httpproxy_password`
|
||||
- `shadowsocks_password`
|
||||
|
||||
By default, `openvpn_user` and `openvpn_password` are set in [docker-compose.yml](docker-compose.yml)
|
||||
|
||||
Note that you can change the secret file path in the container by changing the environment variable in the form `<capitalizedSecretName>_SECRETFILE`.
|
||||
For example, `OPENVPN_PASSWORD_SECRETFILE` defaults to `/run/secrets/openvpn_password` which you can change.
|
||||
|
||||
## Connect to it
|
||||
|
||||
There are various ways to achieve this, depending on your use case.
|
||||
|
||||
@@ -14,25 +14,18 @@ services:
|
||||
# command:
|
||||
volumes:
|
||||
- /yourpath:/gluetun
|
||||
secrets:
|
||||
- openvpn_user
|
||||
- openvpn_password
|
||||
environment:
|
||||
# More variables are available, see the readme table
|
||||
- VPNSP=private internet access
|
||||
|
||||
# Timezone for accurate logs times
|
||||
- TZ=
|
||||
|
||||
# All VPN providers
|
||||
- OPENVPN_USER=js89ds7
|
||||
|
||||
# All VPN providers but Mullvad
|
||||
- OPENVPN_PASSWORD=8fd9s239G
|
||||
|
||||
# Cyberghost only
|
||||
- CLIENT_KEY=
|
||||
|
||||
# All VPN providers but Mullvad
|
||||
- REGION=Austria
|
||||
|
||||
# Mullvad only
|
||||
- COUNTRY=Sweden
|
||||
restart: always
|
||||
|
||||
secrets:
|
||||
openvpn_user:
|
||||
file: ./openvpn_user
|
||||
openvpn_password:
|
||||
file: ./openvpn_password
|
||||
|
||||
@@ -3,8 +3,6 @@ package params
|
||||
import (
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
@@ -25,23 +23,15 @@ func (p *reader) GetCyberghostRegions() (regions []string, err error) {
|
||||
return p.envParams.GetCSVInPossibilities("REGION", constants.CyberghostRegionChoices())
|
||||
}
|
||||
|
||||
// GetCyberghostClientKey obtains the one line client key to use for openvpn from the
|
||||
// file at /gluetun/client.key.
|
||||
// GetCyberghostClientKey obtains the client key to use for openvpn
|
||||
// from the secret file /run/secrets/openvpn_clientkey or from the file
|
||||
// /gluetun/client.key.
|
||||
func (p *reader) GetCyberghostClientKey() (clientKey string, err error) {
|
||||
const filepath = string(constants.ClientKey)
|
||||
file, err := p.os.OpenFile(filepath, os.O_RDONLY, 0)
|
||||
b, err := p.getFromFileOrSecretFile("OPENVPN_CLIENTKEY", string(constants.ClientKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
content, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return "", err
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return extractClientKey(content)
|
||||
return extractClientKey(b)
|
||||
}
|
||||
|
||||
func extractClientKey(b []byte) (key string, err error) {
|
||||
@@ -57,23 +47,15 @@ func extractClientKey(b []byte) (key string, err error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// GetCyberghostClientCertificate obtains the client certificate to use for openvpn from the
|
||||
// file at /gluetun/client.crt.
|
||||
// GetCyberghostClientCertificate obtains the client certificate to use for openvpn
|
||||
// from the secret file /run/secrets/openvpn_clientcrt or from the file
|
||||
// /gluetun/client.crt.
|
||||
func (p *reader) GetCyberghostClientCertificate() (clientCertificate string, err error) {
|
||||
const filepath = string(constants.ClientCertificate)
|
||||
file, err := p.os.OpenFile(filepath, os.O_RDONLY, 0)
|
||||
b, err := p.getFromFileOrSecretFile("OPENVPN_CLIENTCRT", string(constants.ClientCertificate))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
content, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return "", err
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return extractClientCertificate(content)
|
||||
return extractClientCertificate(b)
|
||||
}
|
||||
|
||||
func extractClientCertificate(b []byte) (certificate string, err error) {
|
||||
|
||||
@@ -49,26 +49,28 @@ func (r *reader) GetHTTPProxyPort() (port uint16, err error) {
|
||||
return r.envParams.GetPort("HTTPPROXY_PORT", retroKeysOption, libparams.Default("8888"))
|
||||
}
|
||||
|
||||
// GetHTTPProxyUser obtains the HTTP proxy server user from the environment variable
|
||||
// HTTPPROXY_USER, and using TINYPROXY_USER and PROXY_USER as retro-compatibility names.
|
||||
// GetHTTPProxyUser obtains the HTTP proxy server user.
|
||||
// It first tries to use the HTTPPROXY_USER environment variable (easier for the end user)
|
||||
// and then tries to read from the secret file httpproxy_user if nothing was found.
|
||||
func (r *reader) GetHTTPProxyUser() (user string, err error) {
|
||||
retroKeysOption := libparams.RetroKeys(
|
||||
const compulsory = false
|
||||
return r.getFromEnvOrSecretFile(
|
||||
"HTTPPROXY_USER",
|
||||
compulsory,
|
||||
[]string{"TINYPROXY_USER", "PROXY_USER"},
|
||||
r.onRetroActive,
|
||||
)
|
||||
return r.envParams.GetEnv("HTTPPROXY_USER",
|
||||
retroKeysOption, libparams.CaseSensitiveValue(), libparams.Unset())
|
||||
}
|
||||
|
||||
// GetHTTPProxyPassword obtains the HTTP proxy server password from the environment variable
|
||||
// HTTPPROXY_PASSWORD, and using TINYPROXY_PASSWORD and PROXY_PASSWORD as retro-compatibility names.
|
||||
// GetHTTPProxyPassword obtains the HTTP proxy server password.
|
||||
// It first tries to use the HTTPPROXY_PASSWORD environment variable (easier for the end user)
|
||||
// and then tries to read from the secret file httpproxy_password if nothing was found.
|
||||
func (r *reader) GetHTTPProxyPassword() (password string, err error) {
|
||||
retroKeysOption := libparams.RetroKeys(
|
||||
const compulsory = false
|
||||
return r.getFromEnvOrSecretFile(
|
||||
"HTTPPROXY_USER",
|
||||
compulsory,
|
||||
[]string{"TINYPROXY_PASSWORD", "PROXY_PASSWORD"},
|
||||
r.onRetroActive,
|
||||
)
|
||||
return r.envParams.GetEnv("HTTPPROXY_PASSWORD",
|
||||
retroKeysOption, libparams.CaseSensitiveValue(), libparams.Unset())
|
||||
}
|
||||
|
||||
// GetHTTPProxyStealth obtains the HTTP proxy server stealth mode
|
||||
|
||||
@@ -9,23 +9,19 @@ import (
|
||||
)
|
||||
|
||||
// GetUser obtains the user to use to connect to the VPN servers.
|
||||
// It first tries to use the OPENVPN_USER environment variable (easier for the end user)
|
||||
// and then tries to read from the secret file openvpn_user if nothing was found.
|
||||
func (r *reader) GetUser() (user string, err error) {
|
||||
return r.envParams.GetEnv("OPENVPN_USER",
|
||||
libparams.CaseSensitiveValue(),
|
||||
libparams.Compulsory(),
|
||||
libparams.RetroKeys([]string{"USER"}, r.onRetroActive),
|
||||
libparams.Unset())
|
||||
const compulsory = true
|
||||
return r.getFromEnvOrSecretFile("OPENVPN_USER", compulsory, []string{"USER"})
|
||||
}
|
||||
|
||||
// GetPassword obtains the password to use to connect to the VPN servers.
|
||||
// It first tries to use the OPENVPN_PASSWORD environment variable (easier for the end user)
|
||||
// and then tries to read from the secret file openvpn_password if nothing was found.
|
||||
func (r *reader) GetPassword() (s string, err error) {
|
||||
options := []libparams.GetEnvSetter{
|
||||
libparams.CaseSensitiveValue(),
|
||||
libparams.Unset(),
|
||||
libparams.Compulsory(),
|
||||
libparams.RetroKeys([]string{"PASSWORD"}, r.onRetroActive),
|
||||
}
|
||||
return r.envParams.GetEnv("OPENVPN_PASSWORD", options...)
|
||||
const compulsory = true
|
||||
return r.getFromEnvOrSecretFile("OPENVPN_PASSWORD", compulsory, []string{"PASSWORD"})
|
||||
}
|
||||
|
||||
// GetNetworkProtocol obtains the network protocol to use to connect to the
|
||||
|
||||
108
internal/params/secrets.go
Normal file
108
internal/params/secrets.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package params
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/os"
|
||||
libparams "github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrGetSecretFilepath = errors.New("cannot get secret file path from env")
|
||||
ErrReadSecretFile = errors.New("cannot read secret file")
|
||||
ErrSecretFileIsEmpty = errors.New("secret file is empty")
|
||||
ErrReadNonSecretFile = errors.New("cannot read non secret file")
|
||||
ErrFilesDoNotExist = errors.New("files do not exist")
|
||||
)
|
||||
|
||||
func (r *reader) getFromEnvOrSecretFile(envKey string, compulsory bool, retroKeys []string) (value string, err error) {
|
||||
envOptions := []libparams.GetEnvSetter{
|
||||
libparams.Compulsory(), // to fallback on file reading
|
||||
libparams.CaseSensitiveValue(),
|
||||
libparams.Unset(),
|
||||
libparams.RetroKeys(retroKeys, r.onRetroActive),
|
||||
}
|
||||
value, envErr := r.envParams.GetEnv(envKey, envOptions...)
|
||||
if envErr == nil {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
defaultSecretFile := "/run/secrets/" + strings.ToLower(envKey)
|
||||
filepath, err := r.envParams.GetEnv(envKey+"_SECRETFILE",
|
||||
libparams.CaseSensitiveValue(),
|
||||
libparams.Default(defaultSecretFile),
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w: %s", ErrGetSecretFilepath, err)
|
||||
}
|
||||
|
||||
file, fileErr := r.os.OpenFile(filepath, os.O_RDONLY, 0)
|
||||
if os.IsNotExist(fileErr) {
|
||||
if compulsory {
|
||||
return "", envErr
|
||||
}
|
||||
return "", nil
|
||||
} else if fileErr != nil {
|
||||
return "", fmt.Errorf("%w: %s", ErrReadSecretFile, fileErr)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w: %s", ErrReadSecretFile, err)
|
||||
}
|
||||
|
||||
value = string(b)
|
||||
if compulsory && len(value) == 0 {
|
||||
return "", ErrSecretFileIsEmpty
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// Tries to read from the secret file then the non secret file.
|
||||
func (r *reader) getFromFileOrSecretFile(secretName, filepath string) (
|
||||
b []byte, err error) {
|
||||
defaultSecretFile := "/run/secrets/" + strings.ToLower(secretName)
|
||||
secretFilepath, err := r.envParams.GetEnv(strings.ToUpper(secretName)+"_SECRETFILE",
|
||||
libparams.CaseSensitiveValue(),
|
||||
libparams.Default(defaultSecretFile),
|
||||
)
|
||||
if err != nil {
|
||||
return b, fmt.Errorf("%w: %s", ErrGetSecretFilepath, err)
|
||||
}
|
||||
|
||||
b, err = readFromFile(r.os.OpenFile, secretFilepath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return b, fmt.Errorf("%w: %s", ErrReadSecretFile, err)
|
||||
} else if err == nil {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Secret file does not exist, try the non secret file
|
||||
b, err = readFromFile(r.os.OpenFile, filepath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("%w: %s", ErrReadSecretFile, err)
|
||||
} else if err == nil {
|
||||
return b, nil
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %s and %s", ErrFilesDoNotExist, secretFilepath, filepath)
|
||||
}
|
||||
|
||||
func readFromFile(openFile os.OpenFileFunc, filepath string) (b []byte, err error) {
|
||||
file, err := openFile(filepath, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err = ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
@@ -32,10 +32,12 @@ func (r *reader) GetShadowSocksPort() (port uint16, err error) {
|
||||
return uint16(portUint64), err
|
||||
}
|
||||
|
||||
// GetShadowSocksPassword obtains the ShadowSocks server password from the environment variable
|
||||
// SHADOWSOCKS_PASSWORD.
|
||||
// GetShadowSocksPassword obtains the ShadowSocks server password.
|
||||
// It first tries to use the SHADOWSOCKS_PASSWORD environment variable (easier for the end user)
|
||||
// and then tries to read from the secret file shadowsocks_password if nothing was found.
|
||||
func (r *reader) GetShadowSocksPassword() (password string, err error) {
|
||||
return r.envParams.GetEnv("SHADOWSOCKS_PASSWORD", libparams.CaseSensitiveValue(), libparams.Unset())
|
||||
const compulsory = false
|
||||
return r.getFromEnvOrSecretFile("SHADOWSOCKS_PASSWORD", compulsory, nil)
|
||||
}
|
||||
|
||||
// GetShadowSocksMethod obtains the ShadowSocks method to use from the environment variable
|
||||
|
||||
Reference in New Issue
Block a user