Configuration package (#369)
This commit is contained in:
3
internal/configuration/configuration.go
Normal file
3
internal/configuration/configuration.go
Normal file
@@ -0,0 +1,3 @@
|
||||
// Package configuration reads initial settings from environment variables
|
||||
// and secret files.
|
||||
package configuration
|
||||
6
internal/configuration/constants.go
Normal file
6
internal/configuration/constants.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package configuration
|
||||
|
||||
const (
|
||||
lastIndent = "|--"
|
||||
indent = " "
|
||||
)
|
||||
107
internal/configuration/cyberghost.go
Normal file
107
internal/configuration/cyberghost.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
func (settings *Provider) cyberghostLines() (lines []string) {
|
||||
lines = append(lines, lastIndent+"Server group: "+settings.ServerSelection.Group)
|
||||
|
||||
if len(settings.ServerSelection.Regions) > 0 {
|
||||
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (settings *Provider) readCyberghost(r reader) (err error) {
|
||||
settings.Name = constants.Cyberghost
|
||||
|
||||
settings.ServerSelection.Protocol, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ExtraConfigOptions.ClientKey, err = readCyberghostClientKey(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ExtraConfigOptions.ClientCertificate, err = readCyberghostClientCertificate(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Group, err = r.env.Inside("CYBERGHOST_GROUP",
|
||||
constants.CyberghostGroupChoices(), params.Default("Premium UDP Europe"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.CyberghostRegionChoices())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readCyberghostClientKey(r reader) (clientKey string, err error) {
|
||||
b, err := r.getFromFileOrSecretFile("OPENVPN_CLIENTKEY", string(constants.ClientKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return extractClientKey(b)
|
||||
}
|
||||
|
||||
func extractClientKey(b []byte) (key string, err error) {
|
||||
pemBlock, _ := pem.Decode(b)
|
||||
if pemBlock == nil {
|
||||
return "", fmt.Errorf("cannot decode PEM block from client key")
|
||||
}
|
||||
parsedBytes := pem.EncodeToMemory(pemBlock)
|
||||
s := string(parsedBytes)
|
||||
s = strings.ReplaceAll(s, "\n", "")
|
||||
s = strings.TrimPrefix(s, "-----BEGIN PRIVATE KEY-----")
|
||||
s = strings.TrimSuffix(s, "-----END PRIVATE KEY-----")
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func readCyberghostClientCertificate(r reader) (clientCertificate string, err error) {
|
||||
b, err := r.getFromFileOrSecretFile("OPENVPN_CLIENTCRT", string(constants.ClientCertificate))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return extractClientCertificate(b)
|
||||
}
|
||||
|
||||
func extractClientCertificate(b []byte) (certificate string, err error) {
|
||||
pemBlock, _ := pem.Decode(b)
|
||||
if pemBlock == nil {
|
||||
return "", fmt.Errorf("cannot decode PEM block from client certificate")
|
||||
}
|
||||
parsedBytes := pem.EncodeToMemory(pemBlock)
|
||||
s := string(parsedBytes)
|
||||
s = strings.ReplaceAll(s, "\n", "")
|
||||
s = strings.TrimPrefix(s, "-----BEGIN CERTIFICATE-----")
|
||||
s = strings.TrimSuffix(s, "-----END CERTIFICATE-----")
|
||||
return s, nil
|
||||
}
|
||||
175
internal/configuration/cyberghost_test.go
Normal file
175
internal/configuration/cyberghost_test.go
Normal file
@@ -0,0 +1,175 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_extractClientKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
const validPEM = `
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
zZjrL52saevO25cigVl+hxcnY8DTpbk=
|
||||
-----END PRIVATE KEY-----
|
||||
`
|
||||
const validKeyString = "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" //nolint:lll
|
||||
testCases := map[string]struct {
|
||||
b []byte
|
||||
key string
|
||||
err error
|
||||
}{
|
||||
"no input": {
|
||||
err: fmt.Errorf("cannot decode PEM block from client key"),
|
||||
},
|
||||
"bad input": {
|
||||
b: []byte{1, 2, 3},
|
||||
err: fmt.Errorf("cannot decode PEM block from client key"),
|
||||
},
|
||||
"valid key": {
|
||||
b: []byte(validPEM),
|
||||
key: validKeyString,
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
key, err := extractClientKey(testCase.b)
|
||||
if testCase.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, testCase.key, key)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_extractClientCertificate(t *testing.T) {
|
||||
t.Parallel()
|
||||
const validPEM = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIGrDCCBJSgAwIBAgIEAdTnfTANBgkqhkiG9w0BAQsFADB7MQswCQYDVQQGEwJS
|
||||
TzESMBAGA1UEBxMJQnVjaGFyZXN0MRgwFgYDVQQKEw9DeWJlckdob3N0IFMuQS4x
|
||||
GzAZBgNVBAMTEkN5YmVyR2hvc3QgUm9vdCBDQTEhMB8GCSqGSIb3DQEJARYSaW5m
|
||||
b0BjeWJlcmdob3N0LnJvMB4XDTIwMDcwNDE1MjkzNloXDTMwMDcwMjE1MjkzNlow
|
||||
fTELMAkGA1UEBhMCUk8xEjAQBgNVBAcMCUJ1Y2hhcmVzdDEYMBYGA1UECgwPQ3li
|
||||
ZXJHaG9zdCBTLkEuMR0wGwYDVQQDDBRjLmoua2xhdmVyQGdtYWlsLmNvbTEhMB8G
|
||||
CSqGSIb3DQEJARYSaW5mb0BjeWJlcmdob3N0LnJvMIICIjANBgkqhkiG9w0BAQEF
|
||||
AAOCAg8AMIICCgKCAgEAobp2NlGUHMNBe08YEOnVG3QJjF3ZaXbRhE/II9rmtgJT
|
||||
NZtDohGChvFlNRsExKzVrKxHCeuJkVffwzQ6fYk4/M1RdYLJUh0UVw3e4WdApw8E
|
||||
7TJZxDYm4SHQNXUvt1Rt5TjslcXxIpDZgrMSc/kHROYEL9tdgdzPZErUJehXyJPh
|
||||
EzIrzmAJh501x7WwKPz9ctSVlItyavqEWFF2vyUa6X9DYmD9mQTz5c+VXNO5DkXm
|
||||
PFBIaEVDnvFtcjGJ56yEvFnWVukL+OUX7ezowrIOFOcp9udjgpeiHq+XvsQ6ER0D
|
||||
Jt25MiEId3NjkxtZ8BitDftTcLN/kt81hWKT7adMVc3kpIZ80cxrwRCttMd7sHAz
|
||||
KI9u7pMxv10eUOsIEY87ewBe3l6KvEnjA+9uIjim6gLLebDIaEH50Ee9PzNJ8fqQ
|
||||
2u54Ab4bt00/H1sUnJ6Ss/+WsQDOK1BsPRKKcnHZntOlHrs2Tu5+txKNU2cOapI8
|
||||
SjVULUNKrRXASbpfWnLUfri/HO742bJb/TjkOJcOxta3hTPFAhaRWBusVlB41XVH
|
||||
euH5DAhugYXeSNK6/6Ul8YvKUNH/7QbxuGIGXfth19Xl4QLI1umyEjZopSlt3tOi
|
||||
O2V1soVNSQCCfxXVoCTMESMLjhkjWdmBDhdy2GTW7S4YoJfqVKiS18rYkN7I4ZMC
|
||||
AwEAAaOCATQwggEwMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMDQGCWCGSAGG+EIB
|
||||
DQQnFiVDeWJlckdob3N0IEdlbmVyYXRlZCBVc2VyIENlcnRpZmljYXRlMBEGCWCG
|
||||
SAGG+EIBAQQEAwIHgDAdBgNVHQ4EFgQULwUtU5s6pL2NN9gPeEnKX0dhwiswga0G
|
||||
A1UdIwSBpTCBooAU6tdK1g/He5qzjeAoM5eHt4in9iWhf6R9MHsxCzAJBgNVBAYT
|
||||
AlJPMRIwEAYDVQQHEwlCdWNoYXJlc3QxGDAWBgNVBAoTD0N5YmVyR2hvc3QgUy5B
|
||||
LjEbMBkGA1UEAxMSQ3liZXJHaG9zdCBSb290IENBMSEwHwYJKoZIhvcNAQkBFhJp
|
||||
bmZvQGN5YmVyZ2hvc3Qucm+CCQCcVButZsQ0uzANBgkqhkiG9w0BAQsFAAOCAgEA
|
||||
ystGIMYhQWaEdTqlnLCytrr8657t+PuidZMNNIaPB3wN2Fi2xKf14DTg03mqxjmP
|
||||
Pb+f+PVNIOV5PdWD4jcQwOP1GEboGV0DFzlRGeAtDcvKwdee4oASJbZq1CETqDao
|
||||
hQTxKEWC+UBk2F36nOaEI6Sab+Mb4cR9//PAwvzOqrXuGF5NuIOX7eFtCMQSgQq6
|
||||
lRRqTQjekm0Dxigx4JA92Jo2qZRwCJ0T3IXBJGL831HCFJbDWv8PV3lsfFb/i2+v
|
||||
r54uywFQVWWp18dYi97gipfuQ4zRg2Ldx5aXSmnhhKpg5ioZvtk043QofF12YORh
|
||||
obElqavRbvvhZvlCouvcuoq9QKi7IPe5SJZkZ1X7ezMesCwBzwFpt6vRUAcslsNF
|
||||
bcYS1iSENlY/PTcDqBhbKuc9yAhq+/aUgaY/8VF5RWVzSRZufbf3BPwOkE4K0Uyb
|
||||
aobO/YX0JOkCacAD+4tdR6YSXNIMMRAOCBQvxbxFXaHzhwhzBAjdsC56FrJKwXvQ
|
||||
rRLU3tF4P0zFMeNTay8uTtUXugDK7EnklLESuYdpUJ8bUMlAUhJBi6UFI9/icMud
|
||||
xXvLRvhnBW9EtKib5JnVFUovcEUt+3EJbyst05nkL4YPjQS4TC9DHdo5SyRAy1Tp
|
||||
iOCYTbretAFZRhh6ycUN5hBeN8GMQxiMreMtDV4PEIQ=
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
const validCertificateString = "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" //nolint:lll
|
||||
testCases := map[string]struct {
|
||||
b []byte
|
||||
certificate string
|
||||
err error
|
||||
}{
|
||||
"no input": {
|
||||
err: fmt.Errorf("cannot decode PEM block from client certificate"),
|
||||
},
|
||||
"bad input": {
|
||||
b: []byte{1, 2, 3},
|
||||
err: fmt.Errorf("cannot decode PEM block from client certificate"),
|
||||
},
|
||||
"valid key": {
|
||||
b: []byte(validPEM),
|
||||
certificate: validCertificateString,
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
certificate, err := extractClientCertificate(testCase.b)
|
||||
if testCase.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, testCase.certificate, certificate)
|
||||
})
|
||||
}
|
||||
}
|
||||
154
internal/configuration/dns.go
Normal file
154
internal/configuration/dns.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
unboundmodels "github.com/qdm12/dns/pkg/models"
|
||||
unbound "github.com/qdm12/dns/pkg/unbound"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
// DNS contains settings to configure Unbound for DNS over TLS operation.
|
||||
type DNS struct { //nolint:maligned
|
||||
Enabled bool
|
||||
PlaintextAddress net.IP
|
||||
KeepNameserver bool
|
||||
BlockMalicious bool
|
||||
BlockAds bool
|
||||
BlockSurveillance bool
|
||||
UpdatePeriod time.Duration
|
||||
Unbound unboundmodels.Settings
|
||||
}
|
||||
|
||||
func (settings *DNS) String() string {
|
||||
return strings.Join(settings.lines(), "\n")
|
||||
}
|
||||
|
||||
func (settings *DNS) lines() (lines []string) {
|
||||
lines = append(lines, lastIndent+"DNS:")
|
||||
|
||||
if settings.PlaintextAddress != nil {
|
||||
lines = append(lines, indent+lastIndent+"Plaintext address: "+settings.PlaintextAddress.String())
|
||||
}
|
||||
|
||||
if settings.KeepNameserver {
|
||||
lines = append(lines, indent+lastIndent+"Keep nameserver (disabled blocking): yes")
|
||||
}
|
||||
|
||||
if !settings.Enabled {
|
||||
return lines
|
||||
}
|
||||
|
||||
lines = append(lines, indent+lastIndent+"DNS over TLS:")
|
||||
|
||||
lines = append(lines, indent+indent+lastIndent+"Unbound:")
|
||||
for _, line := range settings.Unbound.Lines() {
|
||||
lines = append(lines, indent+indent+indent+line)
|
||||
}
|
||||
|
||||
if settings.BlockMalicious {
|
||||
lines = append(lines, indent+indent+lastIndent+"Block malicious: enabled")
|
||||
}
|
||||
|
||||
if settings.BlockAds {
|
||||
lines = append(lines, indent+indent+lastIndent+"Block ads: enabled")
|
||||
}
|
||||
|
||||
if settings.BlockSurveillance {
|
||||
lines = append(lines, indent+indent+lastIndent+"Block surveillance: enabled")
|
||||
}
|
||||
|
||||
if settings.UpdatePeriod > 0 {
|
||||
lines = append(lines, indent+indent+lastIndent+"Update: every "+settings.UpdatePeriod.String())
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
var (
|
||||
ErrUnboundSettings = errors.New("failed getting Unbound settings")
|
||||
ErrDNSProviderNoData = errors.New("DNS provider has no associated data")
|
||||
ErrDNSProviderNoTLS = errors.New("DNS provider does not support DNS over TLS")
|
||||
ErrDNSNoIPv6Support = errors.New("no DNS provider supports IPv6")
|
||||
)
|
||||
|
||||
func (settings *DNS) read(r reader) (err error) {
|
||||
settings.Enabled, err = r.env.OnOff("DOT", params.Default("on"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Plain DNS settings
|
||||
if err := settings.readDNSPlaintext(r.env); err != nil {
|
||||
return err
|
||||
}
|
||||
settings.KeepNameserver, err = r.env.OnOff("DNS_KEEP_NAMESERVER", params.Default("off"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// DNS over TLS external settings
|
||||
settings.BlockMalicious, err = r.env.OnOff("BLOCK_MALICIOUS", params.Default("on"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings.BlockSurveillance, err = r.env.OnOff("BLOCK_SURVEILLANCE", params.Default("on"),
|
||||
params.RetroKeys([]string{"BLOCK_NSA"}, r.onRetroActive))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings.BlockAds, err = r.env.OnOff("BLOCK_ADS", params.Default("off"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings.UpdatePeriod, err = r.env.Duration("DNS_UPDATE_PERIOD", params.Default("24h"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := settings.readUnbound(r); err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrUnboundSettings, err)
|
||||
}
|
||||
|
||||
// Consistency check
|
||||
IPv6Support := false
|
||||
for _, provider := range settings.Unbound.Providers {
|
||||
providerData, ok := unbound.GetProviderData(provider)
|
||||
switch {
|
||||
case !ok:
|
||||
return fmt.Errorf("%w: %s", ErrDNSProviderNoData, provider)
|
||||
case !providerData.SupportsTLS:
|
||||
return fmt.Errorf("%w: %s", ErrDNSProviderNoTLS, provider)
|
||||
case providerData.SupportsIPv6:
|
||||
IPv6Support = true
|
||||
}
|
||||
}
|
||||
|
||||
if settings.Unbound.IPv6 && !IPv6Support {
|
||||
return ErrDNSNoIPv6Support
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrDNSAddressNotAnIP = errors.New("DNS plaintext address is not an IP address")
|
||||
)
|
||||
|
||||
func (settings *DNS) readDNSPlaintext(env params.Env) error {
|
||||
s, err := env.Get("DNS_PLAINTEXT_ADDRESS", params.Default("1.1.1.1"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.PlaintextAddress = net.ParseIP(s)
|
||||
if settings.PlaintextAddress == nil {
|
||||
return fmt.Errorf("%w: %s", ErrDNSAddressNotAnIP, s)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
73
internal/configuration/dns_test.go
Normal file
73
internal/configuration/dns_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/dns/pkg/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_DNS_Lines(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCases := map[string]struct {
|
||||
settings DNS
|
||||
lines []string
|
||||
}{
|
||||
"disabled DOT": {
|
||||
settings: DNS{
|
||||
PlaintextAddress: net.IP{1, 1, 1, 1},
|
||||
},
|
||||
lines: []string{
|
||||
"|--DNS:",
|
||||
" |--Plaintext address: 1.1.1.1",
|
||||
},
|
||||
},
|
||||
"enabled DOT": {
|
||||
settings: DNS{
|
||||
Enabled: true,
|
||||
KeepNameserver: true,
|
||||
Unbound: models.Settings{
|
||||
Providers: []string{"cloudflare"},
|
||||
},
|
||||
BlockMalicious: true,
|
||||
BlockAds: true,
|
||||
BlockSurveillance: true,
|
||||
UpdatePeriod: time.Hour,
|
||||
},
|
||||
lines: []string{
|
||||
"|--DNS:",
|
||||
" |--Keep nameserver (disabled blocking): yes",
|
||||
" |--DNS over TLS:",
|
||||
" |--Unbound:",
|
||||
" |--DNS over TLS providers:",
|
||||
" |--cloudflare",
|
||||
" |--Listening port: 0",
|
||||
" |--Access control:",
|
||||
" |--Allowed:",
|
||||
" |--Caching: disabled",
|
||||
" |--IPv4 resolution: disabled",
|
||||
" |--IPv6 resolution: disabled",
|
||||
" |--Verbosity level: 0/5",
|
||||
" |--Verbosity details level: 0/4",
|
||||
" |--Validation log level: 0/2",
|
||||
" |--Blocked hostnames:",
|
||||
" |--Blocked IP addresses:",
|
||||
" |--Allowed hostnames:",
|
||||
" |--Block malicious: enabled",
|
||||
" |--Block ads: enabled",
|
||||
" |--Block surveillance: enabled",
|
||||
" |--Update: every 1h0m0s",
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
lines := testCase.settings.lines()
|
||||
assert.Equal(t, testCase.lines, lines)
|
||||
})
|
||||
}
|
||||
}
|
||||
93
internal/configuration/firewall.go
Normal file
93
internal/configuration/firewall.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
// Firewall contains settings to customize the firewall operation.
|
||||
type Firewall struct {
|
||||
VPNInputPorts []uint16
|
||||
InputPorts []uint16
|
||||
OutboundSubnets []net.IPNet
|
||||
Enabled bool
|
||||
Debug bool
|
||||
}
|
||||
|
||||
func (settings *Firewall) String() string {
|
||||
return strings.Join(settings.lines(), "\n")
|
||||
}
|
||||
|
||||
func (settings *Firewall) lines() (lines []string) {
|
||||
if !settings.Enabled {
|
||||
lines = append(lines, lastIndent+"Firewall: disabled ⚠️")
|
||||
return lines
|
||||
}
|
||||
|
||||
lines = append(lines, lastIndent+"Firewall:")
|
||||
|
||||
if settings.Debug {
|
||||
lines = append(lines, indent+lastIndent+"Debug: on")
|
||||
}
|
||||
|
||||
if len(settings.VPNInputPorts) > 0 {
|
||||
lines = append(lines, indent+lastIndent+"VPN input ports: "+
|
||||
strings.Join(uint16sToStrings(settings.VPNInputPorts), ", "))
|
||||
}
|
||||
|
||||
if len(settings.InputPorts) > 0 {
|
||||
lines = append(lines, indent+lastIndent+"Input ports: "+
|
||||
strings.Join(uint16sToStrings(settings.InputPorts), ", "))
|
||||
}
|
||||
|
||||
if len(settings.OutboundSubnets) > 0 {
|
||||
lines = append(lines, indent+lastIndent+"Outbound subnets: "+
|
||||
strings.Join(ipNetsToStrings(settings.OutboundSubnets), ", "))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Firewall) read(r reader) (err error) {
|
||||
settings.Enabled, err = r.env.OnOff("FIREWALL", params.Default("on"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.Debug, err = r.env.OnOff("FIREWALL_DEBUG", params.Default("off"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := settings.readVPNInputPorts(r.env); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := settings.readInputPorts(r.env); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := settings.readOutboundSubnets(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (settings *Firewall) readVPNInputPorts(env params.Env) (err error) {
|
||||
settings.VPNInputPorts, err = readCSVPorts(env, "FIREWALL_VPN_INPUT_PORTS")
|
||||
return err
|
||||
}
|
||||
|
||||
func (settings *Firewall) readInputPorts(env params.Env) (err error) {
|
||||
settings.VPNInputPorts, err = readCSVPorts(env, "FIREWALL_INPUT_PORTS")
|
||||
return err
|
||||
}
|
||||
|
||||
func (settings *Firewall) readOutboundSubnets(r reader) (err error) {
|
||||
retroOption := params.RetroKeys([]string{"EXTRA_SUBNETS"}, r.onRetroActive)
|
||||
settings.OutboundSubnets, err = readCSVIPNets(r.env, "FIREWALL_OUTBOUND_SUBNETS", retroOption)
|
||||
return err
|
||||
}
|
||||
105
internal/configuration/httpproxy.go
Normal file
105
internal/configuration/httpproxy.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
// HTTPProxy contains settings to configure the HTTP proxy.
|
||||
type HTTPProxy struct {
|
||||
User string
|
||||
Password string
|
||||
Port uint16
|
||||
Enabled bool
|
||||
Stealth bool
|
||||
Log bool
|
||||
}
|
||||
|
||||
func (settings *HTTPProxy) String() string {
|
||||
return strings.Join(settings.lines(), "\n")
|
||||
}
|
||||
|
||||
func (settings *HTTPProxy) lines() (lines []string) {
|
||||
if !settings.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
lines = append(lines, lastIndent+"HTTP proxy:")
|
||||
|
||||
lines = append(lines, indent+lastIndent+"Port: "+strconv.Itoa(int(settings.Port)))
|
||||
|
||||
if settings.User != "" {
|
||||
lines = append(lines, indent+lastIndent+"Authentication: enabled")
|
||||
}
|
||||
|
||||
if settings.Log {
|
||||
lines = append(lines, indent+lastIndent+"Log: enabled")
|
||||
}
|
||||
|
||||
if settings.Stealth {
|
||||
lines = append(lines, indent+lastIndent+"Stealth: enabled")
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *HTTPProxy) read(r reader) (err error) {
|
||||
settings.Enabled, err = r.env.OnOff("HTTPPROXY", params.Default("off"),
|
||||
params.RetroKeys([]string{"TINYPROXY", "PROXY"}, r.onRetroActive))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.User, err = r.getFromEnvOrSecretFile("HTTPPROXY_USER", false, // compulsory
|
||||
[]string{"TINYPROXY_USER", "PROXY_USER"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.Password, err = r.getFromEnvOrSecretFile("HTTPPROXY_USER", false,
|
||||
[]string{"TINYPROXY_PASSWORD", "PROXY_PASSWORD"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.Stealth, err = r.env.OnOff("HTTPPROXY_STEALTH", params.Default("off"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := settings.readLog(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var warning string
|
||||
settings.Port, warning, err = r.env.ListeningPort("HTTPPROXY_PORT", params.Default("8888"),
|
||||
params.RetroKeys([]string{"TINYPROXY_PORT", "PROXY_PORT"}, r.onRetroActive))
|
||||
if len(warning) > 0 {
|
||||
r.logger.Warn(warning)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (settings *HTTPProxy) readLog(r reader) error {
|
||||
s, err := r.env.Get("HTTPPROXY_LOG",
|
||||
params.RetroKeys([]string{"PROXY_LOG_LEVEL", "TINYPROXY_LOG"}, r.onRetroActive))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch strings.ToLower(s) {
|
||||
case "on":
|
||||
settings.Enabled = true
|
||||
// Retro compatibility
|
||||
case "info", "connect", "notice":
|
||||
settings.Enabled = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
22
internal/configuration/lines.go
Normal file
22
internal/configuration/lines.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func uint16sToStrings(uint16s []uint16) (strings []string) {
|
||||
strings = make([]string, len(uint16s))
|
||||
for i := range uint16s {
|
||||
strings[i] = strconv.Itoa(int(uint16s[i]))
|
||||
}
|
||||
return strings
|
||||
}
|
||||
|
||||
func ipNetsToStrings(ipNets []net.IPNet) (strings []string) {
|
||||
strings = make([]string, len(ipNets))
|
||||
for i := range ipNets {
|
||||
strings[i] = ipNets[i].String()
|
||||
}
|
||||
return strings
|
||||
}
|
||||
79
internal/configuration/mullvad.go
Normal file
79
internal/configuration/mullvad.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
func (settings *Provider) mullvadLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Countries) > 0 {
|
||||
lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Cities) > 0 {
|
||||
lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.ISPs) > 0 {
|
||||
lines = append(lines, lastIndent+"ISPs: "+commaJoin(settings.ServerSelection.ISPs))
|
||||
}
|
||||
|
||||
if settings.ServerSelection.CustomPort > 0 {
|
||||
lines = append(lines, lastIndent+"Custom port: "+strconv.Itoa(int(settings.ServerSelection.CustomPort)))
|
||||
}
|
||||
|
||||
if settings.ExtraConfigOptions.OpenVPNIPv6 {
|
||||
lines = append(lines, lastIndent+"IPv6: enabled")
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readMullvad(r reader) (err error) {
|
||||
settings.Name = constants.Mullvad
|
||||
|
||||
settings.ServerSelection.Protocol, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.MullvadCountryChoices())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.MullvadCityChoices())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.ISPs, err = r.env.CSVInside("ISP", constants.MullvadISPChoices())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.CustomPort, err = readCustomPort(r.env, settings.ServerSelection.Protocol,
|
||||
[]uint16{80, 443, 1401}, []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Owned, err = r.env.YesNo("OWNED", params.Default("no"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ExtraConfigOptions.OpenVPNIPv6, err = r.env.OnOff("OPENVPN_IPV6", params.Default("off"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
72
internal/configuration/nordvpn.go
Normal file
72
internal/configuration/nordvpn.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
func (settings *Provider) nordvpnLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Regions) > 0 {
|
||||
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
|
||||
}
|
||||
|
||||
if numbersUint16 := settings.ServerSelection.Numbers; len(numbersUint16) > 0 {
|
||||
numbersString := make([]string, len(numbersUint16))
|
||||
for i, numberUint16 := range numbersUint16 {
|
||||
numbersString[i] = strconv.Itoa(int(numberUint16))
|
||||
}
|
||||
lines = append(lines, lastIndent+"Numbers: "+commaJoin(numbersString))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readNordvpn(r reader) (err error) {
|
||||
settings.Name = constants.Nordvpn
|
||||
|
||||
settings.ServerSelection.Protocol, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.NordvpnRegionChoices())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Numbers, err = readNordVPNServerNumbers(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readNordVPNServerNumbers(env params.Env) (numbers []uint16, err error) {
|
||||
possibilities := make([]string, 65537)
|
||||
for i := range possibilities {
|
||||
possibilities[i] = fmt.Sprintf("%d", i)
|
||||
}
|
||||
possibilities[65536] = ""
|
||||
values, err := env.CSVInside("SERVER_NUMBER", possibilities)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
numbers = make([]uint16, len(values))
|
||||
for i := range values {
|
||||
n, err := strconv.Atoi(values[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
numbers[i] = uint16(n)
|
||||
}
|
||||
return numbers, nil
|
||||
}
|
||||
139
internal/configuration/openvpn.go
Normal file
139
internal/configuration/openvpn.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
// OpenVPN contains settings to configure the OpenVPN client.
|
||||
type OpenVPN struct {
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
Verbosity int `json:"verbosity"`
|
||||
MSSFix uint16 `json:"mssfix"`
|
||||
Root bool `json:"run_as_root"`
|
||||
Cipher string `json:"cipher"`
|
||||
Auth string `json:"auth"`
|
||||
Provider Provider `json:"provider"`
|
||||
}
|
||||
|
||||
func (settings *OpenVPN) String() string {
|
||||
return strings.Join(settings.lines(), "\n")
|
||||
}
|
||||
|
||||
func (settings *OpenVPN) lines() (lines []string) {
|
||||
lines = append(lines, lastIndent+"OpenVPN:")
|
||||
|
||||
lines = append(lines, indent+lastIndent+"Verbosity level: "+strconv.Itoa(settings.Verbosity))
|
||||
|
||||
if settings.Root {
|
||||
lines = append(lines, indent+lastIndent+"Run as root: enabled")
|
||||
}
|
||||
|
||||
if len(settings.Cipher) > 0 {
|
||||
lines = append(lines, indent+lastIndent+"Custom cipher: "+settings.Cipher)
|
||||
}
|
||||
if len(settings.Auth) > 0 {
|
||||
lines = append(lines, indent+lastIndent+"Custom auth algorithm: "+settings.Auth)
|
||||
}
|
||||
|
||||
lines = append(lines, indent+lastIndent+"Provider:")
|
||||
for _, line := range settings.Provider.lines() {
|
||||
lines = append(lines, indent+indent+line)
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidVPNProvider = errors.New("invalid VPN provider")
|
||||
)
|
||||
|
||||
func (settings *OpenVPN) read(r reader) (err error) {
|
||||
vpnsp, err := r.env.Inside("VPNSP", []string{
|
||||
"pia", "private internet access", "mullvad", "windscribe", "surfshark",
|
||||
"cyberghost", "vyprvpn", "nordvpn", "purevpn", "privado"},
|
||||
params.Default("private internet access"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if vpnsp == "pia" { // retro compatibility
|
||||
vpnsp = "private internet access"
|
||||
}
|
||||
|
||||
settings.Provider.Name = models.VPNProvider(vpnsp)
|
||||
|
||||
settings.User, err = r.getFromEnvOrSecretFile("OPENVPN_USER", true, []string{"USER"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Remove spaces in user ID to simplify user's life, thanks @JeordyR
|
||||
settings.User = strings.ReplaceAll(settings.User, " ", "")
|
||||
|
||||
if settings.Provider.Name == constants.Mullvad {
|
||||
settings.Password = "m"
|
||||
} else {
|
||||
settings.Password, err = r.getFromEnvOrSecretFile("OPENVPN_PASSWORD", true, []string{"PASSWORD"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
settings.Verbosity, err = r.env.IntRange("OPENVPN_VERBOSITY", 0, 6, params.Default("1"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.Root, err = r.env.YesNo("OPENVPN_ROOT", params.Default("no"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.Cipher, err = r.env.Get("OPENVPN_CIPHER")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.Auth, err = r.env.Get("OPENVPN_AUTH")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mssFix, err := r.env.IntRange("OPENVPN_MSSFIX", 0, 10000, params.Default("0"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings.MSSFix = uint16(mssFix)
|
||||
|
||||
var readProvider func(r reader) error
|
||||
switch settings.Provider.Name {
|
||||
case constants.PrivateInternetAccess:
|
||||
readProvider = settings.Provider.readPrivateInternetAccess
|
||||
case constants.Mullvad:
|
||||
readProvider = settings.Provider.readMullvad
|
||||
case constants.Windscribe:
|
||||
readProvider = settings.Provider.readWindscribe
|
||||
case constants.Surfshark:
|
||||
readProvider = settings.Provider.readSurfshark
|
||||
case constants.Cyberghost:
|
||||
readProvider = settings.Provider.readCyberghost
|
||||
case constants.Vyprvpn:
|
||||
readProvider = settings.Provider.readVyprvpn
|
||||
case constants.Nordvpn:
|
||||
readProvider = settings.Provider.readNordvpn
|
||||
case constants.Purevpn:
|
||||
readProvider = settings.Provider.readPurevpn
|
||||
case constants.Privado:
|
||||
readProvider = settings.Provider.readPrivado
|
||||
default:
|
||||
return fmt.Errorf("%w: %s", ErrInvalidVPNProvider, settings.Provider.Name)
|
||||
}
|
||||
|
||||
return readProvider(r)
|
||||
}
|
||||
27
internal/configuration/openvpn_test.go
Normal file
27
internal/configuration/openvpn_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_OpenVPN_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
in := OpenVPN{
|
||||
Root: true,
|
||||
Provider: Provider{
|
||||
Name: "name",
|
||||
},
|
||||
}
|
||||
data, err := json.Marshal(in)
|
||||
require.NoError(t, err)
|
||||
//nolint:lll
|
||||
assert.Equal(t, `{"user":"","password":"","verbosity":0,"mssfix":0,"run_as_root":true,"cipher":"","auth":"","provider":{"name":"name","server_selection":{"network_protocol":"","regions":null,"group":"","countries":null,"cities":null,"hostnames":null,"isps":null,"owned":false,"custom_port":0,"numbers":null,"encryption_preset":""},"extra_config":{"encryption_preset":"","openvpn_ipv6":false},"port_forwarding":{"enabled":false,"filepath":""}}}`, string(data))
|
||||
var out OpenVPN
|
||||
err = json.Unmarshal(data, &out)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, in, out)
|
||||
}
|
||||
36
internal/configuration/privado.go
Normal file
36
internal/configuration/privado.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
func (settings *Provider) privadoLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Hostnames) > 0 {
|
||||
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readPrivado(r reader) (err error) {
|
||||
settings.Name = constants.Privado
|
||||
|
||||
settings.ServerSelection.Protocol, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
|
||||
constants.PrivadoHostnameChoices(), params.RetroKeys([]string{"HOSTNAME"}, r.onRetroActive))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
79
internal/configuration/privateinternetaccess.go
Normal file
79
internal/configuration/privateinternetaccess.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
func (settings *Provider) privateinternetaccessLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Regions) > 0 {
|
||||
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
|
||||
}
|
||||
|
||||
lines = append(lines, lastIndent+"Encryption preset: "+settings.ServerSelection.EncryptionPreset)
|
||||
|
||||
lines = append(lines, lastIndent+"Custom port: "+strconv.Itoa(int(settings.ServerSelection.CustomPort)))
|
||||
|
||||
if settings.PortForwarding.Enabled {
|
||||
lines = append(lines, lastIndent+"Port forwarding:")
|
||||
for _, line := range settings.PortForwarding.lines() {
|
||||
lines = append(lines, indent+line)
|
||||
}
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readPrivateInternetAccess(r reader) (err error) {
|
||||
settings.Name = constants.PrivateInternetAccess
|
||||
|
||||
settings.ServerSelection.Protocol, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encryptionPreset, err := r.env.Inside("PIA_ENCRYPTION",
|
||||
[]string{constants.PIAEncryptionPresetNormal, constants.PIAEncryptionPresetStrong},
|
||||
params.RetroKeys([]string{"ENCRYPTION"}, r.onRetroActive),
|
||||
params.Default(constants.PIACertificateStrong),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings.ServerSelection.EncryptionPreset = encryptionPreset
|
||||
settings.ExtraConfigOptions.EncryptionPreset = encryptionPreset
|
||||
|
||||
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.PIAGeoChoices())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.CustomPort, err = readPortOrZero(r.env, "PORT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.PortForwarding.Enabled, err = r.env.OnOff("PORT_FORWARDING", params.Default("off"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if settings.PortForwarding.Enabled {
|
||||
filepathStr, err := r.env.Path("PORT_FORWARDING_STATUS_FILE",
|
||||
params.Default("/tmp/gluetun/forwarded_port"), params.CaseSensitiveValue())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings.PortForwarding.Filepath = models.Filepath(filepathStr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
112
internal/configuration/provider.go
Normal file
112
internal/configuration/provider.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
// Provider contains settings specific to a VPN provider.
|
||||
type Provider struct {
|
||||
Name models.VPNProvider `json:"name"`
|
||||
ServerSelection ServerSelection `json:"server_selection"`
|
||||
ExtraConfigOptions ExtraConfigOptions `json:"extra_config"`
|
||||
PortForwarding PortForwarding `json:"port_forwarding"`
|
||||
}
|
||||
|
||||
func (settings *Provider) lines() (lines []string) {
|
||||
lines = append(lines, lastIndent+strings.Title(string(settings.Name))+" settings:")
|
||||
|
||||
lines = append(lines, indent+lastIndent+"Network protocol: "+string(settings.ServerSelection.Protocol))
|
||||
|
||||
if settings.ServerSelection.TargetIP != nil {
|
||||
lines = append(lines, indent+lastIndent+"Target IP address: "+settings.ServerSelection.TargetIP.String())
|
||||
}
|
||||
|
||||
var providerLines []string
|
||||
switch strings.ToLower(string(settings.Name)) {
|
||||
case "cyberghost":
|
||||
providerLines = settings.cyberghostLines()
|
||||
case "mullvad":
|
||||
providerLines = settings.mullvadLines()
|
||||
case "nordvpn":
|
||||
providerLines = settings.nordvpnLines()
|
||||
case "privado":
|
||||
providerLines = settings.privadoLines()
|
||||
case "private internet access":
|
||||
providerLines = settings.privateinternetaccessLines()
|
||||
case "purevpn":
|
||||
providerLines = settings.purevpnLines()
|
||||
case "surfshark":
|
||||
providerLines = settings.surfsharkLines()
|
||||
case "vyprvpn":
|
||||
providerLines = settings.vyprvpnLines()
|
||||
case "windscribe":
|
||||
providerLines = settings.windscribeLines()
|
||||
default:
|
||||
panic(`Missing lines method for provider "` +
|
||||
settings.Name + `"! Please create a Github issue.`)
|
||||
}
|
||||
|
||||
for _, line := range providerLines {
|
||||
lines = append(lines, indent+line)
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func commaJoin(slice []string) string {
|
||||
return strings.Join(slice, ", ")
|
||||
}
|
||||
|
||||
func readProtocol(env params.Env) (protocol models.NetworkProtocol, err error) {
|
||||
s, err := env.Inside("PROTOCOL",
|
||||
[]string{string(constants.TCP), string(constants.UDP)},
|
||||
params.Default(string(constants.UDP)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return models.NetworkProtocol(s), nil
|
||||
}
|
||||
|
||||
func readTargetIP(env params.Env) (targetIP net.IP, err error) {
|
||||
return readIP(env, "OPENVPN_TARGET_IP")
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidProtocol = errors.New("invalid network protocol")
|
||||
)
|
||||
|
||||
func readCustomPort(env params.Env, protocol models.NetworkProtocol,
|
||||
allowedTCP, allowedUDP []uint16) (port uint16, err error) {
|
||||
port, err = readPortOrZero(env, "PORT")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if port == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
switch protocol {
|
||||
case constants.TCP:
|
||||
for i := range allowedTCP {
|
||||
if allowedTCP[i] == port {
|
||||
return port, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("%w: port %d for TCP protocol", ErrInvalidPort, port)
|
||||
case constants.UDP:
|
||||
for i := range allowedUDP {
|
||||
if allowedTCP[i] == port {
|
||||
return port, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("%w: port %d for UDP protocol", ErrInvalidPort, port)
|
||||
default:
|
||||
return 0, fmt.Errorf("%w: %s", ErrInvalidProtocol, protocol)
|
||||
}
|
||||
}
|
||||
246
internal/configuration/provider_test.go
Normal file
246
internal/configuration/provider_test.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/golibs/params/mock_params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var errDummy = errors.New("dummy")
|
||||
|
||||
func Test_Provider_lines(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
settings Provider
|
||||
lines []string
|
||||
}{
|
||||
"cyberghost": {
|
||||
settings: Provider{
|
||||
Name: constants.Cyberghost,
|
||||
ServerSelection: ServerSelection{
|
||||
Protocol: constants.UDP,
|
||||
Group: "group",
|
||||
Regions: []string{"a", "El country"},
|
||||
},
|
||||
ExtraConfigOptions: ExtraConfigOptions{
|
||||
ClientKey: "a",
|
||||
ClientCertificate: "a",
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Cyberghost settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Server group: group",
|
||||
" |--Regions: a, El country",
|
||||
" |--Client key is set",
|
||||
" |--Client certificate is set",
|
||||
},
|
||||
},
|
||||
"mullvad": {
|
||||
settings: Provider{
|
||||
Name: constants.Mullvad,
|
||||
ServerSelection: ServerSelection{
|
||||
Protocol: constants.UDP,
|
||||
Countries: []string{"a", "b"},
|
||||
Cities: []string{"c", "d"},
|
||||
ISPs: []string{"e", "f"},
|
||||
CustomPort: 1,
|
||||
},
|
||||
ExtraConfigOptions: ExtraConfigOptions{
|
||||
OpenVPNIPv6: true,
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Mullvad settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Countries: a, b",
|
||||
" |--Cities: c, d",
|
||||
" |--ISPs: e, f",
|
||||
" |--Custom port: 1",
|
||||
" |--IPv6: enabled",
|
||||
},
|
||||
},
|
||||
"nordvpn": {
|
||||
settings: Provider{
|
||||
Name: constants.Nordvpn,
|
||||
ServerSelection: ServerSelection{
|
||||
Protocol: constants.UDP,
|
||||
Regions: []string{"a", "b"},
|
||||
Numbers: []uint16{1, 2},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Nordvpn settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Regions: a, b",
|
||||
" |--Numbers: 1, 2",
|
||||
},
|
||||
},
|
||||
"privado": {
|
||||
settings: Provider{
|
||||
Name: constants.Privado,
|
||||
ServerSelection: ServerSelection{
|
||||
Protocol: constants.UDP,
|
||||
Hostnames: []string{"a", "b"},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Privado settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Hostnames: a, b",
|
||||
},
|
||||
},
|
||||
"private internet access": {
|
||||
settings: Provider{
|
||||
Name: constants.PrivateInternetAccess,
|
||||
ServerSelection: ServerSelection{
|
||||
Protocol: constants.UDP,
|
||||
Regions: []string{"a", "b"},
|
||||
EncryptionPreset: constants.PIAEncryptionPresetStrong,
|
||||
CustomPort: 1,
|
||||
},
|
||||
PortForwarding: PortForwarding{
|
||||
Enabled: true,
|
||||
Filepath: models.Filepath("/here"),
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Private Internet Access settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Regions: a, b",
|
||||
" |--Encryption preset: strong",
|
||||
" |--Custom port: 1",
|
||||
" |--Port forwarding:",
|
||||
" |--File path: /here",
|
||||
},
|
||||
},
|
||||
"purevpn": {
|
||||
settings: Provider{
|
||||
Name: constants.Purevpn,
|
||||
ServerSelection: ServerSelection{
|
||||
Protocol: constants.UDP,
|
||||
Regions: []string{"a", "b"},
|
||||
Countries: []string{"c", "d"},
|
||||
Cities: []string{"e", "f"},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Purevpn settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Regions: a, b",
|
||||
" |--Countries: c, d",
|
||||
" |--Cities: e, f",
|
||||
},
|
||||
},
|
||||
"surfshark": {
|
||||
settings: Provider{
|
||||
Name: constants.Surfshark,
|
||||
ServerSelection: ServerSelection{
|
||||
Protocol: constants.UDP,
|
||||
Regions: []string{"a", "b"},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Surfshark settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Regions: a, b",
|
||||
},
|
||||
},
|
||||
"vyprvpn": {
|
||||
settings: Provider{
|
||||
Name: constants.Vyprvpn,
|
||||
ServerSelection: ServerSelection{
|
||||
Protocol: constants.UDP,
|
||||
Regions: []string{"a", "b"},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Vyprvpn settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Regions: a, b",
|
||||
},
|
||||
},
|
||||
"windscribe": {
|
||||
settings: Provider{
|
||||
Name: constants.Windscribe,
|
||||
ServerSelection: ServerSelection{
|
||||
Protocol: constants.UDP,
|
||||
Regions: []string{"a", "b"},
|
||||
Cities: []string{"c", "d"},
|
||||
Hostnames: []string{"e", "f"},
|
||||
CustomPort: 1,
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Windscribe settings:",
|
||||
" |--Network protocol: udp",
|
||||
" |--Regions: a, b",
|
||||
" |--Cities: c, d",
|
||||
" |--Hostnames: e, f",
|
||||
" |--Custom port: 1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
lines := testCase.settings.lines()
|
||||
|
||||
assert.Equal(t, testCase.lines, lines)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_readProtocol(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
mockStr string
|
||||
mockErr error
|
||||
protocol models.NetworkProtocol
|
||||
err error
|
||||
}{
|
||||
"error": {
|
||||
mockErr: errDummy,
|
||||
err: errDummy,
|
||||
},
|
||||
"success": {
|
||||
mockStr: "tcp",
|
||||
protocol: constants.TCP,
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
env := mock_params.NewMockEnv(ctrl)
|
||||
env.EXPECT().
|
||||
Inside("PROTOCOL", []string{"tcp", "udp"}, gomock.Any()).
|
||||
Return(testCase.mockStr, testCase.mockErr)
|
||||
|
||||
protocol, err := readProtocol(env)
|
||||
|
||||
if testCase.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testCase.protocol, protocol)
|
||||
})
|
||||
}
|
||||
}
|
||||
48
internal/configuration/publicip.go
Normal file
48
internal/configuration/publicip.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
type PublicIP struct {
|
||||
Period time.Duration `json:"period"`
|
||||
IPFilepath models.Filepath `json:"ip_filepath"`
|
||||
}
|
||||
|
||||
func (settings *PublicIP) String() string {
|
||||
return strings.Join(settings.lines(), "\n")
|
||||
}
|
||||
|
||||
func (settings *PublicIP) lines() (lines []string) {
|
||||
if settings.Period == 0 {
|
||||
lines = append(lines, lastIndent+"Public IP getter: disabled")
|
||||
return lines
|
||||
}
|
||||
|
||||
lines = append(lines, lastIndent+"Public IP getter:")
|
||||
lines = append(lines, indent+lastIndent+"Fetch period: "+settings.Period.String())
|
||||
lines = append(lines, indent+lastIndent+"IP file: "+string(settings.IPFilepath))
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *PublicIP) read(r reader) (err error) {
|
||||
settings.Period, err = r.env.Duration("PUBLICIP_PERIOD", params.Default("12h"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filepathStr, err := r.env.Path("PUBLICIP_FILE", params.CaseSensitiveValue(),
|
||||
params.Default("/tmp/gluetun/ip"),
|
||||
params.RetroKeys([]string{"IP_STATUS_FILE"}, r.onRetroActive))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings.IPFilepath = models.Filepath(filepathStr)
|
||||
|
||||
return nil
|
||||
}
|
||||
52
internal/configuration/purevpn.go
Normal file
52
internal/configuration/purevpn.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func (settings *Provider) purevpnLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Regions) > 0 {
|
||||
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Countries) > 0 {
|
||||
lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Cities) > 0 {
|
||||
lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readPurevpn(r reader) (err error) {
|
||||
settings.Name = constants.Purevpn
|
||||
|
||||
settings.ServerSelection.Protocol, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.PurevpnRegionChoices())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.PurevpnCountryChoices())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.PurevpnCityChoices())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
129
internal/configuration/reader.go
Normal file
129
internal/configuration/reader.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/os"
|
||||
"github.com/qdm12/golibs/params"
|
||||
"github.com/qdm12/golibs/verification"
|
||||
)
|
||||
|
||||
type reader struct {
|
||||
env params.Env
|
||||
logger logging.Logger
|
||||
regex verification.Regex
|
||||
os os.OS
|
||||
}
|
||||
|
||||
func newReader(env params.Env, os os.OS, logger logging.Logger) reader {
|
||||
return reader{
|
||||
env: env,
|
||||
logger: logger,
|
||||
regex: verification.NewRegex(),
|
||||
os: os,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *reader) onRetroActive(oldKey, newKey string) {
|
||||
r.logger.Warn(
|
||||
"You are using the old environment variable %s, please consider changing it to %s",
|
||||
oldKey, newKey,
|
||||
)
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidPort = errors.New("invalid port")
|
||||
)
|
||||
|
||||
func readCSVPorts(env params.Env, key string) (ports []uint16, err error) {
|
||||
s, err := env.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(s) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
portsStr := strings.Split(s, ",")
|
||||
ports = make([]uint16, len(portsStr))
|
||||
for i, portStr := range portsStr {
|
||||
portInt, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %q from environment variable %s: %s",
|
||||
ErrInvalidPort, portStr, key, err)
|
||||
} else if portInt <= 0 || portInt > 65535 {
|
||||
return nil, fmt.Errorf("%w: %d from environment variable %s: must be between 1 and 65535",
|
||||
ErrInvalidPort, portInt, key)
|
||||
}
|
||||
ports[i] = uint16(portInt)
|
||||
}
|
||||
|
||||
return ports, nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidIPNet = errors.New("invalid IP network")
|
||||
)
|
||||
|
||||
func readCSVIPNets(env params.Env, key string, options ...params.OptionSetter) (
|
||||
ipNets []net.IPNet, err error) {
|
||||
s, err := env.Get(key, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if s == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ipNetsStr := strings.Split(s, ",")
|
||||
ipNets = make([]net.IPNet, len(ipNetsStr))
|
||||
for i, ipNetStr := range ipNetsStr {
|
||||
_, ipNet, err := net.ParseCIDR(ipNetStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %q from environment variable %s: %s",
|
||||
ErrInvalidIPNet, ipNetStr, key, err)
|
||||
} else if ipNet == nil {
|
||||
return nil, fmt.Errorf("%w: %q from environment variable %s: subnet is nil",
|
||||
ErrInvalidIPNet, ipNetStr, key)
|
||||
}
|
||||
ipNets[i] = *ipNet
|
||||
}
|
||||
|
||||
return ipNets, nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidIP = errors.New("invalid IP address")
|
||||
)
|
||||
|
||||
func readIP(env params.Env, key string) (ip net.IP, err error) {
|
||||
s, err := env.Get(key)
|
||||
if len(s) == 0 {
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ip = net.ParseIP(s)
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("%w: %s", ErrInvalidIP, s)
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
func readPortOrZero(env params.Env, key string) (port uint16, err error) {
|
||||
s, err := env.Get(key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if len(s) == 0 || s == "0" {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return env.Port(key)
|
||||
}
|
||||
109
internal/configuration/secrets.go
Normal file
109
internal/configuration/secrets.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/golibs/os"
|
||||
"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 := []params.OptionSetter{
|
||||
params.Compulsory(), // to fallback on file reading
|
||||
params.CaseSensitiveValue(),
|
||||
params.Unset(),
|
||||
params.RetroKeys(retroKeys, r.onRetroActive),
|
||||
}
|
||||
value, envErr := r.env.Get(envKey, envOptions...)
|
||||
if envErr == nil {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
defaultSecretFile := "/run/secrets/" + strings.ToLower(envKey)
|
||||
filepath, err := r.env.Get(envKey+"_SECRETFILE",
|
||||
params.CaseSensitiveValue(),
|
||||
params.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)
|
||||
value = strings.TrimSuffix(value, "\n")
|
||||
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.env.Get(strings.ToUpper(secretName)+"_SECRETFILE",
|
||||
params.CaseSensitiveValue(),
|
||||
params.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
|
||||
}
|
||||
55
internal/configuration/selection.go
Normal file
55
internal/configuration/selection.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
type ServerSelection struct {
|
||||
// Common
|
||||
Protocol models.NetworkProtocol `json:"network_protocol"`
|
||||
TargetIP net.IP `json:"target_ip,omitempty"`
|
||||
|
||||
// Cyberghost, PIA, Surfshark, Windscribe, Vyprvpn, NordVPN
|
||||
Regions []string `json:"regions"`
|
||||
|
||||
// Cyberghost
|
||||
Group string `json:"group"`
|
||||
|
||||
Countries []string `json:"countries"` // Mullvad, PureVPN
|
||||
Cities []string `json:"cities"` // Mullvad, PureVPN, Windscribe
|
||||
Hostnames []string `json:"hostnames"` // Windscribe, Privado
|
||||
|
||||
// Mullvad
|
||||
ISPs []string `json:"isps"`
|
||||
Owned bool `json:"owned"`
|
||||
|
||||
// Mullvad, Windscribe, PIA
|
||||
CustomPort uint16 `json:"custom_port"`
|
||||
|
||||
// NordVPN
|
||||
Numbers []uint16 `json:"numbers"`
|
||||
|
||||
// PIA
|
||||
EncryptionPreset string `json:"encryption_preset"`
|
||||
}
|
||||
|
||||
type ExtraConfigOptions struct {
|
||||
ClientCertificate string `json:"-"` // Cyberghost
|
||||
ClientKey string `json:"-"` // Cyberghost
|
||||
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"`
|
||||
Filepath models.Filepath `json:"filepath"`
|
||||
}
|
||||
|
||||
func (p *PortForwarding) lines() (lines []string) {
|
||||
return []string{
|
||||
lastIndent + "File path: " + string(p.Filepath),
|
||||
}
|
||||
}
|
||||
49
internal/configuration/server.go
Normal file
49
internal/configuration/server.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
// ControlServer contains settings to customize the control server operation.
|
||||
type ControlServer struct {
|
||||
Port uint16
|
||||
Log bool
|
||||
}
|
||||
|
||||
func (settings *ControlServer) String() string {
|
||||
return strings.Join(settings.lines(), "\n")
|
||||
}
|
||||
|
||||
func (settings *ControlServer) lines() (lines []string) {
|
||||
lines = append(lines, lastIndent+"HTTP control server:")
|
||||
|
||||
lines = append(lines, indent+lastIndent+"Listening port: "+strconv.Itoa(int(settings.Port)))
|
||||
|
||||
if settings.Log {
|
||||
lines = append(lines, indent+lastIndent+"Logging: enabled")
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *ControlServer) read(r reader) (err error) {
|
||||
settings.Log, err = r.env.OnOff("HTTP_CONTROL_SERVER_LOG", params.Default("on"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var warning string
|
||||
settings.Port, warning, err = r.env.ListeningPort(
|
||||
"HTTP_CONTROL_SERVER_PORT", params.Default("8000"))
|
||||
if len(warning) > 0 {
|
||||
r.logger.Warn(warning)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
93
internal/configuration/settings.go
Normal file
93
internal/configuration/settings.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/os"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
// Settings contains all settings for the program to run.
|
||||
type Settings struct {
|
||||
OpenVPN OpenVPN
|
||||
System System
|
||||
DNS DNS
|
||||
Firewall Firewall
|
||||
HTTPProxy HTTPProxy
|
||||
ShadowSocks ShadowSocks
|
||||
Updater Updater
|
||||
PublicIP PublicIP
|
||||
VersionInformation bool
|
||||
ControlServer ControlServer
|
||||
}
|
||||
|
||||
func (settings *Settings) String() string {
|
||||
return strings.Join(settings.lines(), "\n")
|
||||
}
|
||||
|
||||
func (settings *Settings) lines() (lines []string) {
|
||||
lines = append(lines, "Settings summary below:")
|
||||
lines = append(lines, settings.OpenVPN.lines()...)
|
||||
lines = append(lines, settings.DNS.lines()...)
|
||||
lines = append(lines, settings.Firewall.lines()...)
|
||||
lines = append(lines, settings.System.lines()...)
|
||||
lines = append(lines, settings.HTTPProxy.lines()...)
|
||||
lines = append(lines, settings.ShadowSocks.lines()...)
|
||||
lines = append(lines, settings.ControlServer.lines()...)
|
||||
lines = append(lines, settings.Updater.lines()...)
|
||||
lines = append(lines, settings.PublicIP.lines()...)
|
||||
if settings.VersionInformation {
|
||||
lines = append(lines, lastIndent+"Github version information: enabled")
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
// Read obtains all configuration options for the program and returns an error as soon
|
||||
// as an error is encountered reading them.
|
||||
func (settings *Settings) Read(env params.Env, os os.OS, logger logging.Logger) (err error) {
|
||||
r := newReader(env, os, logger)
|
||||
|
||||
settings.VersionInformation, err = r.env.OnOff("VERSION_INFORMATION", params.Default("on"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := settings.OpenVPN.read(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := settings.System.read(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := settings.DNS.read(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := settings.Firewall.read(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := settings.HTTPProxy.read(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := settings.ShadowSocks.read(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := settings.ControlServer.read(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := settings.Updater.read(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := settings.PublicIP.read(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
55
internal/configuration/settings_test.go
Normal file
55
internal/configuration/settings_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Settings_lines(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
settings Settings
|
||||
lines []string
|
||||
}{
|
||||
"default settings": {
|
||||
settings: Settings{
|
||||
OpenVPN: OpenVPN{
|
||||
Provider: Provider{
|
||||
Name: constants.Mullvad,
|
||||
},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"Settings summary below:",
|
||||
"|--OpenVPN:",
|
||||
" |--Verbosity level: 0",
|
||||
" |--Provider:",
|
||||
" |--Mullvad settings:",
|
||||
" |--Network protocol: ",
|
||||
"|--DNS:",
|
||||
"|--Firewall: disabled ⚠️",
|
||||
"|--System:",
|
||||
" |--Process user ID: 0",
|
||||
" |--Process group ID: 0",
|
||||
" |--Timezone: NOT SET ⚠️ - it can cause time related issues",
|
||||
"|--HTTP control server:",
|
||||
" |--Listening port: 0",
|
||||
"|--Public IP getter: disabled",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
lines := testCase.settings.lines()
|
||||
|
||||
assert.Equal(t, testCase.lines, lines)
|
||||
})
|
||||
}
|
||||
}
|
||||
72
internal/configuration/shadowsocks.go
Normal file
72
internal/configuration/shadowsocks.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
// ShadowSocks contains settings to configure the Shadowsocks server.
|
||||
type ShadowSocks struct {
|
||||
Method string
|
||||
Password string
|
||||
Port uint16
|
||||
Enabled bool
|
||||
Log bool
|
||||
}
|
||||
|
||||
func (settings *ShadowSocks) String() string {
|
||||
return strings.Join(settings.lines(), "\n")
|
||||
}
|
||||
|
||||
func (settings *ShadowSocks) lines() (lines []string) {
|
||||
if !settings.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
lines = append(lines, lastIndent+"Shadowsocks server:")
|
||||
|
||||
lines = append(lines, indent+lastIndent+"Listening port: "+strconv.Itoa(int(settings.Port)))
|
||||
|
||||
lines = append(lines, indent+lastIndent+"Method: "+settings.Method)
|
||||
|
||||
if settings.Log {
|
||||
lines = append(lines, indent+lastIndent+"Logging: enabled")
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *ShadowSocks) read(r reader) (err error) {
|
||||
settings.Enabled, err = r.env.OnOff("SHADOWSOCKS", params.Default("off"))
|
||||
if err != nil || !settings.Enabled {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.Password, err = r.getFromEnvOrSecretFile("SHADOWSOCKS_PASSWORD", false, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.Log, err = r.env.OnOff("SHADOWSOCKS_LOG", params.Default("off"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.Method, err = r.env.Get("SHADOWSOCKS_METHOD", params.Default("chacha20-ietf-poly1305"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var warning string
|
||||
settings.Port, warning, err = r.env.ListeningPort("SHADOWSOCKS_PORT", params.Default("8388"))
|
||||
if len(warning) > 0 {
|
||||
r.logger.Warn(warning)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
34
internal/configuration/surfshark.go
Normal file
34
internal/configuration/surfshark.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func (settings *Provider) surfsharkLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Regions) > 0 {
|
||||
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readSurfshark(r reader) (err error) {
|
||||
settings.Name = constants.Surfshark
|
||||
|
||||
settings.ServerSelection.Protocol, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.SurfsharkRegionChoices())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
53
internal/configuration/system.go
Normal file
53
internal/configuration/system.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
// System contains settings to configure system related elements.
|
||||
type System struct {
|
||||
PUID int
|
||||
PGID int
|
||||
Timezone string
|
||||
}
|
||||
|
||||
func (settings *System) String() string {
|
||||
return strings.Join(settings.lines(), "\n")
|
||||
}
|
||||
|
||||
func (settings *System) lines() (lines []string) {
|
||||
lines = append(lines, lastIndent+"System:")
|
||||
lines = append(lines, indent+lastIndent+"Process user ID: "+strconv.Itoa(settings.PUID))
|
||||
lines = append(lines, indent+lastIndent+"Process group ID: "+strconv.Itoa(settings.PGID))
|
||||
|
||||
if len(settings.Timezone) > 0 {
|
||||
lines = append(lines, indent+lastIndent+"Timezone: "+settings.Timezone)
|
||||
} else {
|
||||
lines = append(lines, indent+lastIndent+"Timezone: NOT SET ⚠️ - it can cause time related issues")
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *System) read(r reader) (err error) {
|
||||
settings.PUID, err = r.env.IntRange("PUID", 0, 65535, params.Default("1000"),
|
||||
params.RetroKeys([]string{"UID"}, r.onRetroActive))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.PGID, err = r.env.IntRange("PGID", 0, 65535, params.Default("1000"),
|
||||
params.RetroKeys([]string{"GID"}, r.onRetroActive))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.Timezone, err = r.env.Get("TZ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
133
internal/configuration/unbound.go
Normal file
133
internal/configuration/unbound.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
unbound "github.com/qdm12/dns/pkg/unbound"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
func (settings *DNS) readUnbound(r reader) (err error) {
|
||||
if err := settings.readUnboundProviders(r.env); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.Unbound.ListeningPort = 53
|
||||
|
||||
settings.Unbound.Caching, err = r.env.OnOff("DOT_CACHING", params.Default("on"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.Unbound.IPv4 = true
|
||||
|
||||
settings.Unbound.IPv6, err = r.env.OnOff("DOT_IPV6", params.Default("off"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
verbosityLevel, err := r.env.IntRange("DOT_VERBOSITY", 0, 5, params.Default("1"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings.Unbound.VerbosityLevel = uint8(verbosityLevel)
|
||||
|
||||
verbosityDetailsLevel, err := r.env.IntRange("DOT_VERBOSITY_DETAILS", 0, 4, params.Default("0"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings.Unbound.VerbosityDetailsLevel = uint8(verbosityDetailsLevel)
|
||||
|
||||
validationLogLevel, err := r.env.IntRange("DOT_VALIDATION_LOGLEVEL", 0, 2, params.Default("0"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings.Unbound.ValidationLogLevel = uint8(validationLogLevel)
|
||||
|
||||
if err := settings.readUnboundPrivateAddresses(r.env); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := settings.readUnboundUnblockedHostnames(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.Unbound.AccessControl.Allowed = []net.IPNet{
|
||||
{
|
||||
IP: net.IPv4zero,
|
||||
Mask: net.IPv4Mask(0, 0, 0, 0),
|
||||
},
|
||||
{
|
||||
IP: net.IPv6zero,
|
||||
Mask: net.IPMask{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidDNSOverTLSProvider = errors.New("invalid DNS over TLS provider")
|
||||
)
|
||||
|
||||
func (settings *DNS) readUnboundProviders(env params.Env) (err error) {
|
||||
s, err := env.Get("DOT_PROVIDERS", params.Default("cloudflare"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, provider := range strings.Split(s, ",") {
|
||||
_, ok := unbound.GetProviderData(provider)
|
||||
if !ok {
|
||||
return fmt.Errorf("%w: %s", ErrInvalidDNSOverTLSProvider, provider)
|
||||
}
|
||||
settings.Unbound.Providers = append(settings.Unbound.Providers, provider)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidPrivateAddress = errors.New("private address is not a valid IP or CIDR range")
|
||||
)
|
||||
|
||||
func (settings *DNS) readUnboundPrivateAddresses(env params.Env) (err error) {
|
||||
privateAddresses, err := env.CSV("DOT_PRIVATE_ADDRESS")
|
||||
if err != nil {
|
||||
return err
|
||||
} else if len(privateAddresses) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, address := range privateAddresses {
|
||||
ip := net.ParseIP(address)
|
||||
_, _, err := net.ParseCIDR(address)
|
||||
if ip == nil && err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrInvalidPrivateAddress, address)
|
||||
}
|
||||
}
|
||||
settings.Unbound.BlockedIPs = append(
|
||||
settings.Unbound.BlockedIPs, privateAddresses...)
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidHostname = errors.New("invalid hostname")
|
||||
)
|
||||
|
||||
func (settings *DNS) readUnboundUnblockedHostnames(r reader) (err error) {
|
||||
hostnames, err := r.env.CSV("UNBLOCK")
|
||||
if err != nil {
|
||||
return err
|
||||
} else if len(hostnames) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, hostname := range hostnames {
|
||||
if !r.regex.MatchHostname(hostname) {
|
||||
return fmt.Errorf("%w: %s", ErrInvalidHostname, hostname)
|
||||
}
|
||||
}
|
||||
settings.Unbound.AllowedHostnames = append(
|
||||
settings.Unbound.AllowedHostnames, hostnames...)
|
||||
return nil
|
||||
}
|
||||
62
internal/configuration/updater.go
Normal file
62
internal/configuration/updater.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
type Updater struct {
|
||||
Period time.Duration `json:"period"`
|
||||
DNSAddress string `json:"dns_address"`
|
||||
Cyberghost bool `json:"cyberghost"`
|
||||
Mullvad bool `json:"mullvad"`
|
||||
Nordvpn bool `json:"nordvpn"`
|
||||
PIA bool `json:"pia"`
|
||||
Privado bool `json:"privado"`
|
||||
Purevpn bool `json:"purevpn"`
|
||||
Surfshark bool `json:"surfshark"`
|
||||
Vyprvpn bool `json:"vyprvpn"`
|
||||
Windscribe bool `json:"windscribe"`
|
||||
// The two below should be used in CLI mode only
|
||||
Stdout bool `json:"-"` // in order to update constants file (maintainer side)
|
||||
CLI bool `json:"-"`
|
||||
}
|
||||
|
||||
func (settings *Updater) String() string {
|
||||
return strings.Join(settings.lines(), "\n")
|
||||
}
|
||||
|
||||
func (settings *Updater) lines() (lines []string) {
|
||||
if settings.Period == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
lines = append(lines, lastIndent+"Updater:")
|
||||
|
||||
lines = append(lines, indent+lastIndent+"Period: every "+settings.Period.String())
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Updater) read(r reader) (err error) {
|
||||
settings.Cyberghost = true
|
||||
settings.Mullvad = true
|
||||
settings.Nordvpn = true
|
||||
settings.PIA = true
|
||||
settings.Purevpn = true
|
||||
settings.Surfshark = true
|
||||
settings.Vyprvpn = true
|
||||
settings.Windscribe = true
|
||||
settings.Stdout = false
|
||||
settings.CLI = false
|
||||
settings.DNSAddress = "127.0.0.1"
|
||||
|
||||
settings.Period, err = r.env.Duration("UPDATER_PERIOD", params.Default("0"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
34
internal/configuration/vyprvpn.go
Normal file
34
internal/configuration/vyprvpn.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func (settings *Provider) vyprvpnLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Regions) > 0 {
|
||||
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readVyprvpn(r reader) (err error) {
|
||||
settings.Name = constants.Vyprvpn
|
||||
|
||||
settings.ServerSelection.Protocol, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.VyprvpnRegionChoices())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
65
internal/configuration/windscribe.go
Normal file
65
internal/configuration/windscribe.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
func (settings *Provider) windscribeLines() (lines []string) {
|
||||
if len(settings.ServerSelection.Regions) > 0 {
|
||||
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Cities) > 0 {
|
||||
lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities))
|
||||
}
|
||||
|
||||
if len(settings.ServerSelection.Hostnames) > 0 {
|
||||
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
|
||||
}
|
||||
|
||||
lines = append(lines, lastIndent+"Custom port: "+strconv.Itoa(int(settings.ServerSelection.CustomPort)))
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (settings *Provider) readWindscribe(r reader) (err error) {
|
||||
settings.Name = constants.Windscribe
|
||||
|
||||
settings.ServerSelection.Protocol, err = readProtocol(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.WindscribeRegionChoices())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.WindscribeCityChoices())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
|
||||
constants.WindscribeHostnameChoices(), params.RetroKeys([]string{"HOSTNAME"}, r.onRetroActive))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.CustomPort, err = readCustomPort(r.env, settings.ServerSelection.Protocol,
|
||||
[]uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783},
|
||||
[]uint16{53, 80, 123, 443, 1194, 54783})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user