Maint: refactor VPN configuration structure

- Paves the way for Wireguard
- VPN struct contains Type, Openvpn and Provider configurations
- OpenVPN specific options (e.g. client key) moved from Provider to Openvpn configuration struct
- Move Provider configuration from OpenVPN configuration to VPN
- HTTP control server returns only openvpn settings (not provider settings)
This commit is contained in:
Quentin McGaw (desktop)
2021-08-17 15:44:11 +00:00
parent a00de75f61
commit cc2235653a
39 changed files with 372 additions and 301 deletions

View File

@@ -17,14 +17,6 @@ func (settings *Provider) cyberghostLines() (lines []string) {
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
}
if settings.ExtraConfigOptions.ClientKey != "" {
lines = append(lines, lastIndent+"Client key is set")
}
if settings.ExtraConfigOptions.ClientCertificate != "" {
lines = append(lines, lastIndent+"Client certificate is set")
}
return lines
}
@@ -41,16 +33,6 @@ func (settings *Provider) readCyberghost(r reader) (err error) {
return err
}
settings.ExtraConfigOptions.ClientKey, err = readClientKey(r)
if err != nil {
return err
}
settings.ExtraConfigOptions.ClientCertificate, err = readClientCertificate(r)
if err != nil {
return err
}
settings.ServerSelection.Groups, err = r.env.CSVInside("CYBERGHOST_GROUP",
constants.CyberghostGroupChoices())
if err != nil {
@@ -69,3 +51,17 @@ func (settings *Provider) readCyberghost(r reader) (err error) {
return nil
}
func (settings *OpenVPN) readCyberghost(r reader) (err error) {
settings.ClientKey, err = readClientKey(r)
if err != nil {
return err
}
settings.ClientCrt, err = readClientCertificate(r)
if err != nil {
return err
}
return nil
}

View File

@@ -29,10 +29,6 @@ func (settings *Provider) mullvadLines() (lines []string) {
lines = append(lines, lastIndent+"Custom port: "+strconv.Itoa(int(settings.ServerSelection.CustomPort)))
}
if settings.ExtraConfigOptions.OpenVPNIPv6 {
lines = append(lines, lastIndent+"IPv6: enabled")
}
return lines
}
@@ -80,10 +76,5 @@ func (settings *Provider) readMullvad(r reader) (err error) {
return fmt.Errorf("environment variable OWNED: %w", err)
}
settings.ExtraConfigOptions.OpenVPNIPv6, err = r.env.OnOff("OPENVPN_IPV6", params.Default("off"))
if err != nil {
return fmt.Errorf("environment variable OPENVPN_IPV6: %w", err)
}
return nil
}

View File

@@ -1,7 +1,6 @@
package configuration
import (
"errors"
"fmt"
"strconv"
"strings"
@@ -20,9 +19,12 @@ type OpenVPN struct {
Root bool `json:"run_as_root"`
Cipher string `json:"cipher"`
Auth string `json:"auth"`
Provider Provider `json:"provider"`
Config string `json:"custom_config"`
Version string `json:"version"`
ClientCrt string `json:"-"` // Cyberghost
ClientKey string `json:"-"` // Cyberghost, VPNUnlimited
EncPreset string `json:"encryption_preset"` // PIA
IPv6 bool `json:"ipv6"` // Mullvad
}
func (settings *OpenVPN) String() string {
@@ -55,48 +57,32 @@ func (settings *OpenVPN) lines() (lines []string) {
lines = append(lines, indent+lastIndent+"Custom configuration: "+settings.Config)
}
if settings.Provider.Name == "" {
lines = append(lines, indent+lastIndent+"Provider: custom configuration")
} else {
lines = append(lines, indent+lastIndent+"Provider:")
for _, line := range settings.Provider.lines() {
lines = append(lines, indent+indent+line)
}
if settings.ClientKey != "" {
lines = append(lines, indent+lastIndent+"Client key is set")
}
if settings.ClientCrt != "" {
lines = append(lines, indent+lastIndent+"Client certificate is set")
}
if settings.IPv6 {
lines = append(lines, indent+lastIndent+"IPv6: enabled")
}
if settings.EncPreset != "" { // PIA only
lines = append(lines, indent+lastIndent+"Encryption preset: "+settings.EncPreset)
}
return lines
}
var (
ErrInvalidVPNProvider = errors.New("invalid VPN provider")
)
func (settings *OpenVPN) read(r reader) (err error) {
vpnsp, err := r.env.Inside("VPNSP", []string{
"cyberghost", "fastestvpn", "hidemyass", "ipvanish", "ivpn", "mullvad", "nordvpn",
"privado", "pia", "private internet access", "privatevpn", "protonvpn",
"purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn", "windscribe"},
params.Default("private internet access"))
if err != nil {
return fmt.Errorf("environment variable VPNSP: %w", err)
}
if vpnsp == "pia" { // retro compatibility
vpnsp = "private internet access"
}
settings.Provider.Name = vpnsp
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)
}
customConfig := settings.Config != ""
if customConfig {
settings.Provider.Name = ""
}
credentialsRequired := !customConfig && settings.Provider.Name != constants.VPNUnlimited
credentialsRequired := settings.Config == "" && serviceProvider != constants.VPNUnlimited
settings.User, err = r.getFromEnvOrSecretFile("OPENVPN_USER", credentialsRequired, []string{"USER"})
if err != nil {
@@ -105,7 +91,7 @@ func (settings *OpenVPN) read(r reader) (err error) {
// Remove spaces in user ID to simplify user's life, thanks @JeordyR
settings.User = strings.ReplaceAll(settings.User, " ", "")
if settings.Provider.Name == constants.Mullvad {
if serviceProvider == constants.Mullvad {
settings.Password = "m"
} else {
settings.Password, err = r.getFromEnvOrSecretFile("OPENVPN_PASSWORD", credentialsRequired, []string{"PASSWORD"})
@@ -155,50 +141,23 @@ func (settings *OpenVPN) read(r reader) (err error) {
return fmt.Errorf("environment variable OPENVPN_MSSFIX: %w", err)
}
settings.MSSFix = uint16(mssFix)
return settings.readProvider(r)
}
func (settings *OpenVPN) readProvider(r reader) error {
var readProvider func(r reader) error
switch settings.Provider.Name {
case "": // custom config
readProvider = func(r reader) error { return nil }
case constants.Cyberghost:
readProvider = settings.Provider.readCyberghost
case constants.Fastestvpn:
readProvider = settings.Provider.readFastestvpn
case constants.HideMyAss:
readProvider = settings.Provider.readHideMyAss
case constants.Ipvanish:
readProvider = settings.Provider.readIpvanish
case constants.Ivpn:
readProvider = settings.Provider.readIvpn
case constants.Mullvad:
readProvider = settings.Provider.readMullvad
case constants.Nordvpn:
readProvider = settings.Provider.readNordvpn
case constants.Privado:
readProvider = settings.Provider.readPrivado
case constants.PrivateInternetAccess:
readProvider = settings.Provider.readPrivateInternetAccess
case constants.Privatevpn:
readProvider = settings.Provider.readPrivatevpn
case constants.Protonvpn:
readProvider = settings.Provider.readProtonvpn
case constants.Purevpn:
readProvider = settings.Provider.readPurevpn
case constants.Surfshark:
readProvider = settings.Provider.readSurfshark
case constants.Torguard:
readProvider = settings.Provider.readTorguard
case constants.VPNUnlimited:
readProvider = settings.Provider.readVPNUnlimited
case constants.Vyprvpn:
readProvider = settings.Provider.readVyprvpn
case constants.Windscribe:
readProvider = settings.Provider.readWindscribe
default:
return fmt.Errorf("%w: %s", ErrInvalidVPNProvider, settings.Provider.Name)
settings.IPv6, err = r.env.OnOff("OPENVPN_IPV6", params.Default("off"))
if err != nil {
return fmt.Errorf("environment variable OPENVPN_IPV6: %w", err)
}
return readProvider(r)
switch serviceProvider {
case constants.Cyberghost:
err = settings.readCyberghost(r)
case constants.PrivateInternetAccess:
err = settings.readPrivateInternetAccess(r)
case constants.VPNUnlimited:
err = settings.readVPNUnlimited(r)
}
if err != nil {
return err
}
return nil
}

View File

@@ -13,9 +13,6 @@ func Test_OpenVPN_JSON(t *testing.T) {
in := OpenVPN{
Root: true,
Flags: []string{},
Provider: Provider{
Name: "name",
},
}
data, err := json.MarshalIndent(in, "", " ")
require.NoError(t, err)
@@ -28,35 +25,10 @@ func Test_OpenVPN_JSON(t *testing.T) {
"run_as_root": true,
"cipher": "",
"auth": "",
"provider": {
"name": "name",
"server_selection": {
"tcp": false,
"regions": null,
"groups": null,
"countries": null,
"cities": null,
"hostnames": null,
"names": null,
"isps": null,
"owned": false,
"custom_port": 0,
"numbers": null,
"encryption_preset": "",
"free_only": false,
"stream_only": false
},
"extra_config": {
"encryption_preset": "",
"openvpn_ipv6": false
},
"port_forwarding": {
"enabled": false,
"filepath": ""
}
},
"custom_config": "",
"version": ""
"version": "",
"encryption_preset": "",
"ipv6": false
}`, string(data))
var out OpenVPN
err = json.Unmarshal(data, &out)

View File

@@ -21,8 +21,6 @@ func (settings *Provider) privateinternetaccessLines() (lines []string) {
lines = append(lines, lastIndent+"Names: "+commaJoin(settings.ServerSelection.Names))
}
lines = append(lines, lastIndent+"Encryption preset: "+settings.ServerSelection.EncryptionPreset)
if settings.ServerSelection.CustomPort > 0 {
lines = append(lines, lastIndent+"Custom port: "+strconv.Itoa(int(settings.ServerSelection.CustomPort)))
}
@@ -50,17 +48,6 @@ func (settings *Provider) readPrivateInternetAccess(r reader) (err error) {
return err
}
encryptionPreset, err := r.env.Inside("PIA_ENCRYPTION",
[]string{constants.PIAEncryptionPresetNone, constants.PIAEncryptionPresetNormal, constants.PIAEncryptionPresetStrong},
params.RetroKeys([]string{"ENCRYPTION"}, r.onRetroActive),
params.Default(constants.PIACertificateStrong),
)
if err != nil {
return fmt.Errorf("environment variable PIA_ENCRYPTION: %w", err)
}
settings.ServerSelection.EncryptionPreset = encryptionPreset
settings.ExtraConfigOptions.EncryptionPreset = encryptionPreset
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.PIAGeoChoices())
if err != nil {
return fmt.Errorf("environment variable REGION: %w", err)
@@ -81,6 +68,11 @@ func (settings *Provider) readPrivateInternetAccess(r reader) (err error) {
return fmt.Errorf("environment variable PORT: %w", err)
}
settings.ServerSelection.EncryptionPreset, err = getPIAEncryptionPreset(r)
if err != nil {
return err
}
settings.PortForwarding.Enabled, err = r.env.OnOff("PORT_FORWARDING", params.Default("off"))
if err != nil {
return fmt.Errorf("environment variable PORT_FORWARDING: %w", err)
@@ -96,3 +88,20 @@ func (settings *Provider) readPrivateInternetAccess(r reader) (err error) {
return nil
}
func (settings *OpenVPN) readPrivateInternetAccess(r reader) (err error) {
settings.EncPreset, err = getPIAEncryptionPreset(r)
return err
}
func getPIAEncryptionPreset(r reader) (encryptionPreset string, err error) {
encryptionPreset, err = r.env.Inside("PIA_ENCRYPTION",
[]string{constants.PIAEncryptionPresetNone, constants.PIAEncryptionPresetNormal, constants.PIAEncryptionPresetStrong},
params.RetroKeys([]string{"ENCRYPTION"}, r.onRetroActive),
params.Default(constants.PIACertificateStrong),
)
if err != nil {
return "", fmt.Errorf("environment variable PIA_ENCRYPTION: %w", err)
}
return encryptionPreset, nil
}

View File

@@ -1,6 +1,7 @@
package configuration
import (
"errors"
"fmt"
"net"
"strings"
@@ -11,10 +12,9 @@ import (
// Provider contains settings specific to a VPN provider.
type Provider struct {
Name string `json:"name"`
ServerSelection ServerSelection `json:"server_selection"`
ExtraConfigOptions ExtraConfigOptions `json:"extra_config"`
PortForwarding PortForwarding `json:"port_forwarding"`
Name string `json:"name"`
ServerSelection ServerSelection `json:"server_selection"`
PortForwarding PortForwarding `json:"port_forwarding"`
}
func (settings *Provider) lines() (lines []string) {
@@ -76,6 +76,79 @@ func (settings *Provider) lines() (lines []string) {
return lines
}
var (
ErrInvalidVPNProvider = errors.New("invalid VPN provider")
)
func (settings *Provider) read(r reader) error {
err := settings.readVPNServiceProvider(r)
if err != nil {
return err
}
var readProvider func(r reader) error
switch settings.Name {
case "": // custom config
readProvider = func(r reader) error { return nil }
case constants.Cyberghost:
readProvider = settings.readCyberghost
case constants.Fastestvpn:
readProvider = settings.readFastestvpn
case constants.HideMyAss:
readProvider = settings.readHideMyAss
case constants.Ipvanish:
readProvider = settings.readIpvanish
case constants.Ivpn:
readProvider = settings.readIvpn
case constants.Mullvad:
readProvider = settings.readMullvad
case constants.Nordvpn:
readProvider = settings.readNordvpn
case constants.Privado:
readProvider = settings.readPrivado
case constants.PrivateInternetAccess:
readProvider = settings.readPrivateInternetAccess
case constants.Privatevpn:
readProvider = settings.readPrivatevpn
case constants.Protonvpn:
readProvider = settings.readProtonvpn
case constants.Purevpn:
readProvider = settings.readPurevpn
case constants.Surfshark:
readProvider = settings.readSurfshark
case constants.Torguard:
readProvider = settings.readTorguard
case constants.VPNUnlimited:
readProvider = settings.readVPNUnlimited
case constants.Vyprvpn:
readProvider = settings.readVyprvpn
case constants.Windscribe:
readProvider = settings.readWindscribe
default:
return fmt.Errorf("%w: %s", ErrInvalidVPNProvider, settings.Name)
}
return readProvider(r)
}
func (settings *Provider) readVPNServiceProvider(r reader) (err error) {
allowedVPNServiceProviders := []string{
"cyberghost", "fastestvpn", "hidemyass", "ipvanish", "ivpn", "mullvad", "nordvpn",
"privado", "pia", "private internet access", "privatevpn", "protonvpn",
"purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn", "windscribe"}
vpnsp, err := r.env.Inside("VPNSP", allowedVPNServiceProviders,
params.Default("private internet access"))
if err != nil {
return fmt.Errorf("environment variable VPNSP: %w", err)
}
if vpnsp == "pia" { // retro compatibility
vpnsp = "private internet access"
}
settings.Name = vpnsp
return nil
}
func commaJoin(slice []string) string {
return strings.Join(slice, ", ")
}

View File

@@ -27,18 +27,12 @@ func Test_Provider_lines(t *testing.T) {
Groups: []string{"group"},
Regions: []string{"a", "El country"},
},
ExtraConfigOptions: ExtraConfigOptions{
ClientKey: "a",
ClientCertificate: "a",
},
},
lines: []string{
"|--Cyberghost settings:",
" |--Network protocol: udp",
" |--Server groups: group",
" |--Regions: a, El country",
" |--Client key is set",
" |--Client certificate is set",
},
},
"fastestvpn": {
@@ -116,9 +110,6 @@ func Test_Provider_lines(t *testing.T) {
ISPs: []string{"e", "f"},
CustomPort: 1,
},
ExtraConfigOptions: ExtraConfigOptions{
OpenVPNIPv6: true,
},
},
lines: []string{
"|--Mullvad settings:",
@@ -127,7 +118,6 @@ func Test_Provider_lines(t *testing.T) {
" |--Cities: c, d",
" |--ISPs: e, f",
" |--Custom port: 1",
" |--IPv6: enabled",
},
},
"nordvpn": {
@@ -200,9 +190,8 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{
Name: constants.PrivateInternetAccess,
ServerSelection: ServerSelection{
Regions: []string{"a", "b"},
EncryptionPreset: constants.PIAEncryptionPresetStrong,
CustomPort: 1,
Regions: []string{"a", "b"},
CustomPort: 1,
},
PortForwarding: PortForwarding{
Enabled: true,
@@ -213,7 +202,6 @@ func Test_Provider_lines(t *testing.T) {
"|--Private Internet Access settings:",
" |--Network protocol: udp",
" |--Regions: a, b",
" |--Encryption preset: strong",
" |--Custom port: 1",
" |--Port forwarding:",
" |--File path: /here",
@@ -276,9 +264,6 @@ func Test_Provider_lines(t *testing.T) {
FreeOnly: true,
StreamOnly: true,
},
ExtraConfigOptions: ExtraConfigOptions{
ClientKey: "a",
},
},
lines: []string{
"|--Vpn Unlimited settings:",
@@ -288,7 +273,6 @@ func Test_Provider_lines(t *testing.T) {
" |--Hostnames: e, f",
" |--Free servers only",
" |--Stream servers only",
" |--Client key is set",
},
},
"vyprvpn": {

View File

@@ -33,7 +33,7 @@ type ServerSelection struct { //nolint:maligned
// NordVPN
Numbers []uint16 `json:"numbers"`
// PIA
// PIA - needed to get the port number
EncryptionPreset string `json:"encryption_preset"`
// ProtonVPN
@@ -43,13 +43,6 @@ type ServerSelection struct { //nolint:maligned
StreamOnly bool `json:"stream_only"`
}
type ExtraConfigOptions struct {
ClientCertificate string `json:"-"` // Cyberghost
ClientKey string `json:"-"` // Cyberghost, VPNUnlimited
EncryptionPreset string `json:"encryption_preset"` // PIA
OpenVPNIPv6 bool `json:"openvpn_ipv6"` // Mullvad
}
// PortForwarding contains settings for port forwarding.
type PortForwarding struct {
Enabled bool `json:"enabled"`

View File

@@ -11,7 +11,7 @@ import (
// Settings contains all settings for the program to run.
type Settings struct {
OpenVPN OpenVPN
VPN VPN
System System
DNS DNS
Firewall Firewall
@@ -30,7 +30,7 @@ func (settings *Settings) String() string {
func (settings *Settings) lines() (lines []string) {
lines = append(lines, "Settings summary below:")
lines = append(lines, settings.OpenVPN.lines()...)
lines = append(lines, settings.VPN.lines()...)
lines = append(lines, settings.DNS.lines()...)
lines = append(lines, settings.Firewall.lines()...)
lines = append(lines, settings.System.lines()...)
@@ -47,7 +47,7 @@ func (settings *Settings) lines() (lines []string) {
}
var (
ErrOpenvpn = errors.New("cannot read Openvpn settings")
ErrVPN = errors.New("cannot read VPN settings")
ErrSystem = errors.New("cannot read System settings")
ErrDNS = errors.New("cannot read DNS settings")
ErrFirewall = errors.New("cannot read firewall settings")
@@ -69,8 +69,8 @@ func (settings *Settings) Read(env params.Env, logger logging.Logger) (err error
return fmt.Errorf("environment variable VERSION_INFORMATION: %w", err)
}
if err := settings.OpenVPN.read(r); err != nil {
return fmt.Errorf("%w: %s", ErrOpenvpn, err)
if err := settings.VPN.read(r); err != nil {
return fmt.Errorf("%w: %s", ErrVPN, err)
}
if err := settings.System.read(r); err != nil {

View File

@@ -16,21 +16,25 @@ func Test_Settings_lines(t *testing.T) {
}{
"default settings": {
settings: Settings{
OpenVPN: OpenVPN{
Version: constants.Openvpn25,
VPN: VPN{
Type: constants.OpenVPN,
Provider: Provider{
Name: constants.Mullvad,
},
OpenVPN: OpenVPN{
Version: constants.Openvpn25,
},
},
},
lines: []string{
"Settings summary below:",
"|--OpenVPN:",
" |--Version: 2.5",
" |--Verbosity level: 0",
" |--Provider:",
" |--Mullvad settings:",
" |--Network protocol: udp",
"|--VPN:",
" |--Type: openvpn",
" |--OpenVPN:",
" |--Version: 2.5",
" |--Verbosity level: 0",
" |--Mullvad settings:",
" |--Network protocol: udp",
"|--DNS:",
"|--Firewall: disabled ⚠️",
"|--System:",

View File

@@ -0,0 +1,71 @@
package configuration
import (
"errors"
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/params"
)
type VPN struct {
Type string `json:"type"`
OpenVPN OpenVPN `json:"openvpn"`
Provider Provider `json:"provider"`
}
func (settings *VPN) String() string {
return strings.Join(settings.lines(), "\n")
}
func (settings *VPN) lines() (lines []string) {
lines = append(lines, lastIndent+"VPN:")
lines = append(lines, indent+lastIndent+"Type: "+settings.Type)
for _, line := range settings.OpenVPN.lines() {
lines = append(lines, indent+line)
}
for _, line := range settings.Provider.lines() {
lines = append(lines, indent+line)
}
return lines
}
var (
errReadProviderSettings = errors.New("cannot read provider settings")
errReadOpenVPNSettings = errors.New("cannot read OpenVPN settings")
)
func (settings *VPN) read(r reader) (err error) {
vpnType, err := r.env.Inside("VPN_TYPE",
[]string{constants.OpenVPN}, params.Default(constants.OpenVPN))
if err != nil {
return fmt.Errorf("environment variable VPN_TYPE: %w", err)
}
settings.Type = vpnType
if !settings.isOpenVPNCustomConfig(r.env) {
if err := settings.Provider.read(r); err != nil {
return fmt.Errorf("%w: %s", errReadProviderSettings, err)
}
}
err = settings.OpenVPN.read(r, settings.Provider.Name)
if err != nil {
return fmt.Errorf("%w: %s", errReadOpenVPNSettings, err)
}
return nil
}
func (settings VPN) isOpenVPNCustomConfig(env params.Env) (ok bool) {
if settings.Type != constants.OpenVPN {
return false
}
s, err := env.Get("OPENVPN_CUSTOM_CONFIG")
return err == nil && s != ""
}

View File

@@ -28,10 +28,6 @@ func (settings *Provider) vpnUnlimitedLines() (lines []string) {
lines = append(lines, lastIndent+"Stream servers only")
}
if settings.ExtraConfigOptions.ClientKey != "" {
lines = append(lines, lastIndent+"Client key is set")
}
return lines
}
@@ -48,16 +44,6 @@ func (settings *Provider) readVPNUnlimited(r reader) (err error) {
return err
}
settings.ExtraConfigOptions.ClientKey, err = readClientKey(r)
if err != nil {
return err
}
settings.ExtraConfigOptions.ClientCertificate, err = readClientCertificate(r)
if err != nil {
return err
}
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.VPNUnlimitedCountryChoices())
if err != nil {
return fmt.Errorf("environment variable COUNTRY: %w", err)
@@ -85,3 +71,17 @@ func (settings *Provider) readVPNUnlimited(r reader) (err error) {
return nil
}
func (settings *OpenVPN) readVPNUnlimited(r reader) (err error) {
settings.ClientKey, err = readClientKey(r)
if err != nil {
return err
}
settings.ClientCrt, err = readClientCertificate(r)
if err != nil {
return err
}
return nil
}