mirror of
https://github.com/coredns/coredns.git
synced 2025-11-03 10:43:20 -05:00
Add plugin ACL for source ip filtering (#3103)
* Add plugin ACL for source ip filtering Signed-off-by: An Xiao <hac@zju.edu.cn> * Allow all arguments to be optional and support multiple qtypes in a single policy Signed-off-by: An Xiao <hac@zju.edu.cn> * Add newline before third party imports Signed-off-by: An Xiao <hac@zju.edu.cn> * Use camel instead of underscore in method name Signed-off-by: An Xiao <hac@zju.edu.cn> * Start with an upper case letter in t.Errorf() Signed-off-by: An Xiao <hac@zju.edu.cn> * Use the qtype parse logic in miekg/dns Signed-off-by: An Xiao <hac@zju.edu.cn> * Use third party trie implementation as the ip filter Signed-off-by: An Xiao <hac@zju.edu.cn> * Update based on rdrozhdzh's comment Signed-off-by: An Xiao <hac@zju.edu.cn> * Change the type of action to int Signed-off-by: An Xiao <hac@zju.edu.cn> * Add IPv6 support Signed-off-by: An Xiao <hac@zju.edu.cn> * Update plugin.cfg Signed-off-by: An Xiao <hac@zju.edu.cn> * Remove file functionality Signed-off-by: An Xiao <hac@zju.edu.cn> * Update Signed-off-by: Xiao An <hac@zju.edu.cn> * Update README Signed-off-by: Xiao An <hac@zju.edu.cn> * remove comments Signed-off-by: Xiao An <hac@zju.edu.cn> * update Signed-off-by: Xiao An <hac@zju.edu.cn> * Update dependency Signed-off-by: Xiao An <hac@zju.edu.cn> * Update Signed-off-by: Xiao An <hac@zju.edu.cn> * Update test Signed-off-by: Xiao An <hac@zju.edu.cn> * Add OWNERS Signed-off-by: Xiao An <hac@zju.edu.cn> * Refactor shouldBlock and skip useless check Signed-off-by: Xiao An <hac@zju.edu.cn> * Introduce ActionNone Signed-off-by: Xiao An <hac@zju.edu.cn> * Update label name Signed-off-by: Xiao An <hac@zju.edu.cn> * Avoid capitalizing private types Signed-off-by: Xiao An <hac@zju.edu.cn>
This commit is contained in:
115
plugin/acl/acl.go
Normal file
115
plugin/acl/acl.go
Normal file
@@ -0,0 +1,115 @@
|
||||
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"
|
||||
}
|
||||
Reference in New Issue
Block a user