From 117a389e40832cdbee69bd9daa04ca35611082ff Mon Sep 17 00:00:00 2001 From: George Shammas Date: Mon, 1 Feb 2021 09:52:23 -0500 Subject: [PATCH] plugin/acl: add the ability to filter records (#4389) Currently ACLs only allow for allow and block, however it isn't always desirable to set the status code to REFUSED. Often times you want to completely hide the fact that those records even exist. Adding the ability to acl to filter results makes it significantly harder for a third party to know that the records are being masked. Signed-off-by: George Shammas --- plugin/acl/README.md | 14 ++++++++++++-- plugin/acl/acl.go | 11 +++++++++++ plugin/acl/acl_test.go | 28 ++++++++++++++++++++++++++++ plugin/acl/metrics.go | 7 +++++++ plugin/acl/setup.go | 4 +++- plugin/acl/setup_test.go | 14 ++++++++++++++ 6 files changed, 75 insertions(+), 3 deletions(-) diff --git a/plugin/acl/README.md b/plugin/acl/README.md index d1c4e5483..5103018ef 100644 --- a/plugin/acl/README.md +++ b/plugin/acl/README.md @@ -6,7 +6,7 @@ ## Description -With `acl` enabled, users are able to block suspicious DNS queries by configuring IP filter rule sets, i.e. allowing authorized queries to recurse or blocking unauthorized queries. +With `acl` enabled, users are able to block or filter suspicious 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. @@ -19,7 +19,7 @@ acl [ZONES...] { ``` - **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. +- **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* - **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. @@ -37,6 +37,16 @@ Block all DNS queries with record type A from 192.168.0.0/16: } ~~~ +Filter all DNS queries with record type A from 192.168.0.0/16: + +~~~ corefile +. { + acl { + filter 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 diff --git a/plugin/acl/acl.go b/plugin/acl/acl.go index ce7b041cb..e684dc42c 100644 --- a/plugin/acl/acl.go +++ b/plugin/acl/acl.go @@ -45,6 +45,8 @@ const ( actionAllow // actionBlock blocks unauthorized queries towards protected DNS zones. actionBlock + // actionFilter returns empty sets for queries towards protected DNS zones. + actionFilter ) // ServeDNS implements the plugin.Handler interface. @@ -73,7 +75,16 @@ RulesCheckLoop: { break RulesCheckLoop } + case actionFilter: + { + m := new(dns.Msg) + m.SetRcode(r, dns.RcodeSuccess) + w.WriteMsg(m) + RequestFilterCount.WithLabelValues(metrics.WithServer(ctx), zone).Inc() + return dns.RcodeSuccess, nil + } } + } RequestAllowCount.WithLabelValues(metrics.WithServer(ctx)).Inc() diff --git a/plugin/acl/acl_test.go b/plugin/acl/acl_test.go index bf0c6f6f7..4c6df95e5 100644 --- a/plugin/acl/acl_test.go +++ b/plugin/acl/acl_test.go @@ -145,6 +145,34 @@ func TestACLServeDNS(t *testing.T) { dns.RcodeSuccess, false, }, + { + "Filter 1 FILTERED", + `acl example.org { + filter type A net 192.168.0.0/16 + }`, + []string{}, + args{ + "www.example.org.", + "192.168.0.2", + dns.TypeA, + }, + dns.RcodeSuccess, + false, + }, + { + "Filter 1 ALLOWED", + `acl example.org { + filter type A net 192.168.0.0/16 + }`, + []string{}, + args{ + "www.example.org.", + "192.167.0.2", + dns.TypeA, + }, + dns.RcodeSuccess, + false, + }, { "Whitelist 1 ALLOWED", `acl example.org { diff --git a/plugin/acl/metrics.go b/plugin/acl/metrics.go index b0357cab1..76f30b5a6 100644 --- a/plugin/acl/metrics.go +++ b/plugin/acl/metrics.go @@ -15,6 +15,13 @@ var ( Name: "blocked_requests_total", Help: "Counter of DNS requests being blocked.", }, []string{"server", "zone"}) + // RequestFilterCount is the number of DNS requests being filtered. + RequestFilterCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: plugin.Namespace, + Subsystem: pluginName, + Name: "filtered_requests_total", + Help: "Counter of DNS requests being filtered.", + }, []string{"server", "zone"}) // RequestAllowCount is the number of DNS requests being Allowed. RequestAllowCount = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: plugin.Namespace, diff --git a/plugin/acl/setup.go b/plugin/acl/setup.go index d00ec7796..1a688a485 100644 --- a/plugin/acl/setup.go +++ b/plugin/acl/setup.go @@ -61,8 +61,10 @@ func parse(c *caddy.Controller) (ACL, error) { p.action = actionAllow } else if action == "block" { p.action = actionBlock + } else if action == "filter" { + p.action = actionFilter } else { - return a, c.Errf("unexpected token %q; expect 'allow' or 'block'", c.Val()) + return a, c.Errf("unexpected token %q; expect 'allow', 'block', or 'filter'", c.Val()) } p.qtypes = make(map[uint16]struct{}) diff --git a/plugin/acl/setup_test.go b/plugin/acl/setup_test.go index 2bf7e778b..1d25dd7af 100644 --- a/plugin/acl/setup_test.go +++ b/plugin/acl/setup_test.go @@ -42,6 +42,13 @@ func TestSetup(t *testing.T) { }`, false, }, + { + "Filter 1", + `acl { + filter type A net 192.168.0.0/16 + }`, + false, + }, { "Whitelist 1", `acl { @@ -153,6 +160,13 @@ func TestSetup(t *testing.T) { }`, false, }, + { + "Filter 1 IPv6", + `acl { + filter type A net 2001:0db8:85a3:0000:0000:8a2e:0370:7334 + }`, + false, + }, { "Whitelist 1 IPv6", `acl {