Skip to content

Commit

Permalink
Config restructuring (#771)
Browse files Browse the repository at this point in the history
To declutter the global top level config options i propose the grouping of ports and logging options as child options of top level options.

New structure:
ports:
  dns: 43
  http: 4000
  https: 4443
  tls: 853
log:
  level: warn
  format: json
  privacy: true
  timestamp: false
  • Loading branch information
kwitsch committed Dec 2, 2022
1 parent 4ad7670 commit b73cd3b
Show file tree
Hide file tree
Showing 16 changed files with 356 additions and 133 deletions.
2 changes: 1 addition & 1 deletion cmd/root.go
Expand Up @@ -86,7 +86,7 @@ func initConfig() {
util.FatalOnError("unable to load configuration: ", err)
}

log.ConfigureLogger(cfg.LogLevel, cfg.LogFormat, cfg.LogTimestamp)
log.ConfigureLogger(&cfg.Log)

if len(cfg.HTTPPorts) != 0 {
split := strings.Split(cfg.HTTPPorts[0], ":")
Expand Down
2 changes: 1 addition & 1 deletion cmd/serve.go
Expand Up @@ -38,7 +38,7 @@ func startServer(_ *cobra.Command, _ []string) error {
return fmt.Errorf("unable to load configuration: %w", err)
}

log.ConfigureLogger(cfg.LogLevel, cfg.LogFormat, cfg.LogTimestamp)
log.ConfigureLogger(&cfg.Log)

signals := make(chan os.Signal, 1)

Expand Down
109 changes: 93 additions & 16 deletions config/config.go
Expand Up @@ -465,26 +465,43 @@ type Config struct {
QueryLog QueryLogConfig `yaml:"queryLog"`
Prometheus PrometheusConfig `yaml:"prometheus"`
Redis RedisConfig `yaml:"redis"`
LogLevel log.Level `yaml:"logLevel" default:"info"`
LogFormat log.FormatType `yaml:"logFormat" default:"text"`
LogPrivacy bool `yaml:"logPrivacy" default:"false"`
LogTimestamp bool `yaml:"logTimestamp" default:"true"`
DNSPorts ListenConfig `yaml:"port" default:"[\"53\"]"`
HTTPPorts ListenConfig `yaml:"httpPort"`
HTTPSPorts ListenConfig `yaml:"httpsPort"`
TLSPorts ListenConfig `yaml:"tlsPort"`
Log log.Config `yaml:"log"`
Ports PortsConfig `yaml:"ports"`
DoHUserAgent string `yaml:"dohUserAgent"`
MinTLSServeVer string `yaml:"minTlsServeVersion" default:"1.2"`
StartVerifyUpstream bool `yaml:"startVerifyUpstream" default:"false"`
CertFile string `yaml:"certFile"`
KeyFile string `yaml:"keyFile"`
BootstrapDNS BootstrapConfig `yaml:"bootstrapDns"`
HostsFile HostsFileConfig `yaml:"hostsFile"`
FqdnOnly bool `yaml:"fqdnOnly" default:"false"`
Filtering FilteringConfig `yaml:"filtering"`
Ede EdeConfig `yaml:"ede"`
// Deprecated
DisableIPv6 bool `yaml:"disableIPv6" default:"false"`
CertFile string `yaml:"certFile"`
KeyFile string `yaml:"keyFile"`
BootstrapDNS BootstrapConfig `yaml:"bootstrapDns"`
HostsFile HostsFileConfig `yaml:"hostsFile"`
FqdnOnly bool `yaml:"fqdnOnly" default:"false"`
Filtering FilteringConfig `yaml:"filtering"`
Ede EdeConfig `yaml:"ede"`
DisableIPv6 bool `yaml:"disableIPv6" default:"false"`
// Deprecated
LogLevel log.Level `yaml:"logLevel" default:"info"`
// Deprecated
LogFormat log.FormatType `yaml:"logFormat" default:"text"`
// Deprecated
LogPrivacy bool `yaml:"logPrivacy" default:"false"`
// Deprecated
LogTimestamp bool `yaml:"logTimestamp" default:"true"`
// Deprecated
DNSPorts ListenConfig `yaml:"port" default:"[\"53\"]"`
// Deprecated
HTTPPorts ListenConfig `yaml:"httpPort"`
// Deprecated
HTTPSPorts ListenConfig `yaml:"httpsPort"`
// Deprecated
TLSPorts ListenConfig `yaml:"tlsPort"`
}

type PortsConfig struct {
DNS ListenConfig `yaml:"dns" default:"[\"53\"]"`
HTTP ListenConfig `yaml:"http"`
HTTPS ListenConfig `yaml:"https"`
TLS ListenConfig `yaml:"tls"`
}

type BootstrapConfig bootstrapConfig // to avoid infinite recursion. See BootstrapConfig.UnmarshalYAML.
Expand Down Expand Up @@ -742,6 +759,66 @@ func validateConfig(cfg *Config) {
log.Log().Warnf("'blocking.startStrategy' with 'fast' will ignore 'blocking.failStartOnListError'.")
}
}

fixDeprecatedLog(cfg)

fixDeprecatedPorts(cfg)
}

// fixDeprecatedLog ensures backwards compatibility for logging options
func fixDeprecatedLog(cfg *Config) {
if cfg.LogLevel != log.LevelInfo && cfg.Log.Level == log.LevelInfo {
log.Log().Warnf("'logLevel' is deprecated. Please use 'log.level' instead.")

cfg.Log.Level = cfg.LogLevel
}

if cfg.LogFormat != log.FormatTypeText && cfg.Log.Format == log.FormatTypeText {
log.Log().Warnf("'logFormat' is deprecated. Please use 'log.format' instead.")

cfg.Log.Format = cfg.LogFormat
}

if cfg.LogPrivacy && !cfg.Log.Privacy {
log.Log().Warnf("'logPrivacy' is deprecated. Please use 'log.privacy' instead.")

cfg.Log.Privacy = cfg.LogPrivacy
}

if !cfg.LogTimestamp && cfg.Log.Timestamp {
log.Log().Warnf("'logTimestamp' is deprecated. Please use 'log.timestamp' instead.")

cfg.Log.Timestamp = cfg.LogTimestamp
}
}

// fixDeprecatedPorts ensures backwards compatibility for ports options
func fixDeprecatedPorts(cfg *Config) {
defaultDNSPort := ListenConfig([]string{"53"})
if (len(cfg.DNSPorts) > 1 || (len(cfg.DNSPorts) == 1 && cfg.DNSPorts[0] != defaultDNSPort[0])) &&
(len(cfg.Ports.DNS) == 1 && cfg.Ports.DNS[0] == defaultDNSPort[0]) {
log.Log().Warnf("'port' is deprecated. Please use 'ports.dns' instead.")

cfg.Ports.DNS = cfg.DNSPorts
}

if len(cfg.HTTPPorts) > 0 && len(cfg.Ports.HTTP) == 0 {
log.Log().Warnf("'httpPort' is deprecated. Please use 'ports.http' instead.")

cfg.Ports.HTTP = cfg.HTTPPorts
}

if len(cfg.HTTPSPorts) > 0 && len(cfg.Ports.HTTPS) == 0 {
log.Log().Warnf("'httpsPort' is deprecated. Please use 'ports.https' instead.")

cfg.Ports.HTTPS = cfg.HTTPSPorts
}

if len(cfg.TLSPorts) > 0 && len(cfg.Ports.TLS) == 0 {
log.Log().Warnf("'tlsPort' is deprecated. Please use 'ports.tls' instead.")

cfg.Ports.TLS = cfg.TLSPorts
}
}

// GetConfig returns the current config
Expand Down
148 changes: 110 additions & 38 deletions config/config_test.go
Expand Up @@ -5,10 +5,11 @@ import (
"net"
"time"

"github.com/creasty/defaults"
"github.com/miekg/dns"

"github.com/0xERR0R/blocky/helpertest"
. "github.com/0xERR0R/blocky/log"
"github.com/0xERR0R/blocky/log"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
Expand All @@ -25,6 +26,111 @@ var _ = Describe("Config", func() {
DeferCleanup(tmpDir.Clean)
})

Describe("Deprecated parameters are converted", func() {
var (
c Config
)
BeforeEach(func() {
err := defaults.Set(&c)
Expect(err).Should(Succeed())
})

When("parameter 'disableIPv6' is set", func() {
It("should add 'AAAA' to filter.queryTypes", func() {
c.DisableIPv6 = true
validateConfig(&c)
Expect(c.Filtering.QueryTypes).Should(HaveKey(QType(dns.TypeAAAA)))
Expect(c.Filtering.QueryTypes.Contains(dns.Type(dns.TypeAAAA))).Should(BeTrue())
})
})

When("parameter 'failStartOnListError' is set", func() {
BeforeEach(func() {
c.Blocking = BlockingConfig{
FailStartOnListError: true,
StartStrategy: StartStrategyTypeBlocking,
}
})
It("should change StartStrategy blocking to failOnError", func() {
validateConfig(&c)
Expect(c.Blocking.StartStrategy).Should(Equal(StartStrategyTypeFailOnError))
})
It("shouldn't change StartStrategy if set to fast", func() {
c.Blocking.StartStrategy = StartStrategyTypeFast
validateConfig(&c)
Expect(c.Blocking.StartStrategy).Should(Equal(StartStrategyTypeFast))
})
})

When("parameter 'logLevel' is set", func() {
It("should convert to log.level", func() {
c.LogLevel = log.LevelDebug
validateConfig(&c)
Expect(c.Log.Level).Should(Equal(log.LevelDebug))
})
})

When("parameter 'logFormat' is set", func() {
It("should convert to log.format", func() {
c.LogFormat = log.FormatTypeJson
validateConfig(&c)
Expect(c.Log.Format).Should(Equal(log.FormatTypeJson))
})
})

When("parameter 'logPrivacy' is set", func() {
It("should convert to log.privacy", func() {
c.LogPrivacy = true
validateConfig(&c)
Expect(c.Log.Privacy).Should(BeTrue())
})
})

When("parameter 'logTimestamp' is set", func() {
It("should convert to log.timestamp", func() {
c.LogTimestamp = false
validateConfig(&c)
Expect(c.Log.Timestamp).Should(BeFalse())
})
})

When("parameter 'port' is set", func() {
It("should convert to ports.dns", func() {
ports := ListenConfig([]string{"5333"})
c.DNSPorts = ports
validateConfig(&c)
Expect(c.Ports.DNS).Should(Equal(ports))
})
})

When("parameter 'httpPort' is set", func() {
It("should convert to ports.http", func() {
ports := ListenConfig([]string{"5333"})
c.HTTPPorts = ports
validateConfig(&c)
Expect(c.Ports.HTTP).Should(Equal(ports))
})
})

When("parameter 'httpsPort' is set", func() {
It("should convert to ports.https", func() {
ports := ListenConfig([]string{"5333"})
c.HTTPSPorts = ports
validateConfig(&c)
Expect(c.Ports.HTTPS).Should(Equal(ports))
})
})

When("parameter 'tlsPort' is set", func() {
It("should convert to ports.tls", func() {
ports := ListenConfig([]string{"5333"})
c.TLSPorts = ports
validateConfig(&c)
Expect(c.Ports.TLS).Should(Equal(ports))
})
})
})

Describe("Creation of Config", func() {
When("Test config file will be parsed", func() {
It("should return a valid config struct", func() {
Expand Down Expand Up @@ -155,15 +261,15 @@ var _ = Describe("Config", func() {
})

When("bootstrapDns is defined", func() {
It("should is backwards compatible", func() {
It("should be backwards compatible", func() {
cfg := Config{}
data := "bootstrapDns: 0.0.0.0"

err := unmarshalConfig([]byte(data), &cfg)
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.BootstrapDNS.Upstream.Host).Should(Equal("0.0.0.0"))
})
It("should is backwards compatible", func() {
It("should be backwards compatible", func() {
cfg := Config{}
data := `
bootstrapDns:
Expand All @@ -189,40 +295,6 @@ bootstrapDns:
})
})

When("Deprecated parameter 'disableIPv6' is set", func() {
It("should add 'AAAA' to filter.queryTypes", func() {
c := &Config{
DisableIPv6: true,
}
validateConfig(c)
Expect(c.Filtering.QueryTypes).Should(HaveKey(QType(dns.TypeAAAA)))
Expect(c.Filtering.QueryTypes.Contains(dns.Type(dns.TypeAAAA))).Should(BeTrue())
})
})

When("Deprecated parameter 'failStartOnListError' is set", func() {
var (
c Config
)
BeforeEach(func() {
c = Config{
Blocking: BlockingConfig{
FailStartOnListError: true,
StartStrategy: StartStrategyTypeBlocking,
},
}
})
It("should change StartStrategy blocking to failOnError", func() {
validateConfig(&c)
Expect(c.Blocking.StartStrategy).Should(Equal(StartStrategyTypeFailOnError))
})
It("shouldn't change StartStrategy if set to fast", func() {
c.Blocking.StartStrategy = StartStrategyTypeFast
validateConfig(&c)
Expect(c.Blocking.StartStrategy).Should(Equal(StartStrategyTypeFast))
})
})

When("config directory does not exist", func() {
It("should return error", func() {
_, err = LoadConfig(tmpDir.JoinPath("config.yml"), true)
Expand All @@ -235,7 +307,7 @@ bootstrapDns:
_, err = LoadConfig(tmpDir.JoinPath("config.yml"), false)

Expect(err).Should(Succeed())
Expect(config.LogLevel).Should(Equal(LevelInfo))
Expect(config.LogLevel).Should(Equal(log.LevelInfo))
})
})
})
Expand Down
37 changes: 22 additions & 15 deletions docs/config.yml
Expand Up @@ -208,13 +208,6 @@ redis:
- redis-sentinel2:26379
- redis-sentinel3:26379

# optional: DNS listener port(s) and bind ip address(es), default 53 (UDP and TCP). Example: 53, :53, "127.0.0.1:5353,[::1]:5353"
port: 53
# optional: Port(s) and bind ip address(es) for DoT (DNS-over-TLS) listener. Example: 853, 127.0.0.1:853
#tlsPort: 853
# optional: HTTPS listener port(s) and bind ip address(es), default empty = no http listener. If > 0, will be used for prometheus metrics, pprof, REST API, DoH... Example: 443, :443, 127.0.0.1:443
httpPort: 4000
#httpsPort: 443
# optional: Mininal TLS version that the DoH and DoT server will use
minTlsServeVersion: 1.3
# if https port > 0: path to cert and key file for SSL encryption. if not set, self-signed certificate will be generated
Expand All @@ -238,14 +231,28 @@ hostsFile:
refreshPeriod: 30m
# optional: Whether loopback hosts addresses (127.0.0.0/8 and ::1) should be filtered or not, default: false
filterLoopback: true
# optional: Log level (one from debug, info, warn, error). Default: info
logLevel: info
# optional: Log format (text or json). Default: text
logFormat: text
# optional: log timestamps. Default: true
logTimestamp: true
# optional: obfuscate log output (replace all alphanumeric characters with *) for user sensitive data like request domains or responses to increase privacy. Default: false
logPrivacy: false

# optional: ports configuration
ports:
# optional: DNS listener port(s) and bind ip address(es), default 53 (UDP and TCP). Example: 53, :53, "127.0.0.1:5353,[::1]:5353"
dns: 53
# optional: Port(s) and bind ip address(es) for DoT (DNS-over-TLS) listener. Example: 853, 127.0.0.1:853
tls: 853
# optional: Port(s) and optional bind ip address(es) to serve HTTPS used for prometheus metrics, pprof, REST API, DoH... If you wish to specify a specific IP, you can do so such as 192.168.0.1:443. Example: 443, :443, 127.0.0.1:443,[::1]:443
https: 443
# optional: Port(s) and optional bind ip address(es) to serve HTTP used for prometheus metrics, pprof, REST API, DoH... If you wish to specify a specific IP, you can do so such as 192.168.0.1:4000. Example: 4000, :4000, 127.0.0.1:4000,[::1]:4000
http: 4000

# optional: logging configuration
log:
# optional: Log level (one from debug, info, warn, error). Default: info
level: info
# optional: Log format (text or json). Default: text
format: text
# optional: log timestamps. Default: true
timestamp: true
# optional: obfuscate log output (replace all alphanumeric characters with *) for user sensitive data like request domains or responses to increase privacy. Default: false
privacy: false

# optional: add EDE error codes to dns response
ede:
Expand Down

0 comments on commit b73cd3b

Please sign in to comment.