Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 12, 14:30 UTC
connection policy: add `local_ip` matcher (#6074)
* connection policy: add `local_ip` Co-authored-by: Matt Holt <mholt@users.noreply.github.com> --------- Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
Mohammed Al Sahaf committed
Apr 15, 2024 at 21:13 UTC
26748d06b4a39f1e1d02863245573a7ecd1bebc4
2 files changed
+150
modules/caddytls/matchers.go
+78
@@ -30,6 +30,7 @@ import (
30
func init() {
31
caddy.RegisterModule(MatchServerName{})
32
caddy.RegisterModule(MatchRemoteIP{})
33
+ caddy.RegisterModule(MatchLocalIP{})
34
}
35
36
// MatchServerName matches based on SNI. Names in
@@ -144,8 +145,85 @@ func (MatchRemoteIP) matches(ip netip.Addr, ranges []netip.Prefix) bool {
145
return false
146
}
147
148
+// MatchLocalIP matches based on the IP address of the interface
149
+// receiving the connection. Specific IPs or CIDR ranges can be specified.
150
+type MatchLocalIP struct {
151
+ // The IPs or CIDR ranges to match.
152
+ Ranges []string `json:"ranges,omitempty"`
153
+
154
+ cidrs []netip.Prefix
155
+ logger *zap.Logger
156
+}
157
+
158
+// CaddyModule returns the Caddy module information.
159
+func (MatchLocalIP) CaddyModule() caddy.ModuleInfo {
160
+ return caddy.ModuleInfo{
161
+ ID: "tls.handshake_match.local_ip",
162
+ New: func() caddy.Module { return new(MatchLocalIP) },
163
+ }
164
+}
165
+
166
+// Provision parses m's IP ranges, either from IP or CIDR expressions.
167
+func (m *MatchLocalIP) Provision(ctx caddy.Context) error {
168
+ m.logger = ctx.Logger()
169
+ for _, str := range m.Ranges {
170
+ cidrs, err := m.parseIPRange(str)
171
+ if err != nil {
172
+ return err
173
+ }
174
+ m.cidrs = append(m.cidrs, cidrs...)
175
+ }
176
+ return nil
177
+}
178
+
179
+// Match matches hello based on the connection's remote IP.
180
+func (m MatchLocalIP) Match(hello *tls.ClientHelloInfo) bool {
181
+ localAddr := hello.Conn.LocalAddr().String()
182
+ ipStr, _, err := net.SplitHostPort(localAddr)
183
+ if err != nil {
184
+ ipStr = localAddr // weird; maybe no port?
185
+ }
186
+ ipAddr, err := netip.ParseAddr(ipStr)
187
+ if err != nil {
188
+ m.logger.Error("invalid local IP addresss", zap.String("ip", ipStr))
189
+ return false
190
+ }
191
+ return (len(m.cidrs) == 0 || m.matches(ipAddr, m.cidrs))
192
+}
193
+
194
+func (MatchLocalIP) parseIPRange(str string) ([]netip.Prefix, error) {
195
+ var cidrs []netip.Prefix
196
+ if strings.Contains(str, "/") {
197
+ ipNet, err := netip.ParsePrefix(str)
198
+ if err != nil {
199
+ return nil, fmt.Errorf("parsing CIDR expression: %v", err)
200
+ }
201
+ cidrs = append(cidrs, ipNet)
202
+ } else {
203
+ ipAddr, err := netip.ParseAddr(str)
204
+ if err != nil {
205
+ return nil, fmt.Errorf("invalid IP address: '%s': %v", str, err)
206
+ }
207
+ ip := netip.PrefixFrom(ipAddr, ipAddr.BitLen())
208
+ cidrs = append(cidrs, ip)
209
+ }
210
+ return cidrs, nil
211
+}
212
+
213
+func (MatchLocalIP) matches(ip netip.Addr, ranges []netip.Prefix) bool {
214
+ for _, ipRange := range ranges {
215
+ if ipRange.Contains(ip) {
216
+ return true
217
+ }
218
+ }
219
+ return false
220
+}
221
+
222
// Interface guards
223
var (
224
_ ConnectionMatcher = (*MatchServerName)(nil)
225
_ ConnectionMatcher = (*MatchRemoteIP)(nil)
226
+
227
+ _ caddy.Provisioner = (*MatchLocalIP)(nil)
228
+ _ ConnectionMatcher = (*MatchLocalIP)(nil)
229
)
modules/caddytls/matchers_test.go
+72
@@ -165,12 +165,84 @@ func TestRemoteIPMatcher(t *testing.T) {
165
}
166
}
167
168
+func TestLocalIPMatcher(t *testing.T) {
169
+ ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
170
+ defer cancel()
171
+
172
+ for i, tc := range []struct {
173
+ ranges []string
174
+ input string
175
+ expect bool
176
+ }{
177
+ {
178
+ ranges: []string{"127.0.0.1"},
179
+ input: "127.0.0.1:12345",
180
+ expect: true,
181
+ },
182
+ {
183
+ ranges: []string{"127.0.0.1"},
184
+ input: "127.0.0.2:12345",
185
+ expect: false,
186
+ },
187
+ {
188
+ ranges: []string{"127.0.0.1/16"},
189
+ input: "127.0.1.23:12345",
190
+ expect: true,
191
+ },
192
+ {
193
+ ranges: []string{"127.0.0.1", "192.168.1.105"},
194
+ input: "192.168.1.105:12345",
195
+ expect: true,
196
+ },
197
+ {
198
+ input: "127.0.0.1:12345",
199
+ expect: true,
200
+ },
201
+ {
202
+ ranges: []string{"127.0.0.1"},
203
+ input: "127.0.0.1:12345",
204
+ expect: true,
205
+ },
206
+ {
207
+ ranges: []string{"127.0.0.2"},
208
+ input: "127.0.0.3:12345",
209
+ expect: false,
210
+ },
211
+ {
212
+ ranges: []string{"127.0.0.2"},
213
+ input: "127.0.0.2",
214
+ expect: true,
215
+ },
216
+ {
217
+ ranges: []string{"127.0.0.2"},
218
+ input: "127.0.0.300",
219
+ expect: false,
220
+ },
221
+ } {
222
+ matcher := MatchLocalIP{Ranges: tc.ranges}
223
+ err := matcher.Provision(ctx)
224
+ if err != nil {
225
+ t.Fatalf("Test %d: Provision failed: %v", i, err)
226
+ }
227
+
228
+ addr := testAddr(tc.input)
229
+ chi := &tls.ClientHelloInfo{Conn: testConn{addr: addr}}
230
+
231
+ actual := matcher.Match(chi)
232
+ if actual != tc.expect {
233
+ t.Errorf("Test %d: Expected %t but got %t (input=%s ranges=%v)",
234
+ i, tc.expect, actual, tc.input, tc.ranges)
235
+ }
236
+ }
237
+}
238
+
239
type testConn struct {
240
*net.TCPConn
241
addr testAddr
242
}
243
244
func (tc testConn) RemoteAddr() net.Addr { return tc.addr }
245
+func (tc testConn) LocalAddr() net.Addr { return tc.addr }
246
247
type testAddr string
248