diff --git a/internal/configuration/settings/openvpn.go b/internal/configuration/settings/openvpn.go index 95be2c3e..74be08e8 100644 --- a/internal/configuration/settings/openvpn.go +++ b/internal/configuration/settings/openvpn.go @@ -19,13 +19,15 @@ type OpenVPN struct { // It can only be "2.4" or "2.5". Version string // User is the OpenVPN authentication username. - // It cannot be an empty string in the internal state - // if OpenVPN is used. - User string + // It cannot be nil in the internal state if OpenVPN is used. + // It is usually required but in some cases can be the empty string + // to indicate no user+password authentication is needed. + User *string // Password is the OpenVPN authentication password. - // It cannot be an empty string in the internal state - // if OpenVPN is used. - Password string + // It cannot be nil in the internal state if OpenVPN is used. + // It is usually required but in some cases can be the empty string + // to indicate no user+password authentication is needed. + Password *string // ConfFile is a custom OpenVPN configuration file path. // It can be set to the empty string for it to be ignored. // It cannot be nil in the internal state. @@ -88,14 +90,14 @@ func (o OpenVPN) validate(vpnProvider string) (err error) { isCustom := vpnProvider == providers.Custom - if !isCustom && o.User == "" { + if !isCustom && *o.User == "" { return ErrOpenVPNUserIsEmpty } passwordRequired := !isCustom && - (vpnProvider != providers.Ivpn || !ivpnAccountID.MatchString(o.User)) + (vpnProvider != providers.Ivpn || !ivpnAccountID.MatchString(*o.User)) - if passwordRequired && o.Password == "" { + if passwordRequired && *o.Password == "" { return ErrOpenVPNPasswordIsEmpty } @@ -204,8 +206,8 @@ func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) { func (o *OpenVPN) copy() (copied OpenVPN) { return OpenVPN{ Version: o.Version, - User: o.User, - Password: o.Password, + User: helpers.CopyStringPtr(o.User), + Password: helpers.CopyStringPtr(o.Password), ConfFile: helpers.CopyStringPtr(o.ConfFile), Ciphers: helpers.CopyStringSlice(o.Ciphers), Auth: helpers.CopyStringPtr(o.Auth), @@ -225,8 +227,8 @@ func (o *OpenVPN) copy() (copied OpenVPN) { // unset field of the receiver settings object. func (o *OpenVPN) mergeWith(other OpenVPN) { o.Version = helpers.MergeWithString(o.Version, other.Version) - o.User = helpers.MergeWithString(o.User, other.User) - o.Password = helpers.MergeWithString(o.Password, other.Password) + o.User = helpers.MergeWithStringPtr(o.User, other.User) + o.Password = helpers.MergeWithStringPtr(o.Password, other.Password) o.ConfFile = helpers.MergeWithStringPtr(o.ConfFile, other.ConfFile) o.Ciphers = helpers.MergeStringSlices(o.Ciphers, other.Ciphers) o.Auth = helpers.MergeWithStringPtr(o.Auth, other.Auth) @@ -246,8 +248,8 @@ func (o *OpenVPN) mergeWith(other OpenVPN) { // settings. func (o *OpenVPN) overrideWith(other OpenVPN) { o.Version = helpers.OverrideWithString(o.Version, other.Version) - o.User = helpers.OverrideWithString(o.User, other.User) - o.Password = helpers.OverrideWithString(o.Password, other.Password) + o.User = helpers.OverrideWithStringPtr(o.User, other.User) + o.Password = helpers.OverrideWithStringPtr(o.Password, other.Password) o.ConfFile = helpers.OverrideWithStringPtr(o.ConfFile, other.ConfFile) o.Ciphers = helpers.OverrideWithStringSlice(o.Ciphers, other.Ciphers) o.Auth = helpers.OverrideWithStringPtr(o.Auth, other.Auth) @@ -264,8 +266,11 @@ func (o *OpenVPN) overrideWith(other OpenVPN) { func (o *OpenVPN) setDefaults(vpnProvider string) { o.Version = helpers.DefaultString(o.Version, openvpn.Openvpn25) + o.User = helpers.DefaultStringPtr(o.User, "") if vpnProvider == providers.Mullvad { - o.Password = "m" + o.Password = helpers.DefaultStringPtr(o.Password, "m") + } else { + o.Password = helpers.DefaultStringPtr(o.Password, "") } o.ConfFile = helpers.DefaultStringPtr(o.ConfFile, "") @@ -293,8 +298,8 @@ func (o OpenVPN) String() string { func (o OpenVPN) toLinesNode() (node *gotree.Node) { node = gotree.New("OpenVPN settings:") node.Appendf("OpenVPN version: %s", o.Version) - node.Appendf("User: %s", helpers.ObfuscatePassword(o.User)) - node.Appendf("Password: %s", helpers.ObfuscatePassword(o.Password)) + node.Appendf("User: %s", helpers.ObfuscatePassword(*o.User)) + node.Appendf("Password: %s", helpers.ObfuscatePassword(*o.Password)) if *o.ConfFile != "" { node.Appendf("Custom configuration file: %s", *o.ConfFile) diff --git a/internal/configuration/sources/env/openvpn.go b/internal/configuration/sources/env/openvpn.go index a0499bdc..413d3c41 100644 --- a/internal/configuration/sources/env/openvpn.go +++ b/internal/configuration/sources/env/openvpn.go @@ -72,14 +72,25 @@ func (r *Reader) readOpenVPN() ( return openVPN, nil } -func (r *Reader) readOpenVPNUser() (user string) { - _, user = r.getEnvWithRetro("OPENVPN_USER", "USER") +func (r *Reader) readOpenVPNUser() (user *string) { + user = new(string) + _, *user = r.getEnvWithRetro("OPENVPN_USER", "USER") + if *user == "" { + return nil + } + // Remove spaces in user ID to simplify user's life, thanks @JeordyR - return strings.ReplaceAll(user, " ", "") + *user = strings.ReplaceAll(*user, " ", "") + return user } -func (r *Reader) readOpenVPNPassword() (password string) { - _, password = r.getEnvWithRetro("OPENVPN_PASSWORD", "PASSWORD") +func (r *Reader) readOpenVPNPassword() (password *string) { + password = new(string) + _, *password = r.getEnvWithRetro("OPENVPN_PASSWORD", "PASSWORD") + if *password == "" { + return nil + } + return password } diff --git a/internal/configuration/sources/secrets/helpers.go b/internal/configuration/sources/secrets/helpers.go index 905a9c99..effa54fb 100644 --- a/internal/configuration/sources/secrets/helpers.go +++ b/internal/configuration/sources/secrets/helpers.go @@ -25,18 +25,3 @@ func readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath string) ( } return files.ReadFromFile(path) } - -func readSecretFileAsString(secretPathEnvKey, defaultSecretPath string) ( - s string, err error) { - path := getCleanedEnv(secretPathEnvKey) - if path == "" { - path = defaultSecretPath - } - stringPtr, err := files.ReadFromFile(path) - if err != nil { - return "", err - } else if stringPtr == nil { - return "", nil - } - return *stringPtr, nil -} diff --git a/internal/configuration/sources/secrets/openvpn.go b/internal/configuration/sources/secrets/openvpn.go index f10dbe92..86c1bb42 100644 --- a/internal/configuration/sources/secrets/openvpn.go +++ b/internal/configuration/sources/secrets/openvpn.go @@ -8,7 +8,7 @@ import ( func readOpenVPN() ( settings settings.OpenVPN, err error) { - settings.User, err = readSecretFileAsString( + settings.User, err = readSecretFileAsStringPtr( "OPENVPN_USER_SECRETFILE", "/run/secrets/openvpn_user", ) @@ -16,7 +16,7 @@ func readOpenVPN() ( return settings, fmt.Errorf("cannot read user file: %w", err) } - settings.Password, err = readSecretFileAsString( + settings.Password, err = readSecretFileAsStringPtr( "OPENVPN_PASSWORD_SECRETFILE", "/run/secrets/openvpn_password", ) diff --git a/internal/provider/custom/openvpnconf.go b/internal/provider/custom/openvpnconf.go index a6ee53ad..66502b08 100644 --- a/internal/provider/custom/openvpnconf.go +++ b/internal/provider/custom/openvpnconf.go @@ -74,7 +74,7 @@ func modifyConfig(lines []string, connection models.Connection, modified = append(modified, "pull-filter ignore \"auth-token\"") // prevent auth failed loop modified = append(modified, "auth-retry nointeract") modified = append(modified, "suppress-timestamps") - if settings.User != "" { + if *settings.User != "" { modified = append(modified, "auth-user-pass "+openvpn.AuthConf) } modified = append(modified, "verb "+strconv.Itoa(*settings.Verbosity)) diff --git a/internal/provider/custom/openvpnconf_test.go b/internal/provider/custom/openvpnconf_test.go index 8d8f6076..34e6b4fc 100644 --- a/internal/provider/custom/openvpnconf_test.go +++ b/internal/provider/custom/openvpnconf_test.go @@ -36,7 +36,7 @@ func Test_modifyConfig(t *testing.T) { "auth bla", }, settings: settings.OpenVPN{ - User: "user", + User: stringPtr("user"), Ciphers: []string{"cipher"}, Auth: stringPtr("auth"), MSSFix: uint16Ptr(1000), diff --git a/internal/server/helpers.go b/internal/server/helpers.go new file mode 100644 index 00000000..b32f2f5a --- /dev/null +++ b/internal/server/helpers.go @@ -0,0 +1,3 @@ +package server + +func stringPtr(s string) *string { return &s } diff --git a/internal/server/openvpn.go b/internal/server/openvpn.go index f5ff1247..0b58ce33 100644 --- a/internal/server/openvpn.go +++ b/internal/server/openvpn.go @@ -94,8 +94,8 @@ func (h *openvpnHandler) setStatus(w http.ResponseWriter, r *http.Request) { func (h *openvpnHandler) getSettings(w http.ResponseWriter) { vpnSettings := h.looper.GetSettings() settings := vpnSettings.OpenVPN - settings.User = "redacted" - settings.Password = "redacted" + settings.User = stringPtr("redacted") + settings.Password = stringPtr("redacted") encoder := json.NewEncoder(w) if err := encoder.Encode(settings); err != nil { h.warner.Warn(err.Error()) diff --git a/internal/vpn/openvpn.go b/internal/vpn/openvpn.go index 7bcb4a07..bb852942 100644 --- a/internal/vpn/openvpn.go +++ b/internal/vpn/openvpn.go @@ -27,8 +27,8 @@ func setupOpenVPN(ctx context.Context, fw Firewall, return nil, "", fmt.Errorf("failed writing configuration to file: %w", err) } - if settings.OpenVPN.User != "" { - err := openvpnConf.WriteAuthFile(settings.OpenVPN.User, settings.OpenVPN.Password) + if *settings.OpenVPN.User != "" { + err := openvpnConf.WriteAuthFile(*settings.OpenVPN.User, *settings.OpenVPN.Password) if err != nil { return nil, "", fmt.Errorf("failed writing auth to file: %w", err) }