initial commit
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ACL decides which clients may use recursion.
|
||||
//
|
||||
// It is deliberately closed by default: an empty allow list denies everyone.
|
||||
// Denies are evaluated before allows, so a narrow exclusion can be carved out
|
||||
// of a broad allowance.
|
||||
type ACL struct {
|
||||
allow []netip.Prefix
|
||||
deny []netip.Prefix
|
||||
}
|
||||
|
||||
// NewACL compiles allow and deny lists. Entries may be CIDR blocks or bare
|
||||
// addresses; an invalid entry is reported rather than silently dropped,
|
||||
// because a typo in an ACL is a security-relevant mistake.
|
||||
func NewACL(allow, deny []string) (*ACL, error) {
|
||||
a := &ACL{}
|
||||
var err error
|
||||
if a.allow, err = parsePrefixes(allow, "allowed"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if a.deny, err = parsePrefixes(deny, "denied"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func parsePrefixes(in []string, label string) ([]netip.Prefix, error) {
|
||||
var out []netip.Prefix
|
||||
for _, s := range in {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(s, "/") {
|
||||
p, err := netip.ParsePrefix(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s network %q is not a valid CIDR block", label, s)
|
||||
}
|
||||
out = append(out, p.Masked())
|
||||
continue
|
||||
}
|
||||
addr, err := netip.ParseAddr(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s network %q is not a valid IP address or CIDR block", label, s)
|
||||
}
|
||||
addr = addr.Unmap()
|
||||
out = append(out, netip.PrefixFrom(addr, addr.BitLen()))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Allowed reports whether addr may use recursion.
|
||||
func (a *ACL) Allowed(addr netip.Addr) bool {
|
||||
if a == nil {
|
||||
return false
|
||||
}
|
||||
ip := addr.Unmap()
|
||||
for _, p := range a.deny {
|
||||
if p.Addr().Is4() == ip.Is4() && p.Contains(ip) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, p := range a.allow {
|
||||
if p.Addr().Is4() == ip.Is4() && p.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Describe renders the ACL for the settings page.
|
||||
func (a *ACL) Describe() (allow, deny []string) {
|
||||
if a == nil {
|
||||
return nil, nil
|
||||
}
|
||||
for _, p := range a.allow {
|
||||
allow = append(allow, p.String())
|
||||
}
|
||||
for _, p := range a.deny {
|
||||
deny = append(deny, p.String())
|
||||
}
|
||||
return allow, deny
|
||||
}
|
||||
|
||||
// Prefix sets for non-security lists (rate-limit exemptions, query-log ignore
|
||||
// lists) live in package netutil. This package keeps only the recursion ACL,
|
||||
// whose constructor deliberately reports errors instead of skipping entries.
|
||||
Reference in New Issue
Block a user