95 lines
2.3 KiB
Go
95 lines
2.3 KiB
Go
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.
|