mirror of
https://github.com/qdm12/gluetun.git
synced 2026-05-10 04:30:20 +02:00
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_LISTENING_PORT=0 \
|
||||||
VPN_PORT_FORWARDING_PROVIDER= \
|
VPN_PORT_FORWARDING_PROVIDER= \
|
||||||
VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
||||||
|
VPN_PORT_FORWARDING_USERNAME= \
|
||||||
|
VPN_PORT_FORWARDING_PASSWORD= \
|
||||||
# # Cyberghost only:
|
# # Cyberghost only:
|
||||||
OPENVPN_CERT= \
|
OPENVPN_CERT= \
|
||||||
OPENVPN_KEY= \
|
OPENVPN_KEY= \
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ var (
|
|||||||
ErrOpenVPNVerbosityIsOutOfBounds = errors.New("verbosity value is out of bounds")
|
ErrOpenVPNVerbosityIsOutOfBounds = errors.New("verbosity value is out of bounds")
|
||||||
ErrOpenVPNVersionIsNotValid = errors.New("version is not valid")
|
ErrOpenVPNVersionIsNotValid = errors.New("version is not valid")
|
||||||
ErrPortForwardingEnabled = errors.New("port forwarding cannot be enabled")
|
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")
|
ErrPublicIPPeriodTooShort = errors.New("public IP address check period is too short")
|
||||||
ErrRegionNotValid = errors.New("the region specified is not valid")
|
ErrRegionNotValid = errors.New("the region specified is not valid")
|
||||||
ErrServerAddressNotValid = errors.New("server listening address 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
|
// forwarded port. The redirection is disabled if it is set to 0, which
|
||||||
// is its default as well.
|
// is its default as well.
|
||||||
ListeningPort *uint16 `json:"listening_port"`
|
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) {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +83,8 @@ func (p *PortForwarding) Copy() (copied PortForwarding) {
|
|||||||
Provider: gosettings.CopyPointer(p.Provider),
|
Provider: gosettings.CopyPointer(p.Provider),
|
||||||
Filepath: gosettings.CopyPointer(p.Filepath),
|
Filepath: gosettings.CopyPointer(p.Filepath),
|
||||||
ListeningPort: gosettings.CopyPointer(p.ListeningPort),
|
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.Provider = gosettings.OverrideWithPointer(p.Provider, other.Provider)
|
||||||
p.Filepath = gosettings.OverrideWithPointer(p.Filepath, other.Filepath)
|
p.Filepath = gosettings.OverrideWithPointer(p.Filepath, other.Filepath)
|
||||||
p.ListeningPort = gosettings.OverrideWithPointer(p.ListeningPort, other.ListeningPort)
|
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() {
|
func (p *PortForwarding) setDefaults() {
|
||||||
@@ -116,6 +133,12 @@ func (p PortForwarding) toLinesNode() (node *gotree.Node) {
|
|||||||
}
|
}
|
||||||
node.Appendf("Forwarded port file path: %s", filepath)
|
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
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,5 +166,10 @@ func (p *PortForwarding) read(r *reader.Reader) (err error) {
|
|||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ type Settings struct {
|
|||||||
ServerName string // needed for PIA
|
ServerName string // needed for PIA
|
||||||
CanPortForward bool // needed for PIA
|
CanPortForward bool // needed for PIA
|
||||||
ListeningPort uint16
|
ListeningPort uint16
|
||||||
|
Username string // needed for PIA
|
||||||
|
Password string // needed for PIA
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Settings) Copy() (copied Settings) {
|
func (s Settings) Copy() (copied Settings) {
|
||||||
@@ -26,6 +28,8 @@ func (s Settings) Copy() (copied Settings) {
|
|||||||
copied.ServerName = s.ServerName
|
copied.ServerName = s.ServerName
|
||||||
copied.CanPortForward = s.CanPortForward
|
copied.CanPortForward = s.CanPortForward
|
||||||
copied.ListeningPort = s.ListeningPort
|
copied.ListeningPort = s.ListeningPort
|
||||||
|
copied.Username = s.Username
|
||||||
|
copied.Password = s.Password
|
||||||
return copied
|
return copied
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,11 +41,15 @@ func (s *Settings) OverrideWith(update Settings) {
|
|||||||
s.ServerName = gosettings.OverrideWithComparable(s.ServerName, update.ServerName)
|
s.ServerName = gosettings.OverrideWithComparable(s.ServerName, update.ServerName)
|
||||||
s.CanPortForward = gosettings.OverrideWithComparable(s.CanPortForward, update.CanPortForward)
|
s.CanPortForward = gosettings.OverrideWithComparable(s.CanPortForward, update.CanPortForward)
|
||||||
s.ListeningPort = gosettings.OverrideWithComparable(s.ListeningPort, update.ListeningPort)
|
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 (
|
var (
|
||||||
ErrPortForwarderNotSet = errors.New("port forwarder not set")
|
ErrPortForwarderNotSet = errors.New("port forwarder not set")
|
||||||
ErrServerNameNotSet = errors.New("server name 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")
|
ErrFilepathNotSet = errors.New("file path not set")
|
||||||
ErrInterfaceNotSet = errors.New("interface 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)
|
return fmt.Errorf("%w", ErrPortForwarderNotSet)
|
||||||
case s.Interface == "":
|
case s.Interface == "":
|
||||||
return fmt.Errorf("%w", ErrInterfaceNotSet)
|
return fmt.Errorf("%w", ErrInterfaceNotSet)
|
||||||
case s.PortForwarder.Name() == providers.PrivateInternetAccess && s.ServerName == "":
|
case s.PortForwarder.Name() == providers.PrivateInternetAccess:
|
||||||
return fmt.Errorf("%w", ErrServerNameNotSet)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ func (s *Service) Start(ctx context.Context) (runError <-chan error, err error)
|
|||||||
Client: s.client,
|
Client: s.client,
|
||||||
ServerName: s.settings.ServerName,
|
ServerName: s.settings.ServerName,
|
||||||
CanPortForward: s.settings.CanPortForward,
|
CanPortForward: s.settings.CanPortForward,
|
||||||
|
Username: s.settings.Username,
|
||||||
|
Password: s.settings.Password,
|
||||||
}
|
}
|
||||||
port, err := s.settings.PortForwarder.PortForward(ctx, obj)
|
port, err := s.settings.PortForwarder.PortForward(ctx, obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ func (p *Provider) PortForward(ctx context.Context,
|
|||||||
panic("server name cannot be empty")
|
panic("server name cannot be empty")
|
||||||
case !objects.Gateway.IsValid():
|
case !objects.Gateway.IsValid():
|
||||||
panic("gateway is not set")
|
panic("gateway is not set")
|
||||||
|
case objects.Username == "":
|
||||||
|
panic("username is not set")
|
||||||
|
case objects.Password == "":
|
||||||
|
panic("password is not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
serverName := objects.ServerName
|
serverName := objects.ServerName
|
||||||
@@ -67,7 +71,7 @@ func (p *Provider) PortForward(ctx context.Context,
|
|||||||
if !dataFound || expired {
|
if !dataFound || expired {
|
||||||
client := objects.Client
|
client := objects.Client
|
||||||
data, err = refreshPIAPortForwardData(ctx, client, privateIPClient, objects.Gateway,
|
data, err = refreshPIAPortForwardData(ctx, client, privateIPClient, objects.Gateway,
|
||||||
p.portForwardPath, p.authFilePath)
|
p.portForwardPath, objects.Username, objects.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("refreshing port forward data: %w", err)
|
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,
|
func refreshPIAPortForwardData(ctx context.Context, client, privateIPClient *http.Client,
|
||||||
gateway netip.Addr, portForwardPath, authFilePath string) (data piaPortForwardData, err error) {
|
gateway netip.Addr, portForwardPath, username, password string) (data piaPortForwardData, err error) {
|
||||||
data.Token, err = fetchToken(ctx, client, authFilePath)
|
data.Token, err = fetchToken(ctx, client, username, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data, fmt.Errorf("fetching token: %w", err)
|
return data, fmt.Errorf("fetching token: %w", err)
|
||||||
}
|
}
|
||||||
@@ -237,12 +241,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func fetchToken(ctx context.Context, client *http.Client,
|
func fetchToken(ctx context.Context, client *http.Client,
|
||||||
authFilePath string) (token string, err error) {
|
username, password string) (token string, err error) {
|
||||||
username, password, err := getOpenvpnCredentials(authFilePath)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("getting username and password: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
errSubstitutions := map[string]string{
|
errSubstitutions := map[string]string{
|
||||||
url.QueryEscape(username): "<username>",
|
url.QueryEscape(username): "<username>",
|
||||||
url.QueryEscape(password): "<password>",
|
url.QueryEscape(password): "<password>",
|
||||||
@@ -287,37 +286,6 @@ func fetchToken(ctx context.Context, client *http.Client,
|
|||||||
return result.Token, nil
|
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) (
|
func fetchPortForwardData(ctx context.Context, client *http.Client, gateway netip.Addr, token string) (
|
||||||
port uint16, signature string, expiration time.Time, err error) {
|
port uint16, signature string, expiration time.Time, err error) {
|
||||||
errSubstitutions := map[string]string{url.QueryEscape(token): "<token>"}
|
errSubstitutions := map[string]string{url.QueryEscape(token): "<token>"}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/openvpn"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
"github.com/qdm12/gluetun/internal/provider/common"
|
"github.com/qdm12/gluetun/internal/provider/common"
|
||||||
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/updater"
|
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/updater"
|
||||||
@@ -18,7 +17,6 @@ type Provider struct {
|
|||||||
common.Fetcher
|
common.Fetcher
|
||||||
// Port forwarding
|
// Port forwarding
|
||||||
portForwardPath string
|
portForwardPath string
|
||||||
authFilePath string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(storage common.Storage, randSource rand.Source,
|
func New(storage common.Storage, randSource rand.Source,
|
||||||
@@ -29,7 +27,6 @@ func New(storage common.Storage, randSource rand.Source,
|
|||||||
timeNow: timeNow,
|
timeNow: timeNow,
|
||||||
randSource: randSource,
|
randSource: randSource,
|
||||||
portForwardPath: jsonPortForwardPath,
|
portForwardPath: jsonPortForwardPath,
|
||||||
authFilePath: openvpn.AuthConf,
|
|
||||||
Fetcher: updater.New(client),
|
Fetcher: updater.New(client),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ type PortForwardObjects struct {
|
|||||||
ServerName string
|
ServerName string
|
||||||
// CanPortForward is used by Private Internet Access for port forwarding.
|
// CanPortForward is used by Private Internet Access for port forwarding.
|
||||||
CanPortForward bool
|
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 {
|
type Routing interface {
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ func (l *Loop) startPortForwarding(data tunnelUpData) (err error) {
|
|||||||
Interface: data.vpnIntf,
|
Interface: data.vpnIntf,
|
||||||
ServerName: data.serverName,
|
ServerName: data.serverName,
|
||||||
CanPortForward: data.canPortForward,
|
CanPortForward: data.canPortForward,
|
||||||
|
Username: data.username,
|
||||||
|
Password: data.password,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return l.portForward.UpdateWith(partialUpdate)
|
return l.portForward.UpdateWith(partialUpdate)
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
|||||||
canPortForward: canPortForward,
|
canPortForward: canPortForward,
|
||||||
portForwarder: portForwarder,
|
portForwarder: portForwarder,
|
||||||
vpnIntf: vpnInterface,
|
vpnIntf: vpnInterface,
|
||||||
|
username: settings.Provider.PortForwarding.Username,
|
||||||
|
password: settings.Provider.PortForwarding.Password,
|
||||||
}
|
}
|
||||||
|
|
||||||
openvpnCtx, openvpnCancel := context.WithCancel(context.Background())
|
openvpnCtx, openvpnCancel := context.WithCancel(context.Background())
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ type tunnelUpData struct {
|
|||||||
vpnIntf string
|
vpnIntf string
|
||||||
serverName string // used for PIA
|
serverName string // used for PIA
|
||||||
canPortForward bool // used for PIA
|
canPortForward bool // used for PIA
|
||||||
|
username string // used for PIA
|
||||||
|
password string // used for PIA
|
||||||
portForwarder PortForwarder
|
portForwarder PortForwarder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user