Skip to content

Commit

Permalink
feat(pia): port forwarding options VPN_PORT_FORWARDING_USERNAME and…
Browse files Browse the repository at this point in the history
… `VPN_PORT_FORWARDING_PASSWORD`

- Retro-compatible with `OPENVPN_USER` + `OPENVPN_PASSWORD`
- No more reading for the OpenVPN auth file
- Allow to use PIA port forwarding with Wireguard
  • Loading branch information
qdm12 committed Jul 9, 2024
1 parent 06c9bc5 commit 0501743
Show file tree
Hide file tree
Showing 11 changed files with 69 additions and 45 deletions.
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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= \
Expand Down
2 changes: 2 additions & 0 deletions internal/configuration/settings/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
28 changes: 28 additions & 0 deletions internal/configuration/settings/portforward.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}

Expand All @@ -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,
}
}

Expand All @@ -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() {
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}
19 changes: 17 additions & 2 deletions internal/portforward/service/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
}

Expand All @@ -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")
)
Expand All @@ -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
}
2 changes: 2 additions & 0 deletions internal/portforward/service/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
48 changes: 8 additions & 40 deletions internal/provider/privateinternetaccess/portforward.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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): "<username>",
url.QueryEscape(password): "<password>",
Expand Down Expand Up @@ -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): "<token>"}
Expand Down
3 changes: 0 additions & 3 deletions internal/provider/privateinternetaccess/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -18,7 +17,6 @@ type Provider struct {
common.Fetcher
// Port forwarding
portForwardPath string
authFilePath string
}

func New(storage common.Storage, randSource rand.Source,
Expand All @@ -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),
}
}
Expand Down
4 changes: 4 additions & 0 deletions internal/provider/utils/portforward.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions internal/vpn/portforward.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions internal/vpn/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
2 changes: 2 additions & 0 deletions internal/vpn/tunnelup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down

0 comments on commit 0501743

Please sign in to comment.