VPNSP value custom for OpenVPN custom config files (#621)

- Retro-compatibility: `OPENVPN_CUSTOM_CONFIG` set implies `VPNSP=custom`
- Change: `up` and `down` options are not filtered out
- Change: `OPENVPN_INTERFACE` overrides the network interface defined in the configuration file
- Change: `PORT` overrides any port found in the configuration file
- Feat: config file is read when building the OpenVPN configuration, so it's effectively reloaded on VPN restarts
- Feat: extract values from custom file at start to log out valid settings
- Maint: `internal/openvpn/extract` package instead of `internal/openvpn/custom` package
- Maint: All providers' `BuildConf` method return an error
- Maint: rename `CustomConfig` to `ConfFile` in Settings structures
This commit is contained in:
Quentin McGaw
2021-09-13 11:30:14 -04:00
committed by GitHub
parent 11af6c10f1
commit f807f756eb
43 changed files with 328 additions and 296 deletions

View File

@@ -0,0 +1,53 @@
package configuration
import (
"errors"
"fmt"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/params"
)
var (
errCustomNotSupported = errors.New("custom provider is not supported")
errCustomExtractFromFile = errors.New("cannot extract configuration from file")
)
func (settings *Provider) readCustom(r reader, vpnType string) (err error) {
settings.Name = constants.Custom
if vpnType != constants.OpenVPN {
return fmt.Errorf("%w: for VPN type %s", errCustomNotSupported, vpnType)
}
return settings.readCustomOpenVPN(r)
}
func (settings *Provider) readCustomOpenVPN(r reader) (err error) {
configFile, err := r.env.Get("OPENVPN_CUSTOM_CONFIG", params.CaseSensitiveValue(), params.Compulsory())
if err != nil {
return fmt.Errorf("environment variable OPENVPN_CUSTOM_CONFIG: %w", err)
}
settings.ServerSelection.OpenVPN.ConfFile = configFile
// For display and consistency purposes only,
// these values are not actually used since the file is re-read
// before each OpenVPN start.
_, connection, err := r.ovpnExt.Data(configFile)
if err != nil {
return fmt.Errorf("%w: %s", errCustomExtractFromFile, err)
}
settings.ServerSelection.OpenVPN.TCP = connection.Protocol == constants.TCP
return nil
}
func (settings *OpenVPN) readCustom(r reader) (err error) {
settings.ConfFile, err = r.env.Path("OPENVPN_CUSTOM_CONFIG",
params.Compulsory(), params.CaseSensitiveValue())
if err != nil {
return fmt.Errorf("environment variable OPENVPN_CUSTOM_CONFIG: %w", err)
}
return nil
}

View File

@@ -21,7 +21,7 @@ type OpenVPN struct {
Root bool `json:"run_as_root"`
Cipher string `json:"cipher"`
Auth string `json:"auth"`
Config string `json:"custom_config"`
ConfFile string `json:"conf_file"`
Version string `json:"version"`
ClientCrt string `json:"-"` // Cyberghost
ClientKey string `json:"-"` // Cyberghost, VPNUnlimited
@@ -59,8 +59,8 @@ func (settings *OpenVPN) lines() (lines []string) {
lines = append(lines, indent+lastIndent+"Custom auth algorithm: "+settings.Auth)
}
if len(settings.Config) > 0 {
lines = append(lines, indent+lastIndent+"Custom configuration: "+settings.Config)
if settings.ConfFile != "" {
lines = append(lines, indent+lastIndent+"Configuration file: "+settings.ConfFile)
}
if settings.ClientKey != "" {
@@ -83,13 +83,14 @@ func (settings *OpenVPN) lines() (lines []string) {
}
func (settings *OpenVPN) read(r reader, serviceProvider string) (err error) {
settings.Config, err = r.env.Get("OPENVPN_CUSTOM_CONFIG", params.CaseSensitiveValue())
if err != nil {
return fmt.Errorf("environment variable OPENVPN_CUSTOM_CONFIG: %w", err)
credentialsRequired := false
switch serviceProvider {
case constants.Custom:
case constants.VPNUnlimited:
default:
credentialsRequired = true
}
credentialsRequired := settings.Config == "" && serviceProvider != constants.VPNUnlimited
settings.User, err = r.getFromEnvOrSecretFile("OPENVPN_USER", credentialsRequired, []string{"USER"})
if err != nil {
return fmt.Errorf("environment variable OPENVPN_USER: %w", err)
@@ -159,6 +160,8 @@ func (settings *OpenVPN) read(r reader, serviceProvider string) (err error) {
}
switch serviceProvider {
case constants.Custom:
err = settings.readCustom(r) // read OPENVPN_CUSTOM_CONFIG
case constants.Cyberghost:
err = settings.readCyberghost(r)
case constants.PrivateInternetAccess:

View File

@@ -25,7 +25,7 @@ func Test_OpenVPN_JSON(t *testing.T) {
"run_as_root": true,
"cipher": "",
"auth": "",
"custom_config": "",
"conf_file": "",
"version": "",
"encryption_preset": "",
"ipv6": false,

View File

@@ -49,6 +49,8 @@ func (settings *Provider) read(r reader, vpnType string) error {
}
switch settings.Name {
case constants.Custom:
err = settings.readCustom(r, vpnType)
case constants.Cyberghost:
err = settings.readCyberghost(r)
case constants.Fastestvpn:
@@ -99,6 +101,7 @@ func (settings *Provider) readVPNServiceProvider(r reader, vpnType string) (err
switch vpnType {
case constants.OpenVPN:
allowedVPNServiceProviders = []string{
constants.Custom,
"cyberghost", "fastestvpn", "hidemyass", "ipvanish", "ivpn", "mullvad", "nordvpn",
"privado", "pia", "private internet access", "privatevpn", "protonvpn",
"purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn", "windscribe"}
@@ -115,6 +118,11 @@ func (settings *Provider) readVPNServiceProvider(r reader, vpnType string) (err
if vpnsp == "pia" { // retro compatibility
vpnsp = "private internet access"
}
if settings.isOpenVPNCustomConfig(r.env) { // retro compatibility
vpnsp = constants.Custom
}
settings.Name = vpnsp
return nil
@@ -199,3 +207,12 @@ func portsToString(ports []uint16) string {
}
return strings.Join(slice, ", ")
}
// isOpenVPNCustomConfig is for retro compatibility to set VPNSP=custom
// if OPENVPN_CUSTOM_CONFIG is set.
func (settings Provider) isOpenVPNCustomConfig(env params.Interface) (ok bool) {
s, _ := env.Get("VPN_TYPE")
isOpenVPN := s == constants.OpenVPN
s, _ = env.Get("OPENVPN_CUSTOM_CONFIG")
return isOpenVPN && s != ""
}

View File

@@ -8,6 +8,7 @@ import (
"strings"
"github.com/qdm12/gluetun/internal/models"
ovpnextract "github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/params"
"github.com/qdm12/golibs/verification"
@@ -18,6 +19,7 @@ type reader struct {
env params.Interface
logger logging.Logger
regex verification.Regex
ovpnExt ovpnextract.Interface
}
func newReader(env params.Interface,
@@ -27,6 +29,7 @@ func newReader(env params.Interface,
env: env,
logger: logger,
regex: verification.NewRegex(),
ovpnExt: ovpnextract.New(),
}
}

View File

@@ -51,22 +51,22 @@ func (r *reader) getFromEnvOrSecretFile(envKey string, compulsory bool, retroKey
file, fileErr := os.OpenFile(filepath, os.O_RDONLY, 0)
if os.IsNotExist(fileErr) {
if compulsory {
return "", envErr
return "", fmt.Errorf("environment variable %s: %w", envKey, envErr)
}
return "", nil
} else if fileErr != nil {
return "", fmt.Errorf("%w: %s", ErrReadSecretFile, fileErr)
return "", fmt.Errorf("%w: %s: %s", ErrReadSecretFile, filepath, fileErr)
}
b, err := io.ReadAll(file)
if err != nil {
return "", fmt.Errorf("%w: %s", ErrReadSecretFile, err)
return "", fmt.Errorf("%w: %s: %s", ErrReadSecretFile, filepath, err)
}
value = string(b)
value = cleanSuffix(value)
if compulsory && value == "" {
return "", ErrSecretFileIsEmpty
return "", fmt.Errorf("%s: %w", filepath, ErrSecretFileIsEmpty)
}
return value, nil

View File

@@ -106,6 +106,7 @@ func (selection ServerSelection) toLines() (lines []string) {
}
type OpenVPNSelection struct {
ConfFile string `json:"conf_file"` // Custom configuration file path
TCP bool `json:"tcp"` // UDP if TCP is false
CustomPort uint16 `json:"custom_port"` // HideMyAss, Mullvad, PIA, ProtonVPN, Windscribe
EncPreset string `json:"encryption_preset"` // PIA - needed to get the port number
@@ -114,6 +115,10 @@ type OpenVPNSelection struct {
func (settings *OpenVPNSelection) lines() (lines []string) {
lines = append(lines, lastIndent+"OpenVPN selection:")
if settings.ConfFile != "" {
lines = append(lines, indent+lastIndent+"Custom configuration file: "+settings.ConfFile)
}
lines = append(lines, indent+lastIndent+"Protocol: "+protoToString(settings.TCP))
if settings.CustomPort != 0 {

View File

@@ -58,10 +58,8 @@ func (settings *VPN) read(r reader) (err error) {
}
settings.Type = vpnType
if !settings.isOpenVPNCustomConfig(r.env) {
if err := settings.Provider.read(r, vpnType); err != nil {
return fmt.Errorf("%w: %s", errReadProviderSettings, err)
}
if err := settings.Provider.read(r, vpnType); err != nil {
return fmt.Errorf("%w: %s", errReadProviderSettings, err)
}
switch settings.Type {
@@ -79,19 +77,3 @@ func (settings *VPN) read(r reader) (err error) {
return nil
}
func (settings VPN) isOpenVPNCustomConfig(env params.Interface) (ok bool) {
if settings.Type != constants.OpenVPN {
return false
}
s, err := env.Get("OPENVPN_CUSTOM_CONFIG")
return err == nil && s != ""
}
func (settings VPN) VPNInterface() (intf string) {
if settings.Type == constants.Wireguard {
return settings.Wireguard.Interface
}
// OpenVPN
return settings.OpenVPN.Interface
}