mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-30 17:53:21 -04:00 
			
		
		
		
	* 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>
		
			
				
	
	
		
			167 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			167 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package acl
 | |
| 
 | |
| import (
 | |
| 	"net"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/coredns/coredns/core/dnsserver"
 | |
| 	"github.com/coredns/coredns/plugin"
 | |
| 	"github.com/coredns/coredns/plugin/metrics"
 | |
| 
 | |
| 	"github.com/caddyserver/caddy"
 | |
| 	"github.com/infobloxopen/go-trees/iptree"
 | |
| 	"github.com/miekg/dns"
 | |
| )
 | |
| 
 | |
| func init() {
 | |
| 	caddy.RegisterPlugin("acl", caddy.Plugin{
 | |
| 		ServerType: "dns",
 | |
| 		Action:     setup,
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func newDefaultFilter() *iptree.Tree {
 | |
| 	defaultFilter := iptree.NewTree()
 | |
| 	_, IPv4All, _ := net.ParseCIDR("0.0.0.0/0")
 | |
| 	_, IPv6All, _ := net.ParseCIDR("::/0")
 | |
| 	defaultFilter.InplaceInsertNet(IPv4All, struct{}{})
 | |
| 	defaultFilter.InplaceInsertNet(IPv6All, struct{}{})
 | |
| 	return defaultFilter
 | |
| }
 | |
| 
 | |
| func setup(c *caddy.Controller) error {
 | |
| 	a, err := parse(c)
 | |
| 	if err != nil {
 | |
| 		return plugin.Error("acl", err)
 | |
| 	}
 | |
| 
 | |
| 	dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
 | |
| 		a.Next = next
 | |
| 		return a
 | |
| 	})
 | |
| 
 | |
| 	// Register all metrics.
 | |
| 	c.OnStartup(func() error {
 | |
| 		metrics.MustRegister(c, RequestBlockCount, RequestAllowCount)
 | |
| 		return nil
 | |
| 	})
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func parse(c *caddy.Controller) (ACL, error) {
 | |
| 	a := ACL{}
 | |
| 	for c.Next() {
 | |
| 		r := rule{}
 | |
| 		r.zones = c.RemainingArgs()
 | |
| 		if len(r.zones) == 0 {
 | |
| 			// if empty, the zones from the configuration block are used.
 | |
| 			r.zones = make([]string, len(c.ServerBlockKeys))
 | |
| 			copy(r.zones, c.ServerBlockKeys)
 | |
| 		}
 | |
| 		for i := range r.zones {
 | |
| 			r.zones[i] = plugin.Host(r.zones[i]).Normalize()
 | |
| 		}
 | |
| 
 | |
| 		for c.NextBlock() {
 | |
| 			p := policy{}
 | |
| 
 | |
| 			action := strings.ToLower(c.Val())
 | |
| 			if action == "allow" {
 | |
| 				p.action = actionAllow
 | |
| 			} else if action == "block" {
 | |
| 				p.action = actionBlock
 | |
| 			} else {
 | |
| 				return a, c.Errf("unexpected token %q; expect 'allow' or 'block'", c.Val())
 | |
| 			}
 | |
| 
 | |
| 			p.qtypes = make(map[uint16]struct{})
 | |
| 			p.filter = iptree.NewTree()
 | |
| 
 | |
| 			hasTypeSection := false
 | |
| 			hasNetSection := false
 | |
| 
 | |
| 			remainingTokens := c.RemainingArgs()
 | |
| 			for len(remainingTokens) > 0 {
 | |
| 				if !isPreservedIdentifier(remainingTokens[0]) {
 | |
| 					return a, c.Errf("unexpected token %q; expect 'type | net'", remainingTokens[0])
 | |
| 				}
 | |
| 				section := strings.ToLower(remainingTokens[0])
 | |
| 
 | |
| 				i := 1
 | |
| 				var tokens []string
 | |
| 				for ; i < len(remainingTokens) && !isPreservedIdentifier(remainingTokens[i]); i++ {
 | |
| 					tokens = append(tokens, remainingTokens[i])
 | |
| 				}
 | |
| 				remainingTokens = remainingTokens[i:]
 | |
| 
 | |
| 				if len(tokens) == 0 {
 | |
| 					return a, c.Errf("no token specified in %q section", section)
 | |
| 				}
 | |
| 
 | |
| 				switch section {
 | |
| 				case "type":
 | |
| 					hasTypeSection = true
 | |
| 					for _, token := range tokens {
 | |
| 						if token == "*" {
 | |
| 							p.qtypes[dns.TypeNone] = struct{}{}
 | |
| 							break
 | |
| 						}
 | |
| 						qtype, ok := dns.StringToType[token]
 | |
| 						if !ok {
 | |
| 							return a, c.Errf("unexpected token %q; expect legal QTYPE", token)
 | |
| 						}
 | |
| 						p.qtypes[qtype] = struct{}{}
 | |
| 					}
 | |
| 				case "net":
 | |
| 					hasNetSection = true
 | |
| 					for _, token := range tokens {
 | |
| 						if token == "*" {
 | |
| 							p.filter = newDefaultFilter()
 | |
| 							break
 | |
| 						}
 | |
| 						token = normalize(token)
 | |
| 						_, source, err := net.ParseCIDR(token)
 | |
| 						if err != nil {
 | |
| 							return a, c.Errf("illegal CIDR notation %q", token)
 | |
| 						}
 | |
| 						p.filter.InplaceInsertNet(source, struct{}{})
 | |
| 					}
 | |
| 				default:
 | |
| 					return a, c.Errf("unexpected token %q; expect 'type | net'", section)
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			// optional `type` section means all record types.
 | |
| 			if !hasTypeSection {
 | |
| 				p.qtypes[dns.TypeNone] = struct{}{}
 | |
| 			}
 | |
| 
 | |
| 			// optional `net` means all ip addresses.
 | |
| 			if !hasNetSection {
 | |
| 				p.filter = newDefaultFilter()
 | |
| 			}
 | |
| 
 | |
| 			r.policies = append(r.policies, p)
 | |
| 		}
 | |
| 		a.Rules = append(a.Rules, r)
 | |
| 	}
 | |
| 	return a, nil
 | |
| }
 | |
| 
 | |
| func isPreservedIdentifier(token string) bool {
 | |
| 	identifier := strings.ToLower(token)
 | |
| 	return identifier == "type" || identifier == "net"
 | |
| }
 | |
| 
 | |
| // normalize appends '/32' for any single IPv4 address and '/128' for IPv6.
 | |
| func normalize(rawNet string) string {
 | |
| 	if idx := strings.IndexAny(rawNet, "/"); idx >= 0 {
 | |
| 		return rawNet
 | |
| 	}
 | |
| 
 | |
| 	if idx := strings.IndexAny(rawNet, ":"); idx >= 0 {
 | |
| 		return rawNet + "/128"
 | |
| 	}
 | |
| 	return rawNet + "/32"
 | |
| }
 |