feat(pia): port forwarding options VPN_PORT_FORWARDING_USERNAME and 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
This commit is contained in:
@@ -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= \
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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): "<username>",
|
||||
url.QueryEscape(password): "<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): "<token>"}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user