diff --git a/Dockerfile b/Dockerfile index 1015df48..4edd45b2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -120,6 +120,8 @@ ENV VPN_SERVICE_PROVIDER=pia \ VPN_PORT_FORWARDING_LISTENING_PORT=0 \ VPN_PORT_FORWARDING_PROVIDER= \ VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \ + VPN_PORT_FORWARDING_USERNAME= \ + VPN_PORT_FORWARDING_PASSWORD= \ # # Cyberghost only: OPENVPN_CERT= \ OPENVPN_KEY= \ diff --git a/internal/configuration/settings/errors.go b/internal/configuration/settings/errors.go index ad60b02c..87de8866 100644 --- a/internal/configuration/settings/errors.go +++ b/internal/configuration/settings/errors.go @@ -28,6 +28,8 @@ var ( ErrOpenVPNVerbosityIsOutOfBounds = errors.New("verbosity value is out of bounds") ErrOpenVPNVersionIsNotValid = errors.New("version is not valid") ErrPortForwardingEnabled = errors.New("port forwarding cannot be enabled") + ErrPortForwardingUserEmpty = errors.New("port forwarding username is empty") + ErrPortForwardingPasswordEmpty = errors.New("port forwarding password is empty") ErrPublicIPPeriodTooShort = errors.New("public IP address check period is too short") ErrRegionNotValid = errors.New("the region specified is not valid") ErrServerAddressNotValid = errors.New("server listening address is not valid") diff --git a/internal/configuration/settings/portforward.go b/internal/configuration/settings/portforward.go index 2cf01767..6999d30e 100644 --- a/internal/configuration/settings/portforward.go +++ b/internal/configuration/settings/portforward.go @@ -33,6 +33,10 @@ type PortForwarding struct { // forwarded port. The redirection is disabled if it is set to 0, which // is its default as well. ListeningPort *uint16 `json:"listening_port"` + // Username is only used for Private Internet Access port forwarding. + Username string `json:"username"` + // Password is only used for Private Internet Access port forwarding. + Password string `json:"password"` } func (p PortForwarding) Validate(vpnProvider string) (err error) { @@ -61,6 +65,15 @@ func (p PortForwarding) Validate(vpnProvider string) (err error) { } } + if providerSelected == providers.PrivateInternetAccess { + switch { + case p.Username == "": + return fmt.Errorf("%w", ErrPortForwardingUserEmpty) + case p.Password == "": + return fmt.Errorf("%w", ErrPortForwardingPasswordEmpty) + } + } + return nil } @@ -70,6 +83,8 @@ func (p *PortForwarding) Copy() (copied PortForwarding) { Provider: gosettings.CopyPointer(p.Provider), Filepath: gosettings.CopyPointer(p.Filepath), ListeningPort: gosettings.CopyPointer(p.ListeningPort), + Username: p.Username, + Password: p.Password, } } @@ -78,6 +93,8 @@ func (p *PortForwarding) OverrideWith(other PortForwarding) { p.Provider = gosettings.OverrideWithPointer(p.Provider, other.Provider) p.Filepath = gosettings.OverrideWithPointer(p.Filepath, other.Filepath) p.ListeningPort = gosettings.OverrideWithPointer(p.ListeningPort, other.ListeningPort) + p.Username = gosettings.OverrideWithComparable(p.Username, other.Username) + p.Password = gosettings.OverrideWithComparable(p.Password, other.Password) } func (p *PortForwarding) setDefaults() { @@ -116,6 +133,12 @@ func (p PortForwarding) toLinesNode() (node *gotree.Node) { } node.Appendf("Forwarded port file path: %s", filepath) + if p.Username != "" { + credentialsNode := node.Appendf("Credentials:") + credentialsNode.Appendf("Username: %s", p.Username) + credentialsNode.Appendf("Password: %s", gosettings.ObfuscateKey(p.Password)) + } + return node } @@ -143,5 +166,10 @@ func (p *PortForwarding) read(r *reader.Reader) (err error) { return err } + p.Username = r.String("VPN_PORT_FORWARDING_USERNAME", + reader.RetroKeys("USER", "OPENVPN_USER"), reader.ForceLowercase(false)) + p.Password = r.String("VPN_PORT_FORWARDING_PASSWORD", + reader.RetroKeys("PASSWORD", "OPENVPN_PASSWORD"), reader.ForceLowercase(false)) + return nil } diff --git a/internal/portforward/service/settings.go b/internal/portforward/service/settings.go index 0e35117d..1782226b 100644 --- a/internal/portforward/service/settings.go +++ b/internal/portforward/service/settings.go @@ -16,6 +16,8 @@ type Settings struct { ServerName string // needed for PIA CanPortForward bool // needed for PIA ListeningPort uint16 + Username string // needed for PIA + Password string // needed for PIA } func (s Settings) Copy() (copied Settings) { @@ -26,6 +28,8 @@ func (s Settings) Copy() (copied Settings) { copied.ServerName = s.ServerName copied.CanPortForward = s.CanPortForward copied.ListeningPort = s.ListeningPort + copied.Username = s.Username + copied.Password = s.Password return copied } @@ -37,11 +41,15 @@ func (s *Settings) OverrideWith(update Settings) { s.ServerName = gosettings.OverrideWithComparable(s.ServerName, update.ServerName) s.CanPortForward = gosettings.OverrideWithComparable(s.CanPortForward, update.CanPortForward) s.ListeningPort = gosettings.OverrideWithComparable(s.ListeningPort, update.ListeningPort) + s.Username = gosettings.OverrideWithComparable(s.Username, update.Username) + s.Password = gosettings.OverrideWithComparable(s.Password, update.Password) } var ( ErrPortForwarderNotSet = errors.New("port forwarder not set") ErrServerNameNotSet = errors.New("server name not set") + ErrUsernameNotSet = errors.New("username not set") + ErrPasswordNotSet = errors.New("password not set") ErrFilepathNotSet = errors.New("file path not set") ErrInterfaceNotSet = errors.New("interface not set") ) @@ -64,8 +72,15 @@ func (s *Settings) Validate(forStartup bool) (err error) { return fmt.Errorf("%w", ErrPortForwarderNotSet) case s.Interface == "": return fmt.Errorf("%w", ErrInterfaceNotSet) - case s.PortForwarder.Name() == providers.PrivateInternetAccess && s.ServerName == "": - return fmt.Errorf("%w", ErrServerNameNotSet) + case s.PortForwarder.Name() == providers.PrivateInternetAccess: + switch { + case s.ServerName == "": + return fmt.Errorf("%w", ErrServerNameNotSet) + case s.Username == "": + return fmt.Errorf("%w", ErrUsernameNotSet) + case s.Password == "": + return fmt.Errorf("%w", ErrPasswordNotSet) + } } return nil } diff --git a/internal/portforward/service/start.go b/internal/portforward/service/start.go index 99f514ec..5c21b3cf 100644 --- a/internal/portforward/service/start.go +++ b/internal/portforward/service/start.go @@ -28,6 +28,8 @@ func (s *Service) Start(ctx context.Context) (runError <-chan error, err error) Client: s.client, ServerName: s.settings.ServerName, CanPortForward: s.settings.CanPortForward, + Username: s.settings.Username, + Password: s.settings.Password, } port, err := s.settings.PortForwarder.PortForward(ctx, obj) if err != nil { diff --git a/internal/provider/privateinternetaccess/portforward.go b/internal/provider/privateinternetaccess/portforward.go index a517356c..3955ced9 100644 --- a/internal/provider/privateinternetaccess/portforward.go +++ b/internal/provider/privateinternetaccess/portforward.go @@ -32,6 +32,10 @@ func (p *Provider) PortForward(ctx context.Context, panic("server name cannot be empty") case !objects.Gateway.IsValid(): panic("gateway is not set") + case objects.Username == "": + panic("username is not set") + case objects.Password == "": + panic("password is not set") } serverName := objects.ServerName @@ -67,7 +71,7 @@ func (p *Provider) PortForward(ctx context.Context, if !dataFound || expired { client := objects.Client data, err = refreshPIAPortForwardData(ctx, client, privateIPClient, objects.Gateway, - p.portForwardPath, p.authFilePath) + p.portForwardPath, objects.Username, objects.Password) if err != nil { return 0, fmt.Errorf("refreshing port forward data: %w", err) } @@ -136,8 +140,8 @@ func (p *Provider) KeepPortForward(ctx context.Context, } func refreshPIAPortForwardData(ctx context.Context, client, privateIPClient *http.Client, - gateway netip.Addr, portForwardPath, authFilePath string) (data piaPortForwardData, err error) { - data.Token, err = fetchToken(ctx, client, authFilePath) + gateway netip.Addr, portForwardPath, username, password string) (data piaPortForwardData, err error) { + data.Token, err = fetchToken(ctx, client, username, password) if err != nil { return data, fmt.Errorf("fetching token: %w", err) } @@ -237,12 +241,7 @@ var ( ) func fetchToken(ctx context.Context, client *http.Client, - authFilePath string) (token string, err error) { - username, password, err := getOpenvpnCredentials(authFilePath) - if err != nil { - return "", fmt.Errorf("getting username and password: %w", err) - } - + username, password string) (token string, err error) { errSubstitutions := map[string]string{ url.QueryEscape(username): "", url.QueryEscape(password): "", @@ -287,37 +286,6 @@ func fetchToken(ctx context.Context, client *http.Client, return result.Token, nil } -var ( - errAuthFileMalformed = errors.New("authentication file is malformed") -) - -func getOpenvpnCredentials(authFilePath string) ( - username, password string, err error) { - file, err := os.Open(authFilePath) - if err != nil { - return "", "", fmt.Errorf("reading OpenVPN authentication file: %w", err) - } - - authData, err := io.ReadAll(file) - if err != nil { - _ = file.Close() - return "", "", fmt.Errorf("reading authentication file: %w", err) - } - - if err := file.Close(); err != nil { - return "", "", err - } - - lines := strings.Split(string(authData), "\n") - const minLines = 2 - if len(lines) < minLines { - return "", "", fmt.Errorf("%w: only %d lines exist", errAuthFileMalformed, len(lines)) - } - - username, password = lines[0], lines[1] - return username, password, nil -} - func fetchPortForwardData(ctx context.Context, client *http.Client, gateway netip.Addr, token string) ( port uint16, signature string, expiration time.Time, err error) { errSubstitutions := map[string]string{url.QueryEscape(token): ""} diff --git a/internal/provider/privateinternetaccess/provider.go b/internal/provider/privateinternetaccess/provider.go index c1183501..6d342c2b 100644 --- a/internal/provider/privateinternetaccess/provider.go +++ b/internal/provider/privateinternetaccess/provider.go @@ -5,7 +5,6 @@ import ( "net/http" "time" - "github.com/qdm12/gluetun/internal/constants/openvpn" "github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/provider/common" "github.com/qdm12/gluetun/internal/provider/privateinternetaccess/updater" @@ -18,7 +17,6 @@ type Provider struct { common.Fetcher // Port forwarding portForwardPath string - authFilePath string } func New(storage common.Storage, randSource rand.Source, @@ -29,7 +27,6 @@ func New(storage common.Storage, randSource rand.Source, timeNow: timeNow, randSource: randSource, portForwardPath: jsonPortForwardPath, - authFilePath: openvpn.AuthConf, Fetcher: updater.New(client), } } diff --git a/internal/provider/utils/portforward.go b/internal/provider/utils/portforward.go index 8a5430a3..0dd2a8f5 100644 --- a/internal/provider/utils/portforward.go +++ b/internal/provider/utils/portforward.go @@ -19,6 +19,10 @@ type PortForwardObjects struct { ServerName string // CanPortForward is used by Private Internet Access for port forwarding. CanPortForward bool + // Username is used by Private Internet Access for port forwarding. + Username string + // Password is used by Private Internet Access for port forwarding. + Password string } type Routing interface { diff --git a/internal/vpn/portforward.go b/internal/vpn/portforward.go index f7eb92f2..c773c3fc 100644 --- a/internal/vpn/portforward.go +++ b/internal/vpn/portforward.go @@ -30,6 +30,8 @@ func (l *Loop) startPortForwarding(data tunnelUpData) (err error) { Interface: data.vpnIntf, ServerName: data.serverName, CanPortForward: data.canPortForward, + Username: data.username, + Password: data.password, }, } return l.portForward.UpdateWith(partialUpdate) diff --git a/internal/vpn/run.go b/internal/vpn/run.go index 8926ac6d..a0cc0274 100644 --- a/internal/vpn/run.go +++ b/internal/vpn/run.go @@ -50,6 +50,8 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) { canPortForward: canPortForward, portForwarder: portForwarder, vpnIntf: vpnInterface, + username: settings.Provider.PortForwarding.Username, + password: settings.Provider.PortForwarding.Password, } openvpnCtx, openvpnCancel := context.WithCancel(context.Background()) diff --git a/internal/vpn/tunnelup.go b/internal/vpn/tunnelup.go index 5126b9c3..6e723041 100644 --- a/internal/vpn/tunnelup.go +++ b/internal/vpn/tunnelup.go @@ -12,6 +12,8 @@ type tunnelUpData struct { vpnIntf string serverName string // used for PIA canPortForward bool // used for PIA + username string // used for PIA + password string // used for PIA portForwarder PortForwarder }