From 768147095f26f8646a52703736fb737d3130b24b Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Sun, 12 Apr 2020 20:05:28 +0000 Subject: [PATCH] Golangcilint in build pipeline and fix lint errors - Fix bad permissions bits for files - VPNSP is 'private internet access' instead of 'pia' (retro compatible) - Check errors of deferred unsetEnv functions in params package - Other lint errors fixing and code simplifications --- .devcontainer/devcontainer.json | 65 +- .golangci.yml | 50 ++ Dockerfile | 8 +- README.md | 2 +- cmd/main.go | 650 ++++++++-------- docker-compose.yml | 2 +- internal/constants/dns.go | 178 ++--- internal/constants/pia.go | 170 ++--- internal/dns/conf.go | 576 +++++++-------- internal/dns/conf_test.go | 1061 +++++++++++++-------------- internal/dns/nameserver.go | 14 +- internal/dns/roots.go | 5 +- internal/dns/roots_test.go | 4 +- internal/dns/wait.go | 2 +- internal/env/env_test.go | 1 - internal/healthcheck/healthcheck.go | 4 +- internal/models/mullvad.go | 2 +- internal/openvpn/tun.go | 2 +- internal/params/dns.go | 246 +++---- internal/params/firewall.go | 58 +- internal/params/mullvad.go | 8 +- internal/params/openvpn.go | 162 ++-- internal/params/params.go | 219 +++--- internal/params/pia.go | 120 +-- internal/params/shadowsocks.go | 97 +-- internal/params/system.go | 56 +- internal/params/tinyproxy.go | 210 +++--- internal/params/version.go | 6 +- internal/params/windscribe.go | 7 +- internal/pia/conf.go | 10 +- internal/pia/portforward.go | 12 +- internal/routing/entry.go | 2 +- internal/settings/dns.go | 271 +++---- internal/settings/firewall.go | 68 +- internal/settings/mullvad.go | 112 +-- internal/settings/openvpn.go | 132 ++-- internal/settings/pia.go | 148 ++-- internal/settings/settings.go | 252 +++---- internal/settings/shadowsocks.go | 120 +-- internal/settings/system.go | 10 +- internal/settings/tinyproxy.go | 118 +-- internal/settings/windscribe.go | 98 +-- internal/splash/splash.go | 2 +- 43 files changed, 2742 insertions(+), 2598 deletions(-) create mode 100644 .golangci.yml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 1f3c7898..12ff8ff1 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,10 +1,14 @@ { "name": "pia-dev", - "dockerComposeFile": ["docker-compose.yml"], + "dockerComposeFile": [ + "docker-compose.yml" + ], "service": "vscode", - "runServices": ["vscode"], + "runServices": [ + "vscode" + ], "shutdownAction": "stopCompose", - // "postCreateCommand": "go mod download", + "postCreateCommand": "go mod download", "workspaceFolder": "/workspace", "extensions": [ "ms-vscode.go", @@ -38,10 +42,54 @@ "deepCompletion": true, "usePlaceholders": false }, + "go.lintTool": "golangci-lint", + "go.lintFlags": [ + "--fast", + "--enable", + "staticcheck", + "--enable", + "bodyclose", + "--enable", + "dogsled", + "--enable", + "gochecknoglobals", + "--enable", + "gochecknoinits", + "--enable", + "gocognit", + "--enable", + "goconst", + "--enable", + "gocritic", + "--enable", + "gocyclo", + "--enable", + "golint", + "--enable", + "gosec", + "--enable", + "interfacer", + "--enable", + "maligned", + "--enable", + "misspell", + "--enable", + "nakedret", + "--enable", + "prealloc", + "--enable", + "scopelint", + "--enable", + "unconvert", + "--enable", + "unparam", + "--enable", + "whitespace" + ], // Golang on save - "go.buildOnSave": "package", - "go.lintOnSave": "package", - "go.vetOnSave": "package", + "go.buildOnSave": "workspace", + "go.lintOnSave": "workspace", + "go.vetOnSave": "workspace", "editor.formatOnSave": true, "[go]": { "editor.codeActionsOnSave": { @@ -56,7 +104,10 @@ "GOFLAGS": "-tags=integration" }, "go.testEnvVars": {}, - "go.testFlags": ["-v"], + "go.testFlags": [ + "-v", + // "-race" + ], "go.testTimeout": "600s" } } \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..b40cdfaa --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,50 @@ +linters-settings: + maligned: + suggest-new: true + misspell: + locale: US + +linters: + disable-all: true + enable: + - bodyclose + - deadcode + - dogsled + - dupl + - errcheck + - gochecknoglobals + - gochecknoinits + - gocognit + - goconst + - gocritic + - gocyclo + - goimports + - golint + - gosec + - gosimple + - govet + - ineffassign + - interfacer + - maligned + - misspell + - nakedret + - prealloc + - rowserrcheck + - scopelint + - staticcheck + - structcheck + - typecheck + - unconvert + - unparam + - unused + - varcheck + - whitespace + +run: + skip-dirs: + - .devcontainer + - .github + - postgres + +service: + golangci-lint-version: 1.24.x # use the fixed version to not introduce new linters unexpectedly diff --git a/Dockerfile b/Dockerfile index 7623aeb3..3fd43f9a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,13 +3,17 @@ ARG GO_VERSION=1.14 FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS builder RUN apk --update add git -WORKDIR /tmp/gobuild ENV CGO_ENABLED=0 +ARG GOLANGCI_LINT_VERSION=v1.24.0 +RUN wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s ${GOLANGCI_LINT_VERSION} +WORKDIR /tmp/gobuild +COPY .golangci.yml . COPY go.mod go.sum ./ RUN go mod download 2>&1 COPY cmd/main.go . COPY internal/ ./internal/ RUN go test ./... +RUN golangci-lint run --timeout=10m RUN go build -ldflags="-s -w" -o entrypoint main.go FROM alpine:${ALPINE_VERSION} @@ -29,7 +33,7 @@ LABEL \ org.opencontainers.image.source="https://github.com/qdm12/private-internet-access-docker" \ org.opencontainers.image.title="PIA client" \ org.opencontainers.image.description="VPN client to tunnel to private internet access servers using OpenVPN, IPtables, DNS over TLS and Alpine Linux" -ENV VPNSP=pia \ +ENV VPNSP="private internet access" \ USER= \ PROTOCOL=udp \ OPENVPN_VERBOSITY=1 \ diff --git a/README.md b/README.md index bc8725ac..c1d7297c 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ docker run --rm --network=container:pia alpine:3.11 wget -qO- https://ipinfo.io | Environment variable | Default | Properties | PIA | Mullvad | Windscribe | Description | Choices | | --- | --- | --- | --- | --- | --- | --- | --- | -| `VPNSP` | `pia` | | ✅ | ✅ | ✅ | VPN Service Provider | `pia`, `mullvad`, `windscribe` | +| `VPNSP` | `private internet access` | | ✅ | ✅ | ✅ | VPN Service Provider | `private internet access`, `mullvad`, `windscribe` | | `REGION` | `Austria` | | ✅ | ❌ | ✅ | VPN server region | One of the [PIA regions](https://www.privateinternetaccess.com/pages/network/) or of the [Windscribe regions](https://windscribe.com/status) | | `COUNTRY` | `Sweden` | Optional | ❌ | ✅ | ❌ | VPN server country | One of the [Mullvad countries](https://mullvad.net/en/servers/#openvpn) | | `CITY` | | Optional | ❌ | ✅ | ❌ | VPN server city | One of the [Mullvad cities](https://mullvad.net/en/servers/#openvpn) | diff --git a/cmd/main.go b/cmd/main.go index d90c86bf..560e7db6 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,325 +1,325 @@ -package main - -import ( - "context" - "fmt" - "net" - "os" - "strings" - "time" - - "github.com/qdm12/golibs/command" - "github.com/qdm12/golibs/files" - libhealthcheck "github.com/qdm12/golibs/healthcheck" - "github.com/qdm12/golibs/logging" - "github.com/qdm12/golibs/network" - "github.com/qdm12/golibs/signals" - "github.com/qdm12/private-internet-access-docker/internal/alpine" - "github.com/qdm12/private-internet-access-docker/internal/constants" - "github.com/qdm12/private-internet-access-docker/internal/dns" - "github.com/qdm12/private-internet-access-docker/internal/env" - "github.com/qdm12/private-internet-access-docker/internal/firewall" - "github.com/qdm12/private-internet-access-docker/internal/healthcheck" - "github.com/qdm12/private-internet-access-docker/internal/models" - "github.com/qdm12/private-internet-access-docker/internal/mullvad" - "github.com/qdm12/private-internet-access-docker/internal/openvpn" - "github.com/qdm12/private-internet-access-docker/internal/params" - "github.com/qdm12/private-internet-access-docker/internal/pia" - "github.com/qdm12/private-internet-access-docker/internal/routing" - "github.com/qdm12/private-internet-access-docker/internal/settings" - "github.com/qdm12/private-internet-access-docker/internal/shadowsocks" - "github.com/qdm12/private-internet-access-docker/internal/splash" - "github.com/qdm12/private-internet-access-docker/internal/tinyproxy" - "github.com/qdm12/private-internet-access-docker/internal/windscribe" -) - -func main() { - logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel, -1) - if err != nil { - panic(err) - } - if libhealthcheck.Mode(os.Args) { - if err := healthcheck.HealthCheck(); err != nil { - fmt.Println(err) - os.Exit(1) - } - os.Exit(0) - } - paramsReader := params.NewParamsReader(logger) - fmt.Println(splash.Splash(paramsReader)) - e := env.New(logger) - client := network.NewClient(15 * time.Second) - // Create configurators - fileManager := files.NewFileManager() - alpineConf := alpine.NewConfigurator(fileManager) - ovpnConf := openvpn.NewConfigurator(logger, fileManager) - dnsConf := dns.NewConfigurator(logger, client, fileManager) - firewallConf := firewall.NewConfigurator(logger) - routingConf := routing.NewRouting(logger, fileManager) - piaConf := pia.NewConfigurator(client, fileManager, firewallConf, logger) - mullvadConf := mullvad.NewConfigurator(fileManager, logger) - windscribeConf := windscribe.NewConfigurator(fileManager) - tinyProxyConf := tinyproxy.NewConfigurator(fileManager, logger) - shadowsocksConf := shadowsocks.NewConfigurator(fileManager, logger) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - streamMerger := command.NewStreamMerger(ctx) - - e.PrintVersion("OpenVPN", ovpnConf.Version) - e.PrintVersion("Unbound", dnsConf.Version) - e.PrintVersion("IPtables", firewallConf.Version) - e.PrintVersion("TinyProxy", tinyProxyConf.Version) - e.PrintVersion("ShadowSocks", shadowsocksConf.Version) - - allSettings, err := settings.GetAllSettings(paramsReader) - e.FatalOnError(err) - logger.Info(allSettings.String()) - - err = alpineConf.CreateUser("nonrootuser", allSettings.System.UID) - e.FatalOnError(err) - err = fileManager.SetOwnership("/etc/unbound", allSettings.System.UID, allSettings.System.GID) - e.FatalOnError(err) - err = fileManager.SetOwnership("/etc/tinyproxy", allSettings.System.UID, allSettings.System.GID) - e.FatalOnError(err) - - if err := ovpnConf.CheckTUN(); err != nil { - logger.Warn(err) - err = ovpnConf.CreateTUN() - e.FatalOnError(err) - } - - var openVPNUser, openVPNPassword string - switch allSettings.VPNSP { - case "pia": - openVPNUser = allSettings.PIA.User - openVPNPassword = allSettings.PIA.Password - case "mullvad": - openVPNUser = allSettings.Mullvad.User - openVPNPassword = "m" - case "windscribe": - openVPNUser = allSettings.Windscribe.User - openVPNPassword = allSettings.Windscribe.Password - } - err = ovpnConf.WriteAuthFile(openVPNUser, openVPNPassword, allSettings.System.UID, allSettings.System.GID) - e.FatalOnError(err) - - defaultInterface, defaultGateway, defaultSubnet, err := routingConf.DefaultRoute() - e.FatalOnError(err) - - // Temporarily reset chain policies allowing Kubernetes sidecar to - // successfully restart the container. Without this, the existing rules will - // pre-exist, preventing the nslookup of the PIA region address. These will - // simply be redundant at Docker runtime as they will already be set this way - // Thanks to @npawelek https://github.com/npawelek - err = firewallConf.AcceptAll() - e.FatalOnError(err) - - go func() { - // Blocking line merging reader for all programs: openvpn, tinyproxy, unbound and shadowsocks - logger.Info("Launching standard output merger") - err = streamMerger.CollectLines(func(line string) { - logger.Info(line) - if strings.Contains(line, "Initialization Sequence Completed") { - onConnected(logger, routingConf, fileManager, piaConf, - defaultInterface, - allSettings.VPNSP, - allSettings.PIA.PortForwarding.Enabled, - allSettings.PIA.PortForwarding.Filepath, - allSettings.System.IPStatusFilepath, - allSettings.System.UID, - allSettings.System.GID) - } - }) - e.FatalOnError(err) - }() - - if allSettings.DNS.Enabled { - initialDNSToUse := constants.DNSProviderMapping()[allSettings.DNS.Providers[0]] - dnsConf.UseDNSInternally(initialDNSToUse.IPs[0]) - err = dnsConf.DownloadRootHints(allSettings.System.UID, allSettings.System.GID) - e.FatalOnError(err) - err = dnsConf.DownloadRootKey(allSettings.System.UID, allSettings.System.GID) - e.FatalOnError(err) - err = dnsConf.MakeUnboundConf(allSettings.DNS, allSettings.System.UID, allSettings.System.GID) - e.FatalOnError(err) - stream, waitFn, err := dnsConf.Start(allSettings.DNS.VerbosityDetailsLevel) - e.FatalOnError(err) - go func() { - e.FatalOnError(waitFn()) - }() - go streamMerger.Merge("unbound", stream) - dnsConf.UseDNSInternally(net.IP{127, 0, 0, 1}) // use Unbound - err = dnsConf.UseDNSSystemWide(net.IP{127, 0, 0, 1}) // use Unbound - e.FatalOnError(err) - err = dnsConf.WaitForUnbound() - e.FatalOnError(err) - } - - var connections []models.OpenVPNConnection - switch allSettings.VPNSP { - case "pia": - connections, err = piaConf.GetOpenVPNConnections( - allSettings.PIA.Region, - allSettings.OpenVPN.NetworkProtocol, - allSettings.PIA.Encryption, - allSettings.OpenVPN.TargetIP) - e.FatalOnError(err) - err = piaConf.BuildConf( - connections, - allSettings.PIA.Encryption, - allSettings.OpenVPN.Verbosity, - allSettings.System.UID, - allSettings.System.GID, - allSettings.OpenVPN.Root, - allSettings.OpenVPN.Cipher, - allSettings.OpenVPN.Auth) - e.FatalOnError(err) - case "mullvad": - connections, err = mullvadConf.GetOpenVPNConnections( - allSettings.Mullvad.Country, - allSettings.Mullvad.City, - allSettings.Mullvad.ISP, - allSettings.OpenVPN.NetworkProtocol, - allSettings.Mullvad.Port, - allSettings.OpenVPN.TargetIP) - e.FatalOnError(err) - err = mullvadConf.BuildConf( - connections, - allSettings.OpenVPN.Verbosity, - allSettings.System.UID, - allSettings.System.GID, - allSettings.OpenVPN.Root, - allSettings.OpenVPN.Cipher) - e.FatalOnError(err) - case "windscribe": - connections, err = windscribeConf.GetOpenVPNConnections( - allSettings.Windscribe.Region, - allSettings.OpenVPN.NetworkProtocol, - allSettings.Windscribe.Port, - allSettings.OpenVPN.TargetIP) - e.FatalOnError(err) - err = windscribeConf.BuildConf( - connections, - allSettings.OpenVPN.Verbosity, - allSettings.System.UID, - allSettings.System.GID, - allSettings.OpenVPN.Root, - allSettings.OpenVPN.Cipher, - allSettings.OpenVPN.Auth) - e.FatalOnError(err) - } - - err = routingConf.AddRoutesVia(allSettings.Firewall.AllowedSubnets, defaultGateway, defaultInterface) - e.FatalOnError(err) - err = firewallConf.Clear() - e.FatalOnError(err) - err = firewallConf.BlockAll() - e.FatalOnError(err) - err = firewallConf.CreateGeneralRules() - e.FatalOnError(err) - err = firewallConf.CreateVPNRules(constants.TUN, defaultInterface, connections) - e.FatalOnError(err) - err = firewallConf.CreateLocalSubnetsRules(defaultSubnet, allSettings.Firewall.AllowedSubnets, defaultInterface) - e.FatalOnError(err) - - if allSettings.TinyProxy.Enabled { - err = tinyProxyConf.MakeConf( - allSettings.TinyProxy.LogLevel, - allSettings.TinyProxy.Port, - allSettings.TinyProxy.User, - allSettings.TinyProxy.Password, - allSettings.System.UID, - allSettings.System.GID) - e.FatalOnError(err) - err = firewallConf.AllowAnyIncomingOnPort(allSettings.TinyProxy.Port) - e.FatalOnError(err) - stream, waitFn, err := tinyProxyConf.Start() - e.FatalOnError(err) - go func() { - if err := waitFn(); err != nil { - logger.Error(err) - } - }() - go streamMerger.Merge("tinyproxy", stream) - } - - if allSettings.ShadowSocks.Enabled { - err = shadowsocksConf.MakeConf( - allSettings.ShadowSocks.Port, - allSettings.ShadowSocks.Password, - allSettings.ShadowSocks.Method, - allSettings.System.UID, - allSettings.System.GID) - e.FatalOnError(err) - err = firewallConf.AllowAnyIncomingOnPort(allSettings.ShadowSocks.Port) - e.FatalOnError(err) - stream, waitFn, err := shadowsocksConf.Start("0.0.0.0", allSettings.ShadowSocks.Port, allSettings.ShadowSocks.Password, allSettings.ShadowSocks.Log) - e.FatalOnError(err) - go func() { - if err := waitFn(); err != nil { - logger.Error(err) - } - }() - go streamMerger.Merge("shadowsocks", stream) - } - - stream, waitFn, err := ovpnConf.Start() - e.FatalOnError(err) - go streamMerger.Merge("openvpn", stream) - go signals.WaitForExit(func(signal string) int { - logger.Warn("Caught OS signal %s, shutting down", signal) - if allSettings.VPNSP == "pia" && allSettings.PIA.PortForwarding.Enabled { - if err := piaConf.ClearPortForward(allSettings.PIA.PortForwarding.Filepath, allSettings.System.UID, allSettings.System.GID); err != nil { - logger.Error(err) - } - } - time.Sleep(100 * time.Millisecond) // wait for other processes to exit - return 0 - }) - e.FatalOnError(waitFn()) -} - -func onConnected( - logger logging.Logger, - routingConf routing.Routing, - fileManager files.FileManager, - piaConf pia.Configurator, - defaultInterface string, - VPNSP string, - portForwarding bool, - portForwardingFilepath models.Filepath, - ipStatusFilepath models.Filepath, - uid, gid int, -) { - ip, err := routingConf.CurrentPublicIP(defaultInterface) - if err != nil { - logger.Error(err) - } else { - logger.Info("Tunnel IP is %s, see more information at https://ipinfo.io/%s", ip, ip) - err := fileManager.WriteLinesToFile( - string(ipStatusFilepath), - []string{ip.String()}, - files.Ownership(uid, gid), - files.Permissions(400)) - if err != nil { - logger.Error(err) - } - } - if VPNSP != "pia" || !portForwarding { - return - } - port, err := piaConf.GetPortForward() - if err != nil { - logger.Error("port forwarding:", err) - return - } - logger.Info("port forwarding: Port %d", port) - if err := piaConf.WritePortForward(portForwardingFilepath, port, uid, gid); err != nil { - logger.Error("port forwarding:", err) - return - } - if err := piaConf.AllowPortForwardFirewall(constants.TUN, port); err != nil { - logger.Error("port forwarding:", err) - return - } -} +package main + +import ( + "context" + "fmt" + "net" + "os" + "strings" + "time" + + "github.com/qdm12/golibs/command" + "github.com/qdm12/golibs/files" + libhealthcheck "github.com/qdm12/golibs/healthcheck" + "github.com/qdm12/golibs/logging" + "github.com/qdm12/golibs/network" + "github.com/qdm12/golibs/signals" + "github.com/qdm12/private-internet-access-docker/internal/alpine" + "github.com/qdm12/private-internet-access-docker/internal/constants" + "github.com/qdm12/private-internet-access-docker/internal/dns" + "github.com/qdm12/private-internet-access-docker/internal/env" + "github.com/qdm12/private-internet-access-docker/internal/firewall" + "github.com/qdm12/private-internet-access-docker/internal/healthcheck" + "github.com/qdm12/private-internet-access-docker/internal/models" + "github.com/qdm12/private-internet-access-docker/internal/mullvad" + "github.com/qdm12/private-internet-access-docker/internal/openvpn" + "github.com/qdm12/private-internet-access-docker/internal/params" + "github.com/qdm12/private-internet-access-docker/internal/pia" + "github.com/qdm12/private-internet-access-docker/internal/routing" + "github.com/qdm12/private-internet-access-docker/internal/settings" + "github.com/qdm12/private-internet-access-docker/internal/shadowsocks" + "github.com/qdm12/private-internet-access-docker/internal/splash" + "github.com/qdm12/private-internet-access-docker/internal/tinyproxy" + "github.com/qdm12/private-internet-access-docker/internal/windscribe" +) + +func main() { + logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel, -1) + if err != nil { + panic(err) + } + if libhealthcheck.Mode(os.Args) { + if err := healthcheck.HealthCheck(); err != nil { + fmt.Println(err) + os.Exit(1) + } + os.Exit(0) + } + paramsReader := params.NewReader(logger) + fmt.Println(splash.Splash(paramsReader)) + e := env.New(logger) + client := network.NewClient(15 * time.Second) + // Create configurators + fileManager := files.NewFileManager() + alpineConf := alpine.NewConfigurator(fileManager) + ovpnConf := openvpn.NewConfigurator(logger, fileManager) + dnsConf := dns.NewConfigurator(logger, client, fileManager) + firewallConf := firewall.NewConfigurator(logger) + routingConf := routing.NewRouting(logger, fileManager) + piaConf := pia.NewConfigurator(client, fileManager, firewallConf, logger) + mullvadConf := mullvad.NewConfigurator(fileManager, logger) + windscribeConf := windscribe.NewConfigurator(fileManager) + tinyProxyConf := tinyproxy.NewConfigurator(fileManager, logger) + shadowsocksConf := shadowsocks.NewConfigurator(fileManager, logger) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + streamMerger := command.NewStreamMerger(ctx) + + e.PrintVersion("OpenVPN", ovpnConf.Version) + e.PrintVersion("Unbound", dnsConf.Version) + e.PrintVersion("IPtables", firewallConf.Version) + e.PrintVersion("TinyProxy", tinyProxyConf.Version) + e.PrintVersion("ShadowSocks", shadowsocksConf.Version) + + allSettings, err := settings.GetAllSettings(paramsReader) + e.FatalOnError(err) + logger.Info(allSettings.String()) + + err = alpineConf.CreateUser("nonrootuser", allSettings.System.UID) + e.FatalOnError(err) + err = fileManager.SetOwnership("/etc/unbound", allSettings.System.UID, allSettings.System.GID) + e.FatalOnError(err) + err = fileManager.SetOwnership("/etc/tinyproxy", allSettings.System.UID, allSettings.System.GID) + e.FatalOnError(err) + + if err := ovpnConf.CheckTUN(); err != nil { + logger.Warn(err) + err = ovpnConf.CreateTUN() + e.FatalOnError(err) + } + + var openVPNUser, openVPNPassword string + switch allSettings.VPNSP { + case constants.PrivateInternetAccess: + openVPNUser = allSettings.PIA.User + openVPNPassword = allSettings.PIA.Password + case constants.Mullvad: + openVPNUser = allSettings.Mullvad.User + openVPNPassword = "m" + case constants.Windscribe: + openVPNUser = allSettings.Windscribe.User + openVPNPassword = allSettings.Windscribe.Password + } + err = ovpnConf.WriteAuthFile(openVPNUser, openVPNPassword, allSettings.System.UID, allSettings.System.GID) + e.FatalOnError(err) + + defaultInterface, defaultGateway, defaultSubnet, err := routingConf.DefaultRoute() + e.FatalOnError(err) + + // Temporarily reset chain policies allowing Kubernetes sidecar to + // successfully restart the container. Without this, the existing rules will + // pre-exist, preventing the nslookup of the PIA region address. These will + // simply be redundant at Docker runtime as they will already be set this way + // Thanks to @npawelek https://github.com/npawelek + err = firewallConf.AcceptAll() + e.FatalOnError(err) + + go func() { + // Blocking line merging paramsReader for all programs: openvpn, tinyproxy, unbound and shadowsocks + logger.Info("Launching standard output merger") + err = streamMerger.CollectLines(func(line string) { + logger.Info(line) + if strings.Contains(line, "Initialization Sequence Completed") { + onConnected(logger, routingConf, fileManager, piaConf, + defaultInterface, + allSettings.VPNSP, + allSettings.PIA.PortForwarding.Enabled, + allSettings.PIA.PortForwarding.Filepath, + allSettings.System.IPStatusFilepath, + allSettings.System.UID, + allSettings.System.GID) + } + }) + e.FatalOnError(err) + }() + + if allSettings.DNS.Enabled { + initialDNSToUse := constants.DNSProviderMapping()[allSettings.DNS.Providers[0]] + dnsConf.UseDNSInternally(initialDNSToUse.IPs[0]) + err = dnsConf.DownloadRootHints(allSettings.System.UID, allSettings.System.GID) + e.FatalOnError(err) + err = dnsConf.DownloadRootKey(allSettings.System.UID, allSettings.System.GID) + e.FatalOnError(err) + err = dnsConf.MakeUnboundConf(allSettings.DNS, allSettings.System.UID, allSettings.System.GID) + e.FatalOnError(err) + stream, waitFn, err := dnsConf.Start(allSettings.DNS.VerbosityDetailsLevel) + e.FatalOnError(err) + go func() { + e.FatalOnError(waitFn()) + }() + go streamMerger.Merge("unbound", stream) + dnsConf.UseDNSInternally(net.IP{127, 0, 0, 1}) // use Unbound + err = dnsConf.UseDNSSystemWide(net.IP{127, 0, 0, 1}) // use Unbound + e.FatalOnError(err) + err = dnsConf.WaitForUnbound() + e.FatalOnError(err) + } + + var connections []models.OpenVPNConnection + switch allSettings.VPNSP { + case constants.PrivateInternetAccess: + connections, err = piaConf.GetOpenVPNConnections( + allSettings.PIA.Region, + allSettings.OpenVPN.NetworkProtocol, + allSettings.PIA.Encryption, + allSettings.OpenVPN.TargetIP) + e.FatalOnError(err) + err = piaConf.BuildConf( + connections, + allSettings.PIA.Encryption, + allSettings.OpenVPN.Verbosity, + allSettings.System.UID, + allSettings.System.GID, + allSettings.OpenVPN.Root, + allSettings.OpenVPN.Cipher, + allSettings.OpenVPN.Auth) + e.FatalOnError(err) + case constants.Mullvad: + connections, err = mullvadConf.GetOpenVPNConnections( + allSettings.Mullvad.Country, + allSettings.Mullvad.City, + allSettings.Mullvad.ISP, + allSettings.OpenVPN.NetworkProtocol, + allSettings.Mullvad.Port, + allSettings.OpenVPN.TargetIP) + e.FatalOnError(err) + err = mullvadConf.BuildConf( + connections, + allSettings.OpenVPN.Verbosity, + allSettings.System.UID, + allSettings.System.GID, + allSettings.OpenVPN.Root, + allSettings.OpenVPN.Cipher) + e.FatalOnError(err) + case constants.Windscribe: + connections, err = windscribeConf.GetOpenVPNConnections( + allSettings.Windscribe.Region, + allSettings.OpenVPN.NetworkProtocol, + allSettings.Windscribe.Port, + allSettings.OpenVPN.TargetIP) + e.FatalOnError(err) + err = windscribeConf.BuildConf( + connections, + allSettings.OpenVPN.Verbosity, + allSettings.System.UID, + allSettings.System.GID, + allSettings.OpenVPN.Root, + allSettings.OpenVPN.Cipher, + allSettings.OpenVPN.Auth) + e.FatalOnError(err) + } + + err = routingConf.AddRoutesVia(allSettings.Firewall.AllowedSubnets, defaultGateway, defaultInterface) + e.FatalOnError(err) + err = firewallConf.Clear() + e.FatalOnError(err) + err = firewallConf.BlockAll() + e.FatalOnError(err) + err = firewallConf.CreateGeneralRules() + e.FatalOnError(err) + err = firewallConf.CreateVPNRules(constants.TUN, defaultInterface, connections) + e.FatalOnError(err) + err = firewallConf.CreateLocalSubnetsRules(defaultSubnet, allSettings.Firewall.AllowedSubnets, defaultInterface) + e.FatalOnError(err) + + if allSettings.TinyProxy.Enabled { + err = tinyProxyConf.MakeConf( + allSettings.TinyProxy.LogLevel, + allSettings.TinyProxy.Port, + allSettings.TinyProxy.User, + allSettings.TinyProxy.Password, + allSettings.System.UID, + allSettings.System.GID) + e.FatalOnError(err) + err = firewallConf.AllowAnyIncomingOnPort(allSettings.TinyProxy.Port) + e.FatalOnError(err) + stream, waitFn, err := tinyProxyConf.Start() + e.FatalOnError(err) + go func() { + if err := waitFn(); err != nil { + logger.Error(err) + } + }() + go streamMerger.Merge("tinyproxy", stream) + } + + if allSettings.ShadowSocks.Enabled { + err = shadowsocksConf.MakeConf( + allSettings.ShadowSocks.Port, + allSettings.ShadowSocks.Password, + allSettings.ShadowSocks.Method, + allSettings.System.UID, + allSettings.System.GID) + e.FatalOnError(err) + err = firewallConf.AllowAnyIncomingOnPort(allSettings.ShadowSocks.Port) + e.FatalOnError(err) + stream, waitFn, err := shadowsocksConf.Start("0.0.0.0", allSettings.ShadowSocks.Port, allSettings.ShadowSocks.Password, allSettings.ShadowSocks.Log) + e.FatalOnError(err) + go func() { + if err := waitFn(); err != nil { + logger.Error(err) + } + }() + go streamMerger.Merge("shadowsocks", stream) + } + + stream, waitFn, err := ovpnConf.Start() + e.FatalOnError(err) + go streamMerger.Merge("openvpn", stream) + go signals.WaitForExit(func(signal string) int { + logger.Warn("Caught OS signal %s, shutting down", signal) + if allSettings.VPNSP == "pia" && allSettings.PIA.PortForwarding.Enabled { + if err := piaConf.ClearPortForward(allSettings.PIA.PortForwarding.Filepath, allSettings.System.UID, allSettings.System.GID); err != nil { + logger.Error(err) + } + } + time.Sleep(100 * time.Millisecond) // wait for other processes to exit + return 0 + }) + e.FatalOnError(waitFn()) +} + +func onConnected( + logger logging.Logger, + routingConf routing.Routing, + fileManager files.FileManager, + piaConf pia.Configurator, + defaultInterface string, + vpnsp models.VPNProvider, + portForwarding bool, + portForwardingFilepath models.Filepath, + ipStatusFilepath models.Filepath, + uid, gid int, +) { + ip, err := routingConf.CurrentPublicIP(defaultInterface) + if err != nil { + logger.Error(err) + } else { + logger.Info("Tunnel IP is %s, see more information at https://ipinfo.io/%s", ip, ip) + err := fileManager.WriteLinesToFile( + string(ipStatusFilepath), + []string{ip.String()}, + files.Ownership(uid, gid), + files.Permissions(0400)) + if err != nil { + logger.Error(err) + } + } + if vpnsp != constants.PrivateInternetAccess || !portForwarding { + return + } + port, err := piaConf.GetPortForward() + if err != nil { + logger.Error("port forwarding:", err) + return + } + logger.Info("port forwarding: Port %d", port) + if err := piaConf.WritePortForward(portForwardingFilepath, port, uid, gid); err != nil { + logger.Error("port forwarding:", err) + return + } + if err := piaConf.AllowPortForwardFirewall(constants.TUN, port); err != nil { + logger.Error("port forwarding:", err) + return + } +} diff --git a/docker-compose.yml b/docker-compose.yml index 225ecf32..16807811 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,7 @@ services: # command: environment: # More variables are available, see the readme table - - VPNSP=pia + - VPNSP=private internet access - USER=js89ds7 - PROTOCOL=udp - OPENVPN_VERBOSITY=1 diff --git a/internal/constants/dns.go b/internal/constants/dns.go index 9bed13e5..4f7a5c93 100644 --- a/internal/constants/dns.go +++ b/internal/constants/dns.go @@ -1,89 +1,89 @@ -package constants - -import ( - "net" - - "github.com/qdm12/private-internet-access-docker/internal/models" -) - -const ( - // Cloudflare is a DNS over TLS provider - Cloudflare models.DNSProvider = "cloudflare" - // Google is a DNS over TLS provider - Google models.DNSProvider = "google" - // Quad9 is a DNS over TLS provider - Quad9 models.DNSProvider = "quad9" - // Quadrant is a DNS over TLS provider - Quadrant models.DNSProvider = "quadrant" - // CleanBrowsing is a DNS over TLS provider - CleanBrowsing models.DNSProvider = "cleanbrowsing" - // SecureDNS is a DNS over TLS provider - SecureDNS models.DNSProvider = "securedns" - // LibreDNS is a DNS over TLS provider - LibreDNS models.DNSProvider = "libredns" -) - -// DNSProviderMapping returns a constant mapping of dns provider name -// to their data such as IP addresses or TLS host name. -func DNSProviderMapping() map[models.DNSProvider]models.DNSProviderData { - return map[models.DNSProvider]models.DNSProviderData{ - Cloudflare: models.DNSProviderData{ - IPs: []net.IP{{1, 1, 1, 1}, {1, 0, 0, 1}, {0x26, 0x6, 0x47, 0x0, 0x47, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x11, 0x11}, {0x26, 0x6, 0x47, 0x0, 0x47, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x01}}, - SupportsTLS: true, - SupportsIPv6: true, - Host: models.DNSHost("cloudflare-dns.com"), - }, - Google: models.DNSProviderData{ - IPs: []net.IP{{8, 8, 8, 8}, {8, 8, 4, 4}, {0x20, 0x1, 0x48, 0x60, 0x48, 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x88, 0x88}, {0x20, 0x1, 0x48, 0x60, 0x48, 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x88, 0x44}}, - SupportsTLS: true, - SupportsIPv6: true, - Host: models.DNSHost("dns.google"), - }, - Quad9: models.DNSProviderData{ - IPs: []net.IP{{9, 9, 9, 9}, {149, 112, 112, 112}, {0x26, 0x20, 0x0, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe}, {0x26, 0x20, 0x0, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9}}, - SupportsTLS: true, - SupportsIPv6: true, - Host: models.DNSHost("dns.quad9.net"), - }, - Quadrant: models.DNSProviderData{ - IPs: []net.IP{{12, 159, 2, 159}, {0x20, 0x1, 0x18, 0x90, 0x14, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x59}}, - SupportsTLS: true, - SupportsIPv6: true, - Host: models.DNSHost("dns-tls.qis.io"), - }, - CleanBrowsing: models.DNSProviderData{ - IPs: []net.IP{{185, 228, 168, 9}, {185, 228, 169, 9}, {0x2a, 0xd, 0x2a, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2}, {0x2a, 0xd, 0x2a, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2}}, - SupportsTLS: true, - SupportsIPv6: true, - Host: models.DNSHost("security-filter-dns.cleanbrowsing.org"), - }, - SecureDNS: models.DNSProviderData{ - IPs: []net.IP{{146, 185, 167, 43}, {0x2a, 0x3, 0xb0, 0xc0, 0x0, 0x0, 0x10, 0x10, 0x0, 0x0, 0x0, 0x0, 0xe, 0x9a, 0x30, 0x1}}, - SupportsTLS: true, - SupportsIPv6: true, - Host: models.DNSHost("dot.securedns.eu"), - }, - LibreDNS: models.DNSProviderData{ - IPs: []net.IP{{116, 203, 115, 192}}, - SupportsTLS: true, - Host: models.DNSHost("dot.libredns.gr"), - }, - } -} - -// Block lists URLs -const ( - AdsBlockListHostnamesURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/ads-hostnames.updated" - AdsBlockListIPsURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/ads-ips.updated" - MaliciousBlockListHostnamesURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/malicious-hostnames.updated" - MaliciousBlockListIPsURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/malicious-ips.updated" - SurveillanceBlockListHostnamesURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/surveillance-hostnames.updated" - SurveillanceBlockListIPsURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/surveillance-ips.updated" -) - -// DNS certificates to fetch -// TODO obtain from source directly, see qdm12/updated) -const ( - NamedRootURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/named.root.updated" - RootKeyURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/root.key.updated" -) +package constants + +import ( + "net" + + "github.com/qdm12/private-internet-access-docker/internal/models" +) + +const ( + // Cloudflare is a DNS over TLS provider + Cloudflare models.DNSProvider = "cloudflare" + // Google is a DNS over TLS provider + Google models.DNSProvider = "google" + // Quad9 is a DNS over TLS provider + Quad9 models.DNSProvider = "quad9" + // Quadrant is a DNS over TLS provider + Quadrant models.DNSProvider = "quadrant" + // CleanBrowsing is a DNS over TLS provider + CleanBrowsing models.DNSProvider = "cleanbrowsing" + // SecureDNS is a DNS over TLS provider + SecureDNS models.DNSProvider = "securedns" + // LibreDNS is a DNS over TLS provider + LibreDNS models.DNSProvider = "libredns" +) + +// DNSProviderMapping returns a constant mapping of dns provider name +// to their data such as IP addresses or TLS host name. +func DNSProviderMapping() map[models.DNSProvider]models.DNSProviderData { + return map[models.DNSProvider]models.DNSProviderData{ + Cloudflare: { + IPs: []net.IP{{1, 1, 1, 1}, {1, 0, 0, 1}, {0x26, 0x6, 0x47, 0x0, 0x47, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x11, 0x11}, {0x26, 0x6, 0x47, 0x0, 0x47, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x01}}, + SupportsTLS: true, + SupportsIPv6: true, + Host: models.DNSHost("cloudflare-dns.com"), + }, + Google: { + IPs: []net.IP{{8, 8, 8, 8}, {8, 8, 4, 4}, {0x20, 0x1, 0x48, 0x60, 0x48, 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x88, 0x88}, {0x20, 0x1, 0x48, 0x60, 0x48, 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x88, 0x44}}, + SupportsTLS: true, + SupportsIPv6: true, + Host: models.DNSHost("dns.google"), + }, + Quad9: { + IPs: []net.IP{{9, 9, 9, 9}, {149, 112, 112, 112}, {0x26, 0x20, 0x0, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe}, {0x26, 0x20, 0x0, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9}}, + SupportsTLS: true, + SupportsIPv6: true, + Host: models.DNSHost("dns.quad9.net"), + }, + Quadrant: { + IPs: []net.IP{{12, 159, 2, 159}, {0x20, 0x1, 0x18, 0x90, 0x14, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x59}}, + SupportsTLS: true, + SupportsIPv6: true, + Host: models.DNSHost("dns-tls.qis.io"), + }, + CleanBrowsing: { + IPs: []net.IP{{185, 228, 168, 9}, {185, 228, 169, 9}, {0x2a, 0xd, 0x2a, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2}, {0x2a, 0xd, 0x2a, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2}}, + SupportsTLS: true, + SupportsIPv6: true, + Host: models.DNSHost("security-filter-dns.cleanbrowsing.org"), + }, + SecureDNS: { + IPs: []net.IP{{146, 185, 167, 43}, {0x2a, 0x3, 0xb0, 0xc0, 0x0, 0x0, 0x10, 0x10, 0x0, 0x0, 0x0, 0x0, 0xe, 0x9a, 0x30, 0x1}}, + SupportsTLS: true, + SupportsIPv6: true, + Host: models.DNSHost("dot.securedns.eu"), + }, + LibreDNS: { + IPs: []net.IP{{116, 203, 115, 192}}, + SupportsTLS: true, + Host: models.DNSHost("dot.libredns.gr"), + }, + } +} + +// Block lists URLs +const ( + AdsBlockListHostnamesURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/ads-hostnames.updated" + AdsBlockListIPsURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/ads-ips.updated" + MaliciousBlockListHostnamesURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/malicious-hostnames.updated" + MaliciousBlockListIPsURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/malicious-ips.updated" + SurveillanceBlockListHostnamesURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/surveillance-hostnames.updated" + SurveillanceBlockListIPsURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/surveillance-ips.updated" +) + +// DNS certificates to fetch +// TODO obtain from source directly, see qdm12/updated) +const ( + NamedRootURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/named.root.updated" + RootKeyURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/root.key.updated" +) diff --git a/internal/constants/pia.go b/internal/constants/pia.go index 43d79fcc..4a3b3d5a 100644 --- a/internal/constants/pia.go +++ b/internal/constants/pia.go @@ -1,85 +1,85 @@ -package constants - -import ( - "github.com/qdm12/private-internet-access-docker/internal/models" -) - -const ( - // PIAEncryptionNormal is the normal level of encryption for communication with PIA servers - PIAEncryptionNormal models.PIAEncryption = "normal" - // PIAEncryptionStrong is the strong level of encryption for communication with PIA servers - PIAEncryptionStrong models.PIAEncryption = "strong" -) - -const ( - PIAX509CRL_NORMAL = "MIICWDCCAUAwDQYJKoZIhvcNAQENBQAwgegxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRlaW50ZXJuZXRhY2Nlc3MuY29tFw0xNjA3MDgxOTAwNDZaFw0zNjA3MDMxOTAwNDZaMCYwEQIBARcMMTYwNzA4MTkwMDQ2MBECAQYXDDE2MDcwODE5MDA0NjANBgkqhkiG9w0BAQ0FAAOCAQEAQZo9X97ci8EcPYu/uK2HB152OZbeZCINmYyluLDOdcSvg6B5jI+ffKN3laDvczsG6CxmY3jNyc79XVpEYUnq4rT3FfveW1+Ralf+Vf38HdpwB8EWB4hZlQ205+21CALLvZvR8HcPxC9KEnev1mU46wkTiov0EKc+EdRxkj5yMgv0V2Reze7AP+NQ9ykvDScH4eYCsmufNpIjBLhpLE2cuZZXBLcPhuRzVoU3l7A9lvzG9mjA5YijHJGHNjlWFqyrn1CfYS6koa4TGEPngBoAziWRbDGdhEgJABHrpoaFYaL61zqyMR6jC0K2ps9qyZAN74LEBedEfK7tBOzWMwr58A==" - PIAX509CRL_STRONG = "MIIDWDCCAUAwDQYJKoZIhvcNAQENBQAwgegxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRlaW50ZXJuZXRhY2Nlc3MuY29tFw0xNjA3MDgxOTAwNDZaFw0zNjA3MDMxOTAwNDZaMCYwEQIBARcMMTYwNzA4MTkwMDQ2MBECAQYXDDE2MDcwODE5MDA0NjANBgkqhkiG9w0BAQ0FAAOCAgEAppFfEpGsasjB1QgJcosGpzbf2kfRhM84o2TlqY1ua+Gi5TMdKydA3LJcNTjlI9a0TYAJfeRX5IkpoglSUuHuJgXhP3nEvX10mjXDpcu/YvM8TdE5JV2+EGqZ80kFtBeOq94WcpiVKFTR4fO+VkOK9zwspFfb1cNs9rHvgJ1QMkRUF8PpLN6AkntHY0+6DnigtSaKqldqjKTDTv2OeH3nPoh80SGrt0oCOmYKfWTJGpggMGKvIdvU3vH9+EuILZKKIskt+1dwdfA5Bkz1GLmiQG7+9ZZBQUjBG9Dos4hfX/rwJ3eU8oUIm4WoTz9rb71SOEuUUjP5NPy9HNx2vx+cVvLsTF4ZDZaUztW9o9JmIURDtbeyqxuHN3prlPWB6aj73IIm2dsDQvs3XXwRIxs8NwLbJ6CyEuvEOVCskdM8rdADWx1J0lRNlOJ0Z8ieLLEmYAA834VN1SboB6wJIAPxQU3rcBhXqO9y8aa2oRMg8NxZ5gr+PnKVMqag1x0IxbIgLxtkXQvxXxQHEMSODzvcOfK/nBRBsqTj30P+R87sU8titOoxNeRnBDRNhdEy/QGAqGh62ShPpQUCJdnKRiRTjnil9hMQHevoSuFKeEMO30FQL7BZyo37GFU+q1WPCplVZgCP9hC8Rn5K2+f6KLFo5bhtowSmu+GY1yZtg+RTtsA=" - PIACertificate_NORMAL = "MIIFqzCCBJOgAwIBAgIJAKZ7D5Yv87qDMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzM1MThaFw0zNDA0MTIxNzM1MThaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPXDL1L9tX6DGf36liA7UBTy5I869z0UVo3lImfOs/GSiFKPtInlesP65577nd7UNzzXlH/P/CnFPdBWlLp5ze3HRBCc/Avgr5CdMRkEsySL5GHBZsx6w2cayQ2EcRhVTwWpcdldeNO+pPr9rIgPrtXqT4SWViTQRBeGM8CDxAyTopTsobjSiYZCF9Ta1gunl0G/8Vfp+SXfYCC+ZzWvP+L1pFhPRqzQQ8k+wMZIovObK1s+nlwPaLyayzw9a8sUnvWB/5rGPdIYnQWPgoNlLN9HpSmsAcw2z8DXI9pIxbr74cb3/HSfuYGOLkRqrOk6h4RCOfuWoTrZup1uEOn+fw8CAwEAAaOCAVQwggFQMB0GA1UdDgQWBBQv63nQ/pJAt5tLy8VJcbHe22ZOsjCCAR8GA1UdIwSCARYwggESgBQv63nQ/pJAt5tLy8VJcbHe22ZOsqGB7qSB6zCB6DELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRMwEQYDVQQHEwpMb3NBbmdlbGVzMSAwHgYDVQQKExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UECxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAMTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQpExdQcml2YXRlIEludGVybmV0IEFjY2VzczEvMC0GCSqGSIb3DQEJARYgc2VjdXJlQHByaXZhdGVpbnRlcm5ldGFjY2Vzcy5jb22CCQCmew+WL/O6gzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQAna5PgrtxfwTumD4+3/SYvwoD66cB8IcK//h1mCzAduU8KgUXocLx7QgJWo9lnZ8xUryXvWab2usg4fqk7FPi00bED4f4qVQFVfGfPZIH9QQ7/48bPM9RyfzImZWUCenK37pdw4Bvgoys2rHLHbGen7f28knT2j/cbMxd78tQc20TIObGjo8+ISTRclSTRBtyCGohseKYpTS9himFERpUgNtefvYHbn70mIOzfOJFTVqfrptf9jXa9N8Mpy3ayfodz1wiqdteqFXkTYoSDctgKMiZ6GdocK9nMroQipIQtpnwd4yBDWIyC6Bvlkrq5TQUtYDQ8z9v+DMO6iwyIDRiU" - PIACertificate_STRONG = "MIIHqzCCBZOgAwIBAgIJAJ0u+vODZJntMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzQwMzNaFw0zNDA0MTIxNzQwMzNaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALVkhjumaqBbL8aSgj6xbX1QPTfTd1qHsAZd2B97m8Vw31c/2yQgZNf5qZY0+jOIHULNDe4R9TIvyBEbvnAg/OkPw8n/+ScgYOeH876VUXzjLDBnDb8DLr/+w9oVsuDeFJ9KV2UFM1OYX0SnkHnrYAN2QLF98ESK4NCSU01h5zkcgmQ+qKSfA9Ny0/UpsKPBFqsQ25NvjDWFhCpeqCHKUJ4Be27CDbSl7lAkBuHMPHJs8f8xPgAbHRXZOxVCpayZ2SNDfCwsnGWpWFoMGvdMbygngCn6jA/W1VSFOlRlfLuuGe7QFfDwA0jaLCxuWt/BgZylp7tAzYKR8lnWmtUCPm4+BtjyVDYtDCiGBD9Z4P13RFWvJHw5aapx/5W/CuvVyI7pKwvc2IT+KPxCUhH1XI8ca5RN3C9NoPJJf6qpg4g0rJH3aaWkoMRrYvQ+5PXXYUzjtRHImghRGd/ydERYoAZXuGSbPkm9Y/p2X8unLcW+F0xpJD98+ZI+tzSsI99Zs5wijSUGYr9/j18KHFTMQ8n+1jauc5bCCegN27dPeKXNSZ5riXFL2XX6BkY68y58UaNzmeGMiUL9BOV1iV+PMb7B7PYs7oFLjAhh0EdyvfHkrh/ZV9BEhtFa7yXp8XR0J6vz1YV9R6DYJmLjOEbhU8N0gc3tZm4Qz39lIIG6w3FDAgMBAAGjggFUMIIBUDAdBgNVHQ4EFgQUrsRtyWJftjpdRM0+925Y6Cl08SUwggEfBgNVHSMEggEWMIIBEoAUrsRtyWJftjpdRM0+925Y6Cl08SWhge6kgeswgegxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRlaW50ZXJuZXRhY2Nlc3MuY29tggkAnS7684Nkme0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOCAgEAJsfhsPk3r8kLXLxY+v+vHzbr4ufNtqnL9/1Uuf8NrsCtpXAoyZ0YqfbkWx3NHTZ7OE9ZRhdMP/RqHQE1p4N4Sa1nZKhTKasV6KhHDqSCt/dvEm89xWm2MVA7nyzQxVlHa9AkcBaemcXEiyT19XdpiXOP4Vhs+J1R5m8zQOxZlV1GtF9vsXmJqWZpOVPmZ8f35BCsYPvv4yMewnrtAC8PFEK/bOPeYcKN50bol22QYaZuLfpkHfNiFTnfMh8sl/ablPyNY7DUNiP5DRcMdIwmfGQxR5WEQoHL3yPJ42LkB5zs6jIm26DGNXfwura/mi105+ENH1CaROtRYwkiHb08U6qLXXJz80mWJkT90nr8Asj35xN2cUppg74nG3YVav/38P48T56hG1NHbYF5uOCske19F6wi9maUoto/3vEr0rnXJUp2KODmKdvBI7co245lHBABWikk8VfejQSlCtDBXn644ZMtAdoxKNfR2WTFVEwJiyd1Fzx0yujuiXDROLhISLQDRjVVAvawrAtLZWYK31bY7KlezPlQnl/D9Asxe85l8jO5+0LdJ6VyOs/Hd4w52alDW/MFySDZSfQHMTIc30hLBJ8OnCEIvluVQQ2UQvoW+no177N9L2Y+M9TcTA62ZyMXShHQGeh20rb4kK8f+iFX8NxtdHVSkxMEFSfDDyQ=" -) - -func PIAGeoChoices() (regions []string) { - for region := range PIAGeoToSubdomainMapping() { - regions = append(regions, string(region)) - } - return regions -} - -func PIAGeoToSubdomainMapping() map[models.PIARegion]string { - return map[models.PIARegion]string{ - models.PIARegion("AU Melbourne"): "au-melbourne", - models.PIARegion("AU Perth"): "au-perth", - models.PIARegion("AU Sydney"): "au-sydney", - models.PIARegion("Austria"): "austria", - models.PIARegion("Belgium"): "belgium", - models.PIARegion("CA Montreal"): "ca-montreal", - models.PIARegion("CA Toronto"): "ca-toronto", - models.PIARegion("CA Vancouver"): "ca-vancouver", - models.PIARegion("Czech Republic"): "czech", - models.PIARegion("DE Berlin"): "de-berlin", - models.PIARegion("DE Frankfurt"): "de-frankfurt", - models.PIARegion("Denmark"): "denmark", - models.PIARegion("Finland"): "fi", - models.PIARegion("France"): "france", - models.PIARegion("Hong Kong"): "hk", - models.PIARegion("Hungary"): "hungary", - models.PIARegion("India"): "in", - models.PIARegion("Ireland"): "ireland", - models.PIARegion("Israel"): "israel", - models.PIARegion("Italy"): "italy", - models.PIARegion("Japan"): "japan", - models.PIARegion("Luxembourg"): "lu", - models.PIARegion("Mexico"): "mexico", - models.PIARegion("Netherlands"): "nl", - models.PIARegion("New Zealand"): "nz", - models.PIARegion("Norway"): "no", - models.PIARegion("Poland"): "poland", - models.PIARegion("Romania"): "ro", - models.PIARegion("Singapore"): "sg", - models.PIARegion("Spain"): "spain", - models.PIARegion("Sweden"): "sweden", - models.PIARegion("Switzerland"): "swiss", - models.PIARegion("UAE"): "ae", - models.PIARegion("UK London"): "uk-london", - models.PIARegion("UK Manchester"): "uk-manchester", - models.PIARegion("UK Southampton"): "uk-southampton", - models.PIARegion("US Atlanta"): "us-atlanta", - models.PIARegion("US California"): "us-california", - models.PIARegion("US Chicago"): "us-chicago", - models.PIARegion("US Denver"): "us-denver", - models.PIARegion("US East"): "us-east", - models.PIARegion("US Florida"): "us-florida", - models.PIARegion("US Houston"): "us-houston", - models.PIARegion("US Las Vegas"): "us-lasvegas", - models.PIARegion("US New York City"): "us-newyorkcity", - models.PIARegion("US Seattle"): "us-seattle", - models.PIARegion("US Silicon Valley"): "us-siliconvalley", - models.PIARegion("US Texas"): "us-texas", - models.PIARegion("US Washington DC"): "us-washingtondc", - models.PIARegion("US West"): "us-west", - } -} - -const ( - PIAPortForwardURL models.URL = "http://209.222.18.222:2000" -) +package constants + +import ( + "github.com/qdm12/private-internet-access-docker/internal/models" +) + +const ( + // PIAEncryptionNormal is the normal level of encryption for communication with PIA servers + PIAEncryptionNormal models.PIAEncryption = "normal" + // PIAEncryptionStrong is the strong level of encryption for communication with PIA servers + PIAEncryptionStrong models.PIAEncryption = "strong" +) + +const ( + PiaX509CRLNormal = "MIICWDCCAUAwDQYJKoZIhvcNAQENBQAwgegxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRlaW50ZXJuZXRhY2Nlc3MuY29tFw0xNjA3MDgxOTAwNDZaFw0zNjA3MDMxOTAwNDZaMCYwEQIBARcMMTYwNzA4MTkwMDQ2MBECAQYXDDE2MDcwODE5MDA0NjANBgkqhkiG9w0BAQ0FAAOCAQEAQZo9X97ci8EcPYu/uK2HB152OZbeZCINmYyluLDOdcSvg6B5jI+ffKN3laDvczsG6CxmY3jNyc79XVpEYUnq4rT3FfveW1+Ralf+Vf38HdpwB8EWB4hZlQ205+21CALLvZvR8HcPxC9KEnev1mU46wkTiov0EKc+EdRxkj5yMgv0V2Reze7AP+NQ9ykvDScH4eYCsmufNpIjBLhpLE2cuZZXBLcPhuRzVoU3l7A9lvzG9mjA5YijHJGHNjlWFqyrn1CfYS6koa4TGEPngBoAziWRbDGdhEgJABHrpoaFYaL61zqyMR6jC0K2ps9qyZAN74LEBedEfK7tBOzWMwr58A==" + PiaX509CRLStrong = "MIIDWDCCAUAwDQYJKoZIhvcNAQENBQAwgegxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRlaW50ZXJuZXRhY2Nlc3MuY29tFw0xNjA3MDgxOTAwNDZaFw0zNjA3MDMxOTAwNDZaMCYwEQIBARcMMTYwNzA4MTkwMDQ2MBECAQYXDDE2MDcwODE5MDA0NjANBgkqhkiG9w0BAQ0FAAOCAgEAppFfEpGsasjB1QgJcosGpzbf2kfRhM84o2TlqY1ua+Gi5TMdKydA3LJcNTjlI9a0TYAJfeRX5IkpoglSUuHuJgXhP3nEvX10mjXDpcu/YvM8TdE5JV2+EGqZ80kFtBeOq94WcpiVKFTR4fO+VkOK9zwspFfb1cNs9rHvgJ1QMkRUF8PpLN6AkntHY0+6DnigtSaKqldqjKTDTv2OeH3nPoh80SGrt0oCOmYKfWTJGpggMGKvIdvU3vH9+EuILZKKIskt+1dwdfA5Bkz1GLmiQG7+9ZZBQUjBG9Dos4hfX/rwJ3eU8oUIm4WoTz9rb71SOEuUUjP5NPy9HNx2vx+cVvLsTF4ZDZaUztW9o9JmIURDtbeyqxuHN3prlPWB6aj73IIm2dsDQvs3XXwRIxs8NwLbJ6CyEuvEOVCskdM8rdADWx1J0lRNlOJ0Z8ieLLEmYAA834VN1SboB6wJIAPxQU3rcBhXqO9y8aa2oRMg8NxZ5gr+PnKVMqag1x0IxbIgLxtkXQvxXxQHEMSODzvcOfK/nBRBsqTj30P+R87sU8titOoxNeRnBDRNhdEy/QGAqGh62ShPpQUCJdnKRiRTjnil9hMQHevoSuFKeEMO30FQL7BZyo37GFU+q1WPCplVZgCP9hC8Rn5K2+f6KLFo5bhtowSmu+GY1yZtg+RTtsA=" + PIACertificateNormal = "MIIFqzCCBJOgAwIBAgIJAKZ7D5Yv87qDMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzM1MThaFw0zNDA0MTIxNzM1MThaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPXDL1L9tX6DGf36liA7UBTy5I869z0UVo3lImfOs/GSiFKPtInlesP65577nd7UNzzXlH/P/CnFPdBWlLp5ze3HRBCc/Avgr5CdMRkEsySL5GHBZsx6w2cayQ2EcRhVTwWpcdldeNO+pPr9rIgPrtXqT4SWViTQRBeGM8CDxAyTopTsobjSiYZCF9Ta1gunl0G/8Vfp+SXfYCC+ZzWvP+L1pFhPRqzQQ8k+wMZIovObK1s+nlwPaLyayzw9a8sUnvWB/5rGPdIYnQWPgoNlLN9HpSmsAcw2z8DXI9pIxbr74cb3/HSfuYGOLkRqrOk6h4RCOfuWoTrZup1uEOn+fw8CAwEAAaOCAVQwggFQMB0GA1UdDgQWBBQv63nQ/pJAt5tLy8VJcbHe22ZOsjCCAR8GA1UdIwSCARYwggESgBQv63nQ/pJAt5tLy8VJcbHe22ZOsqGB7qSB6zCB6DELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRMwEQYDVQQHEwpMb3NBbmdlbGVzMSAwHgYDVQQKExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UECxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAMTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQpExdQcml2YXRlIEludGVybmV0IEFjY2VzczEvMC0GCSqGSIb3DQEJARYgc2VjdXJlQHByaXZhdGVpbnRlcm5ldGFjY2Vzcy5jb22CCQCmew+WL/O6gzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQAna5PgrtxfwTumD4+3/SYvwoD66cB8IcK//h1mCzAduU8KgUXocLx7QgJWo9lnZ8xUryXvWab2usg4fqk7FPi00bED4f4qVQFVfGfPZIH9QQ7/48bPM9RyfzImZWUCenK37pdw4Bvgoys2rHLHbGen7f28knT2j/cbMxd78tQc20TIObGjo8+ISTRclSTRBtyCGohseKYpTS9himFERpUgNtefvYHbn70mIOzfOJFTVqfrptf9jXa9N8Mpy3ayfodz1wiqdteqFXkTYoSDctgKMiZ6GdocK9nMroQipIQtpnwd4yBDWIyC6Bvlkrq5TQUtYDQ8z9v+DMO6iwyIDRiU" + PIACertificateStrong = "MIIHqzCCBZOgAwIBAgIJAJ0u+vODZJntMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzQwMzNaFw0zNDA0MTIxNzQwMzNaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALVkhjumaqBbL8aSgj6xbX1QPTfTd1qHsAZd2B97m8Vw31c/2yQgZNf5qZY0+jOIHULNDe4R9TIvyBEbvnAg/OkPw8n/+ScgYOeH876VUXzjLDBnDb8DLr/+w9oVsuDeFJ9KV2UFM1OYX0SnkHnrYAN2QLF98ESK4NCSU01h5zkcgmQ+qKSfA9Ny0/UpsKPBFqsQ25NvjDWFhCpeqCHKUJ4Be27CDbSl7lAkBuHMPHJs8f8xPgAbHRXZOxVCpayZ2SNDfCwsnGWpWFoMGvdMbygngCn6jA/W1VSFOlRlfLuuGe7QFfDwA0jaLCxuWt/BgZylp7tAzYKR8lnWmtUCPm4+BtjyVDYtDCiGBD9Z4P13RFWvJHw5aapx/5W/CuvVyI7pKwvc2IT+KPxCUhH1XI8ca5RN3C9NoPJJf6qpg4g0rJH3aaWkoMRrYvQ+5PXXYUzjtRHImghRGd/ydERYoAZXuGSbPkm9Y/p2X8unLcW+F0xpJD98+ZI+tzSsI99Zs5wijSUGYr9/j18KHFTMQ8n+1jauc5bCCegN27dPeKXNSZ5riXFL2XX6BkY68y58UaNzmeGMiUL9BOV1iV+PMb7B7PYs7oFLjAhh0EdyvfHkrh/ZV9BEhtFa7yXp8XR0J6vz1YV9R6DYJmLjOEbhU8N0gc3tZm4Qz39lIIG6w3FDAgMBAAGjggFUMIIBUDAdBgNVHQ4EFgQUrsRtyWJftjpdRM0+925Y6Cl08SUwggEfBgNVHSMEggEWMIIBEoAUrsRtyWJftjpdRM0+925Y6Cl08SWhge6kgeswgegxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRlaW50ZXJuZXRhY2Nlc3MuY29tggkAnS7684Nkme0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOCAgEAJsfhsPk3r8kLXLxY+v+vHzbr4ufNtqnL9/1Uuf8NrsCtpXAoyZ0YqfbkWx3NHTZ7OE9ZRhdMP/RqHQE1p4N4Sa1nZKhTKasV6KhHDqSCt/dvEm89xWm2MVA7nyzQxVlHa9AkcBaemcXEiyT19XdpiXOP4Vhs+J1R5m8zQOxZlV1GtF9vsXmJqWZpOVPmZ8f35BCsYPvv4yMewnrtAC8PFEK/bOPeYcKN50bol22QYaZuLfpkHfNiFTnfMh8sl/ablPyNY7DUNiP5DRcMdIwmfGQxR5WEQoHL3yPJ42LkB5zs6jIm26DGNXfwura/mi105+ENH1CaROtRYwkiHb08U6qLXXJz80mWJkT90nr8Asj35xN2cUppg74nG3YVav/38P48T56hG1NHbYF5uOCske19F6wi9maUoto/3vEr0rnXJUp2KODmKdvBI7co245lHBABWikk8VfejQSlCtDBXn644ZMtAdoxKNfR2WTFVEwJiyd1Fzx0yujuiXDROLhISLQDRjVVAvawrAtLZWYK31bY7KlezPlQnl/D9Asxe85l8jO5+0LdJ6VyOs/Hd4w52alDW/MFySDZSfQHMTIc30hLBJ8OnCEIvluVQQ2UQvoW+no177N9L2Y+M9TcTA62ZyMXShHQGeh20rb4kK8f+iFX8NxtdHVSkxMEFSfDDyQ=" +) + +func PIAGeoChoices() (regions []string) { + for region := range PIAGeoToSubdomainMapping() { + regions = append(regions, string(region)) + } + return regions +} + +func PIAGeoToSubdomainMapping() map[models.PIARegion]string { + return map[models.PIARegion]string{ + models.PIARegion("AU Melbourne"): "au-melbourne", + models.PIARegion("AU Perth"): "au-perth", + models.PIARegion("AU Sydney"): "au-sydney", + models.PIARegion("Austria"): "austria", + models.PIARegion("Belgium"): "belgium", + models.PIARegion("CA Montreal"): "ca-montreal", + models.PIARegion("CA Toronto"): "ca-toronto", + models.PIARegion("CA Vancouver"): "ca-vancouver", + models.PIARegion("Czech Republic"): "czech", + models.PIARegion("DE Berlin"): "de-berlin", + models.PIARegion("DE Frankfurt"): "de-frankfurt", + models.PIARegion("Denmark"): "denmark", + models.PIARegion("Finland"): "fi", + models.PIARegion("France"): "france", + models.PIARegion("Hong Kong"): "hk", + models.PIARegion("Hungary"): "hungary", + models.PIARegion("India"): "in", + models.PIARegion("Ireland"): "ireland", + models.PIARegion("Israel"): "israel", + models.PIARegion("Italy"): "italy", + models.PIARegion("Japan"): "japan", + models.PIARegion("Luxembourg"): "lu", + models.PIARegion("Mexico"): "mexico", + models.PIARegion("Netherlands"): "nl", + models.PIARegion("New Zealand"): "nz", + models.PIARegion("Norway"): "no", + models.PIARegion("Poland"): "poland", + models.PIARegion("Romania"): "ro", + models.PIARegion("Singapore"): "sg", + models.PIARegion("Spain"): "spain", + models.PIARegion("Sweden"): "sweden", + models.PIARegion("Switzerland"): "swiss", + models.PIARegion("UAE"): "ae", + models.PIARegion("UK London"): "uk-london", + models.PIARegion("UK Manchester"): "uk-manchester", + models.PIARegion("UK Southampton"): "uk-southampton", + models.PIARegion("US Atlanta"): "us-atlanta", + models.PIARegion("US California"): "us-california", + models.PIARegion("US Chicago"): "us-chicago", + models.PIARegion("US Denver"): "us-denver", + models.PIARegion("US East"): "us-east", + models.PIARegion("US Florida"): "us-florida", + models.PIARegion("US Houston"): "us-houston", + models.PIARegion("US Las Vegas"): "us-lasvegas", + models.PIARegion("US New York City"): "us-newyorkcity", + models.PIARegion("US Seattle"): "us-seattle", + models.PIARegion("US Silicon Valley"): "us-siliconvalley", + models.PIARegion("US Texas"): "us-texas", + models.PIARegion("US Washington DC"): "us-washingtondc", + models.PIARegion("US West"): "us-west", + } +} + +const ( + PIAPortForwardURL models.URL = "http://209.222.18.222:2000" +) diff --git a/internal/dns/conf.go b/internal/dns/conf.go index 0deb96ae..36e43954 100644 --- a/internal/dns/conf.go +++ b/internal/dns/conf.go @@ -1,287 +1,289 @@ -package dns - -import ( - "fmt" - "sort" - "strings" - - "github.com/qdm12/golibs/files" - "github.com/qdm12/golibs/logging" - "github.com/qdm12/golibs/network" - "github.com/qdm12/private-internet-access-docker/internal/constants" - "github.com/qdm12/private-internet-access-docker/internal/settings" -) - -func (c *configurator) MakeUnboundConf(settings settings.DNS, uid, gid int) (err error) { - c.logger.Info("generating Unbound configuration") - lines, warnings, err := generateUnboundConf(settings, c.client, c.logger) - for _, warning := range warnings { - c.logger.Warn(warning) - } - if err != nil { - return err - } - return c.fileManager.WriteLinesToFile( - string(constants.UnboundConf), - lines, - files.Ownership(uid, gid), - files.Permissions(0400)) -} - -// MakeUnboundConf generates an Unbound configuration from the user provided settings -func generateUnboundConf(settings settings.DNS, client network.Client, logger logging.Logger) (lines []string, warnings []error, err error) { - doIPv6 := "no" - if settings.IPv6 { - doIPv6 = "yes" - } - serverSection := map[string]string{ - // Logging - "verbosity": fmt.Sprintf("%d", settings.VerbosityLevel), - "val-log-level": fmt.Sprintf("%d", settings.ValidationLogLevel), - "use-syslog": "no", - // Performance - "num-threads": "1", - "prefetch": "yes", - "prefetch-key": "yes", - "key-cache-size": "16m", - "key-cache-slabs": "4", - "msg-cache-size": "4m", - "msg-cache-slabs": "4", - "rrset-cache-size": "4m", - "rrset-cache-slabs": "4", - "cache-min-ttl": "3600", - "cache-max-ttl": "9000", - // Privacy - "rrset-roundrobin": "yes", - "hide-identity": "yes", - "hide-version": "yes", - // Security - "tls-cert-bundle": fmt.Sprintf("%q", constants.CACertificates), - "root-hints": fmt.Sprintf("%q", constants.RootHints), - "trust-anchor-file": fmt.Sprintf("%q", constants.RootKey), - "harden-below-nxdomain": "yes", - "harden-referral-path": "yes", - "harden-algo-downgrade": "yes", - // Network - "do-ip4": "yes", - "do-ip6": doIPv6, - "interface": "127.0.0.1", - "port": "53", - // Other - "username": "\"nonrootuser\"", - } - - // Block lists - hostnamesLines, ipsLines, warnings := buildBlocked(client, - settings.BlockMalicious, settings.BlockAds, settings.BlockSurveillance, - settings.AllowedHostnames, settings.PrivateAddresses, - ) - logger.Info("%d hostnames blocked overall", len(hostnamesLines)) - logger.Info("%d IP addresses blocked overall", len(ipsLines)) - sort.Slice(hostnamesLines, func(i, j int) bool { // for unit tests really - return hostnamesLines[i] < hostnamesLines[j] - }) - sort.Slice(ipsLines, func(i, j int) bool { // for unit tests really - return ipsLines[i] < ipsLines[j] - }) - - // Server - lines = append(lines, "server:") - var serverLines []string - for k, v := range serverSection { - serverLines = append(serverLines, " "+k+": "+v) - } - sort.Slice(serverLines, func(i, j int) bool { - return serverLines[i] < serverLines[j] - }) - lines = append(lines, serverLines...) - lines = append(lines, hostnamesLines...) - lines = append(lines, ipsLines...) - - // Forward zone - lines = append(lines, "forward-zone:") - forwardZoneSection := map[string]string{ - "name": "\".\"", - "forward-tls-upstream": "yes", - } - if settings.Caching { - forwardZoneSection["forward-no-cache"] = "no" - } else { - forwardZoneSection["forward-no-cache"] = "yes" - } - var forwardZoneLines []string - for k, v := range forwardZoneSection { - forwardZoneLines = append(forwardZoneLines, " "+k+": "+v) - } - sort.Slice(forwardZoneLines, func(i, j int) bool { - return forwardZoneLines[i] < forwardZoneLines[j] - }) - for _, provider := range settings.Providers { - providerData := constants.DNSProviderMapping()[provider] - for _, IP := range providerData.IPs { - forwardZoneLines = append(forwardZoneLines, - fmt.Sprintf(" forward-addr: %s@853#%s", IP.String(), providerData.Host)) - } - } - lines = append(lines, forwardZoneLines...) - return lines, warnings, nil -} - -func buildBlocked(client network.Client, blockMalicious, blockAds, blockSurveillance bool, - allowedHostnames, privateAddresses []string) (hostnamesLines, ipsLines []string, errs []error) { - chHostnames := make(chan []string) - chIPs := make(chan []string) - chErrors := make(chan []error) - go func() { - lines, errs := buildBlockedHostnames(client, blockMalicious, blockAds, blockSurveillance, allowedHostnames) - chHostnames <- lines - chErrors <- errs - }() - go func() { - lines, errs := buildBlockedIPs(client, blockMalicious, blockAds, blockSurveillance, privateAddresses) - chIPs <- lines - chErrors <- errs - }() - n := 2 - for n > 0 { - select { - case lines := <-chHostnames: - hostnamesLines = append(hostnamesLines, lines...) - case lines := <-chIPs: - ipsLines = append(ipsLines, lines...) - case routineErrs := <-chErrors: - errs = append(errs, routineErrs...) - n-- - } - } - return hostnamesLines, ipsLines, errs -} - -func getList(client network.Client, URL string) (results []string, err error) { - content, status, err := client.GetContent(URL) - if err != nil { - return nil, err - } else if status != 200 { - return nil, fmt.Errorf("HTTP status code is %d and not 200", status) - } - results = strings.Split(string(content), "\n") - - // remove empty lines - last := len(results) - 1 - for i := range results { - if len(results[i]) == 0 { - results[i] = results[last] - last-- - } - } - results = results[:last+1] - - if len(results) == 0 { - return nil, nil - } - return results, nil -} - -func buildBlockedHostnames(client network.Client, blockMalicious, blockAds, blockSurveillance bool, - allowedHostnames []string) (lines []string, errs []error) { - chResults := make(chan []string) - chError := make(chan error) - listsLeftToFetch := 0 - if blockMalicious { - listsLeftToFetch++ - go func() { - results, err := getList(client, string(constants.MaliciousBlockListHostnamesURL)) - chResults <- results - chError <- err - }() - } - if blockAds { - listsLeftToFetch++ - go func() { - results, err := getList(client, string(constants.AdsBlockListHostnamesURL)) - chResults <- results - chError <- err - }() - } - if blockSurveillance { - listsLeftToFetch++ - go func() { - results, err := getList(client, string(constants.SurveillanceBlockListHostnamesURL)) - chResults <- results - chError <- err - }() - } - uniqueResults := make(map[string]struct{}) - for listsLeftToFetch > 0 { - select { - case results := <-chResults: - for _, result := range results { - uniqueResults[result] = struct{}{} - } - case err := <-chError: - listsLeftToFetch-- - if err != nil { - errs = append(errs, err) - } - } - } - for _, allowedHostname := range allowedHostnames { - delete(uniqueResults, allowedHostname) - } - for result := range uniqueResults { - lines = append(lines, " local-zone: \""+result+"\" static") - } - return lines, errs -} - -func buildBlockedIPs(client network.Client, blockMalicious, blockAds, blockSurveillance bool, - privateAddresses []string) (lines []string, errs []error) { - chResults := make(chan []string) - chError := make(chan error) - listsLeftToFetch := 0 - if blockMalicious { - listsLeftToFetch++ - go func() { - results, err := getList(client, string(constants.MaliciousBlockListIPsURL)) - chResults <- results - chError <- err - }() - } - if blockAds { - listsLeftToFetch++ - go func() { - results, err := getList(client, string(constants.AdsBlockListIPsURL)) - chResults <- results - chError <- err - }() - } - if blockSurveillance { - listsLeftToFetch++ - go func() { - results, err := getList(client, string(constants.SurveillanceBlockListIPsURL)) - chResults <- results - chError <- err - }() - } - uniqueResults := make(map[string]struct{}) - for listsLeftToFetch > 0 { - select { - case results := <-chResults: - for _, result := range results { - uniqueResults[result] = struct{}{} - } - case err := <-chError: - listsLeftToFetch-- - if err != nil { - errs = append(errs, err) - } - } - } - for _, privateAddress := range privateAddresses { - uniqueResults[privateAddress] = struct{}{} - } - for result := range uniqueResults { - lines = append(lines, " private-address: "+result) - } - return lines, errs -} +package dns + +import ( + "fmt" + "net/http" + "sort" + "strings" + + "github.com/qdm12/golibs/files" + "github.com/qdm12/golibs/logging" + "github.com/qdm12/golibs/network" + "github.com/qdm12/private-internet-access-docker/internal/constants" + "github.com/qdm12/private-internet-access-docker/internal/settings" +) + +func (c *configurator) MakeUnboundConf(settings settings.DNS, uid, gid int) (err error) { + c.logger.Info("generating Unbound configuration") + lines, warnings := generateUnboundConf(settings, c.client, c.logger) + for _, warning := range warnings { + c.logger.Warn(warning) + } + return c.fileManager.WriteLinesToFile( + string(constants.UnboundConf), + lines, + files.Ownership(uid, gid), + files.Permissions(0400)) +} + +// MakeUnboundConf generates an Unbound configuration from the user provided settings +func generateUnboundConf(settings settings.DNS, client network.Client, logger logging.Logger) (lines []string, warnings []error) { + doIPv6 := "no" + if settings.IPv6 { + doIPv6 = "yes" + } + serverSection := map[string]string{ + // Logging + "verbosity": fmt.Sprintf("%d", settings.VerbosityLevel), + "val-log-level": fmt.Sprintf("%d", settings.ValidationLogLevel), + "use-syslog": "no", + // Performance + "num-threads": "1", + "prefetch": "yes", + "prefetch-key": "yes", + "key-cache-size": "16m", + "key-cache-slabs": "4", + "msg-cache-size": "4m", + "msg-cache-slabs": "4", + "rrset-cache-size": "4m", + "rrset-cache-slabs": "4", + "cache-min-ttl": "3600", + "cache-max-ttl": "9000", + // Privacy + "rrset-roundrobin": "yes", + "hide-identity": "yes", + "hide-version": "yes", + // Security + "tls-cert-bundle": fmt.Sprintf("%q", constants.CACertificates), + "root-hints": fmt.Sprintf("%q", constants.RootHints), + "trust-anchor-file": fmt.Sprintf("%q", constants.RootKey), + "harden-below-nxdomain": "yes", + "harden-referral-path": "yes", + "harden-algo-downgrade": "yes", + // Network + "do-ip4": "yes", + "do-ip6": doIPv6, + "interface": "127.0.0.1", + "port": "53", + // Other + "username": "\"nonrootuser\"", + } + + // Block lists + hostnamesLines, ipsLines, warnings := buildBlocked(client, + settings.BlockMalicious, settings.BlockAds, settings.BlockSurveillance, + settings.AllowedHostnames, settings.PrivateAddresses, + ) + logger.Info("%d hostnames blocked overall", len(hostnamesLines)) + logger.Info("%d IP addresses blocked overall", len(ipsLines)) + sort.Slice(hostnamesLines, func(i, j int) bool { // for unit tests really + return hostnamesLines[i] < hostnamesLines[j] + }) + sort.Slice(ipsLines, func(i, j int) bool { // for unit tests really + return ipsLines[i] < ipsLines[j] + }) + + // Server + lines = append(lines, "server:") + serverLines := make([]string, len(serverSection)) + i := 0 + for k, v := range serverSection { + serverLines[i] = " " + k + ": " + v + i++ + } + sort.Slice(serverLines, func(i, j int) bool { + return serverLines[i] < serverLines[j] + }) + lines = append(lines, serverLines...) + lines = append(lines, hostnamesLines...) + lines = append(lines, ipsLines...) + + // Forward zone + lines = append(lines, "forward-zone:") + forwardZoneSection := map[string]string{ + "name": "\".\"", + "forward-tls-upstream": "yes", + } + if settings.Caching { + forwardZoneSection["forward-no-cache"] = "no" + } else { + forwardZoneSection["forward-no-cache"] = "yes" + } + forwardZoneLines := make([]string, len(forwardZoneSection)) + i = 0 + for k, v := range forwardZoneSection { + forwardZoneLines[i] = " " + k + ": " + v + i++ + } + sort.Slice(forwardZoneLines, func(i, j int) bool { + return forwardZoneLines[i] < forwardZoneLines[j] + }) + for _, provider := range settings.Providers { + providerData := constants.DNSProviderMapping()[provider] + for _, IP := range providerData.IPs { + forwardZoneLines = append(forwardZoneLines, + fmt.Sprintf(" forward-addr: %s@853#%s", IP.String(), providerData.Host)) + } + } + lines = append(lines, forwardZoneLines...) + return lines, warnings +} + +func buildBlocked(client network.Client, blockMalicious, blockAds, blockSurveillance bool, + allowedHostnames, privateAddresses []string) (hostnamesLines, ipsLines []string, errs []error) { + chHostnames := make(chan []string) + chIPs := make(chan []string) + chErrors := make(chan []error) + go func() { + lines, errs := buildBlockedHostnames(client, blockMalicious, blockAds, blockSurveillance, allowedHostnames) + chHostnames <- lines + chErrors <- errs + }() + go func() { + lines, errs := buildBlockedIPs(client, blockMalicious, blockAds, blockSurveillance, privateAddresses) + chIPs <- lines + chErrors <- errs + }() + n := 2 + for n > 0 { + select { + case lines := <-chHostnames: + hostnamesLines = append(hostnamesLines, lines...) + case lines := <-chIPs: + ipsLines = append(ipsLines, lines...) + case routineErrs := <-chErrors: + errs = append(errs, routineErrs...) + n-- + } + } + return hostnamesLines, ipsLines, errs +} + +func getList(client network.Client, url string) (results []string, err error) { + content, status, err := client.GetContent(url) + if err != nil { + return nil, err + } else if status != http.StatusOK { + return nil, fmt.Errorf("HTTP status code is %d and not 200", status) + } + results = strings.Split(string(content), "\n") + + // remove empty lines + last := len(results) - 1 + for i := range results { + if len(results[i]) == 0 { + results[i] = results[last] + last-- + } + } + results = results[:last+1] + + if len(results) == 0 { + return nil, nil + } + return results, nil +} + +func buildBlockedHostnames(client network.Client, blockMalicious, blockAds, blockSurveillance bool, + allowedHostnames []string) (lines []string, errs []error) { + chResults := make(chan []string) + chError := make(chan error) + listsLeftToFetch := 0 + if blockMalicious { + listsLeftToFetch++ + go func() { + results, err := getList(client, string(constants.MaliciousBlockListHostnamesURL)) + chResults <- results + chError <- err + }() + } + if blockAds { + listsLeftToFetch++ + go func() { + results, err := getList(client, string(constants.AdsBlockListHostnamesURL)) + chResults <- results + chError <- err + }() + } + if blockSurveillance { + listsLeftToFetch++ + go func() { + results, err := getList(client, string(constants.SurveillanceBlockListHostnamesURL)) + chResults <- results + chError <- err + }() + } + uniqueResults := make(map[string]struct{}) + for listsLeftToFetch > 0 { + select { + case results := <-chResults: + for _, result := range results { + uniqueResults[result] = struct{}{} + } + case err := <-chError: + listsLeftToFetch-- + if err != nil { + errs = append(errs, err) + } + } + } + for _, allowedHostname := range allowedHostnames { + delete(uniqueResults, allowedHostname) + } + for result := range uniqueResults { + lines = append(lines, " local-zone: \""+result+"\" static") + } + return lines, errs +} + +func buildBlockedIPs(client network.Client, blockMalicious, blockAds, blockSurveillance bool, + privateAddresses []string) (lines []string, errs []error) { + chResults := make(chan []string) + chError := make(chan error) + listsLeftToFetch := 0 + if blockMalicious { + listsLeftToFetch++ + go func() { + results, err := getList(client, string(constants.MaliciousBlockListIPsURL)) + chResults <- results + chError <- err + }() + } + if blockAds { + listsLeftToFetch++ + go func() { + results, err := getList(client, string(constants.AdsBlockListIPsURL)) + chResults <- results + chError <- err + }() + } + if blockSurveillance { + listsLeftToFetch++ + go func() { + results, err := getList(client, string(constants.SurveillanceBlockListIPsURL)) + chResults <- results + chError <- err + }() + } + uniqueResults := make(map[string]struct{}) + for listsLeftToFetch > 0 { + select { + case results := <-chResults: + for _, result := range results { + uniqueResults[result] = struct{}{} + } + case err := <-chError: + listsLeftToFetch-- + if err != nil { + errs = append(errs, err) + } + } + } + for _, privateAddress := range privateAddresses { + uniqueResults[privateAddress] = struct{}{} + } + for result := range uniqueResults { + lines = append(lines, " private-address: "+result) + } + return lines, errs +} diff --git a/internal/dns/conf_test.go b/internal/dns/conf_test.go index 19b17a0c..dc118a79 100644 --- a/internal/dns/conf_test.go +++ b/internal/dns/conf_test.go @@ -1,531 +1,530 @@ -package dns - -import ( - "fmt" - "strings" - "testing" - - "github.com/golang/mock/gomock" - "github.com/qdm12/golibs/logging/mock_logging" - "github.com/qdm12/golibs/network/mock_network" - "github.com/qdm12/private-internet-access-docker/internal/constants" - "github.com/qdm12/private-internet-access-docker/internal/models" - "github.com/qdm12/private-internet-access-docker/internal/settings" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_generateUnboundConf(t *testing.T) { - t.Parallel() - settings := settings.DNS{ - Providers: []models.DNSProvider{constants.Cloudflare, constants.Quad9}, - AllowedHostnames: []string{"a"}, - PrivateAddresses: []string{"9.9.9.9"}, - BlockMalicious: true, - BlockSurveillance: false, - BlockAds: false, - VerbosityLevel: 2, - ValidationLogLevel: 3, - Caching: true, - IPv6: true, - } - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - client := mock_network.NewMockClient(mockCtrl) - client.EXPECT().GetContent(string(constants.MaliciousBlockListHostnamesURL)). - Return([]byte("b\na\nc"), 200, nil).Times(1) - client.EXPECT().GetContent(string(constants.MaliciousBlockListIPsURL)). - Return([]byte("c\nd\n"), 200, nil).Times(1) - logger := mock_logging.NewMockLogger(mockCtrl) - logger.EXPECT().Info("%d hostnames blocked overall", 2).Times(1) - logger.EXPECT().Info("%d IP addresses blocked overall", 3).Times(1) - lines, warnings, err := generateUnboundConf(settings, client, logger) - require.Len(t, warnings, 0) - require.NoError(t, err) - expected := ` -server: - cache-max-ttl: 9000 - cache-min-ttl: 3600 - do-ip4: yes - do-ip6: yes - harden-algo-downgrade: yes - harden-below-nxdomain: yes - harden-referral-path: yes - hide-identity: yes - hide-version: yes - interface: 127.0.0.1 - key-cache-size: 16m - key-cache-slabs: 4 - msg-cache-size: 4m - msg-cache-slabs: 4 - num-threads: 1 - port: 53 - prefetch-key: yes - prefetch: yes - root-hints: "/etc/unbound/root.hints" - rrset-cache-size: 4m - rrset-cache-slabs: 4 - rrset-roundrobin: yes - tls-cert-bundle: "/etc/ssl/certs/ca-certificates.crt" - trust-anchor-file: "/etc/unbound/root.key" - use-syslog: no - username: "nonrootuser" - val-log-level: 3 - verbosity: 2 - local-zone: "b" static - local-zone: "c" static - private-address: 9.9.9.9 - private-address: c - private-address: d -forward-zone: - forward-no-cache: no - forward-tls-upstream: yes - name: "." - forward-addr: 1.1.1.1@853#cloudflare-dns.com - forward-addr: 1.0.0.1@853#cloudflare-dns.com - forward-addr: 2606:4700:4700::1111@853#cloudflare-dns.com - forward-addr: 2606:4700:4700::1001@853#cloudflare-dns.com - forward-addr: 9.9.9.9@853#dns.quad9.net - forward-addr: 149.112.112.112@853#dns.quad9.net - forward-addr: 2620:fe::fe@853#dns.quad9.net - forward-addr: 2620:fe::9@853#dns.quad9.net` - assert.Equal(t, expected, "\n"+strings.Join(lines, "\n")) -} - -func Test_buildBlocked(t *testing.T) { - t.Parallel() - type blockParams struct { - blocked bool - content []byte - clientErr error - } - tests := map[string]struct { - malicious blockParams - ads blockParams - surveillance blockParams - allowedHostnames []string - privateAddresses []string - hostnamesLines []string - ipsLines []string - errsString []string - }{ - "none blocked": {}, - "all blocked without lists": { - malicious: blockParams{ - blocked: true, - }, - ads: blockParams{ - blocked: true, - }, - surveillance: blockParams{ - blocked: true, - }, - }, - "all blocked with lists": { - malicious: blockParams{ - blocked: true, - content: []byte("malicious"), - }, - ads: blockParams{ - blocked: true, - content: []byte("ads"), - }, - surveillance: blockParams{ - blocked: true, - content: []byte("surveillance"), - }, - hostnamesLines: []string{ - " local-zone: \"ads\" static", - " local-zone: \"malicious\" static", - " local-zone: \"surveillance\" static"}, - ipsLines: []string{ - " private-address: ads", - " private-address: malicious", - " private-address: surveillance"}, - }, - "all blocked with allowed hostnames": { - malicious: blockParams{ - blocked: true, - content: []byte("malicious"), - }, - ads: blockParams{ - blocked: true, - content: []byte("ads"), - }, - surveillance: blockParams{ - blocked: true, - content: []byte("surveillance"), - }, - allowedHostnames: []string{"ads"}, - hostnamesLines: []string{ - " local-zone: \"malicious\" static", - " local-zone: \"surveillance\" static"}, - ipsLines: []string{ - " private-address: ads", - " private-address: malicious", - " private-address: surveillance"}, - }, - "all blocked with private addresses": { - malicious: blockParams{ - blocked: true, - content: []byte("malicious"), - }, - ads: blockParams{ - blocked: true, - content: []byte("ads"), - }, - surveillance: blockParams{ - blocked: true, - content: []byte("surveillance"), - }, - privateAddresses: []string{"ads", "192.100.1.5"}, - hostnamesLines: []string{ - " local-zone: \"ads\" static", - " local-zone: \"malicious\" static", - " local-zone: \"surveillance\" static"}, - ipsLines: []string{ - " private-address: 192.100.1.5", - " private-address: ads", - " private-address: malicious", - " private-address: surveillance"}, - }, - "all blocked with lists and one error": { - malicious: blockParams{ - blocked: true, - content: []byte("malicious"), - }, - ads: blockParams{ - blocked: true, - content: []byte("ads"), - clientErr: fmt.Errorf("ads error"), - }, - surveillance: blockParams{ - blocked: true, - content: []byte("surveillance"), - }, - hostnamesLines: []string{ - " local-zone: \"malicious\" static", - " local-zone: \"surveillance\" static"}, - ipsLines: []string{ - " private-address: malicious", - " private-address: surveillance"}, - errsString: []string{"ads error", "ads error"}, - }, - "all blocked with errors": { - malicious: blockParams{ - blocked: true, - clientErr: fmt.Errorf("malicious"), - }, - ads: blockParams{ - blocked: true, - clientErr: fmt.Errorf("ads"), - }, - surveillance: blockParams{ - blocked: true, - clientErr: fmt.Errorf("surveillance"), - }, - errsString: []string{"malicious", "malicious", "ads", "ads", "surveillance", "surveillance"}, - }, - } - for name, tc := range tests { - tc := tc - t.Run(name, func(t *testing.T) { - t.Parallel() - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - client := mock_network.NewMockClient(mockCtrl) - if tc.malicious.blocked { - client.EXPECT().GetContent(string(constants.MaliciousBlockListHostnamesURL)). - Return(tc.malicious.content, 200, tc.malicious.clientErr).Times(1) - client.EXPECT().GetContent(string(constants.MaliciousBlockListIPsURL)). - Return(tc.malicious.content, 200, tc.malicious.clientErr).Times(1) - } - if tc.ads.blocked { - client.EXPECT().GetContent(string(constants.AdsBlockListHostnamesURL)). - Return(tc.ads.content, 200, tc.ads.clientErr).Times(1) - client.EXPECT().GetContent(string(constants.AdsBlockListIPsURL)). - Return(tc.ads.content, 200, tc.ads.clientErr).Times(1) - } - if tc.surveillance.blocked { - client.EXPECT().GetContent(string(constants.SurveillanceBlockListHostnamesURL)). - Return(tc.surveillance.content, 200, tc.surveillance.clientErr).Times(1) - client.EXPECT().GetContent(string(constants.SurveillanceBlockListIPsURL)). - Return(tc.surveillance.content, 200, tc.surveillance.clientErr).Times(1) - } - hostnamesLines, ipsLines, errs := buildBlocked(client, tc.malicious.blocked, tc.ads.blocked, tc.surveillance.blocked, - tc.allowedHostnames, tc.privateAddresses) - var errsString []string - for _, err := range errs { - errsString = append(errsString, err.Error()) - } - assert.ElementsMatch(t, tc.errsString, errsString) - assert.ElementsMatch(t, tc.hostnamesLines, hostnamesLines) - assert.ElementsMatch(t, tc.ipsLines, ipsLines) - }) - } -} - -func Test_getList(t *testing.T) { - t.Parallel() - tests := map[string]struct { - content []byte - status int - clientErr error - results []string - err error - }{ - "no result": {nil, 200, nil, nil, nil}, - "bad status": {nil, 500, nil, nil, fmt.Errorf("HTTP status code is 500 and not 200")}, - "network error": {nil, 200, fmt.Errorf("error"), nil, fmt.Errorf("error")}, - "results": {[]byte("a\nb\nc\n"), 200, nil, []string{"a", "b", "c"}, nil}, - } - for name, tc := range tests { - tc := tc - t.Run(name, func(t *testing.T) { - t.Parallel() - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - client := mock_network.NewMockClient(mockCtrl) - client.EXPECT().GetContent("irrelevant_url"). - Return(tc.content, tc.status, tc.clientErr).Times(1) - results, err := getList(client, "irrelevant_url") - if tc.err != nil { - require.Error(t, err) - assert.Equal(t, tc.err.Error(), err.Error()) - } else { - assert.NoError(t, err) - } - assert.Equal(t, tc.results, results) - }) - } -} - -func Test_buildBlockedHostnames(t *testing.T) { - t.Parallel() - type blockParams struct { - blocked bool - content []byte - clientErr error - } - tests := map[string]struct { - malicious blockParams - ads blockParams - surveillance blockParams - allowedHostnames []string - lines []string - errsString []string - }{ - "nothing blocked": { - lines: nil, - errsString: nil, - }, - "only malicious blocked": { - malicious: blockParams{ - blocked: true, - content: []byte("site_a\nsite_b"), - clientErr: nil, - }, - lines: []string{ - " local-zone: \"site_a\" static", - " local-zone: \"site_b\" static"}, - errsString: nil, - }, - "all blocked with some duplicates": { - malicious: blockParams{ - blocked: true, - content: []byte("site_a\nsite_b"), - }, - ads: blockParams{ - blocked: true, - content: []byte("site_a\nsite_c"), - }, - surveillance: blockParams{ - blocked: true, - content: []byte("site_c\nsite_a"), - }, - lines: []string{ - " local-zone: \"site_a\" static", - " local-zone: \"site_b\" static", - " local-zone: \"site_c\" static"}, - errsString: nil, - }, - "all blocked with one errored": { - malicious: blockParams{ - blocked: true, - content: []byte("site_a\nsite_b"), - }, - ads: blockParams{ - blocked: true, - content: []byte("site_a\nsite_c"), - }, - surveillance: blockParams{ - blocked: true, - clientErr: fmt.Errorf("surveillance error"), - }, - lines: []string{ - " local-zone: \"site_a\" static", - " local-zone: \"site_b\" static", - " local-zone: \"site_c\" static"}, - errsString: []string{"surveillance error"}, - }, - "blocked with allowed hostnames": { - malicious: blockParams{ - blocked: true, - content: []byte("site_a\nsite_b"), - }, - ads: blockParams{ - blocked: true, - content: []byte("site_c\nsite_d"), - }, - allowedHostnames: []string{"site_b", "site_c"}, - lines: []string{ - " local-zone: \"site_a\" static", - " local-zone: \"site_d\" static"}, - }, - } - for name, tc := range tests { - tc := tc - t.Run(name, func(t *testing.T) { - t.Parallel() - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - client := mock_network.NewMockClient(mockCtrl) - if tc.malicious.blocked { - client.EXPECT().GetContent(string(constants.MaliciousBlockListHostnamesURL)). - Return(tc.malicious.content, 200, tc.malicious.clientErr).Times(1) - } - if tc.ads.blocked { - client.EXPECT().GetContent(string(constants.AdsBlockListHostnamesURL)). - Return(tc.ads.content, 200, tc.ads.clientErr).Times(1) - } - if tc.surveillance.blocked { - client.EXPECT().GetContent(string(constants.SurveillanceBlockListHostnamesURL)). - Return(tc.surveillance.content, 200, tc.surveillance.clientErr).Times(1) - } - lines, errs := buildBlockedHostnames(client, - tc.malicious.blocked, tc.ads.blocked, tc.surveillance.blocked, tc.allowedHostnames) - var errsString []string - for _, err := range errs { - errsString = append(errsString, err.Error()) - } - assert.ElementsMatch(t, tc.errsString, errsString) - assert.ElementsMatch(t, tc.lines, lines) - }) - } -} - -func Test_buildBlockedIPs(t *testing.T) { - t.Parallel() - type blockParams struct { - blocked bool - content []byte - clientErr error - } - tests := map[string]struct { - malicious blockParams - ads blockParams - surveillance blockParams - privateAddresses []string - lines []string - errsString []string - }{ - "nothing blocked": { - lines: nil, - errsString: nil, - }, - "only malicious blocked": { - malicious: blockParams{ - blocked: true, - content: []byte("site_a\nsite_b"), - clientErr: nil, - }, - lines: []string{ - " private-address: site_a", - " private-address: site_b"}, - errsString: nil, - }, - "all blocked with some duplicates": { - malicious: blockParams{ - blocked: true, - content: []byte("site_a\nsite_b"), - }, - ads: blockParams{ - blocked: true, - content: []byte("site_a\nsite_c"), - }, - surveillance: blockParams{ - blocked: true, - content: []byte("site_c\nsite_a"), - }, - lines: []string{ - " private-address: site_a", - " private-address: site_b", - " private-address: site_c"}, - errsString: nil, - }, - "all blocked with one errored": { - malicious: blockParams{ - blocked: true, - content: []byte("site_a\nsite_b"), - }, - ads: blockParams{ - blocked: true, - content: []byte("site_a\nsite_c"), - }, - surveillance: blockParams{ - blocked: true, - clientErr: fmt.Errorf("surveillance error"), - }, - lines: []string{ - " private-address: site_a", - " private-address: site_b", - " private-address: site_c"}, - errsString: []string{"surveillance error"}, - }, - "blocked with private addresses": { - malicious: blockParams{ - blocked: true, - content: []byte("site_a\nsite_b"), - }, - ads: blockParams{ - blocked: true, - content: []byte("site_c"), - }, - privateAddresses: []string{"site_c", "site_d"}, - lines: []string{ - " private-address: site_a", - " private-address: site_b", - " private-address: site_c", - " private-address: site_d"}, - }, - } - for name, tc := range tests { - tc := tc - t.Run(name, func(t *testing.T) { - t.Parallel() - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - client := mock_network.NewMockClient(mockCtrl) - if tc.malicious.blocked { - client.EXPECT().GetContent(string(constants.MaliciousBlockListIPsURL)). - Return(tc.malicious.content, 200, tc.malicious.clientErr).Times(1) - } - if tc.ads.blocked { - client.EXPECT().GetContent(string(constants.AdsBlockListIPsURL)). - Return(tc.ads.content, 200, tc.ads.clientErr).Times(1) - } - if tc.surveillance.blocked { - client.EXPECT().GetContent(string(constants.SurveillanceBlockListIPsURL)). - Return(tc.surveillance.content, 200, tc.surveillance.clientErr).Times(1) - } - lines, errs := buildBlockedIPs(client, - tc.malicious.blocked, tc.ads.blocked, tc.surveillance.blocked, tc.privateAddresses) - var errsString []string - for _, err := range errs { - errsString = append(errsString, err.Error()) - } - assert.ElementsMatch(t, tc.errsString, errsString) - assert.ElementsMatch(t, tc.lines, lines) - }) - } -} +package dns + +import ( + "fmt" + "strings" + "testing" + + "github.com/golang/mock/gomock" + "github.com/qdm12/golibs/logging/mock_logging" + "github.com/qdm12/golibs/network/mock_network" + "github.com/qdm12/private-internet-access-docker/internal/constants" + "github.com/qdm12/private-internet-access-docker/internal/models" + "github.com/qdm12/private-internet-access-docker/internal/settings" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_generateUnboundConf(t *testing.T) { + t.Parallel() + settings := settings.DNS{ + Providers: []models.DNSProvider{constants.Cloudflare, constants.Quad9}, + AllowedHostnames: []string{"a"}, + PrivateAddresses: []string{"9.9.9.9"}, + BlockMalicious: true, + BlockSurveillance: false, + BlockAds: false, + VerbosityLevel: 2, + ValidationLogLevel: 3, + Caching: true, + IPv6: true, + } + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + client := mock_network.NewMockClient(mockCtrl) + client.EXPECT().GetContent(string(constants.MaliciousBlockListHostnamesURL)). + Return([]byte("b\na\nc"), 200, nil).Times(1) + client.EXPECT().GetContent(string(constants.MaliciousBlockListIPsURL)). + Return([]byte("c\nd\n"), 200, nil).Times(1) + logger := mock_logging.NewMockLogger(mockCtrl) + logger.EXPECT().Info("%d hostnames blocked overall", 2).Times(1) + logger.EXPECT().Info("%d IP addresses blocked overall", 3).Times(1) + lines, warnings := generateUnboundConf(settings, client, logger) + require.Len(t, warnings, 0) + expected := ` +server: + cache-max-ttl: 9000 + cache-min-ttl: 3600 + do-ip4: yes + do-ip6: yes + harden-algo-downgrade: yes + harden-below-nxdomain: yes + harden-referral-path: yes + hide-identity: yes + hide-version: yes + interface: 127.0.0.1 + key-cache-size: 16m + key-cache-slabs: 4 + msg-cache-size: 4m + msg-cache-slabs: 4 + num-threads: 1 + port: 53 + prefetch-key: yes + prefetch: yes + root-hints: "/etc/unbound/root.hints" + rrset-cache-size: 4m + rrset-cache-slabs: 4 + rrset-roundrobin: yes + tls-cert-bundle: "/etc/ssl/certs/ca-certificates.crt" + trust-anchor-file: "/etc/unbound/root.key" + use-syslog: no + username: "nonrootuser" + val-log-level: 3 + verbosity: 2 + local-zone: "b" static + local-zone: "c" static + private-address: 9.9.9.9 + private-address: c + private-address: d +forward-zone: + forward-no-cache: no + forward-tls-upstream: yes + name: "." + forward-addr: 1.1.1.1@853#cloudflare-dns.com + forward-addr: 1.0.0.1@853#cloudflare-dns.com + forward-addr: 2606:4700:4700::1111@853#cloudflare-dns.com + forward-addr: 2606:4700:4700::1001@853#cloudflare-dns.com + forward-addr: 9.9.9.9@853#dns.quad9.net + forward-addr: 149.112.112.112@853#dns.quad9.net + forward-addr: 2620:fe::fe@853#dns.quad9.net + forward-addr: 2620:fe::9@853#dns.quad9.net` + assert.Equal(t, expected, "\n"+strings.Join(lines, "\n")) +} + +func Test_buildBlocked(t *testing.T) { + t.Parallel() + type blockParams struct { + blocked bool + content []byte + clientErr error + } + tests := map[string]struct { + malicious blockParams + ads blockParams + surveillance blockParams + allowedHostnames []string + privateAddresses []string + hostnamesLines []string + ipsLines []string + errsString []string + }{ + "none blocked": {}, + "all blocked without lists": { + malicious: blockParams{ + blocked: true, + }, + ads: blockParams{ + blocked: true, + }, + surveillance: blockParams{ + blocked: true, + }, + }, + "all blocked with lists": { + malicious: blockParams{ + blocked: true, + content: []byte("malicious"), + }, + ads: blockParams{ + blocked: true, + content: []byte("ads"), + }, + surveillance: blockParams{ + blocked: true, + content: []byte("surveillance"), + }, + hostnamesLines: []string{ + " local-zone: \"ads\" static", + " local-zone: \"malicious\" static", + " local-zone: \"surveillance\" static"}, + ipsLines: []string{ + " private-address: ads", + " private-address: malicious", + " private-address: surveillance"}, + }, + "all blocked with allowed hostnames": { + malicious: blockParams{ + blocked: true, + content: []byte("malicious"), + }, + ads: blockParams{ + blocked: true, + content: []byte("ads"), + }, + surveillance: blockParams{ + blocked: true, + content: []byte("surveillance"), + }, + allowedHostnames: []string{"ads"}, + hostnamesLines: []string{ + " local-zone: \"malicious\" static", + " local-zone: \"surveillance\" static"}, + ipsLines: []string{ + " private-address: ads", + " private-address: malicious", + " private-address: surveillance"}, + }, + "all blocked with private addresses": { + malicious: blockParams{ + blocked: true, + content: []byte("malicious"), + }, + ads: blockParams{ + blocked: true, + content: []byte("ads"), + }, + surveillance: blockParams{ + blocked: true, + content: []byte("surveillance"), + }, + privateAddresses: []string{"ads", "192.100.1.5"}, + hostnamesLines: []string{ + " local-zone: \"ads\" static", + " local-zone: \"malicious\" static", + " local-zone: \"surveillance\" static"}, + ipsLines: []string{ + " private-address: 192.100.1.5", + " private-address: ads", + " private-address: malicious", + " private-address: surveillance"}, + }, + "all blocked with lists and one error": { + malicious: blockParams{ + blocked: true, + content: []byte("malicious"), + }, + ads: blockParams{ + blocked: true, + content: []byte("ads"), + clientErr: fmt.Errorf("ads error"), + }, + surveillance: blockParams{ + blocked: true, + content: []byte("surveillance"), + }, + hostnamesLines: []string{ + " local-zone: \"malicious\" static", + " local-zone: \"surveillance\" static"}, + ipsLines: []string{ + " private-address: malicious", + " private-address: surveillance"}, + errsString: []string{"ads error", "ads error"}, + }, + "all blocked with errors": { + malicious: blockParams{ + blocked: true, + clientErr: fmt.Errorf("malicious"), + }, + ads: blockParams{ + blocked: true, + clientErr: fmt.Errorf("ads"), + }, + surveillance: blockParams{ + blocked: true, + clientErr: fmt.Errorf("surveillance"), + }, + errsString: []string{"malicious", "malicious", "ads", "ads", "surveillance", "surveillance"}, + }, + } + for name, tc := range tests { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + client := mock_network.NewMockClient(mockCtrl) + if tc.malicious.blocked { + client.EXPECT().GetContent(string(constants.MaliciousBlockListHostnamesURL)). + Return(tc.malicious.content, 200, tc.malicious.clientErr).Times(1) + client.EXPECT().GetContent(string(constants.MaliciousBlockListIPsURL)). + Return(tc.malicious.content, 200, tc.malicious.clientErr).Times(1) + } + if tc.ads.blocked { + client.EXPECT().GetContent(string(constants.AdsBlockListHostnamesURL)). + Return(tc.ads.content, 200, tc.ads.clientErr).Times(1) + client.EXPECT().GetContent(string(constants.AdsBlockListIPsURL)). + Return(tc.ads.content, 200, tc.ads.clientErr).Times(1) + } + if tc.surveillance.blocked { + client.EXPECT().GetContent(string(constants.SurveillanceBlockListHostnamesURL)). + Return(tc.surveillance.content, 200, tc.surveillance.clientErr).Times(1) + client.EXPECT().GetContent(string(constants.SurveillanceBlockListIPsURL)). + Return(tc.surveillance.content, 200, tc.surveillance.clientErr).Times(1) + } + hostnamesLines, ipsLines, errs := buildBlocked(client, tc.malicious.blocked, tc.ads.blocked, tc.surveillance.blocked, + tc.allowedHostnames, tc.privateAddresses) + var errsString []string + for _, err := range errs { + errsString = append(errsString, err.Error()) + } + assert.ElementsMatch(t, tc.errsString, errsString) + assert.ElementsMatch(t, tc.hostnamesLines, hostnamesLines) + assert.ElementsMatch(t, tc.ipsLines, ipsLines) + }) + } +} + +func Test_getList(t *testing.T) { + t.Parallel() + tests := map[string]struct { + content []byte + status int + clientErr error + results []string + err error + }{ + "no result": {nil, 200, nil, nil, nil}, + "bad status": {nil, 500, nil, nil, fmt.Errorf("HTTP status code is 500 and not 200")}, + "network error": {nil, 200, fmt.Errorf("error"), nil, fmt.Errorf("error")}, + "results": {[]byte("a\nb\nc\n"), 200, nil, []string{"a", "b", "c"}, nil}, + } + for name, tc := range tests { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + client := mock_network.NewMockClient(mockCtrl) + client.EXPECT().GetContent("irrelevant_url"). + Return(tc.content, tc.status, tc.clientErr).Times(1) + results, err := getList(client, "irrelevant_url") + if tc.err != nil { + require.Error(t, err) + assert.Equal(t, tc.err.Error(), err.Error()) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tc.results, results) + }) + } +} + +func Test_buildBlockedHostnames(t *testing.T) { + t.Parallel() + type blockParams struct { + blocked bool + content []byte + clientErr error + } + tests := map[string]struct { + malicious blockParams + ads blockParams + surveillance blockParams + allowedHostnames []string + lines []string + errsString []string + }{ + "nothing blocked": { + lines: nil, + errsString: nil, + }, + "only malicious blocked": { + malicious: blockParams{ + blocked: true, + content: []byte("site_a\nsite_b"), + clientErr: nil, + }, + lines: []string{ + " local-zone: \"site_a\" static", + " local-zone: \"site_b\" static"}, + errsString: nil, + }, + "all blocked with some duplicates": { + malicious: blockParams{ + blocked: true, + content: []byte("site_a\nsite_b"), + }, + ads: blockParams{ + blocked: true, + content: []byte("site_a\nsite_c"), + }, + surveillance: blockParams{ + blocked: true, + content: []byte("site_c\nsite_a"), + }, + lines: []string{ + " local-zone: \"site_a\" static", + " local-zone: \"site_b\" static", + " local-zone: \"site_c\" static"}, + errsString: nil, + }, + "all blocked with one errored": { + malicious: blockParams{ + blocked: true, + content: []byte("site_a\nsite_b"), + }, + ads: blockParams{ + blocked: true, + content: []byte("site_a\nsite_c"), + }, + surveillance: blockParams{ + blocked: true, + clientErr: fmt.Errorf("surveillance error"), + }, + lines: []string{ + " local-zone: \"site_a\" static", + " local-zone: \"site_b\" static", + " local-zone: \"site_c\" static"}, + errsString: []string{"surveillance error"}, + }, + "blocked with allowed hostnames": { + malicious: blockParams{ + blocked: true, + content: []byte("site_a\nsite_b"), + }, + ads: blockParams{ + blocked: true, + content: []byte("site_c\nsite_d"), + }, + allowedHostnames: []string{"site_b", "site_c"}, + lines: []string{ + " local-zone: \"site_a\" static", + " local-zone: \"site_d\" static"}, + }, + } + for name, tc := range tests { //nolint:dupl + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + client := mock_network.NewMockClient(mockCtrl) + if tc.malicious.blocked { + client.EXPECT().GetContent(string(constants.MaliciousBlockListHostnamesURL)). + Return(tc.malicious.content, 200, tc.malicious.clientErr).Times(1) + } + if tc.ads.blocked { + client.EXPECT().GetContent(string(constants.AdsBlockListHostnamesURL)). + Return(tc.ads.content, 200, tc.ads.clientErr).Times(1) + } + if tc.surveillance.blocked { + client.EXPECT().GetContent(string(constants.SurveillanceBlockListHostnamesURL)). + Return(tc.surveillance.content, 200, tc.surveillance.clientErr).Times(1) + } + lines, errs := buildBlockedHostnames(client, + tc.malicious.blocked, tc.ads.blocked, tc.surveillance.blocked, tc.allowedHostnames) + var errsString []string + for _, err := range errs { + errsString = append(errsString, err.Error()) + } + assert.ElementsMatch(t, tc.errsString, errsString) + assert.ElementsMatch(t, tc.lines, lines) + }) + } +} + +func Test_buildBlockedIPs(t *testing.T) { + t.Parallel() + type blockParams struct { + blocked bool + content []byte + clientErr error + } + tests := map[string]struct { + malicious blockParams + ads blockParams + surveillance blockParams + privateAddresses []string + lines []string + errsString []string + }{ + "nothing blocked": { + lines: nil, + errsString: nil, + }, + "only malicious blocked": { + malicious: blockParams{ + blocked: true, + content: []byte("site_a\nsite_b"), + clientErr: nil, + }, + lines: []string{ + " private-address: site_a", + " private-address: site_b"}, + errsString: nil, + }, + "all blocked with some duplicates": { + malicious: blockParams{ + blocked: true, + content: []byte("site_a\nsite_b"), + }, + ads: blockParams{ + blocked: true, + content: []byte("site_a\nsite_c"), + }, + surveillance: blockParams{ + blocked: true, + content: []byte("site_c\nsite_a"), + }, + lines: []string{ + " private-address: site_a", + " private-address: site_b", + " private-address: site_c"}, + errsString: nil, + }, + "all blocked with one errored": { + malicious: blockParams{ + blocked: true, + content: []byte("site_a\nsite_b"), + }, + ads: blockParams{ + blocked: true, + content: []byte("site_a\nsite_c"), + }, + surveillance: blockParams{ + blocked: true, + clientErr: fmt.Errorf("surveillance error"), + }, + lines: []string{ + " private-address: site_a", + " private-address: site_b", + " private-address: site_c"}, + errsString: []string{"surveillance error"}, + }, + "blocked with private addresses": { + malicious: blockParams{ + blocked: true, + content: []byte("site_a\nsite_b"), + }, + ads: blockParams{ + blocked: true, + content: []byte("site_c"), + }, + privateAddresses: []string{"site_c", "site_d"}, + lines: []string{ + " private-address: site_a", + " private-address: site_b", + " private-address: site_c", + " private-address: site_d"}, + }, + } + for name, tc := range tests { //nolint:dupl + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + client := mock_network.NewMockClient(mockCtrl) + if tc.malicious.blocked { + client.EXPECT().GetContent(string(constants.MaliciousBlockListIPsURL)). + Return(tc.malicious.content, 200, tc.malicious.clientErr).Times(1) + } + if tc.ads.blocked { + client.EXPECT().GetContent(string(constants.AdsBlockListIPsURL)). + Return(tc.ads.content, 200, tc.ads.clientErr).Times(1) + } + if tc.surveillance.blocked { + client.EXPECT().GetContent(string(constants.SurveillanceBlockListIPsURL)). + Return(tc.surveillance.content, 200, tc.surveillance.clientErr).Times(1) + } + lines, errs := buildBlockedIPs(client, + tc.malicious.blocked, tc.ads.blocked, tc.surveillance.blocked, tc.privateAddresses) + var errsString []string + for _, err := range errs { + errsString = append(errsString, err.Error()) + } + assert.ElementsMatch(t, tc.errsString, errsString) + assert.ElementsMatch(t, tc.lines, lines) + }) + } +} diff --git a/internal/dns/nameserver.go b/internal/dns/nameserver.go index 139d15e8..79b32495 100644 --- a/internal/dns/nameserver.go +++ b/internal/dns/nameserver.go @@ -9,20 +9,20 @@ import ( ) // UseDNSInternally is to change the Go program DNS only -func (c *configurator) UseDNSInternally(IP net.IP) { - c.logger.Info("using DNS address %s internally", IP.String()) +func (c *configurator) UseDNSInternally(ip net.IP) { + c.logger.Info("using DNS address %s internally", ip.String()) net.DefaultResolver = &net.Resolver{ PreferGo: true, Dial: func(ctx context.Context, network, address string) (net.Conn, error) { d := net.Dialer{} - return d.DialContext(ctx, "udp", net.JoinHostPort(IP.String(), "53")) + return d.DialContext(ctx, "udp", net.JoinHostPort(ip.String(), "53")) }, } } // UseDNSSystemWide changes the nameserver to use for DNS system wide -func (c *configurator) UseDNSSystemWide(IP net.IP) error { - c.logger.Info("using DNS address %s system wide", IP.String()) +func (c *configurator) UseDNSSystemWide(ip net.IP) error { + c.logger.Info("using DNS address %s system wide", ip.String()) data, err := c.fileManager.ReadFile(string(constants.ResolvConf)) if err != nil { return err @@ -35,12 +35,12 @@ func (c *configurator) UseDNSSystemWide(IP net.IP) error { found := false for i := range lines { if strings.HasPrefix(lines[i], "nameserver ") { - lines[i] = "nameserver " + IP.String() + lines[i] = "nameserver " + ip.String() found = true } } if !found { - lines = append(lines, "nameserver "+IP.String()) + lines = append(lines, "nameserver "+ip.String()) } data = []byte(strings.Join(lines, "\n")) return c.fileManager.WriteToFile(string(constants.ResolvConf), data) diff --git a/internal/dns/roots.go b/internal/dns/roots.go index 12bd6982..c8e91147 100644 --- a/internal/dns/roots.go +++ b/internal/dns/roots.go @@ -2,6 +2,7 @@ package dns import ( "fmt" + "net/http" "github.com/qdm12/golibs/files" "github.com/qdm12/private-internet-access-docker/internal/constants" @@ -12,7 +13,7 @@ func (c *configurator) DownloadRootHints(uid, gid int) error { content, status, err := c.client.GetContent(string(constants.NamedRootURL)) if err != nil { return err - } else if status != 200 { + } else if status != http.StatusOK { return fmt.Errorf("HTTP status code is %d for %s", status, constants.NamedRootURL) } return c.fileManager.WriteToFile( @@ -27,7 +28,7 @@ func (c *configurator) DownloadRootKey(uid, gid int) error { content, status, err := c.client.GetContent(string(constants.RootKeyURL)) if err != nil { return err - } else if status != 200 { + } else if status != http.StatusOK { return fmt.Errorf("HTTP status code is %d for %s", status, constants.RootKeyURL) } return c.fileManager.WriteToFile( diff --git a/internal/dns/roots_test.go b/internal/dns/roots_test.go index ab8f0aaf..c31cbb93 100644 --- a/internal/dns/roots_test.go +++ b/internal/dns/roots_test.go @@ -16,7 +16,7 @@ import ( "github.com/qdm12/private-internet-access-docker/internal/constants" ) -func Test_DownloadRootHints(t *testing.T) { +func Test_DownloadRootHints(t *testing.T) { //nolint:dupl t.Parallel() tests := map[string]struct { content []byte @@ -78,7 +78,7 @@ func Test_DownloadRootHints(t *testing.T) { } } -func Test_DownloadRootKey(t *testing.T) { +func Test_DownloadRootKey(t *testing.T) { //nolint:dupl t.Parallel() tests := map[string]struct { content []byte diff --git a/internal/dns/wait.go b/internal/dns/wait.go index c7793c4b..e6d295ec 100644 --- a/internal/dns/wait.go +++ b/internal/dns/wait.go @@ -14,7 +14,7 @@ func (c *configurator) WaitForUnbound() (err error) { return nil } c.logger.Warn("could not resolve %s (try %d of %d): %s", hostToResolve, try, maxTries, err) - time.Sleep(time.Duration(maxTries * 50 * time.Millisecond)) + time.Sleep(maxTries * 50 * time.Millisecond) } return fmt.Errorf("Unbound does not seem to be working after %d tries", maxTries) } diff --git a/internal/env/env_test.go b/internal/env/env_test.go index 4e3898be..e4993988 100644 --- a/internal/env/env_test.go +++ b/internal/env/env_test.go @@ -68,7 +68,6 @@ func Test_PrintVersion(t *testing.T) { logger.EXPECT().Error(tc.commandErr).Do(func(err error) { logged = err.Error() }).Times(1) - } else { logger.EXPECT().Info("%s version: %s", tc.program, tc.commandVersion). Do(func(format, program, version string) { diff --git a/internal/healthcheck/healthcheck.go b/internal/healthcheck/healthcheck.go index 1f0b6029..e28b290b 100644 --- a/internal/healthcheck/healthcheck.go +++ b/internal/healthcheck/healthcheck.go @@ -10,8 +10,8 @@ import ( func HealthCheck() error { // DNS, HTTP and HTTPs check on github.com - connectivty := connectivity.NewConnectivity(3 * time.Second) - errs := connectivty.Checks("github.com") + connectivity := connectivity.NewConnectivity(3 * time.Second) + errs := connectivity.Checks("github.com") if len(errs) > 0 { var errsStr []string for _, err := range errs { diff --git a/internal/models/mullvad.go b/internal/models/mullvad.go index a193aa4c..b9126961 100644 --- a/internal/models/mullvad.go +++ b/internal/models/mullvad.go @@ -3,10 +3,10 @@ package models import "net" type MullvadServer struct { + IPs []net.IP Country MullvadCountry City MullvadCity Provider MullvadProvider Owned bool - IPs []net.IP DefaultPort uint16 } diff --git a/internal/openvpn/tun.go b/internal/openvpn/tun.go index 3f4d0676..cd6ddce4 100644 --- a/internal/openvpn/tun.go +++ b/internal/openvpn/tun.go @@ -30,7 +30,7 @@ func (c *configurator) CreateTUN() error { if err := c.mkNod(string(constants.TunnelDevice), unix.S_IFCHR, int(dev)); err != nil { return err } - if err := c.fileManager.SetUserPermissions(string(constants.TunnelDevice), 666); err != nil { + if err := c.fileManager.SetUserPermissions(string(constants.TunnelDevice), 0666); err != nil { return err } return nil diff --git a/internal/params/dns.go b/internal/params/dns.go index 4fd74d2b..dd48f0d0 100644 --- a/internal/params/dns.go +++ b/internal/params/dns.go @@ -1,124 +1,122 @@ -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_SURVEILLANCE -// and BLOCK_NSA for retrocompatibility -func (p *paramsReader) GetDNSSurveillanceBlocking() (blocking bool, err error) { - // Retro-compatibility - s, err := p.envParams.GetEnv("BLOCK_NSA") - if err != nil { - return false, err - } else if len(s) != 0 { - p.logger.Warn("You are using the old environment variable BLOCK_NSA, please consider changing it to BLOCK_SURVEILLANCE") - return p.envParams.GetOnOff("BLOCK_NSA", libparams.Compulsory()) - } - return p.envParams.GetOnOff("BLOCK_SURVEILLANCE", 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 -} - -// GetDNSOverTLSCaching obtains if Unbound caching should be enable or not -// from the environment variable DOT_CACHING -func (p *paramsReader) GetDNSOverTLSCaching() (caching bool, err error) { - return p.envParams.GetOnOff("DOT_CACHING") -} - -// GetDNSOverTLSPrivateAddresses obtains if Unbound caching should be enable or not -// from the environment variable DOT_PRIVATE_ADDRESS -func (p *paramsReader) GetDNSOverTLSPrivateAddresses() (privateAddresses []string) { - s, _ := p.envParams.GetEnv("DOT_PRIVATE_ADDRESS") - for _, s := range strings.Split(s, ",") { - privateAddresses = append(privateAddresses, s) - } - return privateAddresses -} - -// GetDNSOverTLSIPv6 obtains if Unbound should resolve ipv6 addresses using ipv6 DNS over TLS -// servers from the environment variable DOT_IPV6 -func (p *paramsReader) GetDNSOverTLSIPv6() (ipv6 bool, err error) { - return p.envParams.GetOnOff("DOT_IPV6") -} +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 *reader) GetDNSOverTLS() (DNSOverTLS bool, err error) { //nolint:gocritic + 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 *reader) 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 *reader) 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 *reader) 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 *reader) 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 *reader) 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_SURVEILLANCE +// and BLOCK_NSA for retrocompatibility +func (p *reader) GetDNSSurveillanceBlocking() (blocking bool, err error) { + // Retro-compatibility + s, err := p.envParams.GetEnv("BLOCK_NSA") + if err != nil { + return false, err + } else if len(s) != 0 { + p.logger.Warn("You are using the old environment variable BLOCK_NSA, please consider changing it to BLOCK_SURVEILLANCE") + return p.envParams.GetOnOff("BLOCK_NSA", libparams.Compulsory()) + } + return p.envParams.GetOnOff("BLOCK_SURVEILLANCE", 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 *reader) 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 *reader) 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 +} + +// GetDNSOverTLSCaching obtains if Unbound caching should be enable or not +// from the environment variable DOT_CACHING +func (p *reader) GetDNSOverTLSCaching() (caching bool, err error) { + return p.envParams.GetOnOff("DOT_CACHING") +} + +// GetDNSOverTLSPrivateAddresses obtains if Unbound caching should be enable or not +// from the environment variable DOT_PRIVATE_ADDRESS +func (p *reader) GetDNSOverTLSPrivateAddresses() (privateAddresses []string) { + s, _ := p.envParams.GetEnv("DOT_PRIVATE_ADDRESS") + privateAddresses = append(privateAddresses, strings.Split(s, ",")...) + return privateAddresses +} + +// GetDNSOverTLSIPv6 obtains if Unbound should resolve ipv6 addresses using ipv6 DNS over TLS +// servers from the environment variable DOT_IPV6 +func (p *reader) GetDNSOverTLSIPv6() (ipv6 bool, err error) { + return p.envParams.GetOnOff("DOT_IPV6") +} diff --git a/internal/params/firewall.go b/internal/params/firewall.go index 2618ea89..aa260a00 100644 --- a/internal/params/firewall.go +++ b/internal/params/firewall.go @@ -1,29 +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 -} +package params + +import ( + "fmt" + "net" + "strings" +) + +// GetExtraSubnets obtains the CIDR subnets from the comma separated list of the +// environment variable EXTRA_SUBNETS +func (p *reader) 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 +} diff --git a/internal/params/mullvad.go b/internal/params/mullvad.go index 8ccff5d4..f2e18396 100644 --- a/internal/params/mullvad.go +++ b/internal/params/mullvad.go @@ -10,7 +10,7 @@ import ( // GetMullvadCountry obtains the country for the Mullvad server from the // environment variable COUNTRY -func (p *paramsReader) GetMullvadCountry() (country models.MullvadCountry, err error) { +func (p *reader) GetMullvadCountry() (country models.MullvadCountry, err error) { choices := append(constants.MullvadCountryChoices(), "") s, err := p.envParams.GetValueIfInside("COUNTRY", choices) return models.MullvadCountry(strings.ToLower(s)), err @@ -18,7 +18,7 @@ func (p *paramsReader) GetMullvadCountry() (country models.MullvadCountry, err e // GetMullvadCity obtains the city for the Mullvad server from the // environment variable CITY -func (p *paramsReader) GetMullvadCity() (country models.MullvadCity, err error) { +func (p *reader) GetMullvadCity() (country models.MullvadCity, err error) { choices := append(constants.MullvadCityChoices(), "") s, err := p.envParams.GetValueIfInside("CITY", choices) return models.MullvadCity(strings.ToLower(s)), err @@ -26,7 +26,7 @@ func (p *paramsReader) GetMullvadCity() (country models.MullvadCity, err error) // GetMullvadISP obtains the ISP for the Mullvad server from the // environment variable ISP -func (p *paramsReader) GetMullvadISP() (country models.MullvadProvider, err error) { +func (p *reader) GetMullvadISP() (country models.MullvadProvider, err error) { choices := append(constants.MullvadProviderChoices(), "") s, err := p.envParams.GetValueIfInside("ISP", choices) return models.MullvadProvider(strings.ToLower(s)), err @@ -34,7 +34,7 @@ func (p *paramsReader) GetMullvadISP() (country models.MullvadProvider, err erro // GetMullvadPort obtains the port to reach the Mullvad server on from the // environment variable PORT -func (p *paramsReader) GetMullvadPort() (port uint16, err error) { +func (p *reader) GetMullvadPort() (port uint16, err error) { n, err := p.envParams.GetEnvIntRange("PORT", 0, 65535, libparams.Default("0")) return uint16(n), err } diff --git a/internal/params/openvpn.go b/internal/params/openvpn.go index b72b11c1..cd872337 100644 --- a/internal/params/openvpn.go +++ b/internal/params/openvpn.go @@ -1,80 +1,82 @@ -package params - -import ( - "fmt" - "net" - "strings" - - libparams "github.com/qdm12/golibs/params" - "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 - } - }() - return p.envParams.GetEnv("USER", libparams.CaseSensitiveValue(), libparams.Compulsory()) -} - -// 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 - } - }() - return p.envParams.GetEnv("PASSWORD", libparams.CaseSensitiveValue(), libparams.Compulsory()) -} - -// 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 -} - -// GetOpenVPNVerbosity obtains the verbosity level for verbosity between 0 and 6 -// from the environment variable OPENVPN_VERBOSITY -func (p *paramsReader) GetOpenVPNVerbosity() (verbosity int, err error) { - return p.envParams.GetEnvIntRange("OPENVPN_VERBOSITY", 0, 6, libparams.Default("1")) -} - -// GetOpenVPNRoot obtains if openvpn should be run as root -// from the environment variable OPENVPN_ROOT -func (p *paramsReader) GetOpenVPNRoot() (root bool, err error) { - return p.envParams.GetYesNo("OPENVPN_ROOT", libparams.Default("no")) -} - -// GetTargetIP obtains the IP address to choose from the list of IP addresses -// available for a particular region, from the environment variable -// OPENVPN_TARGET_IP -func (p *paramsReader) GetTargetIP() (ip net.IP, err error) { - s, err := p.envParams.GetEnv("OPENVPN_TARGET_IP") - if len(s) == 0 { - return nil, nil - } - ip = net.ParseIP(s) - if ip == nil { - return nil, fmt.Errorf("target IP address %q is not valid", s) - } - return ip, nil -} - -// GetOpenVPNCipher obtains a custom cipher to use with OpenVPN -// from the environment variable OPENVPN_CIPHER -func (p *paramsReader) GetOpenVPNCipher() (cipher string, err error) { - cipher, err = p.envParams.GetEnv("OPENVPN_CIPHER") - return strings.ToLower(cipher), err -} - -// GetOpenVPNAuth obtains a custom auth algorithm to use with OpenVPN -// from the environment variable OPENVPN_AUTH -func (p *paramsReader) GetOpenVPNAuth() (auth string, err error) { - auth, err = p.envParams.GetEnv("OPENVPN_AUTH") - return strings.ToLower(auth), err -} +package params + +import ( + "fmt" + "net" + "strings" + + libparams "github.com/qdm12/golibs/params" + "github.com/qdm12/private-internet-access-docker/internal/models" +) + +// GetUser obtains the user to use to connect to the VPN servers +func (p *reader) GetUser() (s string, err error) { + defer func() { + unsetenvErr := p.unsetEnv("USER") + if err == nil { + err = unsetenvErr + } + }() + return p.envParams.GetEnv("USER", libparams.CaseSensitiveValue(), libparams.Compulsory()) +} + +// GetPassword obtains the password to use to connect to the VPN servers +func (p *reader) GetPassword() (s string, err error) { + defer func() { + unsetenvErr := p.unsetEnv("PASSWORD") + if err == nil { + err = unsetenvErr + } + }() + return p.envParams.GetEnv("PASSWORD", libparams.CaseSensitiveValue(), libparams.Compulsory()) +} + +// GetNetworkProtocol obtains the network protocol to use to connect to the +// VPN servers from the environment variable PROTOCOL +func (p *reader) GetNetworkProtocol() (protocol models.NetworkProtocol, err error) { + s, err := p.envParams.GetValueIfInside("PROTOCOL", []string{"tcp", "udp"}, libparams.Default("udp")) + return models.NetworkProtocol(s), err +} + +// GetOpenVPNVerbosity obtains the verbosity level for verbosity between 0 and 6 +// from the environment variable OPENVPN_VERBOSITY +func (p *reader) GetOpenVPNVerbosity() (verbosity int, err error) { + return p.envParams.GetEnvIntRange("OPENVPN_VERBOSITY", 0, 6, libparams.Default("1")) +} + +// GetOpenVPNRoot obtains if openvpn should be run as root +// from the environment variable OPENVPN_ROOT +func (p *reader) GetOpenVPNRoot() (root bool, err error) { + return p.envParams.GetYesNo("OPENVPN_ROOT", libparams.Default("no")) +} + +// GetTargetIP obtains the IP address to choose from the list of IP addresses +// available for a particular region, from the environment variable +// OPENVPN_TARGET_IP +func (p *reader) GetTargetIP() (ip net.IP, err error) { + s, err := p.envParams.GetEnv("OPENVPN_TARGET_IP") + if len(s) == 0 { + return nil, nil + } else if err != nil { + return nil, err + } + ip = net.ParseIP(s) + if ip == nil { + return nil, fmt.Errorf("target IP address %q is not valid", s) + } + return ip, nil +} + +// GetOpenVPNCipher obtains a custom cipher to use with OpenVPN +// from the environment variable OPENVPN_CIPHER +func (p *reader) GetOpenVPNCipher() (cipher string, err error) { + cipher, err = p.envParams.GetEnv("OPENVPN_CIPHER") + return strings.ToLower(cipher), err +} + +// GetOpenVPNAuth obtains a custom auth algorithm to use with OpenVPN +// from the environment variable OPENVPN_AUTH +func (p *reader) GetOpenVPNAuth() (auth string, err error) { + auth, err = p.envParams.GetEnv("OPENVPN_AUTH") + return strings.ToLower(auth), err +} diff --git a/internal/params/params.go b/internal/params/params.go index d2f0150b..a20ac5b8 100644 --- a/internal/params/params.go +++ b/internal/params/params.go @@ -1,108 +1,111 @@ -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 { - GetVPNSP() (vpnServiceProvider string, err error) - - // DNS over TLS getters - GetDNSOverTLS() (DNSOverTLS bool, err error) - GetDNSOverTLSProviders() (providers []models.DNSProvider, err error) - GetDNSOverTLSCaching() (caching bool, 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) - GetDNSOverTLSPrivateAddresses() (privateAddresses []string) - GetDNSOverTLSIPv6() (ipv6 bool, err error) - - // System - GetUID() (uid int, err error) - GetGID() (gid int, err error) - GetTimezone() (timezone string, err error) - GetIPStatusFilepath() (filepath models.Filepath, err error) - - // Firewall getters - GetExtraSubnets() (extraSubnets []net.IPNet, err error) - - // VPN getters - GetUser() (s string, err error) - GetPassword() (s string, err error) - GetNetworkProtocol() (protocol models.NetworkProtocol, err error) - GetOpenVPNVerbosity() (verbosity int, err error) - GetOpenVPNRoot() (root bool, err error) - GetTargetIP() (ip net.IP, err error) - GetOpenVPNCipher() (cipher string, err error) - GetOpenVPNAuth() (auth string, err error) - - // PIA getters - GetPortForwarding() (activated bool, err error) - GetPortForwardingStatusFilepath() (filepath models.Filepath, err error) - GetPIAEncryption() (models.PIAEncryption, error) - GetPIARegion() (models.PIARegion, error) - - // Mullvad getters - GetMullvadCountry() (country models.MullvadCountry, err error) - GetMullvadCity() (country models.MullvadCity, err error) - GetMullvadISP() (country models.MullvadProvider, err error) - GetMullvadPort() (port uint16, err error) - - // Windscribe getters - GetWindscribeRegion() (country models.WindscribeRegion, err error) - GetWindscribePort(protocol models.NetworkProtocol) (port uint16, err error) - - // Shadowsocks getters - GetShadowSocks() (activated bool, err error) - GetShadowSocksLog() (activated bool, err error) - GetShadowSocksPort() (port uint16, err error) - GetShadowSocksPassword() (password string, err error) - GetShadowSocksMethod() (method 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 -} - -// NewParamsReader returns a paramsReadeer object to read parameters from -// environment variables -func NewParamsReader(logger logging.Logger) ParamsReader { - return ¶msReader{ - envParams: libparams.NewEnvParams(), - logger: logger, - verifier: verification.NewVerifier(), - unsetEnv: os.Unsetenv, - } -} - -// GetVPNSP obtains the VPN service provider to use from the environment variable VPNSP -func (p *paramsReader) GetVPNSP() (vpnServiceProvider string, err error) { - s, err := p.envParams.GetValueIfInside("VPNSP", []string{"pia", "mullvad", "windscribe"}) - return s, err -} +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" +) + +// Reader contains methods to obtain parameters +type Reader interface { + GetVPNSP() (vpnServiceProvider models.VPNProvider, err error) + + // DNS over TLS getters + GetDNSOverTLS() (DNSOverTLS bool, err error) + GetDNSOverTLSProviders() (providers []models.DNSProvider, err error) + GetDNSOverTLSCaching() (caching bool, 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) + GetDNSOverTLSPrivateAddresses() (privateAddresses []string) + GetDNSOverTLSIPv6() (ipv6 bool, err error) + + // System + GetUID() (uid int, err error) + GetGID() (gid int, err error) + GetTimezone() (timezone string, err error) + GetIPStatusFilepath() (filepath models.Filepath, err error) + + // Firewall getters + GetExtraSubnets() (extraSubnets []net.IPNet, err error) + + // VPN getters + GetUser() (s string, err error) + GetPassword() (s string, err error) + GetNetworkProtocol() (protocol models.NetworkProtocol, err error) + GetOpenVPNVerbosity() (verbosity int, err error) + GetOpenVPNRoot() (root bool, err error) + GetTargetIP() (ip net.IP, err error) + GetOpenVPNCipher() (cipher string, err error) + GetOpenVPNAuth() (auth string, err error) + + // PIA getters + GetPortForwarding() (activated bool, err error) + GetPortForwardingStatusFilepath() (filepath models.Filepath, err error) + GetPIAEncryption() (models.PIAEncryption, error) + GetPIARegion() (models.PIARegion, error) + + // Mullvad getters + GetMullvadCountry() (country models.MullvadCountry, err error) + GetMullvadCity() (country models.MullvadCity, err error) + GetMullvadISP() (country models.MullvadProvider, err error) + GetMullvadPort() (port uint16, err error) + + // Windscribe getters + GetWindscribeRegion() (country models.WindscribeRegion, err error) + GetWindscribePort(protocol models.NetworkProtocol) (port uint16, err error) + + // Shadowsocks getters + GetShadowSocks() (activated bool, err error) + GetShadowSocksLog() (activated bool, err error) + GetShadowSocksPort() (port uint16, err error) + GetShadowSocksPassword() (password string, err error) + GetShadowSocksMethod() (method 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 reader struct { + envParams libparams.EnvParams + logger logging.Logger + verifier verification.Verifier + unsetEnv func(key string) error +} + +// Newreader returns a paramsReadeer object to read parameters from +// environment variables +func NewReader(logger logging.Logger) Reader { + return &reader{ + envParams: libparams.NewEnvParams(), + logger: logger, + verifier: verification.NewVerifier(), + unsetEnv: os.Unsetenv, + } +} + +// GetVPNSP obtains the VPN service provider to use from the environment variable VPNSP +func (p *reader) GetVPNSP() (vpnServiceProvider models.VPNProvider, err error) { + s, err := p.envParams.GetValueIfInside("VPNSP", []string{"pia", "private internet access", "mullvad", "windscribe"}) + if s == "pia" { + s = "private internet access" + } + return models.VPNProvider(s), err +} diff --git a/internal/params/pia.go b/internal/params/pia.go index 58227e72..0d5c0105 100644 --- a/internal/params/pia.go +++ b/internal/params/pia.go @@ -1,60 +1,60 @@ -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" -) - -// 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"), libparams.CaseSensitiveValue()) - return models.Filepath(filepathStr), err -} - -// GetPIAEncryption obtains the encryption level for the PIA connection -// from the environment variable PIA_ENCRYPTION, and using ENCRYPTION for -// retro compatibility -func (p *paramsReader) GetPIAEncryption() (models.PIAEncryption, error) { - // Retro-compatibility - s, err := p.envParams.GetValueIfInside("ENCRYPTION", []string{"normal", "strong", ""}) - if err != nil { - return "", err - } else if len(s) != 0 { - p.logger.Warn("You are using the old environment variable ENCRYPTION, please consider changing it to PIA_ENCRYPTION") - return models.PIAEncryption(s), nil - } - s, err = p.envParams.GetValueIfInside("PIA_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 := append(constants.PIAGeoChoices(), "") - 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 -} +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" +) + +// GetPortForwarding obtains if port forwarding on the VPN provider server +// side is enabled or not from the environment variable PORT_FORWARDING +func (p *reader) 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 *reader) GetPortForwardingStatusFilepath() (filepath models.Filepath, err error) { + filepathStr, err := p.envParams.GetPath("PORT_FORWARDING_STATUS_FILE", libparams.Default("/forwarded_port"), libparams.CaseSensitiveValue()) + return models.Filepath(filepathStr), err +} + +// GetPIAEncryption obtains the encryption level for the PIA connection +// from the environment variable PIA_ENCRYPTION, and using ENCRYPTION for +// retro compatibility +func (p *reader) GetPIAEncryption() (models.PIAEncryption, error) { + // Retro-compatibility + s, err := p.envParams.GetValueIfInside("ENCRYPTION", []string{"normal", "strong", ""}) + if err != nil { + return "", err + } else if len(s) != 0 { + p.logger.Warn("You are using the old environment variable ENCRYPTION, please consider changing it to PIA_ENCRYPTION") + return models.PIAEncryption(s), nil + } + s, err = p.envParams.GetValueIfInside("PIA_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 *reader) GetPIARegion() (region models.PIARegion, err error) { + choices := append(constants.PIAGeoChoices(), "") + s, err := p.envParams.GetValueIfInside("REGION", choices) + if len(s) == 0 { // Suggestion by @rorph https://github.com/rorph + s = choices[rand.Int()%len(choices)] //nolint:gosec + } + return models.PIARegion(s), err +} diff --git a/internal/params/shadowsocks.go b/internal/params/shadowsocks.go index 3f9952f0..012478a6 100644 --- a/internal/params/shadowsocks.go +++ b/internal/params/shadowsocks.go @@ -1,46 +1,51 @@ -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", libparams.CaseSensitiveValue()) -} - -// GetShadowSocksMethod obtains the ShadowSocks method to use from the environment variable -// SHADOWSOCKS_METHOD -func (p *paramsReader) GetShadowSocksMethod() (method string, err error) { - return p.envParams.GetEnv("SHADOWSOCKS_METHOD", libparams.Default("chacha20-ietf-poly1305")) -} +package params + +import ( + "strconv" + + libparams "github.com/qdm12/golibs/params" +) + +// GetShadowSocks obtains if ShadowSocks is on from the environment variable +// SHADOWSOCKS +func (p *reader) 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 *reader) 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 *reader) 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 *reader) GetShadowSocksPassword() (password string, err error) { + defer func() { + unsetErr := p.unsetEnv("SHADOWSOCKS_PASSWORD") + if err == nil { + err = unsetErr + } + }() + return p.envParams.GetEnv("SHADOWSOCKS_PASSWORD", libparams.CaseSensitiveValue()) +} + +// GetShadowSocksMethod obtains the ShadowSocks method to use from the environment variable +// SHADOWSOCKS_METHOD +func (p *reader) GetShadowSocksMethod() (method string, err error) { + return p.envParams.GetEnv("SHADOWSOCKS_METHOD", libparams.Default("chacha20-ietf-poly1305")) +} diff --git a/internal/params/system.go b/internal/params/system.go index a8224092..71bf3725 100644 --- a/internal/params/system.go +++ b/internal/params/system.go @@ -1,28 +1,28 @@ -package params - -import ( - libparams "github.com/qdm12/golibs/params" - "github.com/qdm12/private-internet-access-docker/internal/models" -) - -// GetUID obtains the user ID to use from the environment variable UID -func (p *paramsReader) GetUID() (uid int, err error) { - return p.envParams.GetEnvIntRange("UID", 0, 65535, libparams.Default("1000")) -} - -// GetGID obtains the group ID to use from the environment variable GID -func (p *paramsReader) GetGID() (gid int, err error) { - return p.envParams.GetEnvIntRange("GID", 0, 65535, libparams.Default("1000")) -} - -// GetTZ obtains the timezone from the environment variable TZ -func (p *paramsReader) GetTimezone() (timezone string, err error) { - return p.envParams.GetEnv("TZ") -} - -// GetIPStatusFilepath obtains the IP status file path -// from the environment variable IP_STATUS_FILE -func (p *paramsReader) GetIPStatusFilepath() (filepath models.Filepath, err error) { - filepathStr, err := p.envParams.GetPath("IP_STATUS_FILE", libparams.Default("/ip"), libparams.CaseSensitiveValue()) - return models.Filepath(filepathStr), err -} +package params + +import ( + libparams "github.com/qdm12/golibs/params" + "github.com/qdm12/private-internet-access-docker/internal/models" +) + +// GetUID obtains the user ID to use from the environment variable UID +func (p *reader) GetUID() (uid int, err error) { + return p.envParams.GetEnvIntRange("UID", 0, 65535, libparams.Default("1000")) +} + +// GetGID obtains the group ID to use from the environment variable GID +func (p *reader) GetGID() (gid int, err error) { + return p.envParams.GetEnvIntRange("GID", 0, 65535, libparams.Default("1000")) +} + +// GetTZ obtains the timezone from the environment variable TZ +func (p *reader) GetTimezone() (timezone string, err error) { + return p.envParams.GetEnv("TZ") +} + +// GetIPStatusFilepath obtains the IP status file path +// from the environment variable IP_STATUS_FILE +func (p *reader) GetIPStatusFilepath() (filepath models.Filepath, err error) { + filepathStr, err := p.envParams.GetPath("IP_STATUS_FILE", libparams.Default("/ip"), libparams.CaseSensitiveValue()) + return models.Filepath(filepathStr), err +} diff --git a/internal/params/tinyproxy.go b/internal/params/tinyproxy.go index 134a4045..fcc11590 100644 --- a/internal/params/tinyproxy.go +++ b/internal/params/tinyproxy.go @@ -1,94 +1,116 @@ -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", "Connect", "Notice", "Warning", "Error", "Critical"}, libparams.Compulsory()) - return models.TinyProxyLogLevel(s), err - } - s, err = p.envParams.GetValueIfInside("TINYPROXY_LOG", []string{"Info", "Connect", "Notice", "Warning", "Error", "Critical"}, libparams.Default("Connect")) - 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", libparams.CaseSensitiveValue()) - 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", libparams.CaseSensitiveValue()) -} - -// 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", libparams.CaseSensitiveValue()) - 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", libparams.CaseSensitiveValue()) -} +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 *reader) 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 *reader) 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", "Connect", "Notice", "Warning", "Error", "Critical"}, libparams.Compulsory()) + return models.TinyProxyLogLevel(s), err + } + s, err = p.envParams.GetValueIfInside("TINYPROXY_LOG", []string{"Info", "Connect", "Notice", "Warning", "Error", "Critical"}, libparams.Default("Connect")) + 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 *reader) GetTinyProxyPort() (port uint16, err error) { + // Retro-compatibility + portStr, err := p.envParams.GetEnv("PROXY_PORT") + switch { + case err != nil: + return 0, err + case len(portStr) != 0: + p.logger.Warn("You are using the old environment variable PROXY_PORT, please consider changing it to TINYPROXY_PORT") + default: + 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 *reader) GetTinyProxyUser() (user string, err error) { + defer func() { + unsetErr := p.unsetEnv("PROXY_USER") + if err == nil { + err = unsetErr + } + }() + defer func() { + unsetErr := p.unsetEnv("TINYPROXY_USER") + if err == nil { + err = unsetErr + } + }() + // Retro-compatibility + user, err = p.envParams.GetEnv("PROXY_USER", libparams.CaseSensitiveValue()) + 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", libparams.CaseSensitiveValue()) +} + +// GetTinyProxyPassword obtains the TinyProxy server password from the environment variable +// TINYPROXY_PASSWORD, and using PROXY_PASSWORD as a retro-compatibility name +func (p *reader) GetTinyProxyPassword() (password string, err error) { + defer func() { + unsetErr := p.unsetEnv("PROXY_PASSWORD") + if err == nil { + err = unsetErr + } + }() + defer func() { + unsetErr := p.unsetEnv("TINYPROXY_PASSWORD") + if err == nil { + err = unsetErr + } + }() + + // Retro-compatibility + password, err = p.envParams.GetEnv("PROXY_PASSWORD", libparams.CaseSensitiveValue()) + 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", libparams.CaseSensitiveValue()) +} diff --git a/internal/params/version.go b/internal/params/version.go index 3241ac6d..596fa64a 100644 --- a/internal/params/version.go +++ b/internal/params/version.go @@ -4,17 +4,17 @@ import ( libparams "github.com/qdm12/golibs/params" ) -func (p *paramsReader) GetVersion() string { +func (p *reader) GetVersion() string { version, _ := p.envParams.GetEnv("VERSION", libparams.Default("?"), libparams.CaseSensitiveValue()) return version } -func (p *paramsReader) GetBuildDate() string { +func (p *reader) GetBuildDate() string { buildDate, _ := p.envParams.GetEnv("BUILD_DATE", libparams.Default("?"), libparams.CaseSensitiveValue()) return buildDate } -func (p *paramsReader) GetVcsRef() string { +func (p *reader) GetVcsRef() string { buildDate, _ := p.envParams.GetEnv("VCS_REF", libparams.Default("?"), libparams.CaseSensitiveValue()) return buildDate } diff --git a/internal/params/windscribe.go b/internal/params/windscribe.go index 76fa1e41..b1ba6dc9 100644 --- a/internal/params/windscribe.go +++ b/internal/params/windscribe.go @@ -11,15 +11,14 @@ import ( // GetWindscribeRegion obtains the region for the Windscribe server from the // environment variable REGION -func (p *paramsReader) GetWindscribeRegion() (country models.WindscribeRegion, err error) { - choices := append(constants.WindscribeRegionChoices()) - s, err := p.envParams.GetValueIfInside("REGION", choices) +func (p *reader) GetWindscribeRegion() (country models.WindscribeRegion, err error) { + s, err := p.envParams.GetValueIfInside("REGION", constants.WindscribeRegionChoices()) return models.WindscribeRegion(strings.ToLower(s)), err } // GetMullvadPort obtains the port to reach the Mullvad server on from the // environment variable PORT -func (p *paramsReader) GetWindscribePort(protocol models.NetworkProtocol) (port uint16, err error) { +func (p *reader) GetWindscribePort(protocol models.NetworkProtocol) (port uint16, err error) { n, err := p.envParams.GetEnvIntRange("PORT", 0, 65535, libparams.Default("0")) if err != nil { return 0, err diff --git a/internal/pia/conf.go b/internal/pia/conf.go index 9907b3d0..5b35f6bf 100644 --- a/internal/pia/conf.go +++ b/internal/pia/conf.go @@ -14,7 +14,7 @@ func (c *configurator) GetOpenVPNConnections(region models.PIARegion, protocol m geoMapping := constants.PIAGeoToSubdomainMapping() var subdomain string for r, s := range geoMapping { - if strings.ToLower(string(region)) == strings.ToLower(string(r)) { + if strings.EqualFold(string(region), string(r)) { subdomain = s break } @@ -75,8 +75,8 @@ func (c *configurator) BuildConf(connections []models.OpenVPNConnection, encrypt if len(auth) == 0 { auth = "sha1" } - X509CRL = constants.PIAX509CRL_NORMAL - certificate = constants.PIACertificate_NORMAL + X509CRL = constants.PiaX509CRLNormal + certificate = constants.PIACertificateNormal } else { // strong encryption if len(cipher) == 0 { cipher = "aes-256-cbc" @@ -84,8 +84,8 @@ func (c *configurator) BuildConf(connections []models.OpenVPNConnection, encrypt if len(auth) == 0 { auth = "sha256" } - X509CRL = constants.PIAX509CRL_STRONG - certificate = constants.PIACertificate_STRONG + X509CRL = constants.PiaX509CRLStrong + certificate = constants.PIACertificateStrong } lines := []string{ "client", diff --git a/internal/pia/portforward.go b/internal/pia/portforward.go index aaa9ed98..c5f568d0 100644 --- a/internal/pia/portforward.go +++ b/internal/pia/portforward.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "encoding/json" "fmt" + "net/http" "github.com/qdm12/golibs/files" "github.com/qdm12/private-internet-access-docker/internal/constants" @@ -19,11 +20,12 @@ func (c *configurator) GetPortForward() (port uint16, err error) { clientID := hex.EncodeToString(b) url := fmt.Sprintf("%s/?client_id=%s", constants.PIAPortForwardURL, clientID) content, status, err := c.client.GetContent(url) - if err != nil { + switch { + case err != nil: return 0, err - } else if status != 200 { + case status != http.StatusOK: return 0, fmt.Errorf("status is %d for %s; does your PIA server support port forwarding?", status, url) - } else if len(content) == 0 { + case len(content) == 0: return 0, fmt.Errorf("port forwarding is already activated on this connection, has expired, or you are not connected to a PIA region that supports port forwarding") } body := struct { @@ -42,7 +44,7 @@ func (c *configurator) WritePortForward(filepath models.Filepath, port uint16, u string(filepath), []string{fmt.Sprintf("%d", port)}, files.Ownership(uid, gid), - files.Permissions(400)) + files.Permissions(0400)) } func (c *configurator) AllowPortForwardFirewall(device models.VPNDevice, port uint16) (err error) { @@ -52,5 +54,5 @@ func (c *configurator) AllowPortForwardFirewall(device models.VPNDevice, port ui func (c *configurator) ClearPortForward(filepath models.Filepath, uid, gid int) (err error) { c.logger.Info("Clearing forwarded port status file %s", filepath) - return c.fileManager.WriteToFile(string(filepath), nil, files.Ownership(uid, gid), files.Permissions(400)) + return c.fileManager.WriteToFile(string(filepath), nil, files.Ownership(uid, gid), files.Permissions(0400)) } diff --git a/internal/routing/entry.go b/internal/routing/entry.go index 5788fadd..e7cfe021 100644 --- a/internal/routing/entry.go +++ b/internal/routing/entry.go @@ -72,7 +72,7 @@ func parseRoutingEntry(s string) (r routingEntry, err error) { return r, nil } -func reversedHexToIPv4(reversedHex string) (IP net.IP, err error) { +func reversedHexToIPv4(reversedHex string) (ip net.IP, err error) { bytes, err := hex.DecodeString(reversedHex) if err != nil { return nil, fmt.Errorf("cannot parse reversed IP hex %q: %s", reversedHex, err) diff --git a/internal/settings/dns.go b/internal/settings/dns.go index 3fb1a363..de5d0762 100644 --- a/internal/settings/dns.go +++ b/internal/settings/dns.go @@ -1,133 +1,138 @@ -package settings - -import ( - "fmt" - "strings" - - "github.com/qdm12/private-internet-access-docker/internal/constants" - "github.com/qdm12/private-internet-access-docker/internal/models" - "github.com/qdm12/private-internet-access-docker/internal/params" -) - -// DNS contains settings to configure Unbound for DNS over TLS operation -type DNS struct { - Enabled bool - Providers []models.DNSProvider - AllowedHostnames []string - PrivateAddresses []string - Caching bool - BlockMalicious bool - BlockSurveillance bool - BlockAds bool - VerbosityLevel uint8 - VerbosityDetailsLevel uint8 - ValidationLogLevel uint8 - IPv6 bool -} - -func (d *DNS) String() string { - if !d.Enabled { - return "DNS over TLS settings: disabled" - } - caching, blockMalicious, blockSurveillance, blockAds, ipv6 := "disabled", "disabed", "disabed", "disabed", "disabed" - if d.Caching { - caching = "enabled" - } - if d.BlockMalicious { - blockMalicious = "enabled" - } - if d.BlockSurveillance { - blockSurveillance = "enabled" - } - if d.BlockAds { - blockAds = "enabled" - } - if d.IPv6 { - ipv6 = "enabled" - } - var providersStr []string - for _, provider := range d.Providers { - providersStr = append(providersStr, string(provider)) - } - settingsList := []string{ - "DNS over TLS settings:", - "DNS over TLS provider:\n |--" + strings.Join(providersStr, "\n |--"), - "Caching: " + caching, - "Block malicious: " + blockMalicious, - "Block surveillance: " + blockSurveillance, - "Block ads: " + blockAds, - "Allowed hostnames:\n |--" + strings.Join(d.AllowedHostnames, "\n |--"), - "Private addresses:\n |--" + strings.Join(d.PrivateAddresses, "\n |--"), - "Verbosity level: " + fmt.Sprintf("%d/5", d.VerbosityLevel), - "Verbosity details level: " + fmt.Sprintf("%d/4", d.VerbosityDetailsLevel), - "Validation log level: " + fmt.Sprintf("%d/2", d.ValidationLogLevel), - "IPv6 resolution: " + ipv6, - } - return strings.Join(settingsList, "\n |--") -} - -// GetDNSSettings obtains DNS over TLS settings from environment variables using the params package. -func GetDNSSettings(params params.ParamsReader) (settings DNS, err error) { - settings.Enabled, err = params.GetDNSOverTLS() - if err != nil || !settings.Enabled { - return settings, err - } - settings.Providers, err = params.GetDNSOverTLSProviders() - if err != nil { - return settings, err - } - settings.AllowedHostnames, err = params.GetDNSUnblockedHostnames() - if err != nil { - return settings, err - } - settings.Caching, err = params.GetDNSOverTLSCaching() - if err != nil { - return settings, err - } - settings.BlockMalicious, err = params.GetDNSMaliciousBlocking() - if err != nil { - return settings, err - } - settings.BlockSurveillance, err = params.GetDNSSurveillanceBlocking() - if err != nil { - return settings, err - } - settings.BlockAds, err = params.GetDNSAdsBlocking() - if err != nil { - return settings, err - } - settings.VerbosityLevel, err = params.GetDNSOverTLSVerbosity() - if err != nil { - return settings, err - } - settings.VerbosityDetailsLevel, err = params.GetDNSOverTLSVerbosityDetails() - if err != nil { - return settings, err - } - settings.ValidationLogLevel, err = params.GetDNSOverTLSValidationLogLevel() - if err != nil { - return settings, err - } - settings.PrivateAddresses = params.GetDNSOverTLSPrivateAddresses() - settings.IPv6, err = params.GetDNSOverTLSIPv6() - if err != nil { - return settings, err - } - - // Consistency check - IPv6Support := false - for _, provider := range settings.Providers { - providerData, ok := constants.DNSProviderMapping()[provider] - if !ok { - return settings, fmt.Errorf("DNS provider %q does not have associated data", provider) - } else if !providerData.SupportsTLS { - return settings, fmt.Errorf("DNS provider %q does not support DNS over TLS", provider) - } else if providerData.SupportsIPv6 { - IPv6Support = true - } - } - if settings.IPv6 && !IPv6Support { - return settings, fmt.Errorf("None of the DNS over TLS provider(s) set support IPv6") - } - return settings, nil -} +package settings + +import ( + "fmt" + "strings" + + "github.com/qdm12/private-internet-access-docker/internal/constants" + "github.com/qdm12/private-internet-access-docker/internal/models" + "github.com/qdm12/private-internet-access-docker/internal/params" +) + +// DNS contains settings to configure Unbound for DNS over TLS operation +type DNS struct { + Enabled bool + Providers []models.DNSProvider + AllowedHostnames []string + PrivateAddresses []string + Caching bool + BlockMalicious bool + BlockSurveillance bool + BlockAds bool + VerbosityLevel uint8 + VerbosityDetailsLevel uint8 + ValidationLogLevel uint8 + IPv6 bool +} + +func (d *DNS) String() string { + if !d.Enabled { + return "DNS over TLS settings: disabled" + } + const ( + enabled = "enabled" + disabled = "disabled" + ) + caching, blockMalicious, blockSurveillance, blockAds, ipv6 := disabled, disabled, disabled, disabled, disabled + if d.Caching { + caching = enabled + } + if d.BlockMalicious { + blockMalicious = enabled + } + if d.BlockSurveillance { + blockSurveillance = enabled + } + if d.BlockAds { + blockAds = enabled + } + if d.IPv6 { + ipv6 = enabled + } + providersStr := make([]string, len(d.Providers)) + for i := range d.Providers { + providersStr[i] = string(d.Providers[i]) + } + settingsList := []string{ + "DNS over TLS settings:", + "DNS over TLS provider:\n |--" + strings.Join(providersStr, "\n |--"), + "Caching: " + caching, + "Block malicious: " + blockMalicious, + "Block surveillance: " + blockSurveillance, + "Block ads: " + blockAds, + "Allowed hostnames:\n |--" + strings.Join(d.AllowedHostnames, "\n |--"), + "Private addresses:\n |--" + strings.Join(d.PrivateAddresses, "\n |--"), + "Verbosity level: " + fmt.Sprintf("%d/5", d.VerbosityLevel), + "Verbosity details level: " + fmt.Sprintf("%d/4", d.VerbosityDetailsLevel), + "Validation log level: " + fmt.Sprintf("%d/2", d.ValidationLogLevel), + "IPv6 resolution: " + ipv6, + } + return strings.Join(settingsList, "\n |--") +} + +// GetDNSSettings obtains DNS over TLS settings from environment variables using the params package. +func GetDNSSettings(paramsReader params.Reader) (settings DNS, err error) { + settings.Enabled, err = paramsReader.GetDNSOverTLS() + if err != nil || !settings.Enabled { + return settings, err + } + settings.Providers, err = paramsReader.GetDNSOverTLSProviders() + if err != nil { + return settings, err + } + settings.AllowedHostnames, err = paramsReader.GetDNSUnblockedHostnames() + if err != nil { + return settings, err + } + settings.Caching, err = paramsReader.GetDNSOverTLSCaching() + if err != nil { + return settings, err + } + settings.BlockMalicious, err = paramsReader.GetDNSMaliciousBlocking() + if err != nil { + return settings, err + } + settings.BlockSurveillance, err = paramsReader.GetDNSSurveillanceBlocking() + if err != nil { + return settings, err + } + settings.BlockAds, err = paramsReader.GetDNSAdsBlocking() + if err != nil { + return settings, err + } + settings.VerbosityLevel, err = paramsReader.GetDNSOverTLSVerbosity() + if err != nil { + return settings, err + } + settings.VerbosityDetailsLevel, err = paramsReader.GetDNSOverTLSVerbosityDetails() + if err != nil { + return settings, err + } + settings.ValidationLogLevel, err = paramsReader.GetDNSOverTLSValidationLogLevel() + if err != nil { + return settings, err + } + settings.PrivateAddresses = paramsReader.GetDNSOverTLSPrivateAddresses() + settings.IPv6, err = paramsReader.GetDNSOverTLSIPv6() + if err != nil { + return settings, err + } + + // Consistency check + IPv6Support := false + for _, provider := range settings.Providers { + providerData, ok := constants.DNSProviderMapping()[provider] + switch { + case !ok: + return settings, fmt.Errorf("DNS provider %q does not have associated data", provider) + case !providerData.SupportsTLS: + return settings, fmt.Errorf("DNS provider %q does not support DNS over TLS", provider) + case providerData.SupportsIPv6: + IPv6Support = true + } + } + if settings.IPv6 && !IPv6Support { + return settings, fmt.Errorf("None of the DNS over TLS provider(s) set support IPv6") + } + return settings, nil +} diff --git a/internal/settings/firewall.go b/internal/settings/firewall.go index 841416d0..5ea25d83 100644 --- a/internal/settings/firewall.go +++ b/internal/settings/firewall.go @@ -1,34 +1,34 @@ -package settings - -import ( - "net" - "strings" - - "github.com/qdm12/private-internet-access-docker/internal/params" -) - -// Firewall contains settings to customize the firewall operation -type Firewall struct { - AllowedSubnets []net.IPNet -} - -func (f *Firewall) String() string { - var allowedSubnets []string - for _, net := range f.AllowedSubnets { - allowedSubnets = append(allowedSubnets, net.String()) - } - settingsList := []string{ - "Firewall settings:", - "Allowed subnets: " + strings.Join(allowedSubnets, ", "), - } - return strings.Join(settingsList, "\n |--") -} - -// GetFirewallSettings obtains firewall settings from environment variables using the params package. -func GetFirewallSettings(params params.ParamsReader) (settings Firewall, err error) { - settings.AllowedSubnets, err = params.GetExtraSubnets() - if err != nil { - return settings, err - } - return settings, nil -} +package settings + +import ( + "net" + "strings" + + "github.com/qdm12/private-internet-access-docker/internal/params" +) + +// Firewall contains settings to customize the firewall operation +type Firewall struct { + AllowedSubnets []net.IPNet +} + +func (f *Firewall) String() string { + allowedSubnets := make([]string, len(f.AllowedSubnets)) + for i := range f.AllowedSubnets { + allowedSubnets[i] = f.AllowedSubnets[i].String() + } + settingsList := []string{ + "Firewall settings:", + "Allowed subnets: " + strings.Join(allowedSubnets, ", "), + } + return strings.Join(settingsList, "\n |--") +} + +// GetFirewallSettings obtains firewall settings from environment variables using the params package. +func GetFirewallSettings(paramsReader params.Reader) (settings Firewall, err error) { + settings.AllowedSubnets, err = paramsReader.GetExtraSubnets() + if err != nil { + return settings, err + } + return settings, nil +} diff --git a/internal/settings/mullvad.go b/internal/settings/mullvad.go index 335b7e70..3b1c9c15 100644 --- a/internal/settings/mullvad.go +++ b/internal/settings/mullvad.go @@ -1,56 +1,56 @@ -package settings - -import ( - "strings" - - "github.com/qdm12/private-internet-access-docker/internal/models" - "github.com/qdm12/private-internet-access-docker/internal/params" -) - -// Mullvad contains the settings to connect to a Mullvad server -type Mullvad struct { - User string - Country models.MullvadCountry - City models.MullvadCity - ISP models.MullvadProvider - Port uint16 -} - -func (m *Mullvad) String() string { - settingsList := []string{ - "Mullvad settings:", - "User: [redacted]", - "Country: " + string(m.Country), - "City: " + string(m.City), - "ISP: " + string(m.ISP), - "Port: " + string(m.Port), - } - return strings.Join(settingsList, "\n |--") -} - -// GetMullvadSettings obtains Mullvad settings from environment variables using the params package. -func GetMullvadSettings(params params.ParamsReader) (settings Mullvad, err error) { - settings.User, err = params.GetUser() - if err != nil { - return settings, err - } - // Remove spaces in user ID to simplify user's life, thanks @JeordyR - settings.User = strings.ReplaceAll(settings.User, " ", "") - settings.Country, err = params.GetMullvadCountry() - if err != nil { - return settings, err - } - settings.City, err = params.GetMullvadCity() - if err != nil { - return settings, err - } - settings.ISP, err = params.GetMullvadISP() - if err != nil { - return settings, err - } - settings.Port, err = params.GetMullvadPort() - if err != nil { - return settings, err - } - return settings, nil -} +package settings + +import ( + "strings" + + "github.com/qdm12/private-internet-access-docker/internal/models" + "github.com/qdm12/private-internet-access-docker/internal/params" +) + +// Mullvad contains the settings to connect to a Mullvad server +type Mullvad struct { + User string + Country models.MullvadCountry + City models.MullvadCity + ISP models.MullvadProvider + Port uint16 +} + +func (m *Mullvad) String() string { + settingsList := []string{ + "Mullvad settings:", + "User: [redacted]", + "Country: " + string(m.Country), + "City: " + string(m.City), + "ISP: " + string(m.ISP), + "Port: " + string(m.Port), + } + return strings.Join(settingsList, "\n |--") +} + +// GetMullvadSettings obtains Mullvad settings from environment variables using the params package. +func GetMullvadSettings(paramsReader params.Reader) (settings Mullvad, err error) { + settings.User, err = paramsReader.GetUser() + if err != nil { + return settings, err + } + // Remove spaces in user ID to simplify user's life, thanks @JeordyR + settings.User = strings.ReplaceAll(settings.User, " ", "") + settings.Country, err = paramsReader.GetMullvadCountry() + if err != nil { + return settings, err + } + settings.City, err = paramsReader.GetMullvadCity() + if err != nil { + return settings, err + } + settings.ISP, err = paramsReader.GetMullvadISP() + if err != nil { + return settings, err + } + settings.Port, err = paramsReader.GetMullvadPort() + if err != nil { + return settings, err + } + return settings, nil +} diff --git a/internal/settings/openvpn.go b/internal/settings/openvpn.go index 338d2225..cd86d502 100644 --- a/internal/settings/openvpn.go +++ b/internal/settings/openvpn.go @@ -1,66 +1,66 @@ -package settings - -import ( - "fmt" - "net" - "strings" - - "github.com/qdm12/private-internet-access-docker/internal/models" - "github.com/qdm12/private-internet-access-docker/internal/params" -) - -// OpenVPN contains settings to configure the OpenVPN client -type OpenVPN struct { - NetworkProtocol models.NetworkProtocol - Verbosity int - Root bool - TargetIP net.IP - Cipher string - Auth string -} - -// GetOpenVPNSettings obtains the OpenVPN settings using the params functions -func GetOpenVPNSettings(params params.ParamsReader) (settings OpenVPN, err error) { - settings.NetworkProtocol, err = params.GetNetworkProtocol() - if err != nil { - return settings, err - } - settings.Verbosity, err = params.GetOpenVPNVerbosity() - if err != nil { - return settings, err - } - settings.Root, err = params.GetOpenVPNRoot() - if err != nil { - return settings, err - } - settings.TargetIP, err = params.GetTargetIP() - if err != nil { - return settings, err - } - settings.Cipher, err = params.GetOpenVPNCipher() - if err != nil { - return settings, err - } - settings.Auth, err = params.GetOpenVPNAuth() - if err != nil { - return settings, err - } - return settings, nil -} - -func (o *OpenVPN) String() string { - runAsRoot := "no" - if o.Root { - runAsRoot = "yes" - } - settingsList := []string{ - "OpenVPN settings:", - "Network protocol: " + string(o.NetworkProtocol), - "Verbosity level: " + fmt.Sprintf("%d", o.Verbosity), - "Run as root: " + runAsRoot, - "Target IP address: " + o.TargetIP.String(), - "Custom cipher: " + o.Cipher, - "Custom auth algorithm: " + o.Auth, - } - return strings.Join(settingsList, "\n|--") -} +package settings + +import ( + "fmt" + "net" + "strings" + + "github.com/qdm12/private-internet-access-docker/internal/models" + "github.com/qdm12/private-internet-access-docker/internal/params" +) + +// OpenVPN contains settings to configure the OpenVPN client +type OpenVPN struct { + NetworkProtocol models.NetworkProtocol + Verbosity int + Root bool + TargetIP net.IP + Cipher string + Auth string +} + +// GetOpenVPNSettings obtains the OpenVPN settings using the params functions +func GetOpenVPNSettings(paramsReader params.Reader) (settings OpenVPN, err error) { + settings.NetworkProtocol, err = paramsReader.GetNetworkProtocol() + if err != nil { + return settings, err + } + settings.Verbosity, err = paramsReader.GetOpenVPNVerbosity() + if err != nil { + return settings, err + } + settings.Root, err = paramsReader.GetOpenVPNRoot() + if err != nil { + return settings, err + } + settings.TargetIP, err = paramsReader.GetTargetIP() + if err != nil { + return settings, err + } + settings.Cipher, err = paramsReader.GetOpenVPNCipher() + if err != nil { + return settings, err + } + settings.Auth, err = paramsReader.GetOpenVPNAuth() + if err != nil { + return settings, err + } + return settings, nil +} + +func (o *OpenVPN) String() string { + runAsRoot := "no" + if o.Root { + runAsRoot = "yes" + } + settingsList := []string{ + "OpenVPN settings:", + "Network protocol: " + string(o.NetworkProtocol), + "Verbosity level: " + fmt.Sprintf("%d", o.Verbosity), + "Run as root: " + runAsRoot, + "Target IP address: " + o.TargetIP.String(), + "Custom cipher: " + o.Cipher, + "Custom auth algorithm: " + o.Auth, + } + return strings.Join(settingsList, "\n|--") +} diff --git a/internal/settings/pia.go b/internal/settings/pia.go index 837dbee2..5904cb51 100644 --- a/internal/settings/pia.go +++ b/internal/settings/pia.go @@ -1,74 +1,74 @@ -package settings - -import ( - "fmt" - "strings" - - "github.com/qdm12/private-internet-access-docker/internal/models" - "github.com/qdm12/private-internet-access-docker/internal/params" -) - -// PIA contains the settings to connect to a PIA server -type PIA struct { - User string - Password string - Encryption models.PIAEncryption - Region models.PIARegion - PortForwarding PortForwarding -} - -// PortForwarding contains settings for port forwarding -type PortForwarding struct { - Enabled bool - Filepath models.Filepath -} - -func (p *PortForwarding) String() string { - if p.Enabled { - return fmt.Sprintf("on, saved in %s", p.Filepath) - } - return "off" -} - -func (p *PIA) String() string { - settingsList := []string{ - "PIA settings:", - "User: [redacted]", - "Password: [redacted]", - "Region: " + string(p.Region), - "Encryption: " + string(p.Encryption), - "Port forwarding: " + p.PortForwarding.String(), - } - return strings.Join(settingsList, "\n |--") -} - -// GetPIASettings obtains PIA settings from environment variables using the params package. -func GetPIASettings(params params.ParamsReader) (settings PIA, err error) { - settings.User, err = params.GetUser() - if err != nil { - return settings, err - } - settings.Password, err = params.GetPassword() - if err != nil { - return settings, err - } - settings.Encryption, err = params.GetPIAEncryption() - if err != nil { - return settings, err - } - settings.Region, err = params.GetPIARegion() - if err != nil { - return settings, err - } - settings.PortForwarding.Enabled, err = params.GetPortForwarding() - if err != nil { - return settings, err - } - if settings.PortForwarding.Enabled { - settings.PortForwarding.Filepath, err = params.GetPortForwardingStatusFilepath() - if err != nil { - return settings, err - } - } - return settings, nil -} +package settings + +import ( + "fmt" + "strings" + + "github.com/qdm12/private-internet-access-docker/internal/models" + "github.com/qdm12/private-internet-access-docker/internal/params" +) + +// PIA contains the settings to connect to a PIA server +type PIA struct { + User string + Password string + Encryption models.PIAEncryption + Region models.PIARegion + PortForwarding PortForwarding +} + +// PortForwarding contains settings for port forwarding +type PortForwarding struct { + Enabled bool + Filepath models.Filepath +} + +func (p *PortForwarding) String() string { + if p.Enabled { + return fmt.Sprintf("on, saved in %s", p.Filepath) + } + return "off" +} + +func (p *PIA) String() string { + settingsList := []string{ + "PIA settings:", + "User: [redacted]", + "Password: [redacted]", + "Region: " + string(p.Region), + "Encryption: " + string(p.Encryption), + "Port forwarding: " + p.PortForwarding.String(), + } + return strings.Join(settingsList, "\n |--") +} + +// GetPIASettings obtains PIA settings from environment variables using the params package. +func GetPIASettings(paramsReader params.Reader) (settings PIA, err error) { + settings.User, err = paramsReader.GetUser() + if err != nil { + return settings, err + } + settings.Password, err = paramsReader.GetPassword() + if err != nil { + return settings, err + } + settings.Encryption, err = paramsReader.GetPIAEncryption() + if err != nil { + return settings, err + } + settings.Region, err = paramsReader.GetPIARegion() + if err != nil { + return settings, err + } + settings.PortForwarding.Enabled, err = paramsReader.GetPortForwarding() + if err != nil { + return settings, err + } + if settings.PortForwarding.Enabled { + settings.PortForwarding.Filepath, err = paramsReader.GetPortForwardingStatusFilepath() + if err != nil { + return settings, err + } + } + return settings, nil +} diff --git a/internal/settings/settings.go b/internal/settings/settings.go index 20541192..673925e8 100644 --- a/internal/settings/settings.go +++ b/internal/settings/settings.go @@ -1,125 +1,127 @@ -package settings - -import ( - "fmt" - "strings" - - "github.com/qdm12/private-internet-access-docker/internal/params" -) - -// Settings contains all settings for the program to run -type Settings struct { - VPNSP string - OpenVPN OpenVPN - PIA PIA - Mullvad Mullvad - Windscribe Windscribe - System System - DNS DNS - Firewall Firewall - TinyProxy TinyProxy - ShadowSocks ShadowSocks -} - -func (s *Settings) String() string { - var vpnServiceProvider string - switch s.VPNSP { - case "pia": - vpnServiceProvider = s.PIA.String() - case "mullvad": - vpnServiceProvider = s.Mullvad.String() - case "windscribe": - vpnServiceProvider = s.Windscribe.String() - } - return strings.Join([]string{ - "Settings summary below:", - s.OpenVPN.String(), - vpnServiceProvider, - s.System.String(), - s.DNS.String(), - s.Firewall.String(), - s.TinyProxy.String(), - s.ShadowSocks.String(), - "", // new line at the end - }, "\n") -} - -// GetAllSettings obtains all settings for the program and returns an error as soon -// as an error is encountered reading them. -func GetAllSettings(params params.ParamsReader) (settings Settings, err error) { - settings.VPNSP, err = params.GetVPNSP() - if err != nil { - return settings, err - } - settings.OpenVPN, err = GetOpenVPNSettings(params) - if err != nil { - return settings, err - } - switch settings.VPNSP { - case "pia": - switch settings.OpenVPN.Cipher { - case "", "aes-128-cbc", "aes-256-cbc", "aes-128-gcm", "aes-256-gcm": - default: - return settings, fmt.Errorf("cipher %q is not supported by Private Internet Access", settings.OpenVPN.Cipher) - } - switch settings.OpenVPN.Auth { - case "", "sha1", "sha256": - default: - return settings, fmt.Errorf("auth algorithm %q is not supported by Private Internet Access", settings.OpenVPN.Auth) - } - settings.PIA, err = GetPIASettings(params) - case "mullvad": - switch settings.OpenVPN.Cipher { - case "": - default: - return settings, fmt.Errorf("cipher %q is not supported by Mullvad", settings.OpenVPN.Cipher) - } - switch settings.OpenVPN.Auth { - case "": - default: - return settings, fmt.Errorf("auth algorithm %q is not supported by Mullvad (not using auth at all)", settings.OpenVPN.Auth) - } - settings.Mullvad, err = GetMullvadSettings(params) - case "windscribe": - switch settings.OpenVPN.Cipher { - case "", "aes-256-cbc", "aes-256-gcm": // TODO check inside params getters - default: - return settings, fmt.Errorf("cipher %q is not supported by Windscribe", settings.OpenVPN.Cipher) - } - switch settings.OpenVPN.Auth { - case "", "sha512": - default: - return settings, fmt.Errorf("auth algorithm %q is not supported by Windscribe", settings.OpenVPN.Auth) - } - settings.Windscribe, err = GetWindscribeSettings(params, settings.OpenVPN.NetworkProtocol) - default: - err = fmt.Errorf("VPN service provider %q is not valid", settings.VPNSP) - } - if err != nil { - return settings, err - } - if err != nil { - return settings, err - } - settings.DNS, err = GetDNSSettings(params) - if err != nil { - return settings, err - } - settings.Firewall, err = GetFirewallSettings(params) - if err != nil { - return settings, err - } - settings.TinyProxy, err = GetTinyProxySettings(params) - if err != nil { - return settings, err - } - settings.ShadowSocks, err = GetShadowSocksSettings(params) - if err != nil { - return settings, err - } - settings.System, err = GetSystemSettings(params) - if err != nil { - return settings, err - } - return settings, nil -} +package settings + +import ( + "fmt" + "strings" + + "github.com/qdm12/private-internet-access-docker/internal/constants" + "github.com/qdm12/private-internet-access-docker/internal/models" + "github.com/qdm12/private-internet-access-docker/internal/params" +) + +// Settings contains all settings for the program to run +type Settings struct { + VPNSP models.VPNProvider + OpenVPN OpenVPN + PIA PIA + Mullvad Mullvad + Windscribe Windscribe + System System + DNS DNS + Firewall Firewall + TinyProxy TinyProxy + ShadowSocks ShadowSocks +} + +func (s *Settings) String() string { + var vpnServiceProviderSettings string + switch s.VPNSP { + case constants.PrivateInternetAccess: + vpnServiceProviderSettings = s.PIA.String() + case constants.Mullvad: + vpnServiceProviderSettings = s.Mullvad.String() + case constants.Windscribe: + vpnServiceProviderSettings = s.Windscribe.String() + } + return strings.Join([]string{ + "Settings summary below:", + s.OpenVPN.String(), + vpnServiceProviderSettings, + s.System.String(), + s.DNS.String(), + s.Firewall.String(), + s.TinyProxy.String(), + s.ShadowSocks.String(), + "", // new line at the end + }, "\n") +} + +// GetAllSettings obtains all settings for the program and returns an error as soon +// as an error is encountered reading them. +func GetAllSettings(paramsReader params.Reader) (settings Settings, err error) { + settings.VPNSP, err = paramsReader.GetVPNSP() + if err != nil { + return settings, err + } + settings.OpenVPN, err = GetOpenVPNSettings(paramsReader) + if err != nil { + return settings, err + } + switch settings.VPNSP { + case constants.PrivateInternetAccess: + switch settings.OpenVPN.Cipher { + case "", "aes-128-cbc", "aes-256-cbc", "aes-128-gcm", "aes-256-gcm": + default: + return settings, fmt.Errorf("cipher %q is not supported by Private Internet Access", settings.OpenVPN.Cipher) + } + switch settings.OpenVPN.Auth { + case "", "sha1", "sha256": + default: + return settings, fmt.Errorf("auth algorithm %q is not supported by Private Internet Access", settings.OpenVPN.Auth) + } + settings.PIA, err = GetPIASettings(paramsReader) + case constants.Mullvad: + switch settings.OpenVPN.Cipher { + case "": + default: + return settings, fmt.Errorf("cipher %q is not supported by Mullvad", settings.OpenVPN.Cipher) + } + switch settings.OpenVPN.Auth { + case "": + default: + return settings, fmt.Errorf("auth algorithm %q is not supported by Mullvad (not using auth at all)", settings.OpenVPN.Auth) + } + settings.Mullvad, err = GetMullvadSettings(paramsReader) + case constants.Windscribe: + switch settings.OpenVPN.Cipher { + case "", "aes-256-cbc", "aes-256-gcm": // TODO check inside params getters + default: + return settings, fmt.Errorf("cipher %q is not supported by Windscribe", settings.OpenVPN.Cipher) + } + switch settings.OpenVPN.Auth { + case "", "sha512": + default: + return settings, fmt.Errorf("auth algorithm %q is not supported by Windscribe", settings.OpenVPN.Auth) + } + settings.Windscribe, err = GetWindscribeSettings(paramsReader, settings.OpenVPN.NetworkProtocol) + default: + err = fmt.Errorf("VPN service provider %q is not valid", settings.VPNSP) + } + if err != nil { + return settings, err + } + if err != nil { + return settings, err + } + settings.DNS, err = GetDNSSettings(paramsReader) + if err != nil { + return settings, err + } + settings.Firewall, err = GetFirewallSettings(paramsReader) + if err != nil { + return settings, err + } + settings.TinyProxy, err = GetTinyProxySettings(paramsReader) + if err != nil { + return settings, err + } + settings.ShadowSocks, err = GetShadowSocksSettings(paramsReader) + if err != nil { + return settings, err + } + settings.System, err = GetSystemSettings(paramsReader) + if err != nil { + return settings, err + } + return settings, nil +} diff --git a/internal/settings/shadowsocks.go b/internal/settings/shadowsocks.go index ce636d8e..be6e6915 100644 --- a/internal/settings/shadowsocks.go +++ b/internal/settings/shadowsocks.go @@ -1,60 +1,60 @@ -package settings - -import ( - "fmt" - "strings" - - "github.com/qdm12/private-internet-access-docker/internal/params" -) - -// ShadowSocks contains settings to configure the Shadowsocks server -type ShadowSocks struct { - Enabled bool - Password string - Log bool - Port uint16 - Method string -} - -func (s *ShadowSocks) String() string { - if !s.Enabled { - return "ShadowSocks settings: disabled" - } - log := "disabled" - if s.Log { - log = "enabled" - } - settingsList := []string{ - "ShadowSocks settings:", - "Password: [redacted]", - "Log: " + log, - fmt.Sprintf("Port: %d", s.Port), - "Method: " + s.Method, - } - return strings.Join(settingsList, "\n |--") -} - -// GetShadowSocksSettings obtains ShadowSocks settings from environment variables using the params package. -func GetShadowSocksSettings(params params.ParamsReader) (settings ShadowSocks, err error) { - settings.Enabled, err = params.GetShadowSocks() - if err != nil || !settings.Enabled { - return settings, err - } - settings.Port, err = params.GetShadowSocksPort() - if err != nil { - return settings, err - } - settings.Password, err = params.GetShadowSocksPassword() - if err != nil { - return settings, err - } - settings.Log, err = params.GetShadowSocksLog() - if err != nil { - return settings, err - } - settings.Method, err = params.GetShadowSocksMethod() - if err != nil { - return settings, err - } - return settings, nil -} +package settings + +import ( + "fmt" + "strings" + + "github.com/qdm12/private-internet-access-docker/internal/params" +) + +// ShadowSocks contains settings to configure the Shadowsocks server +type ShadowSocks struct { + Method string + Password string + Port uint16 + Enabled bool + Log bool +} + +func (s *ShadowSocks) String() string { + if !s.Enabled { + return "ShadowSocks settings: disabled" + } + log := "disabled" + if s.Log { + log = "enabled" + } + settingsList := []string{ + "ShadowSocks settings:", + "Password: [redacted]", + "Log: " + log, + fmt.Sprintf("Port: %d", s.Port), + "Method: " + s.Method, + } + return strings.Join(settingsList, "\n |--") +} + +// GetShadowSocksSettings obtains ShadowSocks settings from environment variables using the params package. +func GetShadowSocksSettings(paramsReader params.Reader) (settings ShadowSocks, err error) { + settings.Enabled, err = paramsReader.GetShadowSocks() + if err != nil || !settings.Enabled { + return settings, err + } + settings.Port, err = paramsReader.GetShadowSocksPort() + if err != nil { + return settings, err + } + settings.Password, err = paramsReader.GetShadowSocksPassword() + if err != nil { + return settings, err + } + settings.Log, err = paramsReader.GetShadowSocksLog() + if err != nil { + return settings, err + } + settings.Method, err = paramsReader.GetShadowSocksMethod() + if err != nil { + return settings, err + } + return settings, nil +} diff --git a/internal/settings/system.go b/internal/settings/system.go index be7290c7..83324c78 100644 --- a/internal/settings/system.go +++ b/internal/settings/system.go @@ -17,20 +17,20 @@ type System struct { } // GetSystemSettings obtains the System settings using the params functions -func GetSystemSettings(params params.ParamsReader) (settings System, err error) { - settings.UID, err = params.GetUID() +func GetSystemSettings(paramsReader params.Reader) (settings System, err error) { + settings.UID, err = paramsReader.GetUID() if err != nil { return settings, err } - settings.GID, err = params.GetGID() + settings.GID, err = paramsReader.GetGID() if err != nil { return settings, err } - settings.Timezone, err = params.GetTimezone() + settings.Timezone, err = paramsReader.GetTimezone() if err != nil { return settings, err } - settings.IPStatusFilepath, err = params.GetIPStatusFilepath() + settings.IPStatusFilepath, err = paramsReader.GetIPStatusFilepath() if err != nil { return settings, err } diff --git a/internal/settings/tinyproxy.go b/internal/settings/tinyproxy.go index d4136a08..4b4c42b2 100644 --- a/internal/settings/tinyproxy.go +++ b/internal/settings/tinyproxy.go @@ -1,59 +1,59 @@ -package settings - -import ( - "fmt" - "strings" - - "github.com/qdm12/private-internet-access-docker/internal/models" - "github.com/qdm12/private-internet-access-docker/internal/params" -) - -// TinyProxy contains settings to configure TinyProxy -type TinyProxy struct { - Enabled bool - User string - Password string - Port uint16 - LogLevel models.TinyProxyLogLevel -} - -func (t *TinyProxy) String() string { - if !t.Enabled { - return "TinyProxy settings: disabled" - } - auth := "disabled" - if t.User != "" { - auth = "enabled" - } - settingsList := []string{ - fmt.Sprintf("Port: %d", t.Port), - "Authentication: " + auth, - "Log level: " + string(t.LogLevel), - } - return "TinyProxy settings:\n" + strings.Join(settingsList, "\n |--") -} - -// GetTinyProxySettings obtains TinyProxy settings from environment variables using the params package. -func GetTinyProxySettings(params params.ParamsReader) (settings TinyProxy, err error) { - settings.Enabled, err = params.GetTinyProxy() - if err != nil || !settings.Enabled { - return settings, err - } - settings.User, err = params.GetTinyProxyUser() - if err != nil { - return settings, err - } - settings.Password, err = params.GetTinyProxyPassword() - if err != nil { - return settings, err - } - settings.Port, err = params.GetTinyProxyPort() - if err != nil { - return settings, err - } - settings.LogLevel, err = params.GetTinyProxyLog() - if err != nil { - return settings, err - } - return settings, nil -} +package settings + +import ( + "fmt" + "strings" + + "github.com/qdm12/private-internet-access-docker/internal/models" + "github.com/qdm12/private-internet-access-docker/internal/params" +) + +// TinyProxy contains settings to configure TinyProxy +type TinyProxy struct { + User string + Password string + LogLevel models.TinyProxyLogLevel + Port uint16 + Enabled bool +} + +func (t *TinyProxy) String() string { + if !t.Enabled { + return "TinyProxy settings: disabled" + } + auth := "disabled" + if t.User != "" { + auth = "enabled" + } + settingsList := []string{ + fmt.Sprintf("Port: %d", t.Port), + "Authentication: " + auth, + "Log level: " + string(t.LogLevel), + } + return "TinyProxy settings:\n" + strings.Join(settingsList, "\n |--") +} + +// GetTinyProxySettings obtains TinyProxy settings from environment variables using the params package. +func GetTinyProxySettings(paramsReader params.Reader) (settings TinyProxy, err error) { + settings.Enabled, err = paramsReader.GetTinyProxy() + if err != nil || !settings.Enabled { + return settings, err + } + settings.User, err = paramsReader.GetTinyProxyUser() + if err != nil { + return settings, err + } + settings.Password, err = paramsReader.GetTinyProxyPassword() + if err != nil { + return settings, err + } + settings.Port, err = paramsReader.GetTinyProxyPort() + if err != nil { + return settings, err + } + settings.LogLevel, err = paramsReader.GetTinyProxyLog() + if err != nil { + return settings, err + } + return settings, nil +} diff --git a/internal/settings/windscribe.go b/internal/settings/windscribe.go index a5c58c2b..45fadf5e 100644 --- a/internal/settings/windscribe.go +++ b/internal/settings/windscribe.go @@ -1,49 +1,49 @@ -package settings - -import ( - "fmt" - "strings" - - "github.com/qdm12/private-internet-access-docker/internal/models" - "github.com/qdm12/private-internet-access-docker/internal/params" -) - -// Windscribe contains the settings to connect to a Windscribe server -type Windscribe struct { - User string - Password string - Region models.WindscribeRegion - Port uint16 -} - -func (w *Windscribe) String() string { - settingsList := []string{ - "Windscribe settings:", - "User: [redacted]", - "Password: [redacted]", - "Region: " + string(w.Region), - "Custom port: " + fmt.Sprintf("%d", w.Port), - } - return strings.Join(settingsList, "\n |--") -} - -// GetWindscribeSettings obtains Windscribe settings from environment variables using the params package. -func GetWindscribeSettings(params params.ParamsReader, protocol models.NetworkProtocol) (settings Windscribe, err error) { - settings.User, err = params.GetUser() - if err != nil { - return settings, err - } - settings.Password, err = params.GetPassword() - if err != nil { - return settings, err - } - settings.Region, err = params.GetWindscribeRegion() - if err != nil { - return settings, err - } - settings.Port, err = params.GetWindscribePort(protocol) - if err != nil { - return settings, err - } - return settings, nil -} +package settings + +import ( + "fmt" + "strings" + + "github.com/qdm12/private-internet-access-docker/internal/models" + "github.com/qdm12/private-internet-access-docker/internal/params" +) + +// Windscribe contains the settings to connect to a Windscribe server +type Windscribe struct { + User string + Password string + Region models.WindscribeRegion + Port uint16 +} + +func (w *Windscribe) String() string { + settingsList := []string{ + "Windscribe settings:", + "User: [redacted]", + "Password: [redacted]", + "Region: " + string(w.Region), + "Custom port: " + fmt.Sprintf("%d", w.Port), + } + return strings.Join(settingsList, "\n |--") +} + +// GetWindscribeSettings obtains Windscribe settings from environment variables using the params package. +func GetWindscribeSettings(paramsReader params.Reader, protocol models.NetworkProtocol) (settings Windscribe, err error) { + settings.User, err = paramsReader.GetUser() + if err != nil { + return settings, err + } + settings.Password, err = paramsReader.GetPassword() + if err != nil { + return settings, err + } + settings.Region, err = paramsReader.GetWindscribeRegion() + if err != nil { + return settings, err + } + settings.Port, err = paramsReader.GetWindscribePort(protocol) + if err != nil { + return settings, err + } + return settings, nil +} diff --git a/internal/splash/splash.go b/internal/splash/splash.go index 57f49421..649f86f6 100644 --- a/internal/splash/splash.go +++ b/internal/splash/splash.go @@ -11,7 +11,7 @@ import ( ) // Splash returns the welcome spash message -func Splash(paramsReader params.ParamsReader) string { +func Splash(paramsReader params.Reader) string { version := paramsReader.GetVersion() vcsRef := paramsReader.GetVcsRef() buildDate := paramsReader.GetBuildDate()