Rewrite of the entrypoint in Golang (#71)

- General improvements
    - Parallel download of only needed files at start
    - Prettier console output with all streams merged (openvpn, unbound, shadowsocks etc.)
    - Simplified Docker final image
    - Faster bootup
- DNS over TLS
    - Finer grain blocking at DNS level: malicious, ads and surveillance
    - Choose your DNS over TLS providers
    - Ability to use multiple DNS over TLS providers for DNS split horizon
    - Environment variables for DNS logging
    - DNS block lists needed are downloaded and built automatically at start, in parallel
- PIA
    - A random region is selected if the REGION parameter is left empty (thanks @rorph for your PR)
    - Routing and iptables adjusted so it can work as a Kubernetes pod sidecar (thanks @rorph for your PR)
This commit is contained in:
Quentin McGaw
2020-02-06 20:42:46 -05:00
committed by GitHub
parent 3de4ffcf66
commit 64649039d9
74 changed files with 4598 additions and 1019 deletions

93
internal/params/dns.go Normal file
View File

@@ -0,0 +1,93 @@
package params
import (
"fmt"
"strings"
libparams "github.com/qdm12/golibs/params"
"github.com/qdm12/private-internet-access-docker/internal/constants"
"github.com/qdm12/private-internet-access-docker/internal/models"
)
// GetDNSOverTLS obtains if the DNS over TLS should be enabled
// from the environment variable DOT
func (p *paramsReader) GetDNSOverTLS() (DNSOverTLS bool, err error) {
return p.envParams.GetOnOff("DOT", libparams.Default("on"))
}
// GetDNSOverTLSProviders obtains the DNS over TLS providers to use
// from the environment variable DOT_PROVIDERS
func (p *paramsReader) GetDNSOverTLSProviders() (providers []models.DNSProvider, err error) {
s, err := p.envParams.GetEnv("DOT_PROVIDERS", libparams.Default("cloudflare"))
if err != nil {
return nil, err
}
for _, word := range strings.Split(s, ",") {
provider := models.DNSProvider(word)
switch provider {
case constants.Cloudflare, constants.Google, constants.Quad9, constants.Quadrant, constants.CleanBrowsing, constants.SecureDNS, constants.LibreDNS:
providers = append(providers, provider)
default:
return nil, fmt.Errorf("DNS over TLS provider %q is not valid", provider)
}
}
return providers, nil
}
// GetDNSOverTLSVerbosity obtains the verbosity level to use for Unbound
// from the environment variable DOT_VERBOSITY
func (p *paramsReader) GetDNSOverTLSVerbosity() (verbosityLevel uint8, err error) {
n, err := p.envParams.GetEnvIntRange("DOT_VERBOSITY", 0, 5, libparams.Default("1"))
return uint8(n), err
}
// GetDNSOverTLSVerbosityDetails obtains the log level to use for Unbound
// from the environment variable DOT_VERBOSITY_DETAILS
func (p *paramsReader) GetDNSOverTLSVerbosityDetails() (verbosityDetailsLevel uint8, err error) {
n, err := p.envParams.GetEnvIntRange("DOT_VERBOSITY_DETAILS", 0, 4, libparams.Default("0"))
return uint8(n), err
}
// GetDNSOverTLSValidationLogLevel obtains the log level to use for Unbound DOT validation
// from the environment variable DOT_VALIDATION_LOGLEVEL
func (p *paramsReader) GetDNSOverTLSValidationLogLevel() (validationLogLevel uint8, err error) {
n, err := p.envParams.GetEnvIntRange("DOT_VALIDATION_LOGLEVEL", 0, 2, libparams.Default("0"))
return uint8(n), err
}
// GetDNSMaliciousBlocking obtains if malicious hostnames/IPs should be blocked
// from being resolved by Unbound, using the environment variable BLOCK_MALICIOUS
func (p *paramsReader) GetDNSMaliciousBlocking() (blocking bool, err error) {
return p.envParams.GetOnOff("BLOCK_MALICIOUS", libparams.Default("on"))
}
// GetDNSSurveillanceBlocking obtains if surveillance hostnames/IPs should be blocked
// from being resolved by Unbound, using the environment variable BLOCK_NSA
func (p *paramsReader) GetDNSSurveillanceBlocking() (blocking bool, err error) {
return p.envParams.GetOnOff("BLOCK_NSA", libparams.Default("off"))
}
// GetDNSAdsBlocking obtains if ads hostnames/IPs should be blocked
// from being resolved by Unbound, using the environment variable BLOCK_ADS
func (p *paramsReader) GetDNSAdsBlocking() (blocking bool, err error) {
return p.envParams.GetOnOff("BLOCK_ADS", libparams.Default("off"))
}
// GetDNSUnblockedHostnames obtains a list of hostnames to unblock from block lists
// from the comma separated list for the environment variable UNBLOCK
func (p *paramsReader) GetDNSUnblockedHostnames() (hostnames []string, err error) {
s, err := p.envParams.GetEnv("UNBLOCK")
if err != nil {
return nil, err
}
if len(s) == 0 {
return nil, nil
}
hostnames = strings.Split(s, ",")
for _, hostname := range hostnames {
if !p.verifier.MatchHostname(hostname) {
return nil, fmt.Errorf("hostname %q does not seem valid", hostname)
}
}
return hostnames, nil
}

View File

@@ -0,0 +1,29 @@
package params
import (
"fmt"
"net"
"strings"
)
// GetExtraSubnets obtains the CIDR subnets from the comma separated list of the
// environment variable EXTRA_SUBNETS
func (p *paramsReader) GetExtraSubnets() (extraSubnets []net.IPNet, err error) {
s, err := p.envParams.GetEnv("EXTRA_SUBNETS")
if err != nil {
return nil, err
} else if s == "" {
return nil, nil
}
subnets := strings.Split(s, ",")
for _, subnet := range subnets {
_, cidr, err := net.ParseCIDR(subnet)
if err != nil {
return nil, fmt.Errorf("could not parse subnet %q from environment variable with key EXTRA_SUBNETS: %w", subnet, err)
} else if cidr == nil {
return nil, fmt.Errorf("parsing subnet %q resulted in a nil CIDR", subnet)
}
extraSubnets = append(extraSubnets, *cidr)
}
return extraSubnets, nil
}

View File

@@ -0,0 +1,13 @@
package params
import (
libparams "github.com/qdm12/golibs/params"
"github.com/qdm12/private-internet-access-docker/internal/models"
)
// GetNetworkProtocol obtains the network protocol to use to connect to the
// VPN servers from the environment variable PROTOCOL
func (p *paramsReader) GetNetworkProtocol() (protocol models.NetworkProtocol, err error) {
s, err := p.envParams.GetValueIfInside("PROTOCOL", []string{"tcp", "udp"}, libparams.Default("udp"))
return models.NetworkProtocol(s), err
}

73
internal/params/params.go Normal file
View File

@@ -0,0 +1,73 @@
package params
import (
"net"
"os"
"github.com/qdm12/golibs/logging"
libparams "github.com/qdm12/golibs/params"
"github.com/qdm12/golibs/verification"
"github.com/qdm12/private-internet-access-docker/internal/models"
)
// ParamsReader contains methods to obtain parameters
type ParamsReader interface {
// DNS over TLS getters
GetDNSOverTLS() (DNSOverTLS bool, err error)
GetDNSOverTLSProviders() (providers []models.DNSProvider, err error)
GetDNSOverTLSVerbosity() (verbosityLevel uint8, err error)
GetDNSOverTLSVerbosityDetails() (verbosityDetailsLevel uint8, err error)
GetDNSOverTLSValidationLogLevel() (validationLogLevel uint8, err error)
GetDNSMaliciousBlocking() (blocking bool, err error)
GetDNSSurveillanceBlocking() (blocking bool, err error)
GetDNSAdsBlocking() (blocking bool, err error)
GetDNSUnblockedHostnames() (hostnames []string, err error)
// Firewall getters
GetExtraSubnets() (extraSubnets []net.IPNet, err error)
// VPN getters
GetNetworkProtocol() (protocol models.NetworkProtocol, err error)
// PIA getters
GetUser() (s string, err error)
GetPassword() (s string, err error)
GetPortForwarding() (activated bool, err error)
GetPortForwardingStatusFilepath() (filepath models.Filepath, err error)
GetPIAEncryption() (models.PIAEncryption, error)
GetPIARegion() (models.PIARegion, error)
// Shadowsocks getters
GetShadowSocks() (activated bool, err error)
GetShadowSocksLog() (activated bool, err error)
GetShadowSocksPort() (port uint16, err error)
GetShadowSocksPassword() (password string, err error)
// Tinyproxy getters
GetTinyProxy() (activated bool, err error)
GetTinyProxyLog() (models.TinyProxyLogLevel, error)
GetTinyProxyPort() (port uint16, err error)
GetTinyProxyUser() (user string, err error)
GetTinyProxyPassword() (password string, err error)
// Version getters
GetVersion() string
GetBuildDate() string
GetVcsRef() string
}
type paramsReader struct {
envParams libparams.EnvParams
logger logging.Logger
verifier verification.Verifier
unsetEnv func(key string) error
}
func NewParamsReader(logger logging.Logger) ParamsReader {
return &paramsReader{
envParams: libparams.NewEnvParams(),
logger: logger,
verifier: verification.NewVerifier(),
unsetEnv: os.Unsetenv,
}
}

87
internal/params/pia.go Normal file
View File

@@ -0,0 +1,87 @@
package params
import (
"fmt"
"math/rand"
libparams "github.com/qdm12/golibs/params"
"github.com/qdm12/private-internet-access-docker/internal/constants"
"github.com/qdm12/private-internet-access-docker/internal/models"
)
// GetUser obtains the user to use to connect to the VPN servers
func (p *paramsReader) GetUser() (s string, err error) {
defer func() {
unsetenvErr := p.unsetEnv("USER")
if err == nil {
err = unsetenvErr
}
}()
s, err = p.envParams.GetEnv("USER")
if err != nil {
return "", err
} else if len(s) == 0 {
return s, fmt.Errorf("USER environment variable cannot be empty")
}
return s, nil
}
// GetPassword obtains the password to use to connect to the VPN servers
func (p *paramsReader) GetPassword() (s string, err error) {
defer func() {
unsetenvErr := p.unsetEnv("PASSWORD")
if err == nil {
err = unsetenvErr
}
}()
s, err = p.envParams.GetEnv("PASSWORD")
if err != nil {
return "", err
} else if len(s) == 0 {
return s, fmt.Errorf("PASSWORD environment variable cannot be empty")
}
return s, nil
}
// GetPortForwarding obtains if port forwarding on the VPN provider server
// side is enabled or not from the environment variable PORT_FORWARDING
func (p *paramsReader) GetPortForwarding() (activated bool, err error) {
s, err := p.envParams.GetEnv("PORT_FORWARDING", libparams.Default("off"))
if err != nil {
return false, err
}
// Custom for retro-compatibility
if s == "false" || s == "off" {
return false, nil
} else if s == "true" || s == "on" {
return true, nil
}
return false, fmt.Errorf("PORT_FORWARDING can only be \"on\" or \"off\"")
}
// GetPortForwardingStatusFilepath obtains the port forwarding status file path
// from the environment variable PORT_FORWARDING_STATUS_FILE
func (p *paramsReader) GetPortForwardingStatusFilepath() (filepath models.Filepath, err error) {
filepathStr, err := p.envParams.GetPath("PORT_FORWARDING_STATUS_FILE", libparams.Default("/forwarded_port"))
return models.Filepath(filepathStr), err
}
// GetPIAEncryption obtains the encryption level for the PIA connection
// from the environment variable ENCRYPTION
func (p *paramsReader) GetPIAEncryption() (models.PIAEncryption, error) {
s, err := p.envParams.GetValueIfInside("ENCRYPTION", []string{"normal", "strong"}, libparams.Default("strong"))
return models.PIAEncryption(s), err
}
// GetPIARegion obtains the region for the PIA server from the
// environment variable REGION
func (p *paramsReader) GetPIARegion() (region models.PIARegion, err error) {
choices := []string{
string(constants.AUMelbourne), string(constants.AUPerth), string(constants.AUSydney), string(constants.Austria), string(constants.Belgium), string(constants.CAMontreal), string(constants.CAToronto), string(constants.CAVancouver), string(constants.CzechRepublic), string(constants.DEBerlin), string(constants.DEFrankfurt), string(constants.Denmark), string(constants.Finland), string(constants.France), string(constants.HongKong), string(constants.Hungary), string(constants.India), string(constants.Ireland), string(constants.Israel), string(constants.Italy), string(constants.Japan), string(constants.Luxembourg), string(constants.Mexico), string(constants.Netherlands), string(constants.NewZealand), string(constants.Norway), string(constants.Poland), string(constants.Romania), string(constants.Singapore), string(constants.Spain), string(constants.Sweden), string(constants.Switzerland), string(constants.UAE), string(constants.UKLondon), string(constants.UKManchester), string(constants.UKSouthampton), string(constants.USAtlanta), string(constants.USCalifornia), string(constants.USChicago), string(constants.USDenver), string(constants.USEast), string(constants.USFlorida), string(constants.USHouston), string(constants.USLasVegas), string(constants.USNewYorkCity), string(constants.USSeattle), string(constants.USSiliconValley), string(constants.USTexas), string(constants.USWashingtonDC), string(constants.USWest),
}
s, err := p.envParams.GetValueIfInside("REGION", choices)
if len(s) == 0 { // Suggestion by @rorph https://github.com/rorph
s = choices[rand.Int()%len(choices)]
}
return models.PIARegion(s), err
}

View File

@@ -0,0 +1,40 @@
package params
import (
"strconv"
libparams "github.com/qdm12/golibs/params"
)
// GetShadowSocks obtains if ShadowSocks is on from the environment variable
// SHADOWSOCKS
func (p *paramsReader) GetShadowSocks() (activated bool, err error) {
return p.envParams.GetOnOff("SHADOWSOCKS", libparams.Default("off"))
}
// GetShadowSocksLog obtains the ShadowSocks log level from the environment variable
// SHADOWSOCKS_LOG
func (p *paramsReader) GetShadowSocksLog() (activated bool, err error) {
return p.envParams.GetOnOff("SHADOWSOCKS_LOG", libparams.Default("off"))
}
// GetShadowSocksPort obtains the ShadowSocks listening port from the environment variable
// SHADOWSOCKS_PORT
func (p *paramsReader) GetShadowSocksPort() (port uint16, err error) {
portStr, err := p.envParams.GetEnv("SHADOWSOCKS_PORT", libparams.Default("8388"))
if err != nil {
return 0, err
}
if err := p.verifier.VerifyPort(portStr); err != nil {
return 0, err
}
portUint64, err := strconv.ParseUint(portStr, 10, 16)
return uint16(portUint64), err
}
// GetShadowSocksPassword obtains the ShadowSocks server password from the environment variable
// SHADOWSOCKS_PASSWORD
func (p *paramsReader) GetShadowSocksPassword() (password string, err error) {
defer p.unsetEnv("SHADOWSOCKS_PASSWORD")
return p.envParams.GetEnv("SHADOWSOCKS_PASSWORD")
}

View File

@@ -0,0 +1,94 @@
package params
import (
"strconv"
libparams "github.com/qdm12/golibs/params"
"github.com/qdm12/private-internet-access-docker/internal/models"
)
// GetTinyProxy obtains if TinyProxy is on from the environment variable
// TINYPROXY, and using PROXY as a retro-compatibility name
func (p *paramsReader) GetTinyProxy() (activated bool, err error) {
// Retro-compatibility
s, err := p.envParams.GetEnv("PROXY")
if err != nil {
return false, err
} else if len(s) != 0 {
p.logger.Warn("You are using the old environment variable PROXY, please consider changing it to TINYPROXY")
return p.envParams.GetOnOff("PROXY", libparams.Compulsory())
}
return p.envParams.GetOnOff("TINYPROXY", libparams.Default("off"))
}
// GetTinyProxyLog obtains the TinyProxy log level from the environment variable
// TINYPROXY_LOG, and using PROXY_LOG_LEVEL as a retro-compatibility name
func (p *paramsReader) GetTinyProxyLog() (models.TinyProxyLogLevel, error) {
// Retro-compatibility
s, err := p.envParams.GetEnv("PROXY_LOG_LEVEL")
if err != nil {
return models.TinyProxyLogLevel(s), err
} else if len(s) != 0 {
p.logger.Warn("You are using the old environment variable PROXY_LOG_LEVEL, please consider changing it to TINYPROXY_LOG")
s, err = p.envParams.GetValueIfInside("PROXY_LOG_LEVEL", []string{"info", "warning", "error", "critical"}, libparams.Compulsory())
return models.TinyProxyLogLevel(s), err
}
s, err = p.envParams.GetValueIfInside("TINYPROXY_LOG", []string{"info", "warning", "error", "critical"}, libparams.Default("info"))
return models.TinyProxyLogLevel(s), err
}
// GetTinyProxyPort obtains the TinyProxy listening port from the environment variable
// TINYPROXY_PORT, and using PROXY_PORT as a retro-compatibility name
func (p *paramsReader) GetTinyProxyPort() (port uint16, err error) {
// Retro-compatibility
portStr, err := p.envParams.GetEnv("PROXY_PORT")
if err != nil {
return 0, err
} else if len(portStr) != 0 {
p.logger.Warn("You are using the old environment variable PROXY_PORT, please consider changing it to TINYPROXY_PORT")
} else {
portStr, err = p.envParams.GetEnv("TINYPROXY_PORT", libparams.Default("8888"))
if err != nil {
return 0, err
}
}
if err := p.verifier.VerifyPort(portStr); err != nil {
return 0, err
}
portUint64, err := strconv.ParseUint(portStr, 10, 16)
return uint16(portUint64), err
}
// GetTinyProxyUser obtains the TinyProxy server user from the environment variable
// TINYPROXY_USER, and using PROXY_USER as a retro-compatibility name
func (p *paramsReader) GetTinyProxyUser() (user string, err error) {
defer p.unsetEnv("PROXY_USER")
defer p.unsetEnv("TINYPROXY_USER")
// Retro-compatibility
user, err = p.envParams.GetEnv("PROXY_USER")
if err != nil {
return user, err
}
if len(user) != 0 {
p.logger.Warn("You are using the old environment variable PROXY_USER, please consider changing it to TINYPROXY_USER")
return user, nil
}
return p.envParams.GetEnv("TINYPROXY_USER")
}
// GetTinyProxyPassword obtains the TinyProxy server password from the environment variable
// TINYPROXY_PASSWORD, and using PROXY_PASSWORD as a retro-compatibility name
func (p *paramsReader) GetTinyProxyPassword() (password string, err error) {
defer p.unsetEnv("PROXY_PASSWORD")
defer p.unsetEnv("TINYPROXY_PASSWORD")
// Retro-compatibility
password, err = p.envParams.GetEnv("PROXY_PASSWORD")
if err != nil {
return password, err
}
if len(password) != 0 {
p.logger.Warn("You are using the old environment variable PROXY_PASSWORD, please consider changing it to TINYPROXY_PASSWORD")
return password, nil
}
return p.envParams.GetEnv("TINYPROXY_PASSWORD")
}

View File

@@ -0,0 +1,20 @@
package params
import (
"github.com/qdm12/golibs/params"
)
func (p *paramsReader) GetVersion() string {
version, _ := p.envParams.GetEnv("VERSION", params.Default("?"))
return version
}
func (p *paramsReader) GetBuildDate() string {
buildDate, _ := p.envParams.GetEnv("BUILD_DATE", params.Default("?"))
return buildDate
}
func (p *paramsReader) GetVcsRef() string {
buildDate, _ := p.envParams.GetEnv("VCS_REF", params.Default("?"))
return buildDate
}