Skip to content
This repository has been archived by the owner on Dec 15, 2024. It is now read-only.

Commit

Permalink
cli: enable running 'check' command (#16)
Browse files Browse the repository at this point in the history
This PR refactors the donutdns standalone server to be able to run
subcommands. The first subcommand added is 'check', which can be
used to determine if a specified domain would be blocked or allowed
by a donutdns server in accordance with a given configuration.
  • Loading branch information
shoenig authored Oct 6, 2022
1 parent 9854dbc commit f42ba2c
Show file tree
Hide file tree
Showing 15 changed files with 342 additions and 147 deletions.
3 changes: 2 additions & 1 deletion agent/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"text/template"

"github.com/coredns/coredns/plugin/pkg/log"
"github.com/shoenig/donutdns/output"
"github.com/shoenig/extractors/env"
)

Expand Down Expand Up @@ -95,7 +96,7 @@ func ConfigFromEnv(e env.Environment) *CoreConfig {
}

// Log cc to plog.
func (cc *CoreConfig) Log(plog log.P) {
func (cc *CoreConfig) Log(logger output.Info) {
log.Infof("DONUT_DNS_PORT: %d", cc.Port)
log.Infof("DONUT_DNS_NO_DEBUG: %t", cc.NoDebug)
log.Infof("DONUT_DNS_NO_LOG: %t", cc.NoLog)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.18
require (
github.com/coredns/caddy v1.1.1
github.com/coredns/coredns v1.8.5
github.com/google/subcommands v1.2.0
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/hashicorp/go-set v0.1.6
github.com/miekg/dns v1.1.43
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
Expand Down
49 changes: 34 additions & 15 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@
package main

import (
"context"
"flag"
"os"
"strconv"

"github.com/coredns/caddy"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/coremain"
"github.com/coredns/coredns/plugin"
_ "github.com/coredns/coredns/plugin/debug"
_ "github.com/coredns/coredns/plugin/forward"
_ "github.com/coredns/coredns/plugin/log"
"github.com/coredns/coredns/plugin/pkg/log"
"github.com/google/subcommands"
"github.com/shoenig/donutdns/agent"
"github.com/shoenig/donutdns/plugins/donutdns"
"github.com/shoenig/donutdns/subcmds"
"github.com/shoenig/extractors/env"
)

Expand All @@ -26,35 +32,48 @@ var directives = []string{
"shutdown",
}

// pLog is the plugin logger associated with donutdns.
var pLog = log.NewWithPlugin(donutdns.PluginName)
// pluginLogger is the plugin logger associated with donutdns.
var pluginLogger = log.NewWithPlugin(donutdns.PluginName)

// getCC generates a CoreDNS CoreConfig file using environment variables associated with
// donutdns configuration.
// getCC generates a CoreDNS CoreConfig file using environment variables associated
// with donutdns configuration.
func getCC() *agent.CoreConfig {
cc := agent.ConfigFromEnv(env.OS)
agent.ApplyDefaults(cc)
cc.Log(pLog)
cc.Log(pluginLogger)
return cc
}

func init() {
func setupCC() {
// get core config from environment
cc := getCC()

// set plugin core config
dnsserver.Port = strconv.Itoa(cc.Port)
dnsserver.Directives = directives
caddy.SetDefaultCaddyfileLoader(donutdns.PluginName, caddy.LoaderFunc(func(serverType string) (caddy.Input, error) {
return caddy.CaddyfileInput{
Filepath: donutdns.PluginName,
Contents: []byte(cc.Generate()),
ServerTypeName: donutdns.ServerType,
}, nil
}))
caddy.SetDefaultCaddyfileLoader(
donutdns.PluginName,
caddy.LoaderFunc(func(serverType string) (caddy.Input, error) {
return caddy.CaddyfileInput{
Filepath: donutdns.PluginName,
Contents: []byte(cc.Generate()),
ServerTypeName: donutdns.ServerType,
}, nil
}))
}

func main() {
// launch CoreDNS; plugin configuration must be in init blocks
coremain.Run()
if len(os.Args) == 1 {
// launch CoreDNS; plugin configuration must be initialized first
setupCC()
plugin.Register(donutdns.PluginName, donutdns.Setup)
coremain.Run()
return
}

subcommands.Register(subcmds.NewCheckCmd(), "donutdns")

flag.Parse()
ctx := context.Background()
os.Exit(int(subcommands.Execute(ctx)))
}
36 changes: 36 additions & 0 deletions output/output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package output

import (
"fmt"
)

// Info represents any type that can log via Infof.
type Info interface {
Infof(string, ...any)
}

// Error represents any type that can log via Errorf.
type Error interface {
Errorf(string, ...any)
}

// CLI is a wrapper around fmt.Print functions for satisfying interfaces.
type CLI struct{}

func (o *CLI) print(msg string, args ...any) {
s := fmt.Sprintf(msg, args...)
fmt.Println(s)
}

func (o *CLI) Infof(msg string, args ...any) {
o.print(msg, args...)
}

func (o *CLI) Errorf(msg string, args ...any) {
o.print(msg, args...)
}

type Logger interface {
Info
Error
}
28 changes: 12 additions & 16 deletions plugins/donutdns/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (

"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/request"
"github.com/hashicorp/go-set"
"github.com/miekg/dns"
"github.com/shoenig/donutdns/sources"
)

const (
Expand All @@ -18,11 +18,7 @@ const (

type DonutDNS struct {
Next plugin.Handler

defaultLists bool
suffix *set.Set[string]
block *set.Set[string]
allow *set.Set[string]
sets *sources.Sets
}

func (dd DonutDNS) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
Expand All @@ -31,22 +27,22 @@ func (dd DonutDNS) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms
origQuery := state.Name()
cleanQuery := strings.Trim(origQuery, ".")

if dd.allow.Contains(cleanQuery) {
pLog.Debugf("query for %s is explicitly allowed", cleanQuery)
if dd.sets.Allow(cleanQuery) {
pluginLogger.Debugf("query for %s is explicitly allowed", cleanQuery)
return plugin.NextOrFailure(dd.Name(), dd.Next, ctx, w, r)
}

if dd.block.Contains(cleanQuery) {
pLog.Debugf("query for %s is blocked by match", cleanQuery)
if dd.sets.BlockByMatch(cleanQuery) {
pluginLogger.Debugf("query for %s is blocked by match", cleanQuery)
return dd.null(state.QType(), origQuery, ctx, w, r)
}

if blockBySuffix(dd.suffix, cleanQuery) {
pLog.Debugf("query for %s is blocked by suffix", cleanQuery)
if dd.sets.BlockBySuffix(cleanQuery) {
pluginLogger.Debugf("query for %s is blocked by suffix", cleanQuery)
return dd.null(state.QType(), origQuery, ctx, w, r)
}

pLog.Debugf("query for %s is implicitly allowed", cleanQuery)
pluginLogger.Debugf("query for %s is implicitly allowed", cleanQuery)
return plugin.NextOrFailure(dd.Name(), dd.Next, ctx, w, r)
}

Expand All @@ -62,18 +58,18 @@ func (dd DonutDNS) null(qType uint16, query string, ctx context.Context, w dns.R
case dns.TypeHTTPS:
answers = dd.https(query)
default:
pLog.Debugf("query: %s type: %s not recognized, fallthrough", query, queryType)
pluginLogger.Debugf("query: %s type: %s not recognized, fallthrough", query, queryType)
return plugin.NextOrFailure(dd.Name(), dd.Next, ctx, w, r)
}

pLog.Infof("BLOCK query (%s) for %s", queryType, query)
pluginLogger.Infof("BLOCK query (%s) for %s", queryType, query)

m := new(dns.Msg)
m.SetReply(r)
m.Authoritative = true
m.Answer = answers
if err := w.WriteMsg(m); err != nil {
pLog.Errorf("failed to write msg: %v", err)
pluginLogger.Errorf("failed to write msg: %v", err)
return dns.RcodeServerFailure, err
}

Expand Down
82 changes: 21 additions & 61 deletions plugins/donutdns/setup.go
Original file line number Diff line number Diff line change
@@ -1,34 +1,24 @@
package donutdns

import (
"os"

"github.com/coredns/caddy"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/log"
"github.com/hashicorp/go-set"
"github.com/shoenig/donutdns/agent"
"github.com/shoenig/donutdns/sources"
"github.com/shoenig/donutdns/sources/extract"
"github.com/shoenig/donutdns/sources/fetch"
"github.com/shoenig/ignore"
)

var pLog = log.NewWithPlugin(PluginName)

func init() {
plugin.Register(PluginName, setup)
}
var pluginLogger = log.NewWithPlugin(PluginName)

// Setup will parse plugin config and register the donutdns plugin
// with the CoreDNS core server.
//
// todo: test with TestController
func setup(c *caddy.Controller) error {
func Setup(c *caddy.Controller) error {

dd := DonutDNS{
defaultLists: true,
suffix: set.New[string](100),
block: set.New[string](100),
allow: set.New[string](100),
}
// reconstruct the parts of CoreConfig for initializing the allow/block lists
cc := new(agent.CoreConfig)

for c.Next() {
_ = c.RemainingArgs()
Expand All @@ -38,61 +28,55 @@ func setup(c *caddy.Controller) error {
if !c.NextArg() {
return c.ArgErr()
}
dd.defaultLists = c.Val() == "true"
if dd.defaultLists {
defaults(dd.block)
}
cc.NoDefaults = c.Val() == "false"

case "allow_file":
if !c.NextArg() {
return c.ArgErr()
}
if filename := c.Val(); filename != "" {
custom(filename, dd.allow)
}
cc.AllowFile = c.Val()

case "block_file":
if !c.NextArg() {
return c.ArgErr()
}
if filename := c.Val(); filename != "" {
custom(filename, dd.block)
}
cc.BlockFile = c.Val()

case "suffix_file":
if !c.NextArg() {
return c.ArgErr()
}
if filename := c.Val(); filename != "" {
custom(filename, dd.suffix)
}
cc.SuffixFile = c.Val()

case "allow":
if !c.NextArg() {
return c.ArgErr()
}
dd.allow.Insert(c.Val())
cc.Allows = append(cc.Allows, c.Val())

case "block":
if !c.NextArg() {
return c.ArgErr()
}
dd.block.Insert(c.Val())
cc.Blocks = append(cc.Blocks, c.Val())

case "suffix":
if !c.NextArg() {
return c.ArgErr()
}
dd.suffix.Insert(c.Val())
cc.Suffix = append(cc.Suffix, c.Val())
}
}
}

pLog.Infof("domains on explicit allow-list: %d", dd.allow.Size())
pLog.Infof("domains on explicit block-list: %d", dd.block.Size())
pLog.Infof("domains on suffixes block-list: %d", dd.suffix.Size())
sets := sources.New(pluginLogger, cc)
allow, block, suffix := sets.Size()
pluginLogger.Infof("domains on explicit allow-list: %d", allow)
pluginLogger.Infof("domains on explicit block-list: %d", block)
pluginLogger.Infof("domains on suffixes block-list: %d", suffix)

// Add the Plugin to CoreDNS, so Servers can use it in their plugin chain.
dd := DonutDNS{sets: sets}
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
dd.Next = next
return dd
Expand All @@ -101,27 +85,3 @@ func setup(c *caddy.Controller) error {
// Plugin loaded okay.
return nil
}

func defaults(set *set.Set[string]) {
downloader := fetch.NewDownloader(pLog)
s, err := downloader.Download(sources.Defaults())
if err != nil {
panic(err)
}
set.InsertSet(s)
}

func custom(filename string, set *set.Set[string]) {
// for now, everything uses the generic domain extractor
ex := extract.New(extract.Generic)
f, err := os.Open(filename)
if err != nil {
panic(err)
}
defer ignore.Close(f)
s, err := ex.Extract(f)
if err != nil {
panic(err)
}
set.InsertSet(s)
}
Loading

0 comments on commit f42ba2c

Please sign in to comment.