coredns/plugin/acl/acl.go
2022-07-10 11:06:33 -07:00

138 lines
3.3 KiB
Go

package acl
import (
"context"
"net"
"strings"
"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"
)
// 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
// actionFilter returns empty sets for queries towards protected DNS zones.
actionFilter
)
var log = clog.NewWithPlugin("acl")
// 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
}
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)
}
// matchWithPolicies matches the DNS query with a list of ACL polices and returns suitable
// action against the query.
func matchWithPolicies(policies []policy, w dns.ResponseWriter, r *dns.Msg) action {
state := request.Request{W: w, Req: r}
var ip net.IP
if idx := strings.IndexByte(state.IP(), '%'); idx >= 0 {
ip = net.ParseIP(state.IP()[:idx])
} else {
ip = net.ParseIP(state.IP())
}
// if the parsing did not return a proper response then we simply return 'actionBlock' to
// block the query
if ip == nil {
log.Errorf("Blocking request. Unable to parse source address: %v", state.IP())
return actionBlock
}
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"
}