chore(config): upgrade to gosettings v0.4.0

- drop qdm12/govalid dependency
- upgrade qdm12/ss-server to v0.6.0
- do not unset sensitive config settings (makes no sense to me)
This commit is contained in:
Quentin McGaw
2024-03-25 19:14:20 +00:00
parent 23b0320cfb
commit ecc80a5a9e
88 changed files with 1371 additions and 2621 deletions

View File

@@ -1,5 +0,0 @@
package files
import "github.com/qdm12/gluetun/internal/configuration/settings"
func (s *Source) ReadHealth() (settings settings.Health, err error) { return settings, nil }

View File

@@ -9,47 +9,45 @@ import (
"github.com/qdm12/gluetun/internal/openvpn/extract"
)
// ReadFromFile reads the content of the file as a string.
// It returns a nil string pointer if the file does not exist.
func ReadFromFile(filepath string) (s *string, err error) {
// ReadFromFile reads the content of the file as a string,
// and returns if the file was present or not with isSet.
func ReadFromFile(filepath string) (content string, isSet bool, err error) {
file, err := os.Open(filepath)
if err != nil {
if os.IsNotExist(err) {
return nil, nil //nolint:nilnil
return "", false, nil
}
return nil, err
return "", false, fmt.Errorf("opening file: %w", err)
}
b, err := io.ReadAll(file)
if err != nil {
_ = file.Close()
return nil, err
return "", false, fmt.Errorf("reading file: %w", err)
}
if err := file.Close(); err != nil {
return nil, err
return "", false, fmt.Errorf("closing file: %w", err)
}
content := string(b)
content = string(b)
content = strings.TrimSuffix(content, "\r\n")
content = strings.TrimSuffix(content, "\n")
return &content, nil
return content, true, nil
}
func readPEMFile(filepath string) (base64Ptr *string, err error) {
pemData, err := ReadFromFile(filepath)
func ReadPEMFile(filepath string) (base64Str string, isSet bool, err error) {
pemData, isSet, err := ReadFromFile(filepath)
if err != nil {
return nil, fmt.Errorf("reading file: %w", err)
return "", false, fmt.Errorf("reading file: %w", err)
} else if !isSet {
return "", false, nil
}
if pemData == nil {
return nil, nil //nolint:nilnil
}
base64Data, err := extract.PEM([]byte(*pemData))
base64Str, err = extract.PEM([]byte(pemData))
if err != nil {
return nil, fmt.Errorf("extracting base64 encoded data from PEM content: %w", err)
return "", false, fmt.Errorf("extracting base64 encoded data from PEM content: %w", err)
}
return &base64Data, nil
return base64Str, true, nil
}

View File

@@ -1,3 +0,0 @@
package files
func ptrTo[T any](x T) *T { return &x }

View File

@@ -0,0 +1,5 @@
package files
type Warner interface {
Warnf(format string, a ...interface{})
}

View File

@@ -1,33 +0,0 @@
package files
import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
const (
// OpenVPNClientKeyPath is the OpenVPN client key filepath.
OpenVPNClientKeyPath = "/gluetun/client.key"
// OpenVPNClientCertificatePath is the OpenVPN client certificate filepath.
OpenVPNClientCertificatePath = "/gluetun/client.crt"
openVPNEncryptedKey = "/gluetun/openvpn_encrypted_key"
)
func (s *Source) readOpenVPN() (settings settings.OpenVPN, err error) {
settings.Key, err = readPEMFile(OpenVPNClientKeyPath)
if err != nil {
return settings, fmt.Errorf("client key: %w", err)
}
settings.Cert, err = readPEMFile(OpenVPNClientCertificatePath)
if err != nil {
return settings, fmt.Errorf("client certificate: %w", err)
}
settings.EncryptedKey, err = readPEMFile(openVPNEncryptedKey)
if err != nil {
return settings, fmt.Errorf("reading encrypted key file: %w", err)
}
return settings, nil
}

View File

@@ -1,16 +0,0 @@
package files
import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (s *Source) readProvider() (provider settings.Provider, err error) {
provider.ServerSelection, err = s.readServerSelection()
if err != nil {
return provider, fmt.Errorf("server selection: %w", err)
}
return provider, nil
}

View File

@@ -1,32 +1,99 @@
package files
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"os"
"path/filepath"
"strings"
)
type Source struct {
wireguardConfigPath string
rootDirectory string
environ map[string]string
warner Warner
cached struct {
wireguardLoaded bool
wireguardConf WireguardConfig
}
}
func New() *Source {
const wireguardConfigPath = "/gluetun/wireguard/wg0.conf"
func New(warner Warner) (source *Source) {
osEnviron := os.Environ()
environ := make(map[string]string, len(osEnviron))
for _, pair := range osEnviron {
const maxSplit = 2
split := strings.SplitN(pair, "=", maxSplit)
environ[split[0]] = split[1]
}
return &Source{
wireguardConfigPath: wireguardConfigPath,
rootDirectory: "/gluetun",
environ: environ,
warner: warner,
}
}
func (s *Source) String() string { return "files" }
func (s *Source) Read() (settings settings.Settings, err error) {
settings.VPN, err = s.readVPN()
if err != nil {
return settings, err
func (s *Source) Get(key string) (value string, isSet bool) {
if key == "" {
return "", false
}
// TODO v4 custom environment variable to set the files parent directory
// and not to set each file to a specific path
envKey := strings.ToUpper(key)
envKey = strings.ReplaceAll(envKey, "-", "_")
envKey += "_FILE"
path := s.environ[envKey]
if path == "" {
path = filepath.Join(s.rootDirectory, key)
}
settings.System, err = s.readSystem()
if err != nil {
return settings, err
// Special file handling
switch key {
// TODO timezone from /etc/localtime
case "client.crt", "client.key":
value, isSet, err := ReadPEMFile(path)
if err != nil {
s.warner.Warnf("skipping %s: parsing PEM: %s", path, err)
}
return value, isSet
case "wireguard_private_key":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().PrivateKey)
case "wireguard_preshared_key":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().PreSharedKey)
case "wireguard_addresses":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().Addresses)
case "wireguard_public_key":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().PublicKey)
case "vpn_endpoint_ip":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointIP)
case "vpn_endpoint_port":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointPort)
}
return settings, nil
value, isSet, err := ReadFromFile(path)
if err != nil {
s.warner.Warnf("skipping %s: reading file: %s", path, err)
}
return value, isSet
}
func (s *Source) KeyTransform(key string) string {
switch key {
// TODO v4 remove these irregular cases
case "OPENVPN_KEY":
return "client.key"
case "OPENVPN_CERT":
return "client.crt"
default:
key = strings.ToLower(key) // HTTPROXY_USER -> httpproxy_user
return key
}
}
func strPtrToStringIsSet(ptr *string) (s string, isSet bool) {
if ptr == nil {
return "", false
}
return *ptr, true
}

View File

@@ -1,16 +0,0 @@
package files
import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (s *Source) readServerSelection() (selection settings.ServerSelection, err error) {
selection.Wireguard, err = s.readWireguardSelection()
if err != nil {
return selection, fmt.Errorf("wireguard: %w", err)
}
return selection, nil
}

View File

@@ -1,10 +0,0 @@
package files
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (s *Source) readSystem() (system settings.System, err error) {
// TODO timezone from /etc/localtime
return system, nil
}

View File

@@ -1,26 +0,0 @@
package files
import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (s *Source) readVPN() (vpn settings.VPN, err error) {
vpn.Provider, err = s.readProvider()
if err != nil {
return vpn, fmt.Errorf("provider: %w", err)
}
vpn.OpenVPN, err = s.readOpenVPN()
if err != nil {
return vpn, fmt.Errorf("OpenVPN: %w", err)
}
vpn.Wireguard, err = s.readWireguard()
if err != nil {
return vpn, fmt.Errorf("wireguard: %w", err)
}
return vpn, nil
}

View File

@@ -1,124 +1,115 @@
package files
import (
"errors"
"fmt"
"net/netip"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"gopkg.in/ini.v1"
)
func (s *Source) readWireguard() (wireguard settings.Wireguard, err error) {
fileStringPtr, err := ReadFromFile(s.wireguardConfigPath)
func (s *Source) lazyLoadWireguardConf() WireguardConfig {
if s.cached.wireguardLoaded {
return s.cached.wireguardConf
}
s.cached.wireguardLoaded = true
var err error
s.cached.wireguardConf, err = ParseWireguardConf(filepath.Join(s.rootDirectory, "wg0.conf"))
if err != nil {
return wireguard, fmt.Errorf("reading file: %w", err)
s.warner.Warnf("skipping Wireguard config: %s", err)
}
return s.cached.wireguardConf
}
if fileStringPtr == nil {
return wireguard, nil
}
rawData := []byte(*fileStringPtr)
return ParseWireguardConf(rawData)
type WireguardConfig struct {
PrivateKey *string
PreSharedKey *string
Addresses *string
PublicKey *string
EndpointIP *string
EndpointPort *string
}
var (
regexINISectionNotExist = regexp.MustCompile(`^section ".+" does not exist$`)
regexINIKeyNotExist = regexp.MustCompile(`key ".*" not exists$`)
)
func ParseWireguardConf(rawData []byte) (wireguard settings.Wireguard, err error) {
iniFile, err := ini.Load(rawData)
func ParseWireguardConf(path string) (config WireguardConfig, err error) {
iniFile, err := ini.Load(path)
if err != nil {
return wireguard, fmt.Errorf("loading ini from reader: %w", err)
if errors.Is(err, os.ErrNotExist) {
return WireguardConfig{}, nil
}
return WireguardConfig{}, fmt.Errorf("loading ini from reader: %w", err)
}
interfaceSection, err := iniFile.GetSection("Interface")
if err == nil {
err = parseWireguardInterfaceSection(interfaceSection, &wireguard)
if err != nil {
return wireguard, fmt.Errorf("parsing interface section: %w", err)
}
config.PrivateKey, config.Addresses = parseWireguardInterfaceSection(interfaceSection)
} else if !regexINISectionNotExist.MatchString(err.Error()) {
// can never happen
return wireguard, fmt.Errorf("getting interface section: %w", err)
return WireguardConfig{}, fmt.Errorf("getting interface section: %w", err)
}
peerSection, err := iniFile.GetSection("Peer")
if err == nil {
wireguard.PreSharedKey, err = parseINIWireguardKey(peerSection, "PresharedKey")
if err != nil {
return wireguard, fmt.Errorf("parsing peer section: %w", err)
}
config.PreSharedKey, config.PublicKey, config.EndpointIP,
config.EndpointPort = parseWireguardPeerSection(peerSection)
} else if !regexINISectionNotExist.MatchString(err.Error()) {
// can never happen
return wireguard, fmt.Errorf("getting peer section: %w", err)
return WireguardConfig{}, fmt.Errorf("getting peer section: %w", err)
}
return wireguard, nil
return config, nil
}
func parseWireguardInterfaceSection(interfaceSection *ini.Section,
wireguard *settings.Wireguard) (err error) {
wireguard.PrivateKey, err = parseINIWireguardKey(interfaceSection, "PrivateKey")
if err != nil {
return err // error is already wrapped correctly
}
wireguard.Addresses, err = parseINIWireguardAddress(interfaceSection)
if err != nil {
return err // error is already wrapped correctly
}
return nil
func parseWireguardInterfaceSection(interfaceSection *ini.Section) (
privateKey, addresses *string) {
privateKey = getINIKeyFromSection(interfaceSection, "PrivateKey")
addresses = getINIKeyFromSection(interfaceSection, "Address")
return privateKey, addresses
}
func parseINIWireguardKey(section *ini.Section, keyName string) (
key *string, err error) {
iniKey, err := section.GetKey(keyName)
var (
ErrEndpointHostNotIP = errors.New("endpoint host is not an IP")
)
func parseWireguardPeerSection(peerSection *ini.Section) (
preSharedKey, publicKey, endpointIP, endpointPort *string) {
preSharedKey = getINIKeyFromSection(peerSection, "PresharedKey")
publicKey = getINIKeyFromSection(peerSection, "PublicKey")
endpoint := getINIKeyFromSection(peerSection, "Endpoint")
if endpoint != nil {
parts := strings.Split(*endpoint, ":")
endpointIP = &parts[0]
const partsWithPort = 2
if len(parts) >= partsWithPort {
endpointPort = new(string)
*endpointPort = strings.Join(parts[1:], ":")
}
}
return preSharedKey, publicKey, endpointIP, endpointPort
}
var (
regexINIKeyNotExist = regexp.MustCompile(`key ".*" not exists$`)
)
func getINIKeyFromSection(section *ini.Section, key string) (value *string) {
iniKey, err := section.GetKey(key)
if err != nil {
if regexINIKeyNotExist.MatchString(err.Error()) {
return nil, nil //nolint:nilnil
return nil
}
// can never happen
return nil, fmt.Errorf("getting %s key: %w", keyName, err)
panic(fmt.Sprintf("getting key %q: %s", key, err))
}
key = new(string)
*key = iniKey.String()
_, err = wgtypes.ParseKey(*key)
if err != nil {
return nil, fmt.Errorf("parsing %s: %s: %w", keyName, *key, err)
}
return key, nil
}
func parseINIWireguardAddress(section *ini.Section) (
addresses []netip.Prefix, err error) {
addressKey, err := section.GetKey("Address")
if err != nil {
if regexINIKeyNotExist.MatchString(err.Error()) {
return nil, nil
}
// can never happen
return nil, fmt.Errorf("getting Address key: %w", err)
}
addressStrings := strings.Split(addressKey.String(), ",")
addresses = make([]netip.Prefix, len(addressStrings))
for i, addressString := range addressStrings {
addressString = strings.TrimSpace(addressString)
if !strings.ContainsRune(addressString, '/') {
addressString += "/32"
}
addresses[i], err = netip.ParsePrefix(addressString)
if err != nil {
return nil, fmt.Errorf("parsing address: %w", err)
}
}
return addresses, nil
value = new(string)
*value = iniKey.String()
return value
}

View File

@@ -1,48 +1,42 @@
package files
import (
"net/netip"
"os"
"path/filepath"
"testing"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/ini.v1"
)
func Test_Source_readWireguard(t *testing.T) {
func ptrTo[T any](value T) *T { return &value }
func Test_Source_ParseWireguardConf(t *testing.T) {
t.Parallel()
t.Run("fail reading from file", func(t *testing.T) {
t.Parallel()
dirPath := t.TempDir()
source := &Source{
wireguardConfigPath: dirPath,
}
wireguard, err := source.readWireguard()
assert.Equal(t, settings.Wireguard{}, wireguard)
wireguard, err := ParseWireguardConf(dirPath)
assert.Equal(t, WireguardConfig{}, wireguard)
assert.Error(t, err)
assert.Regexp(t, `reading file: read .+: is a directory`, err.Error())
assert.Regexp(t, `loading ini from reader: BOM: read .+: is a directory`, err.Error())
})
t.Run("no file", func(t *testing.T) {
t.Parallel()
noFile := filepath.Join(t.TempDir(), "doesnotexist")
source := &Source{
wireguardConfigPath: noFile,
}
wireguard, err := source.readWireguard()
assert.Equal(t, settings.Wireguard{}, wireguard)
wireguard, err := ParseWireguardConf(noFile)
assert.Equal(t, WireguardConfig{}, wireguard)
assert.NoError(t, err)
})
testCases := map[string]struct {
fileContent string
wireguard settings.Wireguard
wireguard WireguardConfig
errMessage string
}{
"ini load error": {
@@ -50,14 +44,14 @@ func Test_Source_readWireguard(t *testing.T) {
errMessage: "loading ini from reader: key-value delimiter not found: invalid",
},
"empty file": {},
"interface section parsing error": {
"interface_section_missing": {
fileContent: `
[Interface]
PrivateKey = x
[Peer]
PresharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g=
`,
errMessage: "parsing interface section: parsing PrivateKey: " +
"x: wgtypes: failed to parse base64-encoded key: " +
"illegal base64 data at input byte 0",
wireguard: WireguardConfig{
PreSharedKey: ptrTo("YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g="),
},
},
"success": {
fileContent: `
@@ -69,12 +63,10 @@ DNS = 193.138.218.74
[Peer]
PresharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g=
`,
wireguard: settings.Wireguard{
wireguard: WireguardConfig{
PrivateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
PreSharedKey: ptrTo("YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g="),
Addresses: []netip.Prefix{
netip.PrefixFrom(netip.AddrFrom4([4]byte{10, 38, 22, 35}), 32),
},
Addresses: ptrTo("10.38.22.35/32"),
},
},
}
@@ -88,11 +80,7 @@ PresharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g=
err := os.WriteFile(configFile, []byte(testCase.fileContent), 0600)
require.NoError(t, err)
source := &Source{
wireguardConfigPath: configFile,
}
wireguard, err := source.readWireguard()
wireguard, err := ParseWireguardConf(configFile)
assert.Equal(t, testCase.wireguard, wireguard)
if testCase.errMessage != "" {
@@ -109,34 +97,26 @@ func Test_parseWireguardInterfaceSection(t *testing.T) {
testCases := map[string]struct {
iniData string
wireguard settings.Wireguard
errMessage string
privateKey *string
addresses *string
}{
"private key error": {
iniData: `[Interface]
PrivateKey = x`,
errMessage: "parsing PrivateKey: x: " +
"wgtypes: failed to parse base64-encoded key: " +
"illegal base64 data at input byte 0",
"no_fields": {
iniData: `[Interface]`,
},
"address error": {
"only_private_key": {
iniData: `[Interface]
Address = x
PrivateKey = x
`,
errMessage: "parsing address: netip.ParsePrefix(\"x/32\"): ParseAddr(\"x\"): unable to parse IP",
privateKey: ptrTo("x"),
},
"success": {
"all_fields": {
iniData: `
[Interface]
PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
Address = 10.38.22.35/32
`,
wireguard: settings.Wireguard{
PrivateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
Addresses: []netip.Prefix{
netip.PrefixFrom(netip.AddrFrom4([4]byte{10, 38, 22, 35}), 32),
},
},
privateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
addresses: ptrTo("10.38.22.35/32"),
},
}
@@ -150,109 +130,74 @@ Address = 10.38.22.35/32
iniSection, err := iniFile.GetSection("Interface")
require.NoError(t, err)
var wireguard settings.Wireguard
err = parseWireguardInterfaceSection(iniSection, &wireguard)
assert.Equal(t, testCase.wireguard, wireguard)
if testCase.errMessage != "" {
assert.EqualError(t, err, testCase.errMessage)
} else {
assert.NoError(t, err)
}
})
}
}
func Test_parseINIWireguardKey(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
fileContent string
keyName string
key *string
errMessage string
}{
"key does not exist": {
fileContent: `[Interface]`,
keyName: "PrivateKey",
},
"bad Wireguard key": {
fileContent: `[Interface]
PrivateKey = x`,
keyName: "PrivateKey",
errMessage: "parsing PrivateKey: x: " +
"wgtypes: failed to parse base64-encoded key: " +
"illegal base64 data at input byte 0",
},
"success": {
fileContent: `[Interface]
PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=`,
keyName: "PrivateKey",
key: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
},
}
for testName, testCase := range testCases {
testCase := testCase
t.Run(testName, func(t *testing.T) {
t.Parallel()
iniFile, err := ini.Load([]byte(testCase.fileContent))
require.NoError(t, err)
iniSection, err := iniFile.GetSection("Interface")
require.NoError(t, err)
key, err := parseINIWireguardKey(iniSection, testCase.keyName)
assert.Equal(t, testCase.key, key)
if testCase.errMessage != "" {
assert.EqualError(t, err, testCase.errMessage)
} else {
assert.NoError(t, err)
}
})
}
}
func Test_parseINIWireguardAddress(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
fileContent string
addresses []netip.Prefix
errMessage string
}{
"key does not exist": {
fileContent: `[Interface]`,
},
"bad address": {
fileContent: `[Interface]
Address = x`,
errMessage: "parsing address: netip.ParsePrefix(\"x/32\"): ParseAddr(\"x\"): unable to parse IP",
},
"success": {
fileContent: `[Interface]
Address = 1.2.3.4/32, 5.6.7.8/32`,
addresses: []netip.Prefix{
netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 32),
netip.PrefixFrom(netip.AddrFrom4([4]byte{5, 6, 7, 8}), 32),
},
},
}
for testName, testCase := range testCases {
testCase := testCase
t.Run(testName, func(t *testing.T) {
t.Parallel()
iniFile, err := ini.Load([]byte(testCase.fileContent))
require.NoError(t, err)
iniSection, err := iniFile.GetSection("Interface")
require.NoError(t, err)
addresses, err := parseINIWireguardAddress(iniSection)
privateKey, addresses := parseWireguardInterfaceSection(iniSection)
assert.Equal(t, testCase.privateKey, privateKey)
assert.Equal(t, testCase.addresses, addresses)
})
}
}
func Test_parseWireguardPeerSection(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
iniData string
preSharedKey *string
publicKey *string
endpointIP *string
endpointPort *string
errMessage string
}{
"public key set": {
iniData: `[Peer]
PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=`,
publicKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
},
"endpoint_only_host": {
iniData: `[Peer]
Endpoint = x`,
endpointIP: ptrTo("x"),
},
"endpoint_no_port": {
iniData: `[Peer]
Endpoint = x:`,
endpointIP: ptrTo("x"),
endpointPort: ptrTo(""),
},
"valid_endpoint": {
iniData: `[Peer]
Endpoint = 1.2.3.4:51820`,
endpointIP: ptrTo("1.2.3.4"),
endpointPort: ptrTo("51820"),
},
"all_set": {
iniData: `[Peer]
PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
Endpoint = 1.2.3.4:51820`,
publicKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
endpointIP: ptrTo("1.2.3.4"),
endpointPort: ptrTo("51820"),
},
}
for testName, testCase := range testCases {
testCase := testCase
t.Run(testName, func(t *testing.T) {
t.Parallel()
iniFile, err := ini.Load([]byte(testCase.iniData))
require.NoError(t, err)
iniSection, err := iniFile.GetSection("Peer")
require.NoError(t, err)
preSharedKey, publicKey, endpointIP,
endpointPort := parseWireguardPeerSection(iniSection)
assert.Equal(t, testCase.preSharedKey, preSharedKey)
assert.Equal(t, testCase.publicKey, publicKey)
assert.Equal(t, testCase.endpointIP, endpointIP)
assert.Equal(t, testCase.endpointPort, endpointPort)
if testCase.errMessage != "" {
assert.EqualError(t, err, testCase.errMessage)
} else {

View File

@@ -1,83 +0,0 @@
package files
import (
"errors"
"fmt"
"net"
"net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/port"
"gopkg.in/ini.v1"
)
var (
ErrEndpointHostNotIP = errors.New("endpoint host is not an IP")
)
func (s *Source) readWireguardSelection() (selection settings.WireguardSelection, err error) {
fileStringPtr, err := ReadFromFile(s.wireguardConfigPath)
if err != nil {
return selection, fmt.Errorf("reading file: %w", err)
}
if fileStringPtr == nil {
return selection, nil
}
rawData := []byte(*fileStringPtr)
iniFile, err := ini.Load(rawData)
if err != nil {
return selection, fmt.Errorf("loading ini from reader: %w", err)
}
peerSection, err := iniFile.GetSection("Peer")
if err == nil {
err = parseWireguardPeerSection(peerSection, &selection)
if err != nil {
return selection, fmt.Errorf("parsing peer section: %w", err)
}
} else if !regexINISectionNotExist.MatchString(err.Error()) {
// can never happen
return selection, fmt.Errorf("getting peer section: %w", err)
}
return selection, nil
}
func parseWireguardPeerSection(peerSection *ini.Section,
selection *settings.WireguardSelection) (err error) {
publicKeyPtr, err := parseINIWireguardKey(peerSection, "PublicKey")
if err != nil {
return err // error is already wrapped correctly
} else if publicKeyPtr != nil {
selection.PublicKey = *publicKeyPtr
}
endpointKey, err := peerSection.GetKey("Endpoint")
if err == nil {
endpoint := endpointKey.String()
host, portString, err := net.SplitHostPort(endpoint)
if err != nil {
return fmt.Errorf("splitting endpoint: %w", err)
}
ip, err := netip.ParseAddr(host)
if err != nil {
return fmt.Errorf("%w: %w", ErrEndpointHostNotIP, err)
}
endpointPort, err := port.Validate(portString)
if err != nil {
return fmt.Errorf("port from Endpoint key: %w", err)
}
selection.EndpointIP = ip
selection.EndpointPort = &endpointPort
} else if !regexINIKeyNotExist.MatchString(err.Error()) {
// can never happen
return fmt.Errorf("getting endpoint key: %w", err)
}
return nil
}

View File

@@ -1,181 +0,0 @@
package files
import (
"net/netip"
"os"
"path/filepath"
"testing"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/ini.v1"
)
func uint16Ptr(n uint16) *uint16 { return &n }
func Test_Source_readWireguardSelection(t *testing.T) {
t.Parallel()
t.Run("fail reading from file", func(t *testing.T) {
t.Parallel()
dirPath := t.TempDir()
source := &Source{
wireguardConfigPath: dirPath,
}
wireguard, err := source.readWireguardSelection()
assert.Equal(t, settings.WireguardSelection{}, wireguard)
assert.Error(t, err)
assert.Regexp(t, `reading file: read .+: is a directory`, err.Error())
})
t.Run("no file", func(t *testing.T) {
t.Parallel()
noFile := filepath.Join(t.TempDir(), "doesnotexist")
source := &Source{
wireguardConfigPath: noFile,
}
wireguard, err := source.readWireguardSelection()
assert.Equal(t, settings.WireguardSelection{}, wireguard)
assert.NoError(t, err)
})
testCases := map[string]struct {
fileContent string
selection settings.WireguardSelection
errMessage string
}{
"ini load error": {
fileContent: "invalid",
errMessage: "loading ini from reader: key-value delimiter not found: invalid",
},
"empty file": {},
"peer section parsing error": {
fileContent: `
[Peer]
PublicKey = x
`,
errMessage: "parsing peer section: parsing PublicKey: " +
"x: wgtypes: failed to parse base64-encoded key: " +
"illegal base64 data at input byte 0",
},
"success": {
fileContent: `
[Peer]
PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
Endpoint = 1.2.3.4:51820
`,
selection: settings.WireguardSelection{
PublicKey: "QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=",
EndpointIP: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
EndpointPort: uint16Ptr(51820),
},
},
}
for testName, testCase := range testCases {
testCase := testCase
t.Run(testName, func(t *testing.T) {
t.Parallel()
configFile := filepath.Join(t.TempDir(), "wg.conf")
err := os.WriteFile(configFile, []byte(testCase.fileContent), 0600)
require.NoError(t, err)
source := &Source{
wireguardConfigPath: configFile,
}
wireguard, err := source.readWireguardSelection()
assert.Equal(t, testCase.selection, wireguard)
if testCase.errMessage != "" {
assert.EqualError(t, err, testCase.errMessage)
} else {
assert.NoError(t, err)
}
})
}
}
func Test_parseWireguardPeerSection(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
iniData string
selection settings.WireguardSelection
errMessage string
}{
"public key error": {
iniData: `[Peer]
PublicKey = x`,
errMessage: "parsing PublicKey: x: " +
"wgtypes: failed to parse base64-encoded key: " +
"illegal base64 data at input byte 0",
},
"public key set": {
iniData: `[Peer]
PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=`,
selection: settings.WireguardSelection{
PublicKey: "QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=",
},
},
"missing port in endpoint": {
iniData: `[Peer]
Endpoint = x`,
errMessage: "splitting endpoint: address x: missing port in address",
},
"endpoint host is not IP": {
iniData: `[Peer]
Endpoint = website.com:51820`,
errMessage: "endpoint host is not an IP: ParseAddr(\"website.com\"): unexpected character (at \"website.com\")",
},
"endpoint port is not valid": {
iniData: `[Peer]
Endpoint = 1.2.3.4:518299`,
errMessage: "port from Endpoint key: port cannot be higher than 65535: 518299",
},
"valid endpoint": {
iniData: `[Peer]
Endpoint = 1.2.3.4:51820`,
selection: settings.WireguardSelection{
EndpointIP: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
EndpointPort: uint16Ptr(51820),
},
},
"all set": {
iniData: `[Peer]
PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
Endpoint = 1.2.3.4:51820`,
selection: settings.WireguardSelection{
PublicKey: "QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=",
EndpointIP: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
EndpointPort: uint16Ptr(51820),
},
},
}
for testName, testCase := range testCases {
testCase := testCase
t.Run(testName, func(t *testing.T) {
t.Parallel()
iniFile, err := ini.Load([]byte(testCase.iniData))
require.NoError(t, err)
iniSection, err := iniFile.GetSection("Peer")
require.NoError(t, err)
var selection settings.WireguardSelection
err = parseWireguardPeerSection(iniSection, &selection)
assert.Equal(t, testCase.selection, selection)
if testCase.errMessage != "" {
assert.EqualError(t, err, testCase.errMessage)
} else {
assert.NoError(t, err)
}
})
}
}