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:
@@ -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