Skip to content

Commit 3f6b7de

Browse files
committed
feat(middlewares/filter): support exempt parent domains for rebinding protection
1 parent 2bd43d9 commit 3f6b7de

4 files changed

Lines changed: 57 additions & 15 deletions

File tree

pkg/middlewares/filter/mapfilter/filter.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@ import (
77
)
88

99
type Filter struct {
10-
fqdnHostnames map[string]struct{}
11-
ipv4 map[[4]byte]struct{}
12-
ipv6 map[[16]byte]struct{}
13-
ipPrefixes []netip.Prefix
14-
privateIPPrefixes []netip.Prefix
15-
allowRebindNames map[string]struct{}
16-
metrics Metrics
17-
logger Logger
18-
updateLock sync.RWMutex
10+
fqdnHostnames map[string]struct{}
11+
ipv4 map[[4]byte]struct{}
12+
ipv6 map[[16]byte]struct{}
13+
ipPrefixes []netip.Prefix
14+
privateIPPrefixes []netip.Prefix
15+
allowRebindNames map[string]struct{}
16+
allowRebindParents map[string]struct{}
17+
metrics Metrics
18+
logger Logger
19+
updateLock sync.RWMutex
1920
}
2021

2122
func New(settings Settings) (filter *Filter, err error) {

pkg/middlewares/filter/mapfilter/response.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package mapfilter
33
import (
44
"net"
55
"net/netip"
6+
"strings"
67

78
"github.com/miekg/dns"
89
"github.com/qdm12/dns/v2/internal/local"
@@ -19,6 +20,16 @@ func (m *Filter) FilterResponse(response *dns.Msg) (blocked bool) {
1920
if len(response.Question) == 1 {
2021
nameIsLocal = local.IsFQDNLocal(response.Question[0].Name)
2122
_, nameCanBeRebinded = m.allowRebindNames[response.Question[0].Name]
23+
if !nameCanBeRebinded && len(m.allowRebindParents) > 0 {
24+
labels := dns.SplitDomainName(response.Question[0].Name)
25+
for i := len(labels) - 1; i >= 0; i-- {
26+
parent := dns.Fqdn(strings.Join(labels[i:], "."))
27+
if _, ok := m.allowRebindParents[parent]; ok {
28+
nameCanBeRebinded = true
29+
break
30+
}
31+
}
32+
}
2233
}
2334

2435
for _, rr := range response.Answer {

pkg/middlewares/filter/mapfilter/update.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,15 @@ func (m *Filter) Update(settings update.Settings) (err error) {
3939
m.allowRebindNames[name] = struct{}{}
4040
}
4141

42+
m.allowRebindParents = make(map[string]struct{}, len(settings.ParentsExemptFromRebindingProtection))
43+
for _, name := range settings.ParentsExemptFromRebindingProtection {
44+
m.allowRebindParents[name] = struct{}{}
45+
}
46+
4247
m.metrics.SetBlockedHostnames(len(m.fqdnHostnames))
4348
m.metrics.SetBlockedIPs(len(m.ipv4) + len(m.ipv6))
4449
m.metrics.SetBlockedIPPrefixes(len(m.ipPrefixes))
45-
m.metrics.SetFqdnExemptFromRebindingProtection(len(m.allowRebindNames))
50+
m.metrics.SetFqdnExemptFromRebindingProtection(len(m.allowRebindNames) + len(m.allowRebindParents))
4651

4752
m.logger.Log(fmt.Sprintf("filter updated: %d hostnames, %d IPs, %d IP prefixes blocked",
4853
len(m.fqdnHostnames), len(m.ipv4)+len(m.ipv6), len(m.ipPrefixes)))

pkg/middlewares/filter/update/settings.go

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"net/netip"
77
"regexp"
8+
"strings"
89

910
"github.com/miekg/dns"
1011
"github.com/qdm12/gosettings/validate"
@@ -22,6 +23,10 @@ type Settings struct {
2223
// FqdnExemptFromRebindingProtection is a list of
2324
// fully qualified domain names that are exempt from rebinding protection.
2425
FqdnExemptFromRebindingProtection []string
26+
// ParentsExemptFromRebindingProtection is a list of fully qualified
27+
// domain names for which all their subdomains are exempt from
28+
// rebinding protection.
29+
ParentsExemptFromRebindingProtection []string
2530
}
2631

2732
func (s *Settings) SetDefaults() {}
@@ -41,6 +46,11 @@ func (s Settings) Validate() (err error) {
4146
return fmt.Errorf("FQDNs exempt from rebinding protection: %w", err)
4247
}
4348

49+
err = validate.AllMatchRegex(s.ParentsExemptFromRebindingProtection, fqdnHostRegex)
50+
if err != nil {
51+
return fmt.Errorf("parent FQDNs exempt from rebinding protection: %w", err)
52+
}
53+
4454
return nil
4555
}
4656

@@ -54,19 +64,27 @@ func (s *Settings) BlockHostnames(hostnames []string) {
5464
}
5565

5666
// SetRebindingProtectionExempt transforms the slice of hostnames given to
57-
// FQDNs and sets these to the settings.
67+
// FQDNs and sets these to the settings. Parent domains can be exempt by
68+
// specifying the "*." prefix to the hostname, for example "*.example.com"
69+
// will exempt all subdomains of example.com from rebinding protection.
70+
// Note the wildcard cannot be used anywhere else otherwise.
5871
func (s *Settings) SetRebindingProtectionExempt(hostnames []string) {
59-
s.FqdnExemptFromRebindingProtection = make([]string, len(hostnames))
60-
for i := range hostnames {
61-
s.FqdnExemptFromRebindingProtection[i] = dns.Fqdn(hostnames[i])
72+
s.FqdnExemptFromRebindingProtection = make([]string, 0, len(hostnames))
73+
for _, hostname := range hostnames {
74+
if strings.HasPrefix(hostname, "*.") {
75+
parent := hostname[2:]
76+
s.ParentsExemptFromRebindingProtection = append(s.ParentsExemptFromRebindingProtection, dns.Fqdn(parent))
77+
} else {
78+
s.FqdnExemptFromRebindingProtection = append(s.FqdnExemptFromRebindingProtection, dns.Fqdn(hostname))
79+
}
6280
}
6381
}
6482

6583
func (s *Settings) String() string {
6684
return s.ToLinesNode().String()
6785
}
6886

69-
func (s *Settings) ToLinesNode() (node *gotree.Node) {
87+
func (s *Settings) ToLinesNode() (node *gotree.Node) { //nolint:cyclop
7088
if len(s.IPs) == 0 && len(s.FqdnHostnames) == 0 &&
7189
len(s.IPPrefixes) == 0 {
7290
return gotree.New("Filter update: disabled")
@@ -93,5 +111,12 @@ func (s *Settings) ToLinesNode() (node *gotree.Node) {
93111
}
94112
}
95113

114+
if len(s.ParentsExemptFromRebindingProtection) > 0 {
115+
subNode := node.Appendf("Parent domains exempt from rebinding protection:")
116+
for _, fqdn := range s.ParentsExemptFromRebindingProtection {
117+
subNode.Appendf("%s", fqdn)
118+
}
119+
}
120+
96121
return node
97122
}

0 commit comments

Comments
 (0)