-
Notifications
You must be signed in to change notification settings - Fork 4.9k
/
matcher.go
134 lines (105 loc) · 3.15 KB
/
matcher.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package tcp
import (
"fmt"
"regexp"
"strings"
"unicode/utf8"
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/ip"
)
var tcpFuncs = map[string]func(*matchersTree, ...string) error{
"ALPN": expect1Parameter(alpn),
"ClientIP": expect1Parameter(clientIP),
"HostSNI": expect1Parameter(hostSNI),
"HostSNIRegexp": expect1Parameter(hostSNIRegexp),
}
func expect1Parameter(fn func(*matchersTree, ...string) error) func(*matchersTree, ...string) error {
return func(route *matchersTree, s ...string) error {
if len(s) != 1 {
return fmt.Errorf("unexpected number of parameters; got %d, expected 1", len(s))
}
return fn(route, s...)
}
}
// alpn checks if any of the connection ALPN protocols matches one of the matcher protocols.
func alpn(tree *matchersTree, protos ...string) error {
proto := protos[0]
if proto == tlsalpn01.ACMETLS1Protocol {
return fmt.Errorf("invalid protocol value for ALPN matcher, %q is not allowed", proto)
}
tree.matcher = func(meta ConnData) bool {
for _, alpnProto := range meta.alpnProtos {
if alpnProto == proto {
return true
}
}
return false
}
return nil
}
func clientIP(tree *matchersTree, clientIP ...string) error {
checker, err := ip.NewChecker(clientIP)
if err != nil {
return fmt.Errorf("initializing IP checker for ClientIP matcher: %w", err)
}
tree.matcher = func(meta ConnData) bool {
ok, err := checker.Contains(meta.remoteIP)
if err != nil {
log.Warn().Err(err).Msg("ClientIP matcher: could not match remote address")
return false
}
return ok
}
return nil
}
var hostOrIP = regexp.MustCompile(`^[[:alnum:]\.\-\:]+$`)
// hostSNI checks if the SNI Host of the connection match the matcher host.
func hostSNI(tree *matchersTree, hosts ...string) error {
host := hosts[0]
if host == "*" {
// Since a HostSNI(`*`) rule has been provided as catchAll for non-TLS TCP,
// it allows matching with an empty serverName.
tree.matcher = func(meta ConnData) bool { return true }
return nil
}
if !hostOrIP.MatchString(host) {
return fmt.Errorf("invalid value for HostSNI matcher, %q is not a valid hostname", host)
}
tree.matcher = func(meta ConnData) bool {
if meta.serverName == "" {
return false
}
if host == meta.serverName {
return true
}
// trim trailing period in case of FQDN
host = strings.TrimSuffix(host, ".")
return host == meta.serverName
}
return nil
}
// hostSNIRegexp checks if the SNI Host of the connection matches the matcher host regexp.
func hostSNIRegexp(tree *matchersTree, templates ...string) error {
template := templates[0]
if !isASCII(template) {
return fmt.Errorf("invalid value for HostSNIRegexp matcher, %q is not a valid hostname", template)
}
re, err := regexp.Compile(template)
if err != nil {
return fmt.Errorf("compiling HostSNIRegexp matcher: %w", err)
}
tree.matcher = func(meta ConnData) bool {
return re.MatchString(meta.serverName)
}
return nil
}
// isASCII checks if the given string contains only ASCII characters.
func isASCII(s string) bool {
for i := range len(s) {
if s[i] >= utf8.RuneSelf {
return false
}
}
return true
}