Rewrite of the entrypoint in Golang (#71)
- General improvements
- Parallel download of only needed files at start
- Prettier console output with all streams merged (openvpn, unbound, shadowsocks etc.)
- Simplified Docker final image
- Faster bootup
- DNS over TLS
- Finer grain blocking at DNS level: malicious, ads and surveillance
- Choose your DNS over TLS providers
- Ability to use multiple DNS over TLS providers for DNS split horizon
- Environment variables for DNS logging
- DNS block lists needed are downloaded and built automatically at start, in parallel
- PIA
- A random region is selected if the REGION parameter is left empty (thanks @rorph for your PR)
- Routing and iptables adjusted so it can work as a Kubernetes pod sidecar (thanks @rorph for your PR)
This commit is contained in:
40
internal/dns/command.go
Normal file
40
internal/dns/command.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/private-internet-access-docker/internal/constants"
|
||||
)
|
||||
|
||||
func (c *configurator) Start(verbosityDetailsLevel uint8) (stdout io.ReadCloser, err error) {
|
||||
c.logger.Info("%s: starting unbound", logPrefix)
|
||||
args := []string{"-d", "-c", string(constants.UnboundConf)}
|
||||
if verbosityDetailsLevel > 0 {
|
||||
args = append(args, "-"+strings.Repeat("v", int(verbosityDetailsLevel)))
|
||||
}
|
||||
// Only logs to stderr
|
||||
_, stdout, _, err = c.commander.Start("unbound", args...)
|
||||
return stdout, err
|
||||
}
|
||||
|
||||
func (c *configurator) Version() (version string, err error) {
|
||||
output, err := c.commander.Run("unbound", "-V")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unbound version: %w", err)
|
||||
}
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
if strings.Contains(line, "Version ") {
|
||||
words := strings.Fields(line)
|
||||
if len(words) < 2 {
|
||||
continue
|
||||
}
|
||||
version = words[1]
|
||||
}
|
||||
}
|
||||
if version == "" {
|
||||
return "", fmt.Errorf("unbound version was not found in %q", output)
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
69
internal/dns/command_test.go
Normal file
69
internal/dns/command_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
commandMocks "github.com/qdm12/golibs/command/mocks"
|
||||
loggingMocks "github.com/qdm12/golibs/logging/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/qdm12/private-internet-access-docker/internal/constants"
|
||||
)
|
||||
|
||||
func Test_Start(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger := &loggingMocks.Logger{}
|
||||
logger.On("Info", "%s: starting unbound", logPrefix).Once()
|
||||
commander := &commandMocks.Commander{}
|
||||
commander.On("Start", "unbound", "-d", "-c", string(constants.UnboundConf), "-vv").
|
||||
Return(nil, nil, nil, nil).Once()
|
||||
c := &configurator{commander: commander, logger: logger}
|
||||
stdout, err := c.Start(2)
|
||||
assert.Nil(t, stdout)
|
||||
assert.NoError(t, err)
|
||||
logger.AssertExpectations(t)
|
||||
commander.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func Test_Version(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := map[string]struct {
|
||||
runOutput string
|
||||
runErr error
|
||||
version string
|
||||
err error
|
||||
}{
|
||||
"no data": {
|
||||
err: fmt.Errorf(`unbound version was not found in ""`),
|
||||
},
|
||||
"2 lines with version": {
|
||||
runOutput: "Version \nVersion 1.0-a hello\n",
|
||||
version: "1.0-a",
|
||||
},
|
||||
"run error": {
|
||||
runErr: fmt.Errorf("error"),
|
||||
err: fmt.Errorf("unbound version: error"),
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
commander := &commandMocks.Commander{}
|
||||
commander.On("Run", "unbound", "-V").
|
||||
Return(tc.runOutput, tc.runErr).Once()
|
||||
c := &configurator{commander: commander}
|
||||
version, err := c.Version()
|
||||
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.version, version)
|
||||
commander.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
280
internal/dns/conf.go
Normal file
280
internal/dns/conf.go
Normal file
@@ -0,0 +1,280 @@
|
||||
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("%s: generating Unbound configuration", logPrefix)
|
||||
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.FileOwnership(uid, gid),
|
||||
files.FilePermissions(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) {
|
||||
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": "\"/etc/ssl/certs/ca-certificates.crt\"",
|
||||
"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": "no",
|
||||
"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("%s: %d hostnames blocked overall", logPrefix, len(hostnamesLines))
|
||||
logger.Info("%s: %d IP addresses blocked overall", logPrefix, 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",
|
||||
}
|
||||
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 {
|
||||
forwardAddresses, ok := constants.DNSAddressesMapping[provider]
|
||||
if !ok || len(forwardAddresses) == 0 {
|
||||
return nil, warnings, fmt.Errorf("DNS provider %q does not have any matching forward addresses", provider)
|
||||
}
|
||||
for _, forwardAddress := range forwardAddresses {
|
||||
forwardZoneLines = append(forwardZoneLines, fmt.Sprintf(" forward-addr: %s", forwardAddress))
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
518
internal/dns/conf_test.go
Normal file
518
internal/dns/conf_test.go
Normal file
@@ -0,0 +1,518 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/network/mocks"
|
||||
"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,
|
||||
}
|
||||
client := &mocks.Client{}
|
||||
client.On("GetContent", string(constants.MaliciousBlockListHostnamesURL)).
|
||||
Return([]byte("b\na\nc"), 200, nil).Once()
|
||||
client.On("GetContent", string(constants.MaliciousBlockListIPsURL)).
|
||||
Return([]byte("c\nd\n"), 200, nil).Once()
|
||||
emptyLogger, err := logging.NewEmptyLogger()
|
||||
require.NoError(t, err)
|
||||
lines, warnings, err := generateUnboundConf(settings, client, emptyLogger)
|
||||
require.Len(t, warnings, 0)
|
||||
require.NoError(t, err)
|
||||
client.AssertExpectations(t)
|
||||
expected := `
|
||||
server:
|
||||
cache-max-ttl: 9000
|
||||
cache-min-ttl: 3600
|
||||
do-ip4: yes
|
||||
do-ip6: no
|
||||
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-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: 9.9.9.9@853#dns.quad9.net
|
||||
forward-addr: 149.112.112.112@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()
|
||||
client := &mocks.Client{}
|
||||
if tc.malicious.blocked {
|
||||
client.On("GetContent", string(constants.MaliciousBlockListHostnamesURL)).
|
||||
Return(tc.malicious.content, 200, tc.malicious.clientErr).Once()
|
||||
client.On("GetContent", string(constants.MaliciousBlockListIPsURL)).
|
||||
Return(tc.malicious.content, 200, tc.malicious.clientErr).Once()
|
||||
}
|
||||
if tc.ads.blocked {
|
||||
client.On("GetContent", string(constants.AdsBlockListHostnamesURL)).
|
||||
Return(tc.ads.content, 200, tc.ads.clientErr).Once()
|
||||
client.On("GetContent", string(constants.AdsBlockListIPsURL)).
|
||||
Return(tc.ads.content, 200, tc.ads.clientErr).Once()
|
||||
}
|
||||
if tc.surveillance.blocked {
|
||||
client.On("GetContent", string(constants.SurveillanceBlockListHostnamesURL)).
|
||||
Return(tc.surveillance.content, 200, tc.surveillance.clientErr).Once()
|
||||
client.On("GetContent", string(constants.SurveillanceBlockListIPsURL)).
|
||||
Return(tc.surveillance.content, 200, tc.surveillance.clientErr).Once()
|
||||
}
|
||||
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)
|
||||
client.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
client := &mocks.Client{}
|
||||
client.On("GetContent", "irrelevant_url").Return(
|
||||
tc.content, tc.status, tc.clientErr,
|
||||
).Once()
|
||||
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)
|
||||
client.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
client := &mocks.Client{}
|
||||
if tc.malicious.blocked {
|
||||
client.On("GetContent", string(constants.MaliciousBlockListHostnamesURL)).
|
||||
Return(tc.malicious.content, 200, tc.malicious.clientErr).Once()
|
||||
}
|
||||
if tc.ads.blocked {
|
||||
client.On("GetContent", string(constants.AdsBlockListHostnamesURL)).
|
||||
Return(tc.ads.content, 200, tc.ads.clientErr).Once()
|
||||
}
|
||||
if tc.surveillance.blocked {
|
||||
client.On("GetContent", string(constants.SurveillanceBlockListHostnamesURL)).
|
||||
Return(tc.surveillance.content, 200, tc.surveillance.clientErr).Once()
|
||||
}
|
||||
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)
|
||||
client.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
client := &mocks.Client{}
|
||||
if tc.malicious.blocked {
|
||||
client.On("GetContent", string(constants.MaliciousBlockListIPsURL)).
|
||||
Return(tc.malicious.content, 200, tc.malicious.clientErr).Once()
|
||||
}
|
||||
if tc.ads.blocked {
|
||||
client.On("GetContent", string(constants.AdsBlockListIPsURL)).
|
||||
Return(tc.ads.content, 200, tc.ads.clientErr).Once()
|
||||
}
|
||||
if tc.surveillance.blocked {
|
||||
client.On("GetContent", string(constants.SurveillanceBlockListIPsURL)).
|
||||
Return(tc.surveillance.content, 200, tc.surveillance.clientErr).Once()
|
||||
}
|
||||
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)
|
||||
client.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
38
internal/dns/dns.go
Normal file
38
internal/dns/dns.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/qdm12/golibs/command"
|
||||
"github.com/qdm12/golibs/files"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/network"
|
||||
"github.com/qdm12/private-internet-access-docker/internal/settings"
|
||||
)
|
||||
|
||||
const logPrefix = "dns configurator"
|
||||
|
||||
type Configurator interface {
|
||||
DownloadRootHints(uid, gid int) error
|
||||
DownloadRootKey(uid, gid int) error
|
||||
MakeUnboundConf(settings settings.DNS, uid, gid int) (err error)
|
||||
SetLocalNameserver() error
|
||||
Start(logLevel uint8) (stdout io.ReadCloser, err error)
|
||||
Version() (version string, err error)
|
||||
}
|
||||
|
||||
type configurator struct {
|
||||
logger logging.Logger
|
||||
client network.Client
|
||||
fileManager files.FileManager
|
||||
commander command.Commander
|
||||
}
|
||||
|
||||
func NewConfigurator(logger logging.Logger, client network.Client, fileManager files.FileManager) Configurator {
|
||||
return &configurator{
|
||||
logger: logger,
|
||||
client: client,
|
||||
fileManager: fileManager,
|
||||
commander: command.NewCommander(),
|
||||
}
|
||||
}
|
||||
32
internal/dns/os.go
Normal file
32
internal/dns/os.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/private-internet-access-docker/internal/constants"
|
||||
)
|
||||
|
||||
func (c *configurator) SetLocalNameserver() error {
|
||||
c.logger.Info("%s: setting local nameserver to 127.0.0.1", logPrefix)
|
||||
data, err := c.fileManager.ReadFile(string(constants.ResolvConf))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s := strings.TrimSuffix(string(data), "\n")
|
||||
lines := strings.Split(s, "\n")
|
||||
if len(lines) == 1 && lines[0] == "" {
|
||||
lines = nil
|
||||
}
|
||||
found := false
|
||||
for i := range lines {
|
||||
if strings.HasPrefix(lines[i], "nameserver ") {
|
||||
lines[i] = "nameserver 127.0.0.1"
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
lines = append(lines, "nameserver 127.0.0.1")
|
||||
}
|
||||
data = []byte(strings.Join(lines, "\n"))
|
||||
return c.fileManager.WriteToFile(string(constants.ResolvConf), data)
|
||||
}
|
||||
72
internal/dns/os_test.go
Normal file
72
internal/dns/os_test.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
filesmocks "github.com/qdm12/golibs/files/mocks"
|
||||
loggingmocks "github.com/qdm12/golibs/logging/mocks"
|
||||
"github.com/qdm12/private-internet-access-docker/internal/constants"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_SetLocalNameserver(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := map[string]struct {
|
||||
data []byte
|
||||
writtenData []byte
|
||||
readErr error
|
||||
writeErr error
|
||||
err error
|
||||
}{
|
||||
"no data": {
|
||||
writtenData: []byte("nameserver 127.0.0.1"),
|
||||
},
|
||||
"read error": {
|
||||
readErr: fmt.Errorf("error"),
|
||||
err: fmt.Errorf("error"),
|
||||
},
|
||||
"write error": {
|
||||
writtenData: []byte("nameserver 127.0.0.1"),
|
||||
writeErr: fmt.Errorf("error"),
|
||||
err: fmt.Errorf("error"),
|
||||
},
|
||||
"lines without nameserver": {
|
||||
data: []byte("abc\ndef\n"),
|
||||
writtenData: []byte("abc\ndef\nnameserver 127.0.0.1"),
|
||||
},
|
||||
"lines with nameserver": {
|
||||
data: []byte("abc\nnameserver abc def\ndef\n"),
|
||||
writtenData: []byte("abc\nnameserver 127.0.0.1\ndef"),
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
fileManager := &filesmocks.FileManager{}
|
||||
fileManager.On("ReadFile", string(constants.ResolvConf)).
|
||||
Return(tc.data, tc.readErr).Once()
|
||||
if tc.readErr == nil {
|
||||
fileManager.On("WriteToFile", string(constants.ResolvConf), tc.writtenData).
|
||||
Return(tc.writeErr).Once()
|
||||
}
|
||||
logger := &loggingmocks.Logger{}
|
||||
logger.On("Info", "%s: setting local nameserver to 127.0.0.1", logPrefix).Once()
|
||||
c := &configurator{
|
||||
fileManager: fileManager,
|
||||
logger: logger,
|
||||
}
|
||||
err := c.SetLocalNameserver()
|
||||
if tc.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, tc.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
fileManager.AssertExpectations(t)
|
||||
logger.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
38
internal/dns/roots.go
Normal file
38
internal/dns/roots.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/golibs/files"
|
||||
"github.com/qdm12/private-internet-access-docker/internal/constants"
|
||||
)
|
||||
|
||||
func (c *configurator) DownloadRootHints(uid, gid int) error {
|
||||
c.logger.Info("%s: downloading root hints from %s", logPrefix, constants.NamedRootURL)
|
||||
content, status, err := c.client.GetContent(string(constants.NamedRootURL))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if status != 200 {
|
||||
return fmt.Errorf("HTTP status code is %d for %s", status, constants.NamedRootURL)
|
||||
}
|
||||
return c.fileManager.WriteToFile(
|
||||
string(constants.RootHints),
|
||||
content,
|
||||
files.FileOwnership(uid, gid),
|
||||
files.FilePermissions(0400))
|
||||
}
|
||||
|
||||
func (c *configurator) DownloadRootKey(uid, gid int) error {
|
||||
c.logger.Info("%s: downloading root key from %s", logPrefix, constants.RootKeyURL)
|
||||
content, status, err := c.client.GetContent(string(constants.RootKeyURL))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if status != 200 {
|
||||
return fmt.Errorf("HTTP status code is %d for %s", status, constants.RootKeyURL)
|
||||
}
|
||||
return c.fileManager.WriteToFile(
|
||||
string(constants.RootKey),
|
||||
content,
|
||||
files.FileOwnership(uid, gid),
|
||||
files.FilePermissions(0400))
|
||||
}
|
||||
144
internal/dns/roots_test.go
Normal file
144
internal/dns/roots_test.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
filesMocks "github.com/qdm12/golibs/files/mocks"
|
||||
loggingMocks "github.com/qdm12/golibs/logging/mocks"
|
||||
networkMocks "github.com/qdm12/golibs/network/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/qdm12/private-internet-access-docker/internal/constants"
|
||||
)
|
||||
|
||||
func Test_DownloadRootHints(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := map[string]struct {
|
||||
content []byte
|
||||
status int
|
||||
clientErr error
|
||||
writeErr error
|
||||
err error
|
||||
}{
|
||||
"no data": {
|
||||
status: http.StatusOK,
|
||||
},
|
||||
"bad status": {
|
||||
status: http.StatusBadRequest,
|
||||
err: fmt.Errorf("HTTP status code is 400 for https://raw.githubusercontent.com/qdm12/files/master/named.root.updated"),
|
||||
},
|
||||
"client error": {
|
||||
clientErr: fmt.Errorf("error"),
|
||||
err: fmt.Errorf("error"),
|
||||
},
|
||||
"write error": {
|
||||
status: http.StatusOK,
|
||||
writeErr: fmt.Errorf("error"),
|
||||
err: fmt.Errorf("error"),
|
||||
},
|
||||
"data": {
|
||||
content: []byte("content"),
|
||||
status: http.StatusOK,
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger := &loggingMocks.Logger{}
|
||||
logger.On("Info", "%s: downloading root hints from %s", logPrefix, constants.NamedRootURL).Once()
|
||||
client := &networkMocks.Client{}
|
||||
client.On("GetContent", string(constants.NamedRootURL)).
|
||||
Return(tc.content, tc.status, tc.clientErr).Once()
|
||||
fileManager := &filesMocks.FileManager{}
|
||||
if tc.clientErr == nil && tc.status == http.StatusOK {
|
||||
fileManager.On(
|
||||
"WriteToFile",
|
||||
string(constants.RootHints),
|
||||
tc.content,
|
||||
mock.AnythingOfType("files.WriteOptionSetter"),
|
||||
mock.AnythingOfType("files.WriteOptionSetter")).
|
||||
Return(tc.writeErr).Once()
|
||||
}
|
||||
c := &configurator{logger: logger, client: client, fileManager: fileManager}
|
||||
err := c.DownloadRootHints(1000, 1000)
|
||||
if tc.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, tc.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
logger.AssertExpectations(t)
|
||||
client.AssertExpectations(t)
|
||||
fileManager.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_DownloadRootKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := map[string]struct {
|
||||
content []byte
|
||||
status int
|
||||
clientErr error
|
||||
writeErr error
|
||||
err error
|
||||
}{
|
||||
"no data": {
|
||||
status: http.StatusOK,
|
||||
},
|
||||
"bad status": {
|
||||
status: http.StatusBadRequest,
|
||||
err: fmt.Errorf("HTTP status code is 400 for https://raw.githubusercontent.com/qdm12/files/master/root.key.updated"),
|
||||
},
|
||||
"client error": {
|
||||
clientErr: fmt.Errorf("error"),
|
||||
err: fmt.Errorf("error"),
|
||||
},
|
||||
"write error": {
|
||||
status: http.StatusOK,
|
||||
writeErr: fmt.Errorf("error"),
|
||||
err: fmt.Errorf("error"),
|
||||
},
|
||||
"data": {
|
||||
content: []byte("content"),
|
||||
status: http.StatusOK,
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger := &loggingMocks.Logger{}
|
||||
logger.On("Info", "%s: downloading root key from %s", logPrefix, constants.RootKeyURL).Once()
|
||||
client := &networkMocks.Client{}
|
||||
client.On("GetContent", string(constants.RootKeyURL)).
|
||||
Return(tc.content, tc.status, tc.clientErr).Once()
|
||||
fileManager := &filesMocks.FileManager{}
|
||||
if tc.clientErr == nil && tc.status == http.StatusOK {
|
||||
fileManager.On(
|
||||
"WriteToFile",
|
||||
string(constants.RootKey),
|
||||
tc.content,
|
||||
mock.AnythingOfType("files.WriteOptionSetter"),
|
||||
mock.AnythingOfType("files.WriteOptionSetter"),
|
||||
).Return(tc.writeErr).Once()
|
||||
}
|
||||
c := &configurator{logger: logger, client: client, fileManager: fileManager}
|
||||
err := c.DownloadRootKey(1000, 1001)
|
||||
if tc.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, tc.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
logger.AssertExpectations(t)
|
||||
client.AssertExpectations(t)
|
||||
fileManager.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user