Files
vibedns/internal/resolver/acl.go
T
2026-08-16 21:18:45 -05:00

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.