diff --git a/plugin/acl/README.md b/plugin/acl/README.md index 6e5b827e1..d957d24ec 100644 --- a/plugin/acl/README.md +++ b/plugin/acl/README.md @@ -25,7 +25,7 @@ acl [ZONES...] { ``` - **ZONES** zones it should be authoritative for. If empty, the zones from the configuration block are used. -- **ACTION** (*allow*, *block*, or *filter*) 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. The difference between *block* and *filter* is that block returns status code of *REFUSED* while filter returns an empty set *NOERROR* +- **ACTION** (*allow*, *block*, *filter*, or *drop*) 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. The difference between *block* and *filter* is that block returns status code of *REFUSED* while filter returns an empty set *NOERROR*. *drop* however returns no response to the client. - **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. @@ -85,6 +85,16 @@ example.org { } ~~~ +Drop all DNS queries from 192.0.2.0/24: + +~~~ corefile +. { + acl { + drop net 192.0.2.0/24 + } +} +~~~ + ## Metrics If monitoring is enabled (via the _prometheus_ plugin) then the following metrics are exported: @@ -95,4 +105,6 @@ If monitoring is enabled (via the _prometheus_ plugin) then the following metric - `coredns_acl_allowed_requests_total{server, view}` - counter of DNS requests being allowed. +- `coredns_acl_dropped_requests_total{server, zone, view}` - counter of DNS requests being dropped. + The `server` and `zone` labels are explained in the _metrics_ plugin documentation. diff --git a/plugin/acl/acl.go b/plugin/acl/acl.go index 7d7b9d600..263232632 100644 --- a/plugin/acl/acl.go +++ b/plugin/acl/acl.go @@ -49,6 +49,8 @@ const ( actionBlock // actionFilter returns empty sets for queries towards protected DNS zones. actionFilter + // actionDrop does not respond for queries towards the protected DNS zones. + actionDrop ) var log = clog.NewWithPlugin("acl") @@ -67,6 +69,11 @@ RulesCheckLoop: action := matchWithPolicies(rule.policies, w, r) switch action { + case actionDrop: + { + RequestDropCount.WithLabelValues(metrics.WithServer(ctx), zone, metrics.WithView(ctx)).Inc() + return dns.RcodeSuccess, nil + } case actionBlock: { m := new(dns.Msg). diff --git a/plugin/acl/acl_test.go b/plugin/acl/acl_test.go index d947879c3..f867d1f52 100644 --- a/plugin/acl/acl_test.go +++ b/plugin/acl/acl_test.go @@ -51,6 +51,7 @@ func TestACLServeDNS(t *testing.T) { wantRcode int wantErr bool wantExtendedErrorCode uint16 + expectNoResponse bool }{ // IPv4 tests. { @@ -205,6 +206,79 @@ func TestACLServeDNS(t *testing.T) { wantRcode: dns.RcodeRefused, wantExtendedErrorCode: dns.ExtendedErrorCodeBlocked, }, + { + name: "Drop 1 DROPPED", + config: `acl example.org { + drop net 192.168.0.0/16 + }`, + zones: []string{}, + args: args{ + domain: "www.example.org.", + sourceIP: "192.168.0.2", + qtype: dns.TypeA, + }, + wantRcode: dns.RcodeSuccess, + expectNoResponse: true, + }, + { + name: "Subnet-Order 1 REFUSED", + config: `acl example.org { + block net 192.168.1.0/24 + drop net 192.168.0.0/16 + }`, + zones: []string{}, + args: args{ + domain: "www.example.org.", + sourceIP: "192.168.1.2", + qtype: dns.TypeA, + }, + wantRcode: dns.RcodeRefused, + wantExtendedErrorCode: dns.ExtendedErrorCodeBlocked, + }, + { + name: "Subnet-Order 2 DROPPED", + config: `acl example.org { + drop net 192.168.0.0/16 + block net 192.168.1.0/24 + }`, + zones: []string{}, + args: args{ + domain: "www.example.org.", + sourceIP: "192.168.1.1", + qtype: dns.TypeA, + }, + wantRcode: dns.RcodeSuccess, + expectNoResponse: true, + }, + { + name: "Drop-Type 1 DROPPED", + config: `acl example.org { + drop type A + allow net 192.168.0.0/16 + }`, + zones: []string{}, + args: args{ + domain: "www.example.org.", + sourceIP: "192.168.1.1", + qtype: dns.TypeA, + }, + wantRcode: dns.RcodeSuccess, + expectNoResponse: true, + }, + { + name: "Drop-Type 2 ALLOWED", + config: `acl example.org { + drop type A + allow net 192.168.0.0/16 + }`, + zones: []string{}, + args: args{ + domain: "www.example.org.", + sourceIP: "192.168.1.1", + qtype: dns.TypeAAAA, + }, + wantRcode: dns.RcodeSuccess, + }, { name: "Fine-Grained 1 REFUSED", config: `acl a.example.org { @@ -402,6 +476,79 @@ func TestACLServeDNS(t *testing.T) { wantRcode: dns.RcodeRefused, wantExtendedErrorCode: dns.ExtendedErrorCodeBlocked, }, + { + name: "Drop 1 DROPPED IPV6", + config: `acl example.org { + drop net 2001:0db8:85a3:0000:0000:8a2e:0370:7334 + }`, + zones: []string{}, + args: args{ + domain: "www.example.org.", + sourceIP: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + qtype: dns.TypeAAAA, + }, + wantRcode: dns.RcodeSuccess, + expectNoResponse: true, + }, + { + name: "Subnet-Order 1 REFUSED IPv6", + config: `acl example.org { + block net 2001:db8:abcd:0012:8000::/66 + drop net 2001:db8:abcd:0012::0/64 + }`, + zones: []string{}, + args: args{ + domain: "www.example.org.", + sourceIP: "2001:db8:abcd:0012:8000::1", + qtype: dns.TypeAAAA, + }, + wantRcode: dns.RcodeRefused, + wantExtendedErrorCode: dns.ExtendedErrorCodeBlocked, + }, + { + name: "Subnet-Order 2 DROPPED IPv6", + config: `acl example.org { + drop net 2001:db8:abcd:0012::0/64 + block net 2001:db8:abcd:0012:8000::/66 + }`, + zones: []string{}, + args: args{ + domain: "www.example.org.", + sourceIP: "2001:db8:abcd:0012:8000::1", + qtype: dns.TypeAAAA, + }, + wantRcode: dns.RcodeSuccess, + expectNoResponse: true, + }, + { + name: "Drop-Type 1 DROPPED IPv6", + config: `acl example.org { + drop type A + allow net 2001:db8:85a3:0000::0/64 + }`, + zones: []string{}, + args: args{ + domain: "www.example.org.", + sourceIP: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + qtype: dns.TypeA, + }, + wantRcode: dns.RcodeSuccess, + expectNoResponse: true, + }, + { + name: "Drop-Type 2 ALLOWED IPv6", + config: `acl example.org { + drop type A + allow net 2001:db8:85a3:0000::0/64 + }`, + zones: []string{}, + args: args{ + domain: "www.example.org.", + sourceIP: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + qtype: dns.TypeAAAA, + }, + wantRcode: dns.RcodeSuccess, + }, } ctx := context.Background() @@ -430,6 +577,9 @@ func TestACLServeDNS(t *testing.T) { if w.Rcode != tt.wantRcode { t.Errorf("Error: acl.ServeDNS() Rcode = %v, want %v", w.Rcode, tt.wantRcode) } + if tt.expectNoResponse && w.Msg != nil { + t.Errorf("Error: acl.ServeDNS() responded to client when not expected") + } if tt.wantExtendedErrorCode != 0 { matched := false for _, opt := range w.Msg.IsEdns0().Option { diff --git a/plugin/acl/metrics.go b/plugin/acl/metrics.go index 04d728bcd..a8d823254 100644 --- a/plugin/acl/metrics.go +++ b/plugin/acl/metrics.go @@ -29,4 +29,11 @@ var ( Name: "allowed_requests_total", Help: "Counter of DNS requests being allowed.", }, []string{"server", "view"}) + // RequestDropCount is the number of DNS requests being dropped. + RequestDropCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: plugin.Namespace, + Subsystem: pluginName, + Name: "dropped_requests_total", + Help: "Counter of DNS requests being dropped.", + }, []string{"server", "zone", "view"}) ) diff --git a/plugin/acl/setup.go b/plugin/acl/setup.go index 3adde0aec..189acc6c4 100644 --- a/plugin/acl/setup.go +++ b/plugin/acl/setup.go @@ -56,8 +56,10 @@ func parse(c *caddy.Controller) (ACL, error) { p.action = actionBlock } else if action == "filter" { p.action = actionFilter + } else if action == "drop" { + p.action = actionDrop } else { - return a, c.Errf("unexpected token %q; expect 'allow', 'block', or 'filter'", c.Val()) + return a, c.Errf("unexpected token %q; expect 'allow', 'block', 'filter' or 'drop'", c.Val()) } p.qtypes = make(map[uint16]struct{}) diff --git a/plugin/acl/setup_test.go b/plugin/acl/setup_test.go index 1d25dd7af..5cd51bb3a 100644 --- a/plugin/acl/setup_test.go +++ b/plugin/acl/setup_test.go @@ -57,6 +57,13 @@ func TestSetup(t *testing.T) { }`, false, }, + { + "Drop 1", + `acl { + drop type * net 192.168.0.0/16 + }`, + false, + }, { "fine-grained 1", `acl a.example.org { @@ -175,6 +182,13 @@ func TestSetup(t *testing.T) { }`, false, }, + { + "Drop 1 IPv6", + `acl { + drop net 2001:db8:abcd:0012::0/64 + }`, + false, + }, { "fine-grained 1 IPv6", `acl a.example.org {