@marcel91200 / caddy Mirror · read-only / commits / 39db06d9
Fork 0

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 )