@marcel91200 / caddy Mirror · read-only / commits / 6a4d638c
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

caddyhttp: Implement CEL matcher (see #3051) (#3155)

* caddyhttp: Implement CEL matcher (see #3051) CEL (Common Expression Language) is a very fast, flexible way to express complex logic, useful for matching requests when the conditions are not easy to express with JSON. This matcher may be considered experimental even after the 2.0 release. * Improve CEL module docs

Matt Holt committed Mar 19, 2020 at 15:46 UTC 6a4d638c1e62caf834a73ac4e3fcfc25bc32eedb
4 files changed +228 -3
go.mod
+2
@@ -10,6 +10,7 @@ require (
10 github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac
11 github.com/go-acme/lego/v3 v3.5.0
12 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
13 + github.com/google/cel-go v0.3.2
14 github.com/ilibs/json5 v1.0.1
15 github.com/jsternberg/zap-logfmt v1.2.0
16 github.com/klauspost/compress v1.10.3
@@ -29,6 +30,7 @@ require (
30 go.uber.org/zap v1.14.1
31 golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6
32 golang.org/x/net v0.0.0-20200301022130-244492dfa37a
33 + google.golang.org/genproto v0.0.0-20200305110556-506484158171
34 gopkg.in/natefinch/lumberjack.v2 v2.0.0
35 gopkg.in/square/go-jose.v2 v2.4.1 // indirect
36 gopkg.in/yaml.v2 v2.2.8
go.sum
+5
@@ -104,6 +104,8 @@ github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDa
104 github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
105 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
106 github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
107 +github.com/antlr/antlr4 v0.0.0-20190819145818-b43a4c3a8015 h1:StuiJFxQUsxSCzcby6NFZRdEhPkXD5vxN7TZ4MD6T84=
108 +github.com/antlr/antlr4 v0.0.0-20190819145818-b43a4c3a8015/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
109 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
110 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
111 github.com/asaskevich/govalidator v0.0.0-20180315120708-ccb8e960c48f/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
@@ -298,6 +300,9 @@ github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039/go.mod h1:qOQCunE
300 github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
301 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
302 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
303 +github.com/google/cel-go v0.3.2 h1:72Lj/nrfpWSJkuXdeEGB/7jfdwVFtV8kPJSL2Mt9rog=
304 +github.com/google/cel-go v0.3.2/go.mod h1:DoRSdzaJzNiP1lVuWhp/RjSnHLDQr/aNPlyqSBasBqA=
305 +github.com/google/cel-spec v0.3.0/go.mod h1:MjQm800JAGhOZXI7vatnVpmIaFTR6L8FHcKk+piiKpI=
306 github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
307 github.com/google/certificate-transparency-go v1.1.0/go.mod h1:i+Q7XY+ArBveOUT36jiHGfuSK1fHICIg6sUkRxPAbCs=
308 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
modules/caddyhttp/celmatcher.go new
+221
@@ -0,0 +1,221 @@
1 +// Copyright 2015 Matthew Holt and The Caddy Authors
2 +//
3 +// Licensed under the Apache License, Version 2.0 (the "License");
4 +// you may not use this file except in compliance with the License.
5 +// You may obtain a copy of the License at
6 +//
7 +// http://www.apache.org/licenses/LICENSE-2.0
8 +//
9 +// Unless required by applicable law or agreed to in writing, software
10 +// distributed under the License is distributed on an "AS IS" BASIS,
11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 +// See the License for the specific language governing permissions and
13 +// limitations under the License.
14 +
15 +package caddyhttp
16 +
17 +import (
18 + "encoding/json"
19 + "fmt"
20 + "net/http"
21 + "reflect"
22 + "regexp"
23 + "strings"
24 +
25 + "github.com/caddyserver/caddy/v2"
26 + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
27 + "github.com/google/cel-go/cel"
28 + "github.com/google/cel-go/checker/decls"
29 + "github.com/google/cel-go/common/types"
30 + "github.com/google/cel-go/common/types/ref"
31 + "github.com/google/cel-go/common/types/traits"
32 + "github.com/google/cel-go/interpreter/functions"
33 + exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
34 +)
35 +
36 +func init() {
37 + caddy.RegisterModule(MatchExpression{})
38 +}
39 +
40 +// MatchExpression matches requests by evaluating a
41 +// [CEL](https://github.com/google/cel-spec) expression.
42 +// This enables complex logic to be expressed using a comfortable,
43 +// familiar syntax.
44 +//
45 +// COMPATIBILITY NOTE: This module is still experimental and is not
46 +// subject to Caddy's compatibility guarantee.
47 +type MatchExpression struct {
48 + // The CEL expression to evaluate. Any Caddy placeholders
49 + // will be expanded and situated into proper CEL function
50 + // calls before evaluating.
51 + Expr string
52 +
53 + expandedExpr string
54 + prg cel.Program
55 +}
56 +
57 +// CaddyModule returns the Caddy module information.
58 +func (MatchExpression) CaddyModule() caddy.ModuleInfo {
59 + return caddy.ModuleInfo{
60 + ID: "http.matchers.expression",
61 + New: func() caddy.Module { return new(MatchExpression) },
62 + }
63 +}
64 +
65 +// MarshalJSON marshals m's expression.
66 +func (m MatchExpression) MarshalJSON() ([]byte, error) {
67 + return json.Marshal(m.Expr)
68 +}
69 +
70 +// UnmarshalJSON unmarshals m's expression.
71 +func (m *MatchExpression) UnmarshalJSON(data []byte) error {
72 + return json.Unmarshal(data, &m.Expr)
73 +}
74 +
75 +// Provision sets ups m.
76 +func (m *MatchExpression) Provision(_ caddy.Context) error {
77 + // replace placeholders with a function call - this is just some
78 + // light (and possibly naïve) syntactic sugar
79 + m.expandedExpr = placeholderRegexp.ReplaceAllString(m.Expr, placeholderExpansion)
80 +
81 + // create the CEL environment
82 + env, err := cel.NewEnv(
83 + cel.Declarations(
84 + decls.NewIdent("request", httpRequestObjectType, nil),
85 + decls.NewFunction(placeholderFuncName,
86 + decls.NewOverload(placeholderFuncName+"_httpRequest_string",
87 + []*exprpb.Type{httpRequestObjectType, decls.String},
88 + decls.String)),
89 + ),
90 + cel.CustomTypeAdapter(celHTTPRequestTypeAdapter{}),
91 + )
92 + if err != nil {
93 + return fmt.Errorf("setting up CEL environment: %v", err)
94 + }
95 +
96 + // parse the expression
97 + parsed, issues := env.Parse(m.expandedExpr)
98 + if issues != nil && issues.Err() != nil {
99 + return fmt.Errorf("parsing CEL program: %s", issues.Err())
100 + }
101 +
102 + // type-check it
103 + checked, issues := env.Check(parsed)
104 + if issues != nil && issues.Err() != nil {
105 + return fmt.Errorf("type-checking CEL program: %s", issues.Err())
106 + }
107 +
108 + // compile the "program"
109 + m.prg, err = env.Program(checked,
110 + cel.Functions(
111 + &functions.Overload{
112 + Operator: placeholderFuncName,
113 + Binary: caddyPlaceholderFunc,
114 + },
115 + ),
116 + )
117 +
118 + if err != nil {
119 + return fmt.Errorf("compiling CEL program: %s", err)
120 + }
121 + return nil
122 +}
123 +
124 +// Match returns true if r matches m.
125 +func (m MatchExpression) Match(r *http.Request) bool {
126 + out, _, _ := m.prg.Eval(map[string]interface{}{
127 + "request": celHTTPRequest{r},
128 + })
129 + if outBool, ok := out.Value().(bool); ok {
130 + return outBool
131 + }
132 + return false
133 +
134 +}
135 +
136 +// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
137 +func (m *MatchExpression) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
138 + for d.Next() {
139 + m.Expr = strings.Join(d.RemainingArgs(), " ")
140 + }
141 + return nil
142 +}
143 +
144 +// httpRequestCELType is the type representation of a native HTTP request.
145 +var httpRequestCELType = types.NewTypeValue("http.Request", traits.ReceiverType)
146 +
147 +// cellHTTPRequest wraps an http.Request with
148 +// methods to satisfy the ref.Val interface.
149 +type celHTTPRequest struct {
150 + *http.Request
151 +}
152 +
153 +func (cr celHTTPRequest) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
154 + return cr.Request, nil
155 +}
156 +func (celHTTPRequest) ConvertToType(typeVal ref.Type) ref.Val {
157 + panic("not implemented")
158 +}
159 +func (cr celHTTPRequest) Equal(other ref.Val) ref.Val {
160 + if o, ok := other.Value().(celHTTPRequest); ok {
161 + return types.Bool(o.Request == cr.Request)
162 + }
163 + return types.ValOrErr(other, "%v is not comparable type", other)
164 +}
165 +func (celHTTPRequest) Type() ref.Type { return httpRequestCELType }
166 +func (cr celHTTPRequest) Value() interface{} { return cr }
167 +
168 +// celHTTPRequestTypeAdapter can adapt a
169 +// celHTTPRequest to a CEL value.
170 +type celHTTPRequestTypeAdapter struct{}
171 +
172 +func (celHTTPRequestTypeAdapter) NativeToValue(value interface{}) ref.Val {
173 + if celReq, ok := value.(celHTTPRequest); ok {
174 + return celReq
175 + }
176 + return types.DefaultTypeAdapter.NativeToValue(value)
177 +}
178 +
179 +// Variables used for replacing Caddy placeholders in CEL
180 +// expressions with a proper CEL function call; this is
181 +// just for syntactic sugar.
182 +var (
183 + placeholderRegexp = regexp.MustCompile(`{([\w.-]+)}`)
184 + placeholderExpansion = `caddyPlaceholder(request, "${1}")`
185 +)
186 +
187 +var httpRequestObjectType = decls.NewObjectType("http.Request")
188 +
189 +// The name of the CEL function which accesses Replacer values.
190 +const placeholderFuncName = "caddyPlaceholder"
191 +
192 +// caddyPlaceholderFunc implements the custom CEL function that
193 +// accesses the Replacer on a request and gets values from it.
194 +func caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val {
195 + celReq, ok := lhs.(celHTTPRequest)
196 + if !ok {
197 + return types.NewErr(
198 + "invalid request of type '%v' to "+placeholderFuncName+"(request, placeholderVarName)",
199 + lhs.Type())
200 + }
201 + phStr, ok := rhs.(types.String)
202 + if !ok {
203 + return types.NewErr(
204 + "invalid placeholder variable name of type '%v' to "+placeholderFuncName+"(request, placeholderVarName)",
205 + rhs.Type())
206 + }
207 +
208 + repl := celReq.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
209 + val, _ := repl.Get(string(phStr))
210 +
211 + return types.String(val)
212 +}
213 +
214 +// Interface guards
215 +var (
216 + _ caddy.Provisioner = (*MatchExpression)(nil)
217 + _ RequestMatcher = (*MatchExpression)(nil)
218 + _ caddyfile.Unmarshaler = (*MatchExpression)(nil)
219 + _ json.Marshaler = (*MatchExpression)(nil)
220 + _ json.Unmarshaler = (*MatchExpression)(nil)
221 +)
modules/caddyhttp/matchers.go
-3
@@ -107,9 +107,6 @@ type (
107
108 Matchers MatcherSet `json:"-"`
109 }
110 -
111 - // MatchTable matches requests by values in the table.
112 - MatchTable string // TODO: finish implementing
110 )
111
112 func init() {