Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 05:15 UTC
Implement IP/CIDR matcher and Not (negated) matcher
Matthew Holt committed
Jun 4, 2019 at 13:42 UTC
39db06d9c4bdf6bb793916225b8915bf777642b4
1 file changed
+131
-15
modules/caddyhttp/matchers.go
+131
-15
@@ -1,8 +1,10 @@
1
package caddyhttp
2
3
import (
4
+ "encoding/json"
5
"fmt"
6
"log"
7
+ "net"
8
"net/http"
9
"net/textproto"
10
"net/url"
@@ -41,6 +43,20 @@ type (
43
// MatchProtocol matches requests by protocol.
44
MatchProtocol string
45
46
+ // MatchRemoteIP matches requests by client IP (or CIDR range).
47
+ MatchRemoteIP struct {
48
+ Ranges []string `json:"ranges,omitempty"`
49
+
50
+ cidrs []*net.IPNet
51
+ }
52
+
53
+ // MatchNegate matches requests by negating its matchers' results.
54
+ MatchNegate struct {
55
+ matchersRaw map[string]json.RawMessage
56
+
57
+ matchers MatcherSet
58
+ }
59
+
60
// MatchStarlarkExpr matches requests by evaluating a Starlark expression.
61
MatchStarlarkExpr string
62
@@ -51,11 +67,11 @@ type (
67
func init() {
68
caddy2.RegisterModule(caddy2.Module{
69
Name: "http.matchers.host",
54
- New: func() interface{} { return MatchHost{} },
70
+ New: func() interface{} { return new(MatchHost) },
71
})
72
caddy2.RegisterModule(caddy2.Module{
73
Name: "http.matchers.path",
58
- New: func() interface{} { return MatchPath{} },
74
+ New: func() interface{} { return new(MatchPath) },
75
})
76
caddy2.RegisterModule(caddy2.Module{
77
Name: "http.matchers.path_regexp",
@@ -63,24 +79,32 @@ func init() {
79
})
80
caddy2.RegisterModule(caddy2.Module{
81
Name: "http.matchers.method",
66
- New: func() interface{} { return MatchMethod{} },
82
+ New: func() interface{} { return new(MatchMethod) },
83
})
84
caddy2.RegisterModule(caddy2.Module{
85
Name: "http.matchers.query",
70
- New: func() interface{} { return MatchQuery{} },
86
+ New: func() interface{} { return new(MatchQuery) },
87
})
88
caddy2.RegisterModule(caddy2.Module{
89
Name: "http.matchers.header",
74
- New: func() interface{} { return MatchHeader{} },
90
+ New: func() interface{} { return new(MatchHeader) },
91
})
92
caddy2.RegisterModule(caddy2.Module{
93
Name: "http.matchers.header_regexp",
78
- New: func() interface{} { return MatchHeaderRE{} },
94
+ New: func() interface{} { return new(MatchHeaderRE) },
95
})
96
caddy2.RegisterModule(caddy2.Module{
97
Name: "http.matchers.protocol",
98
New: func() interface{} { return new(MatchProtocol) },
99
})
100
+ caddy2.RegisterModule(caddy2.Module{
101
+ Name: "http.matchers.remote_ip",
102
+ New: func() interface{} { return new(MatchRemoteIP) },
103
+ })
104
+ caddy2.RegisterModule(caddy2.Module{
105
+ Name: "http.matchers.not",
106
+ New: func() interface{} { return new(MatchNegate) },
107
+ })
108
caddy2.RegisterModule(caddy2.Module{
109
Name: "http.matchers.starlark_expr",
110
New: func() interface{} { return new(MatchStarlarkExpr) },
@@ -229,6 +253,94 @@ func (m MatchProtocol) Match(r *http.Request) bool {
253
return false
254
}
255
256
+// UnmarshalJSON unmarshals data into m's unexported map field.
257
+// This is done because we cannot embed the map directly into
258
+// the struct, but we need a struct because we need another
259
+// field just for the provisioned modules.
260
+func (m *MatchNegate) UnmarshalJSON(data []byte) error {
261
+ return json.Unmarshal(data, &m.matchersRaw)
262
+}
263
+
264
+// Provision loads the matcher modules to be negated.
265
+func (m *MatchNegate) Provision(ctx caddy2.Context) error {
266
+ for modName, rawMsg := range m.matchersRaw {
267
+ val, err := ctx.LoadModule("http.matchers."+modName, rawMsg)
268
+ if err != nil {
269
+ return fmt.Errorf("loading matcher module '%s': %v", modName, err)
270
+ }
271
+ m.matchers = append(m.matchers, val.(RequestMatcher))
272
+ }
273
+ m.matchersRaw = nil // allow GC to deallocate - TODO: Does this help?
274
+ return nil
275
+}
276
+
277
+// Match returns true if r matches m. Since this matcher negates the
278
+// embedded matchers, false is returned if any of its matchers match.
279
+func (m MatchNegate) Match(r *http.Request) bool {
280
+ return !m.matchers.Match(r)
281
+}
282
+
283
+// Provision parses m's IP ranges, either from IP or CIDR expressions.
284
+func (m *MatchRemoteIP) Provision(ctx caddy2.Context) error {
285
+ for _, str := range m.Ranges {
286
+ if strings.Contains(str, "/") {
287
+ _, ipNet, err := net.ParseCIDR(str)
288
+ if err != nil {
289
+ return fmt.Errorf("parsing CIDR expression: %v", err)
290
+ }
291
+ m.cidrs = append(m.cidrs, ipNet)
292
+ } else {
293
+ ip := net.ParseIP(str)
294
+ if ip == nil {
295
+ return fmt.Errorf("invalid IP address: %s", str)
296
+ }
297
+ mask := len(ip) * 8
298
+ m.cidrs = append(m.cidrs, &net.IPNet{
299
+ IP: ip,
300
+ Mask: net.CIDRMask(mask, mask),
301
+ })
302
+ }
303
+ }
304
+ return nil
305
+}
306
+
307
+func (m MatchRemoteIP) getClientIP(r *http.Request) (net.IP, error) {
308
+ var remote string
309
+ if fwdFor := r.Header.Get("X-Forwarded-For"); fwdFor != "" {
310
+ remote = strings.TrimSpace(strings.Split(fwdFor, ",")[0])
311
+ }
312
+ if remote == "" {
313
+ remote = r.RemoteAddr
314
+ }
315
+
316
+ ipStr, _, err := net.SplitHostPort(remote)
317
+ if err != nil {
318
+ ipStr = remote // OK; probably didn't have a port
319
+ }
320
+
321
+ ip := net.ParseIP(ipStr)
322
+ if ip == nil {
323
+ return nil, fmt.Errorf("invalid client IP address: %s", ipStr)
324
+ }
325
+
326
+ return ip, nil
327
+}
328
+
329
+// Match returns true if r matches m.
330
+func (m MatchRemoteIP) Match(r *http.Request) bool {
331
+ clientIP, err := m.getClientIP(r)
332
+ if err != nil {
333
+ log.Printf("[ERROR] remote_ip matcher: %v", err)
334
+ return false
335
+ }
336
+ for _, ipRange := range m.cidrs {
337
+ if ipRange.Contains(clientIP) {
338
+ return true
339
+ }
340
+ }
341
+ return false
342
+}
343
+
344
// Match returns true if r matches m.
345
func (m MatchStarlarkExpr) Match(r *http.Request) bool {
346
input := string(m)
@@ -357,13 +469,17 @@ var wordRE = regexp.MustCompile(`\w+`)
469
470
// Interface guards
471
var (
360
- _ RequestMatcher = (*MatchHost)(nil)
361
- _ RequestMatcher = (*MatchPath)(nil)
362
- _ RequestMatcher = (*MatchPathRE)(nil)
363
- _ RequestMatcher = (*MatchMethod)(nil)
364
- _ RequestMatcher = (*MatchQuery)(nil)
365
- _ RequestMatcher = (*MatchHeader)(nil)
366
- _ RequestMatcher = (*MatchHeaderRE)(nil)
367
- _ RequestMatcher = (*MatchProtocol)(nil)
368
- _ RequestMatcher = (*MatchStarlarkExpr)(nil)
472
+ _ RequestMatcher = (*MatchHost)(nil)
473
+ _ RequestMatcher = (*MatchPath)(nil)
474
+ _ RequestMatcher = (*MatchPathRE)(nil)
475
+ _ RequestMatcher = (*MatchMethod)(nil)
476
+ _ RequestMatcher = (*MatchQuery)(nil)
477
+ _ RequestMatcher = (*MatchHeader)(nil)
478
+ _ RequestMatcher = (*MatchHeaderRE)(nil)
479
+ _ RequestMatcher = (*MatchProtocol)(nil)
480
+ _ RequestMatcher = (*MatchRemoteIP)(nil)
481
+ _ caddy2.Provisioner = (*MatchRemoteIP)(nil)
482
+ _ RequestMatcher = (*MatchNegate)(nil)
483
+ _ caddy2.Provisioner = (*MatchNegate)(nil)
484
+ _ RequestMatcher = (*MatchStarlarkExpr)(nil)
485
)