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:
55
internal/configuration/sources/env/dns.go
vendored
55
internal/configuration/sources/env/dns.go
vendored
@@ -1,55 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readDNS() (dns settings.DNS, err error) {
|
||||
dns.ServerAddress, err = s.readDNSServerAddress()
|
||||
if err != nil {
|
||||
return dns, err
|
||||
}
|
||||
|
||||
dns.KeepNameserver, err = s.env.BoolPtr("DNS_KEEP_NAMESERVER")
|
||||
if err != nil {
|
||||
return dns, err
|
||||
}
|
||||
|
||||
dns.DoT, err = s.readDoT()
|
||||
if err != nil {
|
||||
return dns, fmt.Errorf("DoT settings: %w", err)
|
||||
}
|
||||
|
||||
return dns, nil
|
||||
}
|
||||
|
||||
func (s *Source) readDNSServerAddress() (address netip.Addr, err error) {
|
||||
const currentKey = "DNS_ADDRESS"
|
||||
key := firstKeySet(s.env, "DNS_PLAINTEXT_ADDRESS", currentKey)
|
||||
switch key {
|
||||
case "":
|
||||
return address, nil
|
||||
case currentKey:
|
||||
default: // Retro-compatibility
|
||||
s.handleDeprecatedKey(key, currentKey)
|
||||
}
|
||||
|
||||
address, err = s.env.NetipAddr(key)
|
||||
if err != nil {
|
||||
return address, err
|
||||
}
|
||||
|
||||
// TODO remove in v4
|
||||
if address.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 {
|
||||
s.warner.Warn(key + " is set to " + address.String() +
|
||||
" so the DNS over TLS (DoT) server will not be used." +
|
||||
" The default value changed to 127.0.0.1 so it uses the internal DoT serves." +
|
||||
" If the DoT server fails to start, the IPv4 address of the first plaintext DNS server" +
|
||||
" corresponding to the first DoT provider chosen is used.")
|
||||
}
|
||||
|
||||
return address, nil
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error) {
|
||||
blacklist.BlockMalicious, err = s.env.BoolPtr("BLOCK_MALICIOUS")
|
||||
if err != nil {
|
||||
return blacklist, err
|
||||
}
|
||||
|
||||
blacklist.BlockSurveillance, err = s.env.BoolPtr("BLOCK_SURVEILLANCE",
|
||||
env.RetroKeys("BLOCK_NSA"))
|
||||
if err != nil {
|
||||
return blacklist, err
|
||||
}
|
||||
|
||||
blacklist.BlockAds, err = s.env.BoolPtr("BLOCK_ADS")
|
||||
if err != nil {
|
||||
return blacklist, err
|
||||
}
|
||||
|
||||
blacklist.AddBlockedIPs, blacklist.AddBlockedIPPrefixes,
|
||||
err = s.readDoTPrivateAddresses() // TODO v4 split in 2
|
||||
if err != nil {
|
||||
return blacklist, err
|
||||
}
|
||||
|
||||
blacklist.AllowedHosts = s.env.CSV("UNBLOCK") // TODO v4 change name
|
||||
|
||||
return blacklist, nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrPrivateAddressNotValid = errors.New("private address is not a valid IP or CIDR range")
|
||||
)
|
||||
|
||||
func (s *Source) readDoTPrivateAddresses() (ips []netip.Addr,
|
||||
ipPrefixes []netip.Prefix, err error) {
|
||||
privateAddresses := s.env.CSV("DOT_PRIVATE_ADDRESS")
|
||||
if len(privateAddresses) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
ips = make([]netip.Addr, 0, len(privateAddresses))
|
||||
ipPrefixes = make([]netip.Prefix, 0, len(privateAddresses))
|
||||
|
||||
for _, privateAddress := range privateAddresses {
|
||||
ip, err := netip.ParseAddr(privateAddress)
|
||||
if err == nil {
|
||||
ips = append(ips, ip)
|
||||
continue
|
||||
}
|
||||
|
||||
ipPrefix, err := netip.ParsePrefix(privateAddress)
|
||||
if err == nil {
|
||||
ipPrefixes = append(ipPrefixes, ipPrefix)
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf(
|
||||
"environment variable DOT_PRIVATE_ADDRESS: %w: %s",
|
||||
ErrPrivateAddressNotValid, privateAddress)
|
||||
}
|
||||
|
||||
return ips, ipPrefixes, nil
|
||||
}
|
||||
29
internal/configuration/sources/env/dot.go
vendored
29
internal/configuration/sources/env/dot.go
vendored
@@ -1,29 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readDoT() (dot settings.DoT, err error) {
|
||||
dot.Enabled, err = s.env.BoolPtr("DOT")
|
||||
if err != nil {
|
||||
return dot, err
|
||||
}
|
||||
|
||||
dot.UpdatePeriod, err = s.env.DurationPtr("DNS_UPDATE_PERIOD")
|
||||
if err != nil {
|
||||
return dot, err
|
||||
}
|
||||
|
||||
dot.Unbound, err = s.readUnbound()
|
||||
if err != nil {
|
||||
return dot, err
|
||||
}
|
||||
|
||||
dot.Blacklist, err = s.readDNSBlacklist()
|
||||
if err != nil {
|
||||
return dot, err
|
||||
}
|
||||
|
||||
return dot, nil
|
||||
}
|
||||
36
internal/configuration/sources/env/firewall.go
vendored
36
internal/configuration/sources/env/firewall.go
vendored
@@ -1,36 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readFirewall() (firewall settings.Firewall, err error) {
|
||||
firewall.VPNInputPorts, err = s.env.CSVUint16("FIREWALL_VPN_INPUT_PORTS")
|
||||
if err != nil {
|
||||
return firewall, err
|
||||
}
|
||||
|
||||
firewall.InputPorts, err = s.env.CSVUint16("FIREWALL_INPUT_PORTS")
|
||||
if err != nil {
|
||||
return firewall, err
|
||||
}
|
||||
|
||||
firewall.OutboundSubnets, err = s.env.CSVNetipPrefixes("FIREWALL_OUTBOUND_SUBNETS",
|
||||
env.RetroKeys("EXTRA_SUBNETS"))
|
||||
if err != nil {
|
||||
return firewall, err
|
||||
}
|
||||
|
||||
firewall.Enabled, err = s.env.BoolPtr("FIREWALL")
|
||||
if err != nil {
|
||||
return firewall, err
|
||||
}
|
||||
|
||||
firewall.Debug, err = s.env.BoolPtr("FIREWALL_DEBUG")
|
||||
if err != nil {
|
||||
return firewall, err
|
||||
}
|
||||
|
||||
return firewall, nil
|
||||
}
|
||||
35
internal/configuration/sources/env/health.go
vendored
35
internal/configuration/sources/env/health.go
vendored
@@ -1,35 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) ReadHealth() (health settings.Health, err error) {
|
||||
health.ServerAddress = s.env.String("HEALTH_SERVER_ADDRESS")
|
||||
health.TargetAddress = s.env.String("HEALTH_TARGET_ADDRESS",
|
||||
env.RetroKeys("HEALTH_ADDRESS_TO_PING"))
|
||||
|
||||
successWaitPtr, err := s.env.DurationPtr("HEALTH_SUCCESS_WAIT_DURATION")
|
||||
if err != nil {
|
||||
return health, err
|
||||
} else if successWaitPtr != nil {
|
||||
health.SuccessWait = *successWaitPtr
|
||||
}
|
||||
|
||||
health.VPN.Initial, err = s.env.DurationPtr(
|
||||
"HEALTH_VPN_DURATION_INITIAL",
|
||||
env.RetroKeys("HEALTH_OPENVPN_DURATION_INITIAL"))
|
||||
if err != nil {
|
||||
return health, err
|
||||
}
|
||||
|
||||
health.VPN.Addition, err = s.env.DurationPtr(
|
||||
"HEALTH_VPN_DURATION_ADDITION",
|
||||
env.RetroKeys("HEALTH_OPENVPN_DURATION_ADDITION"))
|
||||
if err != nil {
|
||||
return health, err
|
||||
}
|
||||
|
||||
return health, nil
|
||||
}
|
||||
33
internal/configuration/sources/env/helpers.go
vendored
33
internal/configuration/sources/env/helpers.go
vendored
@@ -1,33 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func unsetEnvKeys(envKeys []string, err error) (newErr error) {
|
||||
newErr = err
|
||||
for _, envKey := range envKeys {
|
||||
unsetErr := os.Unsetenv(envKey)
|
||||
if unsetErr != nil && newErr == nil {
|
||||
newErr = fmt.Errorf("unsetting environment variable %s: %w", envKey, unsetErr)
|
||||
}
|
||||
}
|
||||
return newErr
|
||||
}
|
||||
|
||||
func ptrTo[T any](value T) *T {
|
||||
return &value
|
||||
}
|
||||
|
||||
func firstKeySet(e env.Env, keys ...string) (firstKeySet string) {
|
||||
for _, key := range keys {
|
||||
value := e.Get(key)
|
||||
if value != nil {
|
||||
return key
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
84
internal/configuration/sources/env/httproxy.go
vendored
84
internal/configuration/sources/env/httproxy.go
vendored
@@ -1,84 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
"github.com/qdm12/govalid/binary"
|
||||
)
|
||||
|
||||
func (s *Source) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
|
||||
httpProxy.User = s.env.Get("HTTPPROXY_USER",
|
||||
env.RetroKeys("PROXY_USER", "TINYPROXY_USER"),
|
||||
env.ForceLowercase(false))
|
||||
|
||||
httpProxy.Password = s.env.Get("HTTPPROXY_PASSWORD",
|
||||
env.RetroKeys("PROXY_PASSWORD", "TINYPROXY_PASSWORD"),
|
||||
env.ForceLowercase(false))
|
||||
|
||||
httpProxy.ListeningAddress, err = s.readHTTProxyListeningAddress()
|
||||
if err != nil {
|
||||
return httpProxy, err
|
||||
}
|
||||
|
||||
httpProxy.Enabled, err = s.env.BoolPtr("HTTPPROXY", env.RetroKeys("PROXY", "TINYPROXY"))
|
||||
if err != nil {
|
||||
return httpProxy, err
|
||||
}
|
||||
|
||||
httpProxy.Stealth, err = s.env.BoolPtr("HTTPPROXY_STEALTH")
|
||||
if err != nil {
|
||||
return httpProxy, err
|
||||
}
|
||||
|
||||
httpProxy.Log, err = s.readHTTProxyLog()
|
||||
if err != nil {
|
||||
return httpProxy, err
|
||||
}
|
||||
|
||||
return httpProxy, nil
|
||||
}
|
||||
|
||||
func (s *Source) readHTTProxyListeningAddress() (listeningAddress string, err error) {
|
||||
const currentKey = "HTTPPROXY_LISTENING_ADDRESS"
|
||||
key := firstKeySet(s.env, "HTTPPROXY_PORT", "TINYPROXY_PORT", "PROXY_PORT",
|
||||
currentKey)
|
||||
switch key {
|
||||
case "":
|
||||
return "", nil
|
||||
case currentKey:
|
||||
return s.env.String(key), nil
|
||||
}
|
||||
|
||||
// Retro-compatible keys using a port only
|
||||
s.handleDeprecatedKey(key, currentKey)
|
||||
port, err := s.env.Uint16Ptr(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf(":%d", *port), nil
|
||||
}
|
||||
|
||||
func (s *Source) readHTTProxyLog() (enabled *bool, err error) {
|
||||
const currentKey = "HTTPPROXY_LOG"
|
||||
key := firstKeySet(s.env, "PROXY_LOG", "TINYPROXY_LOG", "HTTPPROXY_LOG")
|
||||
switch key {
|
||||
case "":
|
||||
return nil, nil //nolint:nilnil
|
||||
case currentKey:
|
||||
return s.env.BoolPtr(key)
|
||||
}
|
||||
|
||||
// Retro-compatible keys using different boolean verbs
|
||||
s.handleDeprecatedKey(key, currentKey)
|
||||
value := s.env.String(key)
|
||||
retroOption := binary.OptionEnabled("on", "info", "connect", "notice")
|
||||
|
||||
enabled, err = binary.Validate(value, retroOption)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable %s: %w", key, err)
|
||||
}
|
||||
|
||||
return enabled, nil
|
||||
}
|
||||
53
internal/configuration/sources/env/log.go
vendored
53
internal/configuration/sources/env/log.go
vendored
@@ -1,53 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/log"
|
||||
)
|
||||
|
||||
func (s *Source) readLog() (log settings.Log, err error) {
|
||||
log.Level, err = s.readLogLevel()
|
||||
if err != nil {
|
||||
return log, err
|
||||
}
|
||||
|
||||
return log, nil
|
||||
}
|
||||
|
||||
func (s *Source) readLogLevel() (level *log.Level, err error) {
|
||||
value := s.env.String("LOG_LEVEL")
|
||||
if value == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
level = new(log.Level)
|
||||
*level, err = parseLogLevel(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable LOG_LEVEL: %w", err)
|
||||
}
|
||||
|
||||
return level, nil
|
||||
}
|
||||
|
||||
var ErrLogLevelUnknown = errors.New("log level is unknown")
|
||||
|
||||
func parseLogLevel(s string) (level log.Level, err error) {
|
||||
switch strings.ToLower(s) {
|
||||
case "debug":
|
||||
return log.LevelDebug, nil
|
||||
case "info":
|
||||
return log.LevelInfo, nil
|
||||
case "warning":
|
||||
return log.LevelWarn, nil
|
||||
case "error":
|
||||
return log.LevelError, nil
|
||||
default:
|
||||
return level, fmt.Errorf(
|
||||
"%w: %q is not valid and can be one of debug, info, warning or error",
|
||||
ErrLogLevelUnknown, s)
|
||||
}
|
||||
}
|
||||
77
internal/configuration/sources/env/openvpn.go
vendored
77
internal/configuration/sources/env/openvpn.go
vendored
@@ -1,77 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readOpenVPN() (
|
||||
openVPN settings.OpenVPN, err error) {
|
||||
defer func() {
|
||||
err = unsetEnvKeys([]string{"OPENVPN_KEY", "OPENVPN_CERT",
|
||||
"OPENVPN_KEY_PASSPHRASE", "OPENVPN_ENCRYPTED_KEY"}, err)
|
||||
}()
|
||||
|
||||
openVPN.Version = s.env.String("OPENVPN_VERSION")
|
||||
openVPN.User = s.env.Get("OPENVPN_USER",
|
||||
env.RetroKeys("USER"), env.ForceLowercase(false))
|
||||
openVPN.Password = s.env.Get("OPENVPN_PASSWORD",
|
||||
env.RetroKeys("PASSWORD"), env.ForceLowercase(false))
|
||||
openVPN.ConfFile = s.env.Get("OPENVPN_CUSTOM_CONFIG", env.ForceLowercase(false))
|
||||
openVPN.Ciphers = s.env.CSV("OPENVPN_CIPHERS", env.RetroKeys("OPENVPN_CIPHER"))
|
||||
openVPN.Auth = s.env.Get("OPENVPN_AUTH")
|
||||
openVPN.Cert = s.env.Get("OPENVPN_CERT", env.ForceLowercase(false))
|
||||
openVPN.Key = s.env.Get("OPENVPN_KEY", env.ForceLowercase(false))
|
||||
openVPN.EncryptedKey = s.env.Get("OPENVPN_ENCRYPTED_KEY", env.ForceLowercase(false))
|
||||
openVPN.KeyPassphrase = s.env.Get("OPENVPN_KEY_PASSPHRASE", env.ForceLowercase(false))
|
||||
|
||||
openVPN.PIAEncPreset = s.readPIAEncryptionPreset()
|
||||
|
||||
openVPN.MSSFix, err = s.env.Uint16Ptr("OPENVPN_MSSFIX")
|
||||
if err != nil {
|
||||
return openVPN, err
|
||||
}
|
||||
|
||||
openVPN.Interface = s.env.String("VPN_INTERFACE",
|
||||
env.RetroKeys("OPENVPN_INTERFACE"), env.ForceLowercase(false))
|
||||
|
||||
openVPN.ProcessUser, err = s.readOpenVPNProcessUser()
|
||||
if err != nil {
|
||||
return openVPN, err
|
||||
}
|
||||
|
||||
openVPN.Verbosity, err = s.env.IntPtr("OPENVPN_VERBOSITY")
|
||||
if err != nil {
|
||||
return openVPN, err
|
||||
}
|
||||
|
||||
flagsPtr := s.env.Get("OPENVPN_FLAGS", env.ForceLowercase(false))
|
||||
if flagsPtr != nil {
|
||||
openVPN.Flags = strings.Fields(*flagsPtr)
|
||||
}
|
||||
|
||||
return openVPN, nil
|
||||
}
|
||||
|
||||
func (s *Source) readPIAEncryptionPreset() (presetPtr *string) {
|
||||
return s.env.Get(
|
||||
"PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET",
|
||||
env.RetroKeys("ENCRYPTION", "PIA_ENCRYPTION"))
|
||||
}
|
||||
|
||||
func (s *Source) readOpenVPNProcessUser() (processUser string, err error) {
|
||||
value, err := s.env.BoolPtr("OPENVPN_ROOT") // Retro-compatibility
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if value != nil {
|
||||
if *value {
|
||||
return "root", nil
|
||||
}
|
||||
const defaultNonRootUser = "nonrootuser"
|
||||
return defaultNonRootUser, nil
|
||||
}
|
||||
|
||||
return s.env.String("OPENVPN_PROCESS_USER"), nil
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readOpenVPNSelection() (
|
||||
selection settings.OpenVPNSelection, err error) {
|
||||
selection.ConfFile = s.env.Get("OPENVPN_CUSTOM_CONFIG", env.ForceLowercase(false))
|
||||
|
||||
selection.Protocol = s.env.String("OPENVPN_PROTOCOL", env.RetroKeys("PROTOCOL"))
|
||||
if err != nil {
|
||||
return selection, err
|
||||
}
|
||||
|
||||
selection.CustomPort, err = s.env.Uint16Ptr("VPN_ENDPOINT_PORT",
|
||||
env.RetroKeys("PORT", "OPENVPN_PORT"))
|
||||
if err != nil {
|
||||
return selection, err
|
||||
}
|
||||
|
||||
selection.PIAEncPreset = s.readPIAEncryptionPreset()
|
||||
|
||||
return selection, nil
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readPortForward() (
|
||||
portForwarding settings.PortForwarding, err error) {
|
||||
portForwarding.Enabled, err = s.env.BoolPtr("VPN_PORT_FORWARDING",
|
||||
env.RetroKeys(
|
||||
"PORT_FORWARDING",
|
||||
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING",
|
||||
))
|
||||
if err != nil {
|
||||
return portForwarding, err
|
||||
}
|
||||
|
||||
portForwarding.Provider = s.env.Get("VPN_PORT_FORWARDING_PROVIDER")
|
||||
|
||||
portForwarding.Filepath = s.env.Get("VPN_PORT_FORWARDING_STATUS_FILE",
|
||||
env.ForceLowercase(false),
|
||||
env.RetroKeys(
|
||||
"PORT_FORWARDING_STATUS_FILE",
|
||||
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
|
||||
))
|
||||
|
||||
portForwarding.ListeningPort, err = s.env.Uint16Ptr("VPN_PORT_FORWARDING_LISTENING_PORT")
|
||||
if err != nil {
|
||||
return portForwarding, err
|
||||
}
|
||||
|
||||
return portForwarding, nil
|
||||
}
|
||||
26
internal/configuration/sources/env/pprof.go
vendored
26
internal/configuration/sources/env/pprof.go
vendored
@@ -1,26 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/pprof"
|
||||
)
|
||||
|
||||
func (s *Source) readPprof() (settings pprof.Settings, err error) {
|
||||
settings.Enabled, err = s.env.BoolPtr("PPROF_ENABLED")
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.BlockProfileRate, err = s.env.IntPtr("PPROF_BLOCK_PROFILE_RATE")
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.MutexProfileRate, err = s.env.IntPtr("PPROF_MUTEX_PROFILE_RATE")
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.HTTPServer.Address = s.env.String("PPROF_HTTP_SERVER_ADDRESS")
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
45
internal/configuration/sources/env/provider.go
vendored
45
internal/configuration/sources/env/provider.go
vendored
@@ -1,45 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readProvider(vpnType string) (provider settings.Provider, err error) {
|
||||
provider.Name = s.readVPNServiceProvider(vpnType)
|
||||
|
||||
provider.ServerSelection, err = s.readServerSelection(provider.Name, vpnType)
|
||||
if err != nil {
|
||||
return provider, fmt.Errorf("server selection: %w", err)
|
||||
}
|
||||
|
||||
provider.PortForwarding, err = s.readPortForward()
|
||||
if err != nil {
|
||||
return provider, fmt.Errorf("port forwarding: %w", err)
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func (s *Source) readVPNServiceProvider(vpnType string) (vpnProvider string) {
|
||||
vpnProvider = s.env.String("VPN_SERVICE_PROVIDER", env.RetroKeys("VPNSP"))
|
||||
if vpnProvider == "" {
|
||||
if vpnType != vpn.Wireguard && s.env.Get("OPENVPN_CUSTOM_CONFIG") != nil {
|
||||
// retro compatibility
|
||||
return providers.Custom
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
vpnProvider = strings.ToLower(vpnProvider)
|
||||
if vpnProvider == "pia" { // retro compatibility
|
||||
return providers.PrivateInternetAccess
|
||||
}
|
||||
|
||||
return vpnProvider
|
||||
}
|
||||
22
internal/configuration/sources/env/publicip.go
vendored
22
internal/configuration/sources/env/publicip.go
vendored
@@ -1,22 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readPublicIP() (publicIP settings.PublicIP, err error) {
|
||||
publicIP.Period, err = s.env.DurationPtr("PUBLICIP_PERIOD")
|
||||
if err != nil {
|
||||
return publicIP, err
|
||||
}
|
||||
|
||||
publicIP.IPFilepath = s.env.Get("PUBLICIP_FILE",
|
||||
env.ForceLowercase(false), env.RetroKeys("IP_STATUS_FILE"))
|
||||
|
||||
publicIP.API = s.env.String("PUBLICIP_API")
|
||||
|
||||
publicIP.APIToken = s.env.Get("PUBLICIP_API_TOKEN")
|
||||
|
||||
return publicIP, nil
|
||||
}
|
||||
103
internal/configuration/sources/env/reader.go
vendored
103
internal/configuration/sources/env/reader.go
vendored
@@ -1,103 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
type Source struct {
|
||||
env env.Env
|
||||
warner Warner
|
||||
handleDeprecatedKey func(deprecatedKey, newKey string)
|
||||
}
|
||||
|
||||
type Warner interface {
|
||||
Warn(s string)
|
||||
}
|
||||
|
||||
func New(warner Warner) *Source {
|
||||
handleDeprecatedKey := func(deprecatedKey, newKey string) {
|
||||
warner.Warn(
|
||||
"You are using the old environment variable " + deprecatedKey +
|
||||
", please consider changing it to " + newKey)
|
||||
}
|
||||
|
||||
return &Source{
|
||||
env: *env.New(os.Environ(), handleDeprecatedKey),
|
||||
warner: warner,
|
||||
handleDeprecatedKey: handleDeprecatedKey,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Source) String() string { return "environment variables" }
|
||||
|
||||
func (s *Source) Read() (settings settings.Settings, err error) {
|
||||
settings.VPN, err = s.readVPN()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Firewall, err = s.readFirewall()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.System, err = s.readSystem()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Health, err = s.ReadHealth()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.HTTPProxy, err = s.readHTTPProxy()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Log, err = s.readLog()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.PublicIP, err = s.readPublicIP()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Updater, err = s.readUpdater()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Version, err = s.readVersion()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Shadowsocks, err = s.readShadowsocks()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.DNS, err = s.readDNS()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.ControlServer, err = s.readControlServer()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Pprof, err = s.readPprof()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
16
internal/configuration/sources/env/server.go
vendored
16
internal/configuration/sources/env/server.go
vendored
@@ -1,16 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readControlServer() (controlServer settings.ControlServer, err error) {
|
||||
controlServer.Log, err = s.env.BoolPtr("HTTP_CONTROL_SERVER_LOG")
|
||||
if err != nil {
|
||||
return controlServer, err
|
||||
}
|
||||
|
||||
controlServer.Address = s.env.Get("HTTP_CONTROL_SERVER_ADDRESS")
|
||||
|
||||
return controlServer, nil
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readServerSelection(vpnProvider, vpnType string) (
|
||||
ss settings.ServerSelection, err error) {
|
||||
ss.VPN = vpnType
|
||||
|
||||
ss.TargetIP, err = s.env.NetipAddr("VPN_ENDPOINT_IP",
|
||||
env.RetroKeys("OPENVPN_TARGET_IP"))
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
ss.Countries = s.env.CSV("SERVER_COUNTRIES", env.RetroKeys("COUNTRY"))
|
||||
if vpnProvider == providers.Cyberghost && len(ss.Countries) == 0 {
|
||||
// Retro-compatibility for Cyberghost using the REGION variable
|
||||
ss.Countries = s.env.CSV("REGION")
|
||||
if len(ss.Countries) > 0 {
|
||||
s.handleDeprecatedKey("REGION", "SERVER_COUNTRIES")
|
||||
}
|
||||
}
|
||||
|
||||
ss.Regions = s.env.CSV("SERVER_REGIONS", env.RetroKeys("REGION"))
|
||||
ss.Cities = s.env.CSV("SERVER_CITIES", env.RetroKeys("CITY"))
|
||||
ss.ISPs = s.env.CSV("ISP")
|
||||
ss.Hostnames = s.env.CSV("SERVER_HOSTNAMES", env.RetroKeys("SERVER_HOSTNAME"))
|
||||
ss.Names = s.env.CSV("SERVER_NAMES", env.RetroKeys("SERVER_NAME"))
|
||||
ss.Numbers, err = s.env.CSVUint16("SERVER_NUMBER")
|
||||
ss.Categories = s.env.CSV("SERVER_CATEGORIES")
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// Mullvad only
|
||||
ss.OwnedOnly, err = s.env.BoolPtr("OWNED_ONLY", env.RetroKeys("OWNED"))
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// VPNUnlimited and ProtonVPN only
|
||||
ss.FreeOnly, err = s.env.BoolPtr("FREE_ONLY")
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// VPNSecure only
|
||||
ss.PremiumOnly, err = s.env.BoolPtr("PREMIUM_ONLY")
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// Surfshark only
|
||||
ss.MultiHopOnly, err = s.env.BoolPtr("MULTIHOP_ONLY")
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// VPNUnlimited only
|
||||
ss.StreamOnly, err = s.env.BoolPtr("STREAM_ONLY")
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// PIA only
|
||||
ss.PortForwardOnly, err = s.env.BoolPtr("PORT_FORWARD_ONLY")
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
ss.OpenVPN, err = s.readOpenVPNSelection()
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
ss.Wireguard, err = s.readWireguardSelection()
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
return ss, nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidIP = errors.New("invalid IP address")
|
||||
)
|
||||
@@ -1,42 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readShadowsocks() (shadowsocks settings.Shadowsocks, err error) {
|
||||
shadowsocks.Enabled, err = s.env.BoolPtr("SHADOWSOCKS")
|
||||
if err != nil {
|
||||
return shadowsocks, err
|
||||
}
|
||||
|
||||
shadowsocks.Address, err = s.readShadowsocksAddress()
|
||||
if err != nil {
|
||||
return shadowsocks, err
|
||||
}
|
||||
shadowsocks.LogAddresses, err = s.env.BoolPtr("SHADOWSOCKS_LOG")
|
||||
if err != nil {
|
||||
return shadowsocks, err
|
||||
}
|
||||
shadowsocks.CipherName = s.env.String("SHADOWSOCKS_CIPHER",
|
||||
env.RetroKeys("SHADOWSOCKS_METHOD"))
|
||||
shadowsocks.Password = s.env.Get("SHADOWSOCKS_PASSWORD", env.ForceLowercase(false))
|
||||
|
||||
return shadowsocks, nil
|
||||
}
|
||||
|
||||
func (s *Source) readShadowsocksAddress() (address *string, err error) {
|
||||
const currentKey = "SHADOWSOCKS_LISTENING_ADDRESS"
|
||||
port, err := s.env.Uint16Ptr("SHADOWSOCKS_PORT") // retro-compatibility
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if port != nil {
|
||||
s.handleDeprecatedKey("SHADOWSOCKS_PORT", currentKey)
|
||||
return ptrTo(fmt.Sprintf(":%d", *port)), nil
|
||||
}
|
||||
|
||||
return s.env.Get(currentKey), nil
|
||||
}
|
||||
22
internal/configuration/sources/env/system.go
vendored
22
internal/configuration/sources/env/system.go
vendored
@@ -1,22 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readSystem() (system settings.System, err error) {
|
||||
system.PUID, err = s.env.Uint32Ptr("PUID", env.RetroKeys("UID"))
|
||||
if err != nil {
|
||||
return system, err
|
||||
}
|
||||
|
||||
system.PGID, err = s.env.Uint32Ptr("PGID", env.RetroKeys("GID"))
|
||||
if err != nil {
|
||||
return system, err
|
||||
}
|
||||
|
||||
system.Timezone = s.env.String("TZ")
|
||||
|
||||
return system, nil
|
||||
}
|
||||
36
internal/configuration/sources/env/unbound.go
vendored
36
internal/configuration/sources/env/unbound.go
vendored
@@ -1,36 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readUnbound() (unbound settings.Unbound, err error) {
|
||||
unbound.Providers = s.env.CSV("DOT_PROVIDERS")
|
||||
|
||||
unbound.Caching, err = s.env.BoolPtr("DOT_CACHING")
|
||||
if err != nil {
|
||||
return unbound, err
|
||||
}
|
||||
|
||||
unbound.IPv6, err = s.env.BoolPtr("DOT_IPV6")
|
||||
if err != nil {
|
||||
return unbound, err
|
||||
}
|
||||
|
||||
unbound.VerbosityLevel, err = s.env.Uint8Ptr("DOT_VERBOSITY")
|
||||
if err != nil {
|
||||
return unbound, err
|
||||
}
|
||||
|
||||
unbound.VerbosityDetailsLevel, err = s.env.Uint8Ptr("DOT_VERBOSITY_DETAILS")
|
||||
if err != nil {
|
||||
return unbound, err
|
||||
}
|
||||
|
||||
unbound.ValidationLogLevel, err = s.env.Uint8Ptr("DOT_VALIDATION_LOGLEVEL")
|
||||
if err != nil {
|
||||
return unbound, err
|
||||
}
|
||||
|
||||
return unbound, nil
|
||||
}
|
||||
35
internal/configuration/sources/env/updater.go
vendored
35
internal/configuration/sources/env/updater.go
vendored
@@ -1,35 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readUpdater() (updater settings.Updater, err error) {
|
||||
updater.Period, err = s.env.DurationPtr("UPDATER_PERIOD")
|
||||
if err != nil {
|
||||
return updater, err
|
||||
}
|
||||
|
||||
updater.DNSAddress, err = readUpdaterDNSAddress()
|
||||
if err != nil {
|
||||
return updater, err
|
||||
}
|
||||
|
||||
updater.MinRatio, err = s.env.Float64("UPDATER_MIN_RATIO")
|
||||
if err != nil {
|
||||
return updater, err
|
||||
}
|
||||
|
||||
updater.Providers = s.env.CSV("UPDATER_VPN_SERVICE_PROVIDERS")
|
||||
|
||||
return updater, nil
|
||||
}
|
||||
|
||||
func readUpdaterDNSAddress() (address string, err error) {
|
||||
// TODO this is currently using Cloudflare in
|
||||
// plaintext to not be blocked by DNS over TLS by default.
|
||||
// If a plaintext address is set in the DNS settings, this one will be used.
|
||||
// use custom future encrypted DNS written in Go without blocking
|
||||
// as it's too much trouble to start another parallel unbound instance for now.
|
||||
return "", nil
|
||||
}
|
||||
14
internal/configuration/sources/env/version.go
vendored
14
internal/configuration/sources/env/version.go
vendored
@@ -1,14 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readVersion() (version settings.Version, err error) {
|
||||
version.Enabled, err = s.env.BoolPtr("VERSION_INFORMATION")
|
||||
if err != nil {
|
||||
return version, err
|
||||
}
|
||||
|
||||
return version, nil
|
||||
}
|
||||
28
internal/configuration/sources/env/vpn.go
vendored
28
internal/configuration/sources/env/vpn.go
vendored
@@ -1,28 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readVPN() (vpn settings.VPN, err error) {
|
||||
vpn.Type = s.env.String("VPN_TYPE")
|
||||
|
||||
vpn.Provider, err = s.readProvider(vpn.Type)
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("VPN 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
|
||||
}
|
||||
33
internal/configuration/sources/env/wireguard.go
vendored
33
internal/configuration/sources/env/wireguard.go
vendored
@@ -1,33 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readWireguard() (wireguard settings.Wireguard, err error) {
|
||||
defer func() {
|
||||
err = unsetEnvKeys([]string{"WIREGUARD_PRIVATE_KEY", "WIREGUARD_PRESHARED_KEY"}, err)
|
||||
}()
|
||||
wireguard.PrivateKey = s.env.Get("WIREGUARD_PRIVATE_KEY", env.ForceLowercase(false))
|
||||
wireguard.PreSharedKey = s.env.Get("WIREGUARD_PRESHARED_KEY", env.ForceLowercase(false))
|
||||
wireguard.Interface = s.env.String("VPN_INTERFACE",
|
||||
env.RetroKeys("WIREGUARD_INTERFACE"), env.ForceLowercase(false))
|
||||
wireguard.Implementation = s.env.String("WIREGUARD_IMPLEMENTATION")
|
||||
wireguard.Addresses, err = s.env.CSVNetipPrefixes("WIREGUARD_ADDRESSES",
|
||||
env.RetroKeys("WIREGUARD_ADDRESS"))
|
||||
if err != nil {
|
||||
return wireguard, err // already wrapped
|
||||
}
|
||||
wireguard.AllowedIPs, err = s.env.CSVNetipPrefixes("WIREGUARD_ALLOWED_IPS")
|
||||
if err != nil {
|
||||
return wireguard, err // already wrapped
|
||||
}
|
||||
mtuPtr, err := s.env.Uint16Ptr("WIREGUARD_MTU")
|
||||
if err != nil {
|
||||
return wireguard, err
|
||||
} else if mtuPtr != nil {
|
||||
wireguard.MTU = *mtuPtr
|
||||
}
|
||||
return wireguard, nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readWireguardSelection() (
|
||||
selection settings.WireguardSelection, err error) {
|
||||
selection.EndpointIP, err = s.env.NetipAddr("VPN_ENDPOINT_IP", env.RetroKeys("WIREGUARD_ENDPOINT_IP"))
|
||||
if err != nil {
|
||||
return selection, err
|
||||
}
|
||||
|
||||
selection.EndpointPort, err = s.env.Uint16Ptr("VPN_ENDPOINT_PORT", env.RetroKeys("WIREGUARD_ENDPOINT_PORT"))
|
||||
if err != nil {
|
||||
return selection, err
|
||||
}
|
||||
|
||||
selection.PublicKey = s.env.String("WIREGUARD_PUBLIC_KEY", env.ForceLowercase(false))
|
||||
|
||||
return selection, nil
|
||||
}
|
||||
@@ -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 }
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
package files
|
||||
|
||||
func ptrTo[T any](x T) *T { return &x }
|
||||
5
internal/configuration/sources/files/interfaces.go
Normal file
5
internal/configuration/sources/files/interfaces.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package files
|
||||
|
||||
type Warner interface {
|
||||
Warnf(format string, a ...interface{})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package merge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
type ConfigSource interface {
|
||||
Read() (settings settings.Settings, err error)
|
||||
ReadHealth() (settings settings.Health, err error)
|
||||
String() string
|
||||
}
|
||||
|
||||
type Source struct {
|
||||
sources []ConfigSource
|
||||
}
|
||||
|
||||
func New(sources ...ConfigSource) *Source {
|
||||
return &Source{
|
||||
sources: sources,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Source) String() string {
|
||||
sources := make([]string, len(s.sources))
|
||||
for i := range s.sources {
|
||||
sources[i] = s.sources[i].String()
|
||||
}
|
||||
return strings.Join(sources, ", ")
|
||||
}
|
||||
|
||||
// Read reads the settings for each source, merging unset fields
|
||||
// with field set by the next source.
|
||||
// It then set defaults to remaining unset fields.
|
||||
func (s *Source) Read() (settings settings.Settings, err error) {
|
||||
for _, source := range s.sources {
|
||||
settingsFromSource, err := source.Read()
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading from %s: %w", source, err)
|
||||
}
|
||||
settings.MergeWith(settingsFromSource)
|
||||
}
|
||||
settings.SetDefaults()
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
// ReadHealth reads the health settings for each source, merging unset fields
|
||||
// with field set by the next source.
|
||||
// It then set defaults to remaining unset fields, and validate
|
||||
// all the fields.
|
||||
func (s *Source) ReadHealth() (settings settings.Health, err error) {
|
||||
for _, source := range s.sources {
|
||||
settingsFromSource, err := source.ReadHealth()
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading from %s: %w", source, err)
|
||||
}
|
||||
settings.MergeWith(settingsFromSource)
|
||||
}
|
||||
settings.SetDefaults()
|
||||
|
||||
err = settings.Validate()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package secrets
|
||||
|
||||
import "github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
|
||||
func (s *Source) ReadHealth() (settings settings.Health, err error) { return settings, nil }
|
||||
@@ -1,58 +1,8 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath string) (
|
||||
stringPtr *string, err error) {
|
||||
path := s.env.String(secretPathEnvKey, env.ForceLowercase(false))
|
||||
if path == "" {
|
||||
path = defaultSecretPath
|
||||
func strPtrToStringIsSet(ptr *string) (s string, isSet bool) {
|
||||
if ptr == nil {
|
||||
return "", false
|
||||
}
|
||||
return files.ReadFromFile(path)
|
||||
}
|
||||
|
||||
func (s *Source) readPEMSecretFile(secretPathEnvKey, defaultSecretPath string) (
|
||||
base64Ptr *string, err error) {
|
||||
pemData, err := s.readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading secret file: %w", err)
|
||||
}
|
||||
|
||||
if pemData == nil {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
base64Data, err := extract.PEM([]byte(*pemData))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("extracting base64 encoded data from PEM content: %w", err)
|
||||
}
|
||||
|
||||
return &base64Data, nil
|
||||
}
|
||||
|
||||
func parseAddresses(addressesCSV string) (addresses []netip.Prefix, err error) {
|
||||
if addressesCSV == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
addressStrings := strings.Split(addressesCSV, ",")
|
||||
addresses = make([]netip.Prefix, len(addressStrings))
|
||||
for i, addressString := range addressStrings {
|
||||
addressString = strings.TrimSpace(addressString)
|
||||
addresses[i], err = netip.ParsePrefix(addressString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing address %d of %d: %w",
|
||||
i+1, len(addressStrings), err)
|
||||
}
|
||||
}
|
||||
|
||||
return addresses, nil
|
||||
return *ptr, true
|
||||
}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func ptrTo[T any](value T) *T { return &value }
|
||||
|
||||
func Test_readSecretFileAsStringPtr(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
source func(tempDir string) Source
|
||||
secretPathEnvKey string
|
||||
defaultSecretFileName string
|
||||
setupFile func(tempDir string) error
|
||||
stringPtr *string
|
||||
errWrapped error
|
||||
errMessage string
|
||||
}{
|
||||
"no_secret_file": {
|
||||
defaultSecretFileName: "default_secret_file",
|
||||
secretPathEnvKey: "SECRET_FILE",
|
||||
},
|
||||
"empty_secret_file": {
|
||||
defaultSecretFileName: "default_secret_file",
|
||||
secretPathEnvKey: "SECRET_FILE",
|
||||
setupFile: func(tempDir string) error {
|
||||
secretFilepath := filepath.Join(tempDir, "default_secret_file")
|
||||
return os.WriteFile(secretFilepath, nil, os.ModePerm)
|
||||
},
|
||||
stringPtr: ptrTo(""),
|
||||
},
|
||||
"default_secret_file": {
|
||||
defaultSecretFileName: "default_secret_file",
|
||||
secretPathEnvKey: "SECRET_FILE",
|
||||
setupFile: func(tempDir string) error {
|
||||
secretFilepath := filepath.Join(tempDir, "default_secret_file")
|
||||
return os.WriteFile(secretFilepath, []byte("A"), os.ModePerm)
|
||||
},
|
||||
stringPtr: ptrTo("A"),
|
||||
},
|
||||
"env_specified_secret_file": {
|
||||
source: func(tempDir string) Source {
|
||||
secretFilepath := filepath.Join(tempDir, "secret_file")
|
||||
environ := []string{"SECRET_FILE=" + secretFilepath}
|
||||
return Source{env: *env.New(environ, nil)}
|
||||
},
|
||||
defaultSecretFileName: "default_secret_file",
|
||||
secretPathEnvKey: "SECRET_FILE",
|
||||
setupFile: func(tempDir string) error {
|
||||
secretFilepath := filepath.Join(tempDir, "secret_file")
|
||||
return os.WriteFile(secretFilepath, []byte("B"), os.ModePerm)
|
||||
},
|
||||
stringPtr: ptrTo("B"),
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tempDir := t.TempDir()
|
||||
|
||||
var source Source
|
||||
if testCase.source != nil {
|
||||
source = testCase.source(tempDir)
|
||||
}
|
||||
|
||||
defaultSecretPath := filepath.Join(tempDir, testCase.defaultSecretFileName)
|
||||
if testCase.setupFile != nil {
|
||||
err := testCase.setupFile(tempDir)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
stringPtr, err := source.readSecretFileAsStringPtr(
|
||||
testCase.secretPathEnvKey, defaultSecretPath)
|
||||
|
||||
assert.Equal(t, testCase.stringPtr, stringPtr)
|
||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||
if testCase.errWrapped != nil {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readHTTPProxy() (settings settings.HTTPProxy, err error) {
|
||||
settings.User, err = s.readSecretFileAsStringPtr(
|
||||
"HTTPPROXY_USER_SECRETFILE",
|
||||
"/run/secrets/httpproxy_user",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading HTTP proxy user secret file: %w", err)
|
||||
}
|
||||
|
||||
settings.Password, err = s.readSecretFileAsStringPtr(
|
||||
"HTTPPROXY_PASSWORD_SECRETFILE",
|
||||
"/run/secrets/httpproxy_password",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading HTTP proxy password secret file: %w", err)
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
5
internal/configuration/sources/secrets/interfaces.go
Normal file
5
internal/configuration/sources/secrets/interfaces.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package secrets
|
||||
|
||||
type Warner interface {
|
||||
Warnf(format string, a ...interface{})
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readOpenVPN() (
|
||||
settings settings.OpenVPN, err error) {
|
||||
settings.User, err = s.readSecretFileAsStringPtr(
|
||||
"OPENVPN_USER_SECRETFILE",
|
||||
"/run/secrets/openvpn_user",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading user file: %w", err)
|
||||
}
|
||||
|
||||
settings.Password, err = s.readSecretFileAsStringPtr(
|
||||
"OPENVPN_PASSWORD_SECRETFILE",
|
||||
"/run/secrets/openvpn_password",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading password file: %w", err)
|
||||
}
|
||||
|
||||
settings.Key, err = s.readPEMSecretFile(
|
||||
"OPENVPN_CLIENTKEY_SECRETFILE",
|
||||
"/run/secrets/openvpn_clientkey",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading client key file: %w", err)
|
||||
}
|
||||
|
||||
settings.EncryptedKey, err = s.readPEMSecretFile(
|
||||
"OPENVPN_ENCRYPTED_KEY_SECRETFILE",
|
||||
"/run/secrets/openvpn_encrypted_key",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading encrypted key file: %w", err)
|
||||
}
|
||||
|
||||
settings.KeyPassphrase, err = s.readSecretFileAsStringPtr(
|
||||
"OPENVPN_KEY_PASSPHRASE_SECRETFILE",
|
||||
"/run/secrets/openvpn_key_passphrase",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading key passphrase file: %w", err)
|
||||
}
|
||||
|
||||
settings.Cert, err = s.readPEMSecretFile(
|
||||
"OPENVPN_CLIENTCRT_SECRETFILE",
|
||||
"/run/secrets/openvpn_clientcrt",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading client certificate file: %w", err)
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
@@ -1,46 +1,104 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
||||
)
|
||||
|
||||
type Source struct {
|
||||
env env.Env
|
||||
rootDirectory string
|
||||
environ map[string]string
|
||||
warner Warner
|
||||
cached struct {
|
||||
wireguardLoaded bool
|
||||
wireguardConf files.WireguardConfig
|
||||
}
|
||||
}
|
||||
|
||||
func New() *Source {
|
||||
handleDeprecatedKey := (func(deprecatedKey, newKey string))(nil)
|
||||
func New(warner Warner) (source *Source) {
|
||||
const rootDirectory = "/run/secrets"
|
||||
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{
|
||||
env: *env.New(os.Environ(), handleDeprecatedKey),
|
||||
rootDirectory: rootDirectory,
|
||||
environ: environ,
|
||||
warner: warner,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Source) String() string { return "secret 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 secrets parent directory
|
||||
// and not to set each secret file to a specific path
|
||||
envKey := strings.ToUpper(key)
|
||||
envKey = strings.ReplaceAll(envKey, "-", "_")
|
||||
envKey += "_SECRETFILE" // TODO v4 change _SECRETFILE to _FILE
|
||||
path := s.environ[envKey]
|
||||
if path == "" {
|
||||
path = filepath.Join(s.rootDirectory, key)
|
||||
}
|
||||
|
||||
settings.HTTPProxy, err = s.readHTTPProxy()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
// Special file parsing
|
||||
switch key {
|
||||
// TODO timezone from /etc/localtime
|
||||
case "openvpn_clientcrt", "openvpn_clientkey":
|
||||
value, isSet, err := files.ReadPEMFile(path)
|
||||
if err != nil {
|
||||
s.warner.Warnf("skipping %s: parsing PEM: %s", path, err)
|
||||
}
|
||||
return value, isSet
|
||||
case "wireguard_private_key":
|
||||
privateKey := s.lazyLoadWireguardConf().PrivateKey
|
||||
if privateKey != nil {
|
||||
return *privateKey, true
|
||||
} // else continue to read from individual secret file
|
||||
case "wireguard_preshared_key":
|
||||
preSharedKey := s.lazyLoadWireguardConf().PreSharedKey
|
||||
if preSharedKey != nil {
|
||||
return *preSharedKey, true
|
||||
} // else continue to read from individual secret file
|
||||
case "wireguard_addresses":
|
||||
addresses := s.lazyLoadWireguardConf().Addresses
|
||||
if addresses != nil {
|
||||
return *addresses, true
|
||||
} // else continue to read from individual secret file
|
||||
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)
|
||||
}
|
||||
|
||||
settings.Shadowsocks, err = s.readShadowsocks()
|
||||
value, isSet, err := files.ReadFromFile(path)
|
||||
if err != nil {
|
||||
return settings, err
|
||||
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 "openvpn_clientkey"
|
||||
case "OPENVPN_CERT":
|
||||
return "openvpn_clientcrt"
|
||||
default:
|
||||
key = strings.ToLower(key) // HTTPROXY_USER -> httpproxy_user
|
||||
return key
|
||||
}
|
||||
|
||||
settings.VPN.Wireguard, err = s.readWireguard()
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading Wireguard: %w", err)
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
102
internal/configuration/sources/secrets/reader_test.go
Normal file
102
internal/configuration/sources/secrets/reader_test.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_Source_Get(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
makeSource func(tempDir string) (source *Source, err error)
|
||||
key string
|
||||
value string
|
||||
isSet bool
|
||||
}{
|
||||
"empty_key": {
|
||||
makeSource: func(tempDir string) (source *Source, err error) {
|
||||
return &Source{
|
||||
rootDirectory: tempDir,
|
||||
environ: map[string]string{},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
"no_secret_file": {
|
||||
makeSource: func(tempDir string) (source *Source, err error) {
|
||||
return &Source{
|
||||
rootDirectory: tempDir,
|
||||
environ: map[string]string{},
|
||||
}, nil
|
||||
},
|
||||
key: "test_file",
|
||||
},
|
||||
"empty_secret_file": {
|
||||
makeSource: func(tempDir string) (source *Source, err error) {
|
||||
secretFilepath := filepath.Join(tempDir, "test_file")
|
||||
err = os.WriteFile(secretFilepath, nil, os.ModePerm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Source{
|
||||
rootDirectory: tempDir,
|
||||
environ: map[string]string{},
|
||||
}, nil
|
||||
},
|
||||
key: "test_file",
|
||||
isSet: true,
|
||||
},
|
||||
"default_secret_file": {
|
||||
makeSource: func(tempDir string) (source *Source, err error) {
|
||||
secretFilepath := filepath.Join(tempDir, "test_file")
|
||||
err = os.WriteFile(secretFilepath, []byte{'A'}, os.ModePerm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Source{
|
||||
rootDirectory: tempDir,
|
||||
environ: map[string]string{},
|
||||
}, nil
|
||||
},
|
||||
key: "test_file",
|
||||
value: "A",
|
||||
isSet: true,
|
||||
},
|
||||
"env_specified_secret_file": {
|
||||
makeSource: func(tempDir string) (source *Source, err error) {
|
||||
secretFilepath := filepath.Join(tempDir, "test_file_custom")
|
||||
err = os.WriteFile(secretFilepath, []byte{'A'}, os.ModePerm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Source{
|
||||
rootDirectory: tempDir,
|
||||
environ: map[string]string{
|
||||
"TEST_FILE_SECRETFILE": secretFilepath,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
key: "test_file",
|
||||
value: "A",
|
||||
isSet: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
source, err := testCase.makeSource(t.TempDir())
|
||||
require.NoError(t, err)
|
||||
|
||||
value, isSet := source.Get(testCase.key)
|
||||
assert.Equal(t, testCase.value, value)
|
||||
assert.Equal(t, testCase.isSet, isSet)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readShadowsocks() (settings settings.Shadowsocks, err error) {
|
||||
settings.Password, err = s.readSecretFileAsStringPtr(
|
||||
"SHADOWSOCKS_PASSWORD_SECRETFILE",
|
||||
"/run/secrets/shadowsocks_password",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading Shadowsocks password secret file: %w", err)
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readVPN() (vpn settings.VPN, err error) {
|
||||
vpn.OpenVPN, err = s.readOpenVPN()
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("reading OpenVPN settings: %w", err)
|
||||
}
|
||||
|
||||
vpn.Wireguard, err = s.readWireguard()
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("reading Wireguard settings: %w", err)
|
||||
}
|
||||
|
||||
return vpn, nil
|
||||
}
|
||||
@@ -1,52 +1,27 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
||||
)
|
||||
|
||||
func (s *Source) readWireguard() (settings settings.Wireguard, err error) {
|
||||
wireguardConf, err := s.readSecretFileAsStringPtr(
|
||||
"WIREGUARD_CONF_SECRETFILE",
|
||||
"/run/secrets/wg0.conf",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading Wireguard conf secret file: %w", err)
|
||||
} else if wireguardConf != nil {
|
||||
// Wireguard ini config file takes precedence over individual secrets
|
||||
return files.ParseWireguardConf([]byte(*wireguardConf))
|
||||
func (s *Source) lazyLoadWireguardConf() files.WireguardConfig {
|
||||
if s.cached.wireguardLoaded {
|
||||
return s.cached.wireguardConf
|
||||
}
|
||||
|
||||
settings.PrivateKey, err = s.readSecretFileAsStringPtr(
|
||||
"WIREGUARD_PRIVATE_KEY_SECRETFILE",
|
||||
"/run/secrets/wireguard_private_key",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading private key file: %w", err)
|
||||
path := os.Getenv("WIREGUARD_CONF_SECRETFILE")
|
||||
if path == "" {
|
||||
path = filepath.Join(s.rootDirectory, "wg0.conf")
|
||||
}
|
||||
|
||||
settings.PreSharedKey, err = s.readSecretFileAsStringPtr(
|
||||
"WIREGUARD_PRESHARED_KEY_SECRETFILE",
|
||||
"/run/secrets/wireguard_preshared_key",
|
||||
)
|
||||
s.cached.wireguardLoaded = true
|
||||
var err error
|
||||
s.cached.wireguardConf, err = files.ParseWireguardConf(path)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading preshared key file: %w", err)
|
||||
s.warner.Warnf("skipping Wireguard config: %s", err)
|
||||
}
|
||||
|
||||
wireguardAddressesCSV, err := s.readSecretFileAsStringPtr(
|
||||
"WIREGUARD_ADDRESSES_SECRETFILE",
|
||||
"/run/secrets/wireguard_addresses",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading addresses file: %w", err)
|
||||
} else if wireguardAddressesCSV != nil {
|
||||
settings.Addresses, err = parseAddresses(*wireguardAddressesCSV)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("parsing addresses: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
return s.cached.wireguardConf
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user