chore!(amneziawg): refactor to be separate from wireguard

- amneziawg is now a VPN protocol and no longer a Wireguard implementation
- Use it with VPN_TYPE=amneziawg
- document AMNEZIAWG_* options in Dockerfile
- document amneziawg support in readme
- separate amneziawg settings and code from wireguard
- re-use code from wireguard whenever possible
This commit is contained in:
Quentin McGaw
2026-03-11 16:35:18 +00:00
parent efea169495
commit b04529c380
54 changed files with 1608 additions and 741 deletions
@@ -0,0 +1,84 @@
package files
import (
"errors"
"fmt"
"os"
"path/filepath"
"gopkg.in/ini.v1"
)
func (s *Source) lazyLoadAmneziawgConf() AmneziawgConfig {
if s.cached.amneziawgLoaded {
return s.cached.amneziawgConf
}
s.cached.amneziawgLoaded = true
var err error
s.cached.amneziawgConf, err = ParseAmneziawgConf(filepath.Join(s.rootDirectory, "amneziawg", "awg0.conf"))
if err != nil {
s.warner.Warnf("skipping Amneziawg config: %s", err)
}
return s.cached.amneziawgConf
}
type AmneziawgConfig struct {
Wireguard WireguardConfig
Jc *string
Jmin *string
Jmax *string
S1 *string
S2 *string
S3 *string
S4 *string
H1 *string
H2 *string
H3 *string
H4 *string
I1 *string
I2 *string
I3 *string
I4 *string
I5 *string
}
func ParseAmneziawgConf(path string) (config AmneziawgConfig, err error) {
iniFile, err := ini.InsensitiveLoad(path)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return AmneziawgConfig{}, nil
}
return AmneziawgConfig{}, fmt.Errorf("loading ini from reader: %w", err)
}
config.Wireguard, err = ParseWireguardConf(path)
if err != nil {
return AmneziawgConfig{}, err
}
interfaceSection, err := iniFile.GetSection("Interface")
if err != nil {
// can never happen
return AmneziawgConfig{}, fmt.Errorf("getting interface section: %w", err)
}
config.Jc = getINIKeyFromSection(interfaceSection, "Jc")
config.Jmin = getINIKeyFromSection(interfaceSection, "Jmin")
config.Jmax = getINIKeyFromSection(interfaceSection, "Jmax")
config.S1 = getINIKeyFromSection(interfaceSection, "S1")
config.S2 = getINIKeyFromSection(interfaceSection, "S2")
config.S3 = getINIKeyFromSection(interfaceSection, "S3")
config.S4 = getINIKeyFromSection(interfaceSection, "S4")
config.H1 = getINIKeyFromSection(interfaceSection, "H1")
config.H2 = getINIKeyFromSection(interfaceSection, "H2")
config.H3 = getINIKeyFromSection(interfaceSection, "H3")
config.H4 = getINIKeyFromSection(interfaceSection, "H4")
config.I1 = getINIKeyFromSection(interfaceSection, "I1")
config.I2 = getINIKeyFromSection(interfaceSection, "I2")
config.I3 = getINIKeyFromSection(interfaceSection, "I3")
config.I4 = getINIKeyFromSection(interfaceSection, "I4")
config.I5 = getINIKeyFromSection(interfaceSection, "I5")
return config, nil
}
@@ -0,0 +1,82 @@
package files
import (
"io/fs"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_Source_ParseAmneziawgConf(t *testing.T) {
t.Parallel()
t.Run("no_file", func(t *testing.T) {
t.Parallel()
noFile := filepath.Join(t.TempDir(), "doesnotexist")
wireguard, err := ParseAmneziawgConf(noFile)
assert.Equal(t, AmneziawgConfig{}, wireguard)
assert.NoError(t, err)
})
testCases := map[string]struct {
fileContent string
amneziawg AmneziawgConfig
errMessage string
}{
"ini_load_error": {
fileContent: "invalid",
errMessage: "loading ini from reader: key-value delimiter not found: invalid",
},
"empty_file": {
errMessage: `getting interface section: section "interface" does not exist`,
},
"success": {
fileContent: `
[Interface]
PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
Address = 10.38.22.35/32
DNS = 193.138.218.74
Jc = 4
H1 = 721391205
I1 = <b 0x1234>
[Peer]
PresharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g=
`,
amneziawg: AmneziawgConfig{
Wireguard: WireguardConfig{
PrivateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
PreSharedKey: ptrTo("YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g="),
Addresses: ptrTo("10.38.22.35/32"),
},
Jc: ptrTo("4"),
H1: ptrTo("721391205"),
I1: ptrTo("<b 0x1234>"),
},
},
}
for testName, testCase := range testCases {
t.Run(testName, func(t *testing.T) {
t.Parallel()
configFile := filepath.Join(t.TempDir(), "awg.conf")
const permission = fs.FileMode(0o600)
err := os.WriteFile(configFile, []byte(testCase.fileContent), permission)
require.NoError(t, err)
wireguard, err := ParseAmneziawgConf(configFile)
assert.Equal(t, testCase.amneziawg, wireguard)
if testCase.errMessage != "" {
assert.EqualError(t, err, testCase.errMessage)
} else {
assert.NoError(t, err)
}
})
}
}
+59 -32
View File
@@ -13,6 +13,8 @@ type Source struct {
cached struct {
wireguardLoaded bool
wireguardConf WireguardConfig
amneziawgLoaded bool
amneziawgConf AmneziawgConfig
}
}
@@ -69,38 +71,11 @@ func (s *Source) Get(key string) (value string, isSet bool) {
return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointIP)
case "wireguard_endpoint_port":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointPort)
case "wireguard_jc":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.Jc)
case "wireguard_jmin":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.Jmin)
case "wireguard_jmax":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.Jmax)
case "wireguard_s1":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.S1)
case "wireguard_s2":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.S2)
case "wireguard_s3":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.S3)
case "wireguard_s4":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.S4)
case "wireguard_h1":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.H1)
case "wireguard_h2":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.H2)
case "wireguard_h3":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.H3)
case "wireguard_h4":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.H4)
case "wireguard_i1":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.I1)
case "wireguard_i2":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.I2)
case "wireguard_i3":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.I3)
case "wireguard_i4":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.I4)
case "wireguard_i5":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.I5)
}
value, isSet, matched := s.getAmneziawgKey(key)
if matched {
return value, isSet
}
value, isSet, err := ReadFromFile(path)
@@ -110,6 +85,58 @@ func (s *Source) Get(key string) (value string, isSet bool) {
return value, isSet
}
func (s *Source) getAmneziawgKey(key string) (value string, isSet, matched bool) {
switch key {
case "amnezia_private_key":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.PrivateKey)
case "amnezia_preshared_key":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.PreSharedKey)
case "amnezia_addresses":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.Addresses)
case "amnezia_public_key":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.PublicKey)
case "amnezia_endpoint_ip":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.EndpointIP)
case "amnezia_endpoint_port":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.EndpointPort)
case "amnezia_jc":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Jc)
case "amnezia_jmin":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Jmin)
case "amnezia_jmax":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Jmax)
case "amnezia_s1":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().S1)
case "amnezia_s2":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().S2)
case "amnezia_s3":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().S3)
case "amnezia_s4":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().S4)
case "amnezia_h1":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().H1)
case "amnezia_h2":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().H2)
case "amnezia_h3":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().H3)
case "amnezia_h4":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().H4)
case "amnezia_i1":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().I1)
case "amnezia_i2":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().I2)
case "amnezia_i3":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().I3)
case "amnezia_i4":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().I4)
case "amnezia_i5":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().I5)
default:
return "", false, false
}
return value, isSet, true
}
func (s *Source) KeyTransform(key string) string {
switch key {
// TODO v4 remove these irregular cases
@@ -25,54 +25,13 @@ func (s *Source) lazyLoadWireguardConf() WireguardConfig {
return s.cached.wireguardConf
}
type amneziaWgConfig struct {
Jc *string
Jmin *string
Jmax *string
S1 *string
S2 *string
S3 *string
S4 *string
H1 *string
H2 *string
H3 *string
H4 *string
I1 *string
I2 *string
I3 *string
I4 *string
I5 *string
}
func parseWireguardAmneziaInterfaceSection(interfaceSection *ini.Section) amneziaWgConfig {
return amneziaWgConfig{
Jc: getINIKeyFromSection(interfaceSection, "Jc"),
Jmin: getINIKeyFromSection(interfaceSection, "Jmin"),
Jmax: getINIKeyFromSection(interfaceSection, "Jmax"),
S1: getINIKeyFromSection(interfaceSection, "S1"),
S2: getINIKeyFromSection(interfaceSection, "S2"),
S3: getINIKeyFromSection(interfaceSection, "S3"),
S4: getINIKeyFromSection(interfaceSection, "S4"),
H1: getINIKeyFromSection(interfaceSection, "H1"),
H2: getINIKeyFromSection(interfaceSection, "H2"),
H3: getINIKeyFromSection(interfaceSection, "H3"),
H4: getINIKeyFromSection(interfaceSection, "H4"),
I1: getINIKeyFromSection(interfaceSection, "I1"),
I2: getINIKeyFromSection(interfaceSection, "I2"),
I3: getINIKeyFromSection(interfaceSection, "I3"),
I4: getINIKeyFromSection(interfaceSection, "I4"),
I5: getINIKeyFromSection(interfaceSection, "I5"),
}
}
type WireguardConfig struct {
PrivateKey *string
PreSharedKey *string
Addresses *string
PublicKey *string
EndpointIP *string
EndpointPort *string
AmneziaParams amneziaWgConfig
PrivateKey *string
PreSharedKey *string
Addresses *string
PublicKey *string
EndpointIP *string
EndpointPort *string
}
var regexINISectionNotExist = regexp.MustCompile(`^section ".+" does not exist$`)
@@ -89,7 +48,6 @@ func ParseWireguardConf(path string) (config WireguardConfig, err error) {
interfaceSection, err := iniFile.GetSection("Interface")
if err == nil {
config.PrivateKey, config.Addresses = parseWireguardInterfaceSection(interfaceSection)
config.AmneziaParams = parseWireguardAmneziaInterfaceSection(interfaceSection)
} else if !regexINISectionNotExist.MatchString(err.Error()) {
// can never happen
return WireguardConfig{}, fmt.Errorf("getting interface section: %w", err)
@@ -97,10 +97,9 @@ func Test_parseWireguardInterfaceSection(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
iniData string
privateKey *string
addresses *string
amneziaParams amneziaWgConfig
iniData string
privateKey *string
addresses *string
}{
"no_fields": {
iniData: `[Interface]`,
@@ -116,17 +115,9 @@ PrivateKey = x
[Interface]
PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
Address = 10.38.22.35/32
Jc = 4
H1 = 721391205
I1 = <b 0x1234>
`,
privateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
addresses: ptrTo("10.38.22.35/32"),
amneziaParams: amneziaWgConfig{
Jc: ptrTo("4"),
H1: ptrTo("721391205"),
I1: ptrTo("<b 0x1234>"),
},
},
}
@@ -140,11 +131,9 @@ I1 = <b 0x1234>
require.NoError(t, err)
privateKey, addresses := parseWireguardInterfaceSection(iniSection)
amneziaWgConfig := parseWireguardAmneziaInterfaceSection(iniSection)
assert.Equal(t, testCase.privateKey, privateKey)
assert.Equal(t, testCase.addresses, addresses)
assert.Equal(t, testCase.amneziaParams, amneziaWgConfig)
})
}
}
@@ -0,0 +1,27 @@
package secrets
import (
"os"
"path/filepath"
"github.com/qdm12/gluetun/internal/configuration/sources/files"
)
func (s *Source) lazyLoadAmneziawgConf() files.AmneziawgConfig {
if s.cached.amneziawgLoaded {
return s.cached.amneziawgConf
}
path := os.Getenv("AMNEZIAWG_CONF_SECRETFILE")
if path == "" {
path = filepath.Join(s.rootDirectory, "amneziawg", "awg0.conf")
}
s.cached.amneziawgLoaded = true
var err error
s.cached.amneziawgConf, err = files.ParseAmneziawgConf(path)
if err != nil {
s.warner.Warnf("skipping Amneziawg config: %s", err)
}
return s.cached.amneziawgConf
}
@@ -15,6 +15,8 @@ type Source struct {
cached struct {
wireguardLoaded bool
wireguardConf files.WireguardConfig
amneziawgLoaded bool
amneziawgConf files.AmneziawgConfig
}
}
@@ -60,26 +62,26 @@ func (s *Source) Get(key string) (value string, isSet bool) {
s.warner.Warnf("skipping %s: parsing PEM: %s", path, err)
}
return value, isSet
case "wireguard_private_key":
case "wireguard_private_key", "amneziawg_private_key":
privateKey := s.lazyLoadWireguardConf().PrivateKey
if privateKey != nil {
return *privateKey, true
} // else continue to read from individual secret file
case "wireguard_preshared_key":
case "wireguard_preshared_key", "amneziawg_preshared_key":
preSharedKey := s.lazyLoadWireguardConf().PreSharedKey
if preSharedKey != nil {
return *preSharedKey, true
} // else continue to read from individual secret file
case "wireguard_addresses":
case "wireguard_addresses", "amneziawg_addresses":
addresses := s.lazyLoadWireguardConf().Addresses
if addresses != nil {
return *addresses, true
} // else continue to read from individual secret file
case "wireguard_public_key":
case "wireguard_public_key", "amneziawg_public_key":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().PublicKey)
case "wireguard_endpoint_ip":
case "wireguard_endpoint_ip", "amneziawg_endpoint_ip":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointIP)
case "wireguard_endpoint_port":
case "wireguard_endpoint_port", "amneziawg_endpoint_port":
return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointPort)
}
@@ -112,38 +114,50 @@ func (s *Source) KeyTransform(key string) string {
func (s *Source) getAmneziaWg(key string) (value string, isSet, matched bool) {
switch key {
case "wireguard_jc":
value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.Jc)
case "wireguard_jmin":
value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.Jmin)
case "wireguard_jmax":
value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.Jmax)
case "wireguard_s1":
value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.S1)
case "wireguard_s2":
value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.S2)
case "wireguard_s3":
value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.S3)
case "wireguard_s4":
value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.S4)
case "wireguard_h1":
value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.H1)
case "wireguard_h2":
value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.H2)
case "wireguard_h3":
value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.H3)
case "wireguard_h4":
value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.H4)
case "wireguard_i1":
value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.I1)
case "wireguard_i2":
value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.I2)
case "wireguard_i3":
value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.I3)
case "wireguard_i4":
value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.I4)
case "wireguard_i5":
value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.I5)
case "amneziawg_private_key":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.PrivateKey)
case "amneziawg_preshared_key":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.PreSharedKey)
case "wireguard_addresses", "amneziawg_addresses":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.Addresses)
case "wireguard_public_key", "amneziawg_public_key":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.PublicKey)
case "wireguard_endpoint_ip", "amneziawg_endpoint_ip":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.EndpointIP)
case "wireguard_endpoint_port", "amneziawg_endpoint_port":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.EndpointPort)
case "amneziawg_jc":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Jc)
case "amneziawg_jmin":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Jmin)
case "amneziawg_jmax":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Jmax)
case "amneziawg_s1":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().S1)
case "amneziawg_s2":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().S2)
case "amneziawg_s3":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().S3)
case "amneziawg_s4":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().S4)
case "amneziawg_h1":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().H1)
case "amneziawg_h2":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().H2)
case "amneziawg_h3":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().H3)
case "amneziawg_h4":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().H4)
case "amneziawg_i1":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().I1)
case "amneziawg_i2":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().I2)
case "amneziawg_i3":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().I3)
case "amneziawg_i4":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().I4)
case "amneziawg_i5":
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().I5)
default:
return "", false, false
}