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 <george@shamm.as>
This commit is contained in:
parent
d289b4ea26
commit
117a389e40
6 changed files with 75 additions and 3 deletions
|
@ -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
|
||||
|
|
|
@ -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,9 +75,18 @@ 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()
|
||||
return plugin.NextOrFailure(state.Name(), a.Next, ctx, w, r)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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{})
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue