Commit 79f37a14 authored by An Xiao's avatar An Xiao Committed by Yong Tang

Add plugin ACL for source ip filtering (#3103)

* Add plugin ACL for source ip filtering
Signed-off-by: default avatarAn Xiao <hac@zju.edu.cn>

* Allow all arguments to be optional and support multiple qtypes in a single policy
Signed-off-by: default avatarAn Xiao <hac@zju.edu.cn>

* Add newline before third party imports
Signed-off-by: default avatarAn Xiao <hac@zju.edu.cn>

* Use camel instead of underscore in method name
Signed-off-by: default avatarAn Xiao <hac@zju.edu.cn>

* Start with an upper case letter in t.Errorf()
Signed-off-by: default avatarAn Xiao <hac@zju.edu.cn>

* Use the qtype parse logic in miekg/dns
Signed-off-by: default avatarAn Xiao <hac@zju.edu.cn>

* Use third party trie implementation as the ip filter
Signed-off-by: default avatarAn Xiao <hac@zju.edu.cn>

* Update based on rdrozhdzh's comment
Signed-off-by: default avatarAn Xiao <hac@zju.edu.cn>

* Change the type of action to int
Signed-off-by: default avatarAn Xiao <hac@zju.edu.cn>

* Add IPv6 support
Signed-off-by: default avatarAn Xiao <hac@zju.edu.cn>

* Update plugin.cfg
Signed-off-by: default avatarAn Xiao <hac@zju.edu.cn>

* Remove file functionality
Signed-off-by: default avatarAn Xiao <hac@zju.edu.cn>

* Update
Signed-off-by: default avatarXiao An <hac@zju.edu.cn>

* Update README
Signed-off-by: default avatarXiao An <hac@zju.edu.cn>

* remove comments
Signed-off-by: default avatarXiao An <hac@zju.edu.cn>

* update
Signed-off-by: default avatarXiao An <hac@zju.edu.cn>

* Update dependency
Signed-off-by: default avatarXiao An <hac@zju.edu.cn>

* Update
Signed-off-by: default avatarXiao An <hac@zju.edu.cn>

* Update test
Signed-off-by: default avatarXiao An <hac@zju.edu.cn>

* Add OWNERS
Signed-off-by: default avatarXiao An <hac@zju.edu.cn>

* Refactor shouldBlock and skip useless check
Signed-off-by: default avatarXiao An <hac@zju.edu.cn>

* Introduce ActionNone
Signed-off-by: default avatarXiao An <hac@zju.edu.cn>

* Update label name
Signed-off-by: default avatarXiao An <hac@zju.edu.cn>

* Avoid capitalizing private types
Signed-off-by: default avatarXiao An <hac@zju.edu.cn>
parent 7894154b
...@@ -26,6 +26,7 @@ var Directives = []string{ ...@@ -26,6 +26,7 @@ var Directives = []string{
"errors", "errors",
"log", "log",
"dnstap", "dnstap",
"acl",
"any", "any",
"chaos", "chaos",
"loadbalance", "loadbalance",
......
...@@ -5,6 +5,7 @@ package plugin ...@@ -5,6 +5,7 @@ package plugin
import ( import (
// Include all plugins. // Include all plugins.
_ "github.com/caddyserver/caddy/onevent" _ "github.com/caddyserver/caddy/onevent"
_ "github.com/coredns/coredns/plugin/acl"
_ "github.com/coredns/coredns/plugin/any" _ "github.com/coredns/coredns/plugin/any"
_ "github.com/coredns/coredns/plugin/auto" _ "github.com/coredns/coredns/plugin/auto"
_ "github.com/coredns/coredns/plugin/autopath" _ "github.com/coredns/coredns/plugin/autopath"
......
...@@ -17,6 +17,7 @@ require ( ...@@ -17,6 +17,7 @@ require (
github.com/coreos/bbolt v1.3.2 // indirect github.com/coreos/bbolt v1.3.2 // indirect
github.com/coreos/go-systemd v0.0.0-20190212144455-93d5ec2c7f76 // indirect github.com/coreos/go-systemd v0.0.0-20190212144455-93d5ec2c7f76 // indirect
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc // indirect
github.com/dnstap/golang-dnstap v0.0.0-20170829151710-2cf77a2b5e11 github.com/dnstap/golang-dnstap v0.0.0-20170829151710-2cf77a2b5e11
github.com/evanphx/json-patch v4.1.0+incompatible // indirect github.com/evanphx/json-patch v4.1.0+incompatible // indirect
github.com/farsightsec/golang-framestream v0.0.0-20181102145529-8a0cb8ba8710 github.com/farsightsec/golang-framestream v0.0.0-20181102145529-8a0cb8ba8710
...@@ -26,6 +27,7 @@ require ( ...@@ -26,6 +27,7 @@ require (
github.com/gophercloud/gophercloud v0.0.0-20190307220656-fe1ba5ce12dd // indirect github.com/gophercloud/gophercloud v0.0.0-20190307220656-fe1ba5ce12dd // indirect
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645
github.com/imdario/mergo v0.3.7 // indirect github.com/imdario/mergo v0.3.7 // indirect
github.com/infobloxopen/go-trees v0.0.0-20190313150506-2af4e13f9062
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 github.com/matttproud/golang_protobuf_extensions v1.0.1
github.com/miekg/dns v1.1.16 github.com/miekg/dns v1.1.16
...@@ -37,6 +39,7 @@ require ( ...@@ -37,6 +39,7 @@ require (
github.com/prometheus/client_golang v1.1.0 github.com/prometheus/client_golang v1.1.0
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90
github.com/prometheus/common v0.6.0 github.com/prometheus/common v0.6.0
github.com/seiflotfy/cuckoofilter v0.0.0-20190302225222-764cb5258d9b
github.com/sirupsen/logrus v1.4.2 // indirect github.com/sirupsen/logrus v1.4.2 // indirect
github.com/spf13/cobra v0.0.5 // indirect github.com/spf13/cobra v0.0.5 // indirect
github.com/tinylib/msgp v1.1.0 // indirect github.com/tinylib/msgp v1.1.0 // indirect
......
This diff is collapsed.
...@@ -35,6 +35,7 @@ prometheus:metrics ...@@ -35,6 +35,7 @@ prometheus:metrics
errors:errors errors:errors
log:log log:log
dnstap:dnstap dnstap:dnstap
acl:acl
any:any any:any
chaos:chaos chaos:chaos
loadbalance:loadbalance loadbalance:loadbalance
......
reviewers:
- miekg
- ihac
approvers:
- miekg
- ihac
# acl
*acl* - enforces access control policies on source ip and prevents unauthorized access to DNS servers.
## Description
With `acl` enabled, users are able to block suspicous DNS queries by configuring IP filter rule sets, i.e. allowing authorized queries to recurse or blocking unauthorized queries.
This plugin can be used multiple times per Server Block.
## Syntax
```
acl [ZONES...] {
ACTION [type QTYPE...] [net SOURCE...]
}
```
- **ZONES** zones it should be authoritative for. If empty, the zones from the configuration block are used.
- **ACTION** (*allow* or *block*) defines the way to deal with DNS queries matched by this rule. The default action is *allow*, which means a DNS query not matched by any rules will be allowed to recurse.
- **QTYPE** is the query type to match for the requests to be allowed or blocked. Common resource record types are supported. `*` stands for all record types. The default behavior for an omitted `type QTYPE...` is to match all kinds of DNS queries (same as `type *`).
- **SOURCE** is the source IP address to match for the requests to be allowed or blocked. Typical CIDR notation and single IP address are supported. `*` stands for all possible source IP addresses.
## Examples
To demonstrate the usage of plugin acl, here we provide some typical examples.
Block all DNS queries with record type A from 192.168.0.0/16:
~~~ Corefile
. {
acl {
block type A net 192.168.0.0/16
}
}
~~~
Block all DNS queries from 192.168.0.0/16 except for 192.168.1.0/24:
~~~ Corefile
. {
acl {
allow net 192.168.1.0/24
block net 192.168.0.0/16
}
}
```
Allow only DNS queries from 192.168.0.0/24 and 192.168.1.0/24:
~~~ Corefile
. {
acl {
allow net 192.168.0.0/16 192.168.1.0/24
block
}
}
~~~
Block all DNS queries from 192.168.1.0/24 towards a.example.org:
~~~ Corefile
example.org {
acl a.example.org {
block net 192.168.1.0/24
}
}
~~~
package acl
import (
"context"
"net"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/metrics"
clog "github.com/coredns/coredns/plugin/pkg/log"
"github.com/coredns/coredns/request"
"github.com/infobloxopen/go-trees/iptree"
"github.com/miekg/dns"
)
var log = clog.NewWithPlugin("acl")
// ACL enforces access control policies on DNS queries.
type ACL struct {
Next plugin.Handler
Rules []rule
}
// rule defines a list of Zones and some ACL policies which will be
// enforced on them.
type rule struct {
zones []string
policies []policy
}
// action defines the action against queries.
type action int
// policy defines the ACL policy for DNS queries.
// A policy performs the specified action (block/allow) on all DNS queries
// matched by source IP or QTYPE.
type policy struct {
action action
qtypes map[uint16]struct{}
filter *iptree.Tree
}
const (
// actionNone does nothing on the queries.
actionNone = iota
// actionAllow allows authorized queries to recurse.
actionAllow
// actionBlock blocks unauthorized queries towards protected DNS zones.
actionBlock
)
// ServeDNS implements the plugin.Handler interface.
func (a ACL) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
state := request.Request{W: w, Req: r}
RulesCheckLoop:
for _, rule := range a.Rules {
// check zone.
zone := plugin.Zones(rule.zones).Matches(state.Name())
if zone == "" {
continue
}
action := matchWithPolicies(rule.policies, w, r)
switch action {
case actionBlock:
{
m := new(dns.Msg)
m.SetRcode(r, dns.RcodeRefused)
w.WriteMsg(m)
RequestBlockCount.WithLabelValues(metrics.WithServer(ctx), zone).Inc()
return dns.RcodeSuccess, nil
}
case actionAllow:
{
break RulesCheckLoop
}
}
}
RequestAllowCount.WithLabelValues(metrics.WithServer(ctx)).Inc()
return plugin.NextOrFailure(state.Name(), a.Next, ctx, w, r)
}
// matchWithPolicies matches the DNS query with a list of ACL polices and returns suitable
// action agains the query.
func matchWithPolicies(policies []policy, w dns.ResponseWriter, r *dns.Msg) action {
state := request.Request{W: w, Req: r}
ip := net.ParseIP(state.IP())
qtype := state.QType()
for _, policy := range policies {
// dns.TypeNone matches all query types.
_, matchAll := policy.qtypes[dns.TypeNone]
_, match := policy.qtypes[qtype]
if !matchAll && !match {
continue
}
_, contained := policy.filter.GetByIP(ip)
if !contained {
continue
}
// matched.
return policy.action
}
return actionNone
}
// Name implements the plugin.Handler interface.
func (a ACL) Name() string {
return "acl"
}
package acl
import (
"context"
"testing"
"github.com/coredns/coredns/plugin/test"
"github.com/caddyserver/caddy"
"github.com/miekg/dns"
)
type testResponseWriter struct {
test.ResponseWriter
Rcode int
}
func (t *testResponseWriter) setRemoteIP(ip string) {
t.RemoteIP = ip
}
// WriteMsg implement dns.ResponseWriter interface.
func (t *testResponseWriter) WriteMsg(m *dns.Msg) error {
t.Rcode = m.Rcode
return nil
}
func NewTestControllerWithZones(input string, zones []string) *caddy.Controller {
ctr := caddy.NewTestController("dns", input)
for _, zone := range zones {
ctr.ServerBlockKeys = append(ctr.ServerBlockKeys, zone)
}
return ctr
}
func TestACLServeDNS(t *testing.T) {
type args struct {
domain string
sourceIP string
qtype uint16
}
tests := []struct {
name string
config string
zones []string
args args
wantRcode int
wantErr bool
}{
// IPv4 tests.
{
"Blacklist 1 BLOCKED",
`acl example.org {
block type A net 192.168.0.0/16
}`,
[]string{},
args{
"www.example.org.",
"192.168.0.2",
dns.TypeA,
},
dns.RcodeRefused,
false,
},
{
"Blacklist 1 ALLOWED",
`acl example.org {
block type A net 192.168.0.0/16
}`,
[]string{},
args{
"www.example.org.",
"192.167.0.2",
dns.TypeA,
},
dns.RcodeSuccess,
false,
},
{
"Blacklist 2 BLOCKED",
`
acl example.org {
block type * net 192.168.0.0/16
}`,
[]string{},
args{
"www.example.org.",
"192.168.0.2",
dns.TypeAAAA,
},
dns.RcodeRefused,
false,
},
{
"Blacklist 3 BLOCKED",
`acl example.org {
block type A
}`,
[]string{},
args{
"www.example.org.",
"10.1.0.2",
dns.TypeA,
},
dns.RcodeRefused,
false,
},
{
"Blacklist 3 ALLOWED",
`acl example.org {
block type A
}`,
[]string{},
args{
"www.example.org.",
"10.1.0.2",
dns.TypeAAAA,
},
dns.RcodeSuccess,
false,
},
{
"Blacklist 4 Single IP BLOCKED",
`acl example.org {
block type A net 192.168.1.2
}`,
[]string{},
args{
"www.example.org.",
"192.168.1.2",
dns.TypeA,
},
dns.RcodeRefused,
false,
},
{
"Blacklist 4 Single IP ALLOWED",
`acl example.org {
block type A net 192.168.1.2
}`,
[]string{},
args{
"www.example.org.",
"192.168.1.3",
dns.TypeA,
},
dns.RcodeSuccess,
false,
},
{
"Whitelist 1 ALLOWED",
`acl example.org {
allow net 192.168.0.0/16
block
}`,
[]string{},
args{
"www.example.org.",
"192.168.0.2",
dns.TypeA,
},
dns.RcodeSuccess,
false,
},
{
"Whitelist 1 REFUSED",
`acl example.org {
allow type * net 192.168.0.0/16
block
}`,
[]string{},
args{
"www.example.org.",
"10.1.0.2",
dns.TypeA,
},
dns.RcodeRefused,
false,
},
{
"Fine-Grained 1 REFUSED",
`acl a.example.org {
block type * net 192.168.1.0/24
}`,
[]string{"example.org"},
args{
"a.example.org.",
"192.168.1.2",
dns.TypeA,
},
dns.RcodeRefused,
false,
},
{
"Fine-Grained 1 ALLOWED",
`acl a.example.org {
block net 192.168.1.0/24
}`,
[]string{"example.org"},
args{
"www.example.org.",
"192.168.1.2",
dns.TypeA,
},
dns.RcodeSuccess,
false,
},
{
"Fine-Grained 2 REFUSED",
`acl {
block net 192.168.1.0/24
}`,
[]string{"example.org"},
args{
"a.example.org.",
"192.168.1.2",
dns.TypeA,
},
dns.RcodeRefused,
false,
},
{
"Fine-Grained 2 ALLOWED",
`acl {
block net 192.168.1.0/24
}`,
[]string{"example.org"},
args{
"a.example.com.",
"192.168.1.2",
dns.TypeA,
},
dns.RcodeSuccess,
false,
},
{
"Fine-Grained 3 REFUSED",
`acl a.example.org {
block net 192.168.1.0/24
}
acl b.example.org {
block type * net 192.168.2.0/24
}`,
[]string{"example.org"},
args{
"b.example.org.",
"192.168.2.2",
dns.TypeA,
},
dns.RcodeRefused,
false,
},
{
"Fine-Grained 3 ALLOWED",
`acl a.example.org {
block net 192.168.1.0/24
}
acl b.example.org {
block net 192.168.2.0/24
}`,
[]string{"example.org"},
args{
"b.example.org.",
"192.168.1.2",
dns.TypeA,
},
dns.RcodeSuccess,
false,
},
// IPv6 tests.
{
"Blacklist 1 BLOCKED IPv6",
`acl example.org {
block type A net 2001:db8:abcd:0012::0/64
}`,
[]string{},
args{
"www.example.org.",
"2001:db8:abcd:0012::1230",
dns.TypeA,
},
dns.RcodeRefused,
false,
},
{
"Blacklist 1 ALLOWED IPv6",
`acl example.org {
block type A net 2001:db8:abcd:0012::0/64
}`,
[]string{},
args{
"www.example.org.",
"2001:db8:abcd:0013::0",
dns.TypeA,
},
dns.RcodeSuccess,
false,
},
{
"Blacklist 2 BLOCKED IPv6",
`acl example.org {
block type A
}`,
[]string{},
args{
"www.example.org.",
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
dns.TypeA,
},
dns.RcodeRefused,
false,
},
{
"Blacklist 3 Single IP BLOCKED IPv6",
`acl example.org {
block type A net 2001:0db8:85a3:0000:0000:8a2e:0370:7334
}`,
[]string{},
args{
"www.example.org.",
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
dns.TypeA,
},
dns.RcodeRefused,
false,
},
{
"Blacklist 3 Single IP ALLOWED IPv6",
`acl example.org {
block type A net 2001:0db8:85a3:0000:0000:8a2e:0370:7334
}`,
[]string{},
args{
"www.example.org.",
"2001:0db8:85a3:0000:0000:8a2e:0370:7335",
dns.TypeA,
},
dns.RcodeSuccess,
false,
},
{
"Fine-Grained 1 REFUSED IPv6",
`acl a.example.org {
block type * net 2001:db8:abcd:0012::0/64
}`,
[]string{"example.org"},
args{
"a.example.org.",
"2001:db8:abcd:0012:2019::0",
dns.TypeA,
},
dns.RcodeRefused,
false,
},
{
"Fine-Grained 1 ALLOWED IPv6",
`acl a.example.org {
block net 2001:db8:abcd:0012::0/64
}`,
[]string{"example.org"},
args{
"www.example.org.",
"2001:db8:abcd:0012:2019::0",
dns.TypeA,
},
dns.RcodeSuccess,
false,
},
}
ctx := context.Background()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctr := NewTestControllerWithZones(tt.config, tt.zones)
a, err := parse(ctr)
a.Next = test.NextHandler(dns.RcodeSuccess, nil)
if err != nil {
t.Errorf("Error: Cannot parse acl from config: %v", err)
return
}
w := &testResponseWriter{}
m := new(dns.Msg)
w.setRemoteIP(tt.args.sourceIP)
m.SetQuestion(tt.args.domain, tt.args.qtype)
_, err = a.ServeDNS(ctx, w, m)
if (err != nil) != tt.wantErr {
t.Errorf("Error: acl.ServeDNS() error = %v, wantErr %v", err, tt.wantErr)
return
}
if w.Rcode != tt.wantRcode {
t.Errorf("Error: acl.ServeDNS() Rcode = %v, want %v", w.Rcode, tt.wantRcode)
}
})
}
}
package acl
import (
"github.com/coredns/coredns/plugin"
"github.com/prometheus/client_golang/prometheus"
)
var (
// RequestBlockCount is the number of DNS requests being blocked.
RequestBlockCount = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: plugin.Namespace,
Subsystem: "dns",
Name: "request_block_count_total",
Help: "Counter of DNS requests being blocked.",
}, []string{"server", "zone"})
// RequestAllowCount is the number of DNS requests being Allowed.
RequestAllowCount = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: plugin.Namespace,
Subsystem: "dns",
Name: "request_allow_count_total",
Help: "Counter of DNS requests being allowed.",
}, []string{"server"})
)
package acl
import (
"net"
"strings"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/metrics"
"github.com/caddyserver/caddy"
"github.com/infobloxopen/go-trees/iptree"
"github.com/miekg/dns"
)
func init() {
caddy.RegisterPlugin("acl", caddy.Plugin{
ServerType: "dns",
Action: setup,
})
}
func newDefaultFilter() *iptree.Tree {
defaultFilter := iptree.NewTree()
_, IPv4All, _ := net.ParseCIDR("0.0.0.0/0")
_, IPv6All, _ := net.ParseCIDR("::/0")
defaultFilter.InplaceInsertNet(IPv4All, struct{}{})
defaultFilter.InplaceInsertNet(IPv6All, struct{}{})
return defaultFilter
}
func setup(c *caddy.Controller) error {
a, err := parse(c)
if err != nil {
return plugin.Error("acl", err)
}
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
a.Next = next
return a
})
// Register all metrics.
c.OnStartup(func() error {
metrics.MustRegister(c, RequestBlockCount, RequestAllowCount)
return nil
})
return nil
}
func parse(c *caddy.Controller) (ACL, error) {
a := ACL{}
for c.Next() {
r := rule{}
r.zones = c.RemainingArgs()
if len(r.zones) == 0 {
// if empty, the zones from the configuration block are used.
r.zones = make([]string, len(c.ServerBlockKeys))
copy(r.zones, c.ServerBlockKeys)
}
for i := range r.zones {
r.zones[i] = plugin.Host(r.zones[i]).Normalize()
}
for c.NextBlock() {
p := policy{}
action := strings.ToLower(c.Val())
if action == "allow" {
p.action = actionAllow
} else if action == "block" {
p.action = actionBlock
} else {
return a, c.Errf("unexpected token %q; expect 'allow' or 'block'", c.Val())
}
p.qtypes = make(map[uint16]struct{})
p.filter = iptree.NewTree()
hasTypeSection := false
hasNetSection := false
remainingTokens := c.RemainingArgs()
for len(remainingTokens) > 0 {
if !isPreservedIdentifier(remainingTokens[0]) {
return a, c.Errf("unexpected token %q; expect 'type | net'", remainingTokens[0])
}
section := strings.ToLower(remainingTokens[0])
i := 1
var tokens []string
for ; i < len(remainingTokens) && !isPreservedIdentifier(remainingTokens[i]); i++ {
tokens = append(tokens, remainingTokens[i])
}
remainingTokens = remainingTokens[i:]
if len(tokens) == 0 {
return a, c.Errf("no token specified in %q section", section)
}
switch section {
case "type":
hasTypeSection = true
for _, token := range tokens {
if token == "*" {
p.qtypes[dns.TypeNone] = struct{}{}
break
}
qtype, ok := dns.StringToType[token]
if !ok {
return a, c.Errf("unexpected token %q; expect legal QTYPE", token)
}
p.qtypes[qtype] = struct{}{}
}
case "net":
hasNetSection = true
for _, token := range tokens {
if token == "*" {
p.filter = newDefaultFilter()
break
}
token = normalize(token)
_, source, err := net.ParseCIDR(token)
if err != nil {
return a, c.Errf("illegal CIDR notation %q", token)
}
p.filter.InplaceInsertNet(source, struct{}{})
}
default:
return a, c.Errf("unexpected token %q; expect 'type | net'", section)
}
}
// optional `type` section means all record types.
if !hasTypeSection {
p.qtypes[dns.TypeNone] = struct{}{}
}
// optional `net` means all ip addresses.
if !hasNetSection {
p.filter = newDefaultFilter()
}
r.policies = append(r.policies, p)
}
a.Rules = append(a.Rules, r)
}
return a, nil
}
func isPreservedIdentifier(token string) bool {
identifier := strings.ToLower(token)
return identifier == "type" || identifier == "net"
}
// normalize appends '/32' for any single IPv4 address and '/128' for IPv6.
func normalize(rawNet string) string {
if idx := strings.IndexAny(rawNet, "/"); idx >= 0 {
return rawNet
}
if idx := strings.IndexAny(rawNet, ":"); idx >= 0 {
return rawNet + "/128"
}
return rawNet + "/32"
}
package acl
import (
"testing"
"github.com/caddyserver/caddy"
)
func TestSetup(t *testing.T) {
tests := []struct {
name string
config string
wantErr bool
}{
// IPv4 tests.
{
"Blacklist 1",
`acl {
block type A net 192.168.0.0/16
}`,
false,
},
{
"Blacklist 2",
`acl {
block type * net 192.168.0.0/16
}`,
false,
},
{
"Blacklist 3",
`acl {
block type A net *
}`,
false,
},
{
"Blacklist 4",
`acl {
allow type * net 192.168.1.0/24
block type * net 192.168.0.0/16
}`,
false,
},
{
"Whitelist 1",
`acl {
allow type * net 192.168.0.0/16
block type * net *
}`,
false,
},
{
"fine-grained 1",
`acl a.example.org {
block type * net 192.168.1.0/24
}`,
false,
},
{
"fine-grained 2",
`acl a.example.org {
block type * net 192.168.1.0/24
}
acl b.example.org {
block type * net 192.168.2.0/24
}`,
false,
},
{
"Multiple Networks 1",
`acl example.org {
block type * net 192.168.1.0/24 192.168.3.0/24
}`,
false,
},
{
"Multiple Qtypes 1",
`acl example.org {
block type TXT ANY CNAME net 192.168.3.0/24
}`,
false,
},
{
"Missing argument 1",
`acl {
block A net 192.168.0.0/16
}`,
true,
},
{
"Missing argument 2",
`acl {
block type net 192.168.0.0/16
}`,
true,
},
{
"Illegal argument 1",
`acl {
block type ABC net 192.168.0.0/16
}`,
true,
},
{
"Illegal argument 2",
`acl {
blck type A net 192.168.0.0/16
}`,
true,
},
{
"Illegal argument 3",
`acl {
block type A net 192.168.0/16
}`,
true,
},
{
"Illegal argument 4",
`acl {
block type A net 192.168.0.0/33
}`,
true,
},
// IPv6 tests.
{
"Blacklist 1 IPv6",
`acl {
block type A net 2001:0db8:85a3:0000:0000:8a2e:0370:7334
}`,
false,
},
{
"Blacklist 2 IPv6",
`acl {
block type * net 2001:db8:85a3::8a2e:370:7334
}`,
false,
},
{
"Blacklist 3 IPv6",
`acl {
block type A
}`,
false,
},
{
"Blacklist 4 IPv6",
`acl {
allow net 2001:db8:abcd:0012::0/64
block net 2001:db8:abcd:0012::0/48
}`,
false,
},
{
"Whitelist 1 IPv6",
`acl {
allow net 2001:db8:abcd:0012::0/64
block
}`,
false,
},
{
"fine-grained 1 IPv6",
`acl a.example.org {
block net 2001:db8:abcd:0012::0/64
}`,
false,
},
{
"fine-grained 2 IPv6",
`acl a.example.org {
block net 2001:db8:abcd:0012::0/64
}
acl b.example.org {
block net 2001:db8:abcd:0013::0/64
}`,
false,
},
{
"Multiple Networks 1 IPv6",
`acl example.org {
block net 2001:db8:abcd:0012::0/64 2001:db8:85a3::8a2e:370:7334/64
}`,
false,
},
{
"Illegal argument 1 IPv6",
`acl {
block type A net 2001::85a3::8a2e:370:7334
}`,
true,
},
{
"Illegal argument 2 IPv6",
`acl {
block type A net 2001:db8:85a3:::8a2e:370:7334
}`,
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctr := caddy.NewTestController("dns", tt.config)
if err := setup(ctr); (err != nil) != tt.wantErr {
t.Errorf("Error: setup() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestNormalize(t *testing.T) {
type args struct {
rawNet string
}
tests := []struct {
name string
args args
want string
}{
{
"Network range 1",
args{"10.218.10.8/24"},
"10.218.10.8/24",
},
{
"IP address 1",
args{"10.218.10.8"},
"10.218.10.8/32",
},
{
"IPv6 address 1",
args{"2001:0db8:85a3:0000:0000:8a2e:0370:7334"},
"2001:0db8:85a3:0000:0000:8a2e:0370:7334/128",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := normalize(tt.args.rawNet); got != tt.want {
t.Errorf("Error: normalize() = %v, want %v", got, tt.want)
}
})
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment