@marcel91200 / caddy Mirror · read-only
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 11, 01:00 UTC

master
go 874 lines 29.2 KB
Raw
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 "crypto/x509/pkix"
19 "encoding/json"
20 "errors"
21 "fmt"
22 "net/http"
23 "reflect"
24 "regexp"
25 "slices"
26 "strings"
27 "time"
28
29 "cel.dev/cel-go/cel"
30 "cel.dev/cel-go/common"
31 "cel.dev/cel-go/common/ast"
32 "cel.dev/cel-go/common/operators"
33 "cel.dev/cel-go/common/types"
34 "cel.dev/cel-go/common/types/ref"
35 "cel.dev/cel-go/common/types/traits"
36 "cel.dev/cel-go/ext"
37 "cel.dev/cel-go/interpreter"
38 "cel.dev/cel-go/interpreter/functions"
39 "cel.dev/cel-go/parser"
40 "go.uber.org/zap"
41
42 "github.com/caddyserver/caddy/v2"
43 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
44 )
45
46 func init() {
47 caddy.RegisterModule(MatchExpression{})
48 }
49
50 // MatchExpression matches requests by evaluating a
51 // [CEL](https://github.com/google/cel-spec) expression.
52 // This enables complex logic to be expressed using a comfortable,
53 // familiar syntax. Please refer to
54 // [the standard definitions of CEL functions and operators](https://github.com/google/cel-spec/blob/master/doc/langdef.md#standard-definitions).
55 //
56 // This matcher's JSON interface is actually a string, not a struct.
57 // The generated docs are not correct because this type has custom
58 // marshaling logic.
59 //
60 // COMPATIBILITY NOTE: This module is still experimental and is not
61 // subject to Caddy's compatibility guarantee.
62 type MatchExpression struct {
63 // The CEL expression to evaluate. Any Caddy placeholders
64 // will be expanded and situated into proper CEL function
65 // calls before evaluating.
66 Expr string `json:"expr,omitempty"`
67
68 // Name is an optional name for this matcher.
69 // This is used to populate the name for regexp
70 // matchers that appear in the expression.
71 Name string `json:"name,omitempty"`
72
73 expandedExpr string
74 placeholders []string
75 prg cel.Program
76 ta types.Adapter
77
78 log *zap.Logger
79 }
80
81 // CaddyModule returns the Caddy module information.
82 func (MatchExpression) CaddyModule() caddy.ModuleInfo {
83 return caddy.ModuleInfo{
84 ID: "http.matchers.expression",
85 New: func() caddy.Module { return new(MatchExpression) },
86 }
87 }
88
89 // MarshalJSON marshals m's expression.
90 func (m MatchExpression) MarshalJSON() ([]byte, error) {
91 // if the name is empty, then we can marshal just the expression string
92 if m.Name == "" {
93 return json.Marshal(m.Expr)
94 }
95 // otherwise, we need to marshal the full object, using an
96 // anonymous struct to avoid infinite recursion
97 return json.Marshal(struct {
98 Expr string `json:"expr"`
99 Name string `json:"name"`
100 }{
101 Expr: m.Expr,
102 Name: m.Name,
103 })
104 }
105
106 // UnmarshalJSON unmarshals m's expression.
107 func (m *MatchExpression) UnmarshalJSON(data []byte) error {
108 // if the data is a string, then it's just the expression
109 if data[0] == '"' {
110 return json.Unmarshal(data, &m.Expr)
111 }
112 // otherwise, it's a full object, so unmarshal it,
113 // using a temp map to avoid infinite recursion
114 var tmpJson map[string]any
115 err := json.Unmarshal(data, &tmpJson)
116 *m = MatchExpression{
117 Expr: tmpJson["expr"].(string),
118 Name: tmpJson["name"].(string),
119 }
120 return err
121 }
122
123 // Provision sets up m.
124 func (m *MatchExpression) Provision(ctx caddy.Context) error {
125 m.log = ctx.Logger()
126
127 // replace placeholders with a function call - this is just some
128 // light (and possibly naïve) syntactic sugar
129 m.expandedExpr = placeholderRegexp.ReplaceAllString(m.Expr, placeholderExpansion)
130
131 // as a second pass, we'll strip the escape character from an escaped
132 // placeholder, so that it can be used as an input to other CEL functions
133 m.expandedExpr = escapedPlaceholderRegexp.ReplaceAllString(m.expandedExpr, escapedPlaceholderExpansion)
134
135 // our type adapter expands CEL's standard type support
136 m.ta = celTypeAdapter{}
137
138 // initialize the CEL libraries from the Matcher implementations which
139 // have been configured to support CEL.
140 matcherLibProducers := []CELLibraryProducer{}
141 for _, info := range caddy.GetModules("http.matchers") {
142 p, ok := info.New().(CELLibraryProducer)
143 if ok {
144 matcherLibProducers = append(matcherLibProducers, p)
145 }
146 }
147
148 // add the matcher name to the context so that the matcher name
149 // can be used by regexp matchers being provisioned
150 ctx = ctx.WithValue(MatcherNameCtxKey, m.Name)
151
152 // Assemble the compilation and program options from the different library
153 // producers into a single cel.Library implementation.
154 matcherEnvOpts := []cel.EnvOption{}
155 matcherProgramOpts := []cel.ProgramOption{}
156 for _, producer := range matcherLibProducers {
157 l, err := producer.CELLibrary(ctx)
158 if err != nil {
159 return fmt.Errorf("error initializing CEL library for %T: %v", producer, err)
160 }
161 matcherEnvOpts = append(matcherEnvOpts, l.CompileOptions()...)
162 matcherProgramOpts = append(matcherProgramOpts, l.ProgramOptions()...)
163 }
164 matcherLib := cel.Lib(NewMatcherCELLibrary(matcherEnvOpts, matcherProgramOpts))
165
166 // create the CEL environment
167 env, err := cel.NewEnv(
168 cel.Function(CELPlaceholderFuncName, cel.SingletonBinaryBinding(m.caddyPlaceholderFunc), cel.Overload(
169 CELPlaceholderFuncName+"_httpRequest_string",
170 []*cel.Type{httpRequestObjectType, cel.StringType},
171 cel.AnyType,
172 )),
173 cel.Variable(CELRequestVarName, httpRequestObjectType),
174 cel.CustomTypeAdapter(m.ta),
175 ext.Strings(),
176 ext.Bindings(),
177 ext.Lists(),
178 ext.Math(),
179 matcherLib,
180 )
181 if err != nil {
182 return fmt.Errorf("setting up CEL environment: %v", err)
183 }
184
185 // parse and type-check the expression
186 checked, issues := env.Compile(m.expandedExpr)
187 if issues.Err() != nil {
188 return fmt.Errorf("compiling CEL program: %s", issues.Err())
189 }
190
191 // request matching is a boolean operation, so we don't really know
192 // what to do if the expression returns a non-boolean type
193 if checked.OutputType() != cel.BoolType {
194 return fmt.Errorf("CEL request matcher expects return type of bool, not %s", checked.OutputType())
195 }
196
197 m.placeholders = celPlaceholderNames(checked.NativeRep().Expr())
198
199 // compile the "program"
200 m.prg, err = env.Program(checked, cel.EvalOptions(cel.OptOptimize))
201 if err != nil {
202 return fmt.Errorf("compiling CEL program: %s", err)
203 }
204 return nil
205 }
206
207 // Placeholders returns the names of the placeholders the provisioned
208 // expression reads. A name that is only known at evaluation time is
209 // returned as an empty string.
210 func (m MatchExpression) Placeholders() []string {
211 return slices.Clone(m.placeholders)
212 }
213
214 // Match returns true if r matches m.
215 func (m MatchExpression) Match(r *http.Request) bool {
216 match, err := m.MatchWithError(r)
217 if err != nil {
218 SetVar(r.Context(), MatcherErrorVarKey, err)
219 }
220 return match
221 }
222
223 // MatchWithError returns true if r matches m.
224 func (m MatchExpression) MatchWithError(r *http.Request) (bool, error) {
225 celReq := celHTTPRequest{r}
226 out, _, err := m.prg.Eval(celReq)
227 if err != nil {
228 m.log.Error("evaluating expression", zap.Error(err))
229 return false, err
230 }
231 if outBool, ok := out.Value().(bool); ok {
232 return outBool, nil
233 }
234 return false, nil
235 }
236
237 // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
238 func (m *MatchExpression) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
239 d.Next() // consume matcher name
240
241 // if there's multiple args, then we need to keep the raw
242 // tokens because the user may have used quotes within their
243 // CEL expression (e.g. strings) and we should retain that
244 if d.CountRemainingArgs() > 1 {
245 m.Expr = strings.Join(d.RemainingArgsRaw(), " ")
246 } else {
247 // there should at least be one arg
248 if !d.NextArg() {
249 return d.ArgErr()
250 }
251
252 // if there's only one token, then we can safely grab the
253 // cleaned token (no quotes) and use that as the expression
254 // because there's no valid CEL expression that is only a
255 // quoted string; commonly quotes are used in Caddyfile to
256 // define the expression
257 m.Expr = d.Val()
258
259 // use the named matcher's name, to fill regexp
260 // matchers names by default
261 m.Name = d.GetContextString(caddyfile.MatcherNameCtxKey)
262 }
263
264 // a matcher set holds only one expression matcher
265 if d.Next() {
266 return d.Err("repeated expression matchers are not supported, combine them into one expression with &&")
267 }
268
269 return nil
270 }
271
272 // caddyPlaceholderFunc implements the custom CEL function that accesses the
273 // Replacer on a request and gets values from it.
274 func (m MatchExpression) caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val {
275 celReq, ok := lhs.(celHTTPRequest)
276 if !ok {
277 return types.NewErr(
278 "invalid request of type '%v' to %s(request, placeholderVarName)",
279 lhs.Type(),
280 CELPlaceholderFuncName,
281 )
282 }
283 phStr, ok := rhs.(types.String)
284 if !ok {
285 return types.NewErr(
286 "invalid placeholder variable name of type '%v' to %s(request, placeholderVarName)",
287 rhs.Type(),
288 CELPlaceholderFuncName,
289 )
290 }
291
292 repl := celReq.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
293 val, _ := repl.Get(string(phStr))
294
295 return m.ta.NativeToValue(val)
296 }
297
298 // httpRequestCELType is the type representation of a native HTTP request.
299 var httpRequestCELType = cel.ObjectType("http.Request", traits.ReceiverType)
300
301 // celHTTPRequest wraps an http.Request with ref.Val interface methods.
302 //
303 // This type also implements the interpreter.Activation interface which
304 // drops allocation costs for CEL expression evaluations by roughly half.
305 type celHTTPRequest struct{ *http.Request }
306
307 func (cr celHTTPRequest) ResolveName(name string) (any, bool) {
308 if name == CELRequestVarName {
309 return cr, true
310 }
311 return nil, false
312 }
313
314 func (cr celHTTPRequest) Parent() interpreter.Activation {
315 return nil
316 }
317
318 func (cr celHTTPRequest) ConvertToNative(typeDesc reflect.Type) (any, error) {
319 return cr.Request, nil
320 }
321
322 func (celHTTPRequest) ConvertToType(typeVal ref.Type) ref.Val {
323 panic("not implemented")
324 }
325
326 func (cr celHTTPRequest) Equal(other ref.Val) ref.Val {
327 if o, ok := other.Value().(celHTTPRequest); ok {
328 return types.Bool(o.Request == cr.Request)
329 }
330 return types.ValOrErr(other, "%v is not comparable type", other)
331 }
332 func (celHTTPRequest) Type() ref.Type { return httpRequestCELType }
333 func (cr celHTTPRequest) Value() any { return cr }
334
335 var pkixNameCELType = cel.ObjectType("pkix.Name", traits.ReceiverType)
336
337 // celPkixName wraps a pkix.Name with
338 // methods to satisfy the ref.Val interface.
339 type celPkixName struct{ *pkix.Name }
340
341 func (pn celPkixName) ConvertToNative(typeDesc reflect.Type) (any, error) {
342 return pn.Name, nil
343 }
344
345 func (pn celPkixName) ConvertToType(typeVal ref.Type) ref.Val {
346 if typeVal.TypeName() == "string" {
347 return types.String(pn.Name.String())
348 }
349 panic("not implemented")
350 }
351
352 func (pn celPkixName) Equal(other ref.Val) ref.Val {
353 if o, ok := other.Value().(string); ok {
354 return types.Bool(pn.Name.String() == o)
355 }
356 return types.ValOrErr(other, "%v is not comparable type", other)
357 }
358 func (celPkixName) Type() ref.Type { return pkixNameCELType }
359 func (pn celPkixName) Value() any { return pn }
360
361 // celTypeAdapter can adapt our custom types to a CEL value.
362 type celTypeAdapter struct{}
363
364 func (celTypeAdapter) NativeToValue(value any) ref.Val {
365 switch v := value.(type) {
366 case celHTTPRequest:
367 return v
368 case pkix.Name:
369 return celPkixName{&v}
370 case time.Time:
371 return types.Timestamp{Time: v}
372 case error:
373 return types.WrapErr(v)
374 }
375 return types.DefaultTypeAdapter.NativeToValue(value)
376 }
377
378 // CELLibraryProducer provide CEL libraries that expose a Matcher
379 // implementation as a first class function within the CEL expression
380 // matcher.
381 type CELLibraryProducer interface {
382 // CELLibrary creates a cel.Library which makes it possible to use the
383 // target object within CEL expression matchers.
384 CELLibrary(caddy.Context) (cel.Library, error)
385 }
386
387 // CELMatcherImpl creates a new cel.Library based on the following pieces of
388 // data:
389 //
390 // - macroName: the function name to be used within CEL. This will be a macro
391 // and not a function proper.
392 // - funcName: the function overload name generated by the CEL macro used to
393 // represent the matcher.
394 // - matcherDataTypes: the argument types to the macro.
395 // - fac: a matcherFactory implementation which converts from CEL constant
396 // values to a Matcher instance.
397 //
398 // Note, macro names and function names must not collide with other macros or
399 // functions exposed within CEL expressions, or an error will be produced
400 // during the expression matcher plan time.
401 //
402 // The existing CELMatcherImpl support methods are configured to support a
403 // limited set of function signatures. For strong type validation you may need
404 // to provide a custom macro which does a more detailed analysis of the CEL
405 // literal provided to the macro as an argument.
406 func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*cel.Type, fac any) (cel.Library, error) {
407 requestType := cel.ObjectType("http.Request")
408 var macro parser.Macro
409 switch len(matcherDataTypes) {
410 case 1:
411 matcherDataType := matcherDataTypes[0]
412 switch matcherDataType.String() {
413 case "list(string)":
414 macro = parser.NewGlobalVarArgMacro(macroName, celMatcherStringListMacroExpander(funcName))
415 case cel.StringType.String():
416 macro = parser.NewGlobalMacro(macroName, 1, celMatcherStringMacroExpander(funcName))
417 case CELTypeJSON.String():
418 macro = parser.NewGlobalMacro(macroName, 1, celMatcherJSONMacroExpander(funcName))
419 default:
420 return nil, fmt.Errorf("unsupported matcher data type: %s", matcherDataType)
421 }
422 case 2:
423 if matcherDataTypes[0] == cel.StringType && matcherDataTypes[1] == cel.StringType {
424 macro = parser.NewGlobalMacro(macroName, 2, celMatcherStringListMacroExpander(funcName))
425 matcherDataTypes = []*cel.Type{cel.ListType(cel.StringType)}
426 } else {
427 return nil, fmt.Errorf("unsupported matcher data type: %s, %s", matcherDataTypes[0], matcherDataTypes[1])
428 }
429 case 3:
430 // nolint:gosec // false positive, impossible to be out of bounds; see: https://github.com/securego/gosec/issues/1525
431 if matcherDataTypes[0] == cel.StringType && matcherDataTypes[1] == cel.StringType && matcherDataTypes[2] == cel.StringType {
432 macro = parser.NewGlobalMacro(macroName, 3, celMatcherStringListMacroExpander(funcName))
433 matcherDataTypes = []*cel.Type{cel.ListType(cel.StringType)}
434 } else {
435 // nolint:gosec // false positive, impossible to be out of bounds; see: https://github.com/securego/gosec/issues/1525
436 return nil, fmt.Errorf("unsupported matcher data type: %s, %s, %s", matcherDataTypes[0], matcherDataTypes[1], matcherDataTypes[2])
437 }
438 }
439 envOptions := []cel.EnvOption{
440 cel.Macros(macro),
441 cel.Function(funcName,
442 cel.Overload(funcName, append([]*cel.Type{requestType}, matcherDataTypes...), cel.BoolType),
443 cel.SingletonBinaryBinding(CELMatcherRuntimeFunction(funcName, fac))),
444 }
445 programOptions := []cel.ProgramOption{
446 cel.CustomDecorator(CELMatcherDecorator(funcName, fac)),
447 }
448 return NewMatcherCELLibrary(envOptions, programOptions), nil
449 }
450
451 // CELMatcherFactory converts a constant CEL value into a RequestMatcher.
452 // Deprecated: Use CELMatcherWithErrorFactory instead.
453 type CELMatcherFactory = func(data ref.Val) (RequestMatcher, error)
454
455 // CELMatcherWithErrorFactory converts a constant CEL value into a RequestMatcherWithError.
456 type CELMatcherWithErrorFactory = func(data ref.Val) (RequestMatcherWithError, error)
457
458 // matcherCELLibrary is a simplistic configurable cel.Library implementation.
459 type matcherCELLibrary struct {
460 envOptions []cel.EnvOption
461 programOptions []cel.ProgramOption
462 }
463
464 // NewMatcherCELLibrary creates a matcherLibrary from option setes.
465 func NewMatcherCELLibrary(envOptions []cel.EnvOption, programOptions []cel.ProgramOption) cel.Library {
466 return &matcherCELLibrary{
467 envOptions: envOptions,
468 programOptions: programOptions,
469 }
470 }
471
472 func (lib *matcherCELLibrary) CompileOptions() []cel.EnvOption {
473 return lib.envOptions
474 }
475
476 func (lib *matcherCELLibrary) ProgramOptions() []cel.ProgramOption {
477 return lib.programOptions
478 }
479
480 // CELMatcherDecorator matches a call overload generated by a CEL macro
481 // that takes a single argument, and optimizes the implementation to precompile
482 // the matcher and return a function that references the precompiled and
483 // provisioned matcher.
484 func CELMatcherDecorator(funcName string, fac any) interpreter.InterpretableDecorator {
485 return func(i interpreter.Interpretable) (interpreter.Interpretable, error) {
486 call, ok := i.(interpreter.InterpretableCall)
487 if !ok {
488 return i, nil
489 }
490 if call.OverloadID() != funcName {
491 return i, nil
492 }
493 callArgs := call.Args()
494 reqAttr, ok := callArgs[0].(interpreter.InterpretableAttribute)
495 if !ok {
496 return nil, errors.New("missing 'req' argument")
497 }
498 nsAttr, ok := reqAttr.Attr().(interpreter.NamespacedAttribute)
499 if !ok {
500 return nil, errors.New("missing 'req' argument")
501 }
502 varNames := nsAttr.CandidateVariableNames()
503 if len(varNames) != 1 || len(varNames) == 1 && varNames[0] != CELRequestVarName {
504 return nil, errors.New("missing 'req' argument")
505 }
506 matcherData, ok := callArgs[1].(interpreter.InterpretableConst)
507 if !ok {
508 // If the matcher arguments are not constant, then this means
509 // they contain a Caddy placeholder reference and the evaluation
510 // and matcher provisioning should be handled at dynamically.
511 return i, nil
512 }
513
514 if factory, ok := fac.(CELMatcherWithErrorFactory); ok {
515 matcher, err := factory(matcherData.Value())
516 if err != nil {
517 return nil, err
518 }
519 return interpreter.NewCall(
520 i.ID(), funcName, funcName+"_opt",
521 []interpreter.InterpretableV2{reqAttr},
522 func(args ...ref.Val) ref.Val {
523 // The request value, guaranteed to be of type celHTTPRequest
524 celReq := args[0]
525 // If needed this call could be changed to convert the value
526 // to a *http.Request using CEL's ConvertToNative method.
527 httpReq := celReq.Value().(celHTTPRequest)
528 match, err := matcher.MatchWithError(httpReq.Request)
529 if err != nil {
530 return types.WrapErr(err)
531 }
532 return types.Bool(match)
533 },
534 ), nil
535 }
536
537 if factory, ok := fac.(CELMatcherFactory); ok {
538 matcher, err := factory(matcherData.Value())
539 if err != nil {
540 return nil, err
541 }
542 return interpreter.NewCall(
543 i.ID(), funcName, funcName+"_opt",
544 []interpreter.InterpretableV2{reqAttr},
545 func(args ...ref.Val) ref.Val {
546 // The request value, guaranteed to be of type celHTTPRequest
547 celReq := args[0]
548 // If needed this call could be changed to convert the value
549 // to a *http.Request using CEL's ConvertToNative method.
550 httpReq := celReq.Value().(celHTTPRequest)
551 if m, ok := matcher.(RequestMatcherWithError); ok {
552 match, err := m.MatchWithError(httpReq.Request)
553 if err != nil {
554 return types.WrapErr(err)
555 }
556 return types.Bool(match)
557 }
558 return types.Bool(matcher.Match(httpReq.Request))
559 },
560 ), nil
561 }
562
563 return nil, fmt.Errorf("invalid matcher factory, must be CELMatcherFactory or CELMatcherWithErrorFactory: %T", fac)
564 }
565 }
566
567 // CELMatcherRuntimeFunction creates a function binding for when the input to the matcher
568 // is dynamically resolved rather than a set of static constant values.
569 func CELMatcherRuntimeFunction(funcName string, fac any) functions.BinaryOp {
570 return func(celReq, matcherData ref.Val) ref.Val {
571 if factory, ok := fac.(CELMatcherWithErrorFactory); ok {
572 matcher, err := factory(matcherData)
573 if err != nil {
574 return types.WrapErr(err)
575 }
576 httpReq := celReq.Value().(celHTTPRequest)
577 match, err := matcher.MatchWithError(httpReq.Request)
578 if err != nil {
579 return types.WrapErr(err)
580 }
581 return types.Bool(match)
582 }
583 if factory, ok := fac.(CELMatcherFactory); ok {
584 matcher, err := factory(matcherData)
585 if err != nil {
586 return types.WrapErr(err)
587 }
588 httpReq := celReq.Value().(celHTTPRequest)
589 if m, ok := matcher.(RequestMatcherWithError); ok {
590 match, err := m.MatchWithError(httpReq.Request)
591 if err != nil {
592 return types.WrapErr(err)
593 }
594 return types.Bool(match)
595 }
596 return types.Bool(matcher.Match(httpReq.Request))
597 }
598 return types.NewErr("CELMatcherRuntimeFunction invalid matcher factory: %T", fac)
599 }
600 }
601
602 // celMatcherStringListMacroExpander validates that the macro is called
603 // with a variable number of string arguments (at least one).
604 //
605 // The arguments are collected into a single list argument the following
606 // function call returned: <funcName>(request, [args])
607 func celMatcherStringListMacroExpander(funcName string) cel.MacroFactory {
608 return func(eh cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *common.Error) {
609 matchArgs := []ast.Expr{}
610 if len(args) == 0 {
611 return nil, eh.NewError(0, "matcher requires at least one argument")
612 }
613 for _, arg := range args {
614 if isCELStringExpr(arg) {
615 matchArgs = append(matchArgs, arg)
616 } else {
617 return nil, eh.NewError(arg.ID(), "matcher arguments must be string constants")
618 }
619 }
620 return eh.NewCall(funcName, eh.NewIdent(CELRequestVarName), eh.NewList(matchArgs...)), nil
621 }
622 }
623
624 // celMatcherStringMacroExpander validates that the macro is called a single
625 // string argument.
626 //
627 // The following function call is returned: <funcName>(request, arg)
628 func celMatcherStringMacroExpander(funcName string) parser.MacroExpander {
629 return func(eh cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *common.Error) {
630 if len(args) != 1 {
631 return nil, eh.NewError(0, "matcher requires one argument")
632 }
633 if isCELStringExpr(args[0]) {
634 return eh.NewCall(funcName, eh.NewIdent(CELRequestVarName), args[0]), nil
635 }
636 return nil, eh.NewError(args[0].ID(), "matcher argument must be a string literal")
637 }
638 }
639
640 // celMatcherJSONMacroExpander validates that the macro is called a single
641 // map literal argument.
642 //
643 // The following function call is returned: <funcName>(request, arg)
644 func celMatcherJSONMacroExpander(funcName string) parser.MacroExpander {
645 return func(eh cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *common.Error) {
646 if len(args) != 1 {
647 return nil, eh.NewError(0, "matcher requires a map literal argument")
648 }
649 arg := args[0]
650
651 switch arg.Kind() {
652 case ast.StructKind:
653 return nil, eh.NewError(arg.ID(),
654 fmt.Sprintf("matcher input must be a map literal, not a %s", arg.AsStruct().TypeName()))
655 case ast.MapKind:
656 mapExpr := arg.AsMap()
657 for _, entry := range mapExpr.Entries() {
658 isStringPlaceholder := isCELStringExpr(entry.AsMapEntry().Key())
659 if !isStringPlaceholder {
660 return nil, eh.NewError(entry.ID(), "matcher map keys must be string literals")
661 }
662 isStringListPlaceholder := isCELStringExpr(entry.AsMapEntry().Value()) ||
663 isCELStringListLiteral(entry.AsMapEntry().Value())
664 if !isStringListPlaceholder {
665 return nil, eh.NewError(entry.AsMapEntry().Value().ID(), "matcher map values must be string or list literals")
666 }
667 }
668 return eh.NewCall(funcName, eh.NewIdent(CELRequestVarName), arg), nil
669 case ast.UnspecifiedExprKind, ast.CallKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.SelectKind:
670 // appeasing the linter :)
671 }
672
673 return nil, eh.NewError(arg.ID(), "matcher requires a map literal argument")
674 }
675 }
676
677 // CELValueToMapStrList converts a CEL value to a map[string][]string
678 //
679 // Earlier validation stages should guarantee that the value has this type
680 // at compile time, and that the runtime value type is map[string]any.
681 // The reason for the slight difference in value type is that CEL allows for
682 // map literals containing heterogeneous values, in this case string and list
683 // of string.
684 func CELValueToMapStrList(data ref.Val) (map[string][]string, error) {
685 // Prefer map[string]any, but newer cel-go versions may return map[any]any
686 mapStrType := reflect.TypeFor[map[string]any]()
687 mapStrRaw, err := data.ConvertToNative(mapStrType)
688 var mapStrIface map[string]any
689 if err != nil {
690 // Try map[any]any and convert keys to strings
691 mapAnyType := reflect.TypeFor[map[any]any]()
692 mapAnyRaw, err2 := data.ConvertToNative(mapAnyType)
693 if err2 != nil {
694 return nil, err
695 }
696 mapAnyIface := mapAnyRaw.(map[any]any)
697 mapStrIface = make(map[string]any, len(mapAnyIface))
698 for k, v := range mapAnyIface {
699 ks, ok := k.(string)
700 if !ok {
701 return nil, fmt.Errorf("unsupported map key type in header match: %T", k)
702 }
703 mapStrIface[ks] = v
704 }
705 } else {
706 mapStrIface = mapStrRaw.(map[string]any)
707 }
708 mapStrListStr := make(map[string][]string, len(mapStrIface))
709 for k, v := range mapStrIface {
710 switch val := v.(type) {
711 case string:
712 mapStrListStr[k] = []string{val}
713 case types.String:
714 mapStrListStr[k] = []string{string(val)}
715 case []string:
716 mapStrListStr[k] = val
717 case []ref.Val:
718 convVals := make([]string, len(val))
719 for i, elem := range val {
720 strVal, ok := elem.(types.String)
721 if !ok {
722 return nil, fmt.Errorf("unsupported value type in matcher input: %T", val)
723 }
724 convVals[i] = string(strVal)
725 }
726 mapStrListStr[k] = convVals
727 case []any:
728 convVals := make([]string, len(val))
729 for i, elem := range val {
730 switch e := elem.(type) {
731 case string:
732 convVals[i] = e
733 case types.String:
734 convVals[i] = string(e)
735 default:
736 return nil, fmt.Errorf("unsupported element type in matcher input list: %T", elem)
737 }
738 }
739 mapStrListStr[k] = convVals
740 default:
741 return nil, fmt.Errorf("unsupported value type in matcher input: %T", val)
742 }
743 }
744 return mapStrListStr, nil
745 }
746
747 // celPlaceholderNames returns the name passed to each placeholder call in e,
748 // or an empty string where the name is not a string literal.
749 func celPlaceholderNames(e ast.Expr) []string {
750 var names []string
751 ast.PreOrderVisit(e, ast.NewExprVisitor(func(e ast.Expr) {
752 if !isCELCaddyPlaceholderCall(e) {
753 return
754 }
755 name := ""
756 if args := e.AsCall().Args(); len(args) == 2 && isCELStringLiteral(args[1]) {
757 name = args[1].AsLiteral().Value().(string)
758 }
759 names = append(names, name)
760 }))
761 return names
762 }
763
764 // isCELStringExpr indicates whether the expression is a supported string expression
765 func isCELStringExpr(e ast.Expr) bool {
766 return isCELStringLiteral(e) || isCELCaddyPlaceholderCall(e) || isCELConcatCall(e)
767 }
768
769 // isCELStringLiteral returns whether the expression is a CEL string literal.
770 func isCELStringLiteral(e ast.Expr) bool {
771 switch e.Kind() {
772 case ast.LiteralKind:
773 constant := e.AsLiteral()
774 switch constant.Type() {
775 case types.StringType:
776 return true
777 }
778 case ast.UnspecifiedExprKind, ast.CallKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.MapKind, ast.SelectKind, ast.StructKind:
779 // appeasing the linter :)
780 }
781 return false
782 }
783
784 // isCELCaddyPlaceholderCall returns whether the expression is a caddy placeholder call.
785 func isCELCaddyPlaceholderCall(e ast.Expr) bool {
786 switch e.Kind() {
787 case ast.CallKind:
788 call := e.AsCall()
789 if call.FunctionName() == CELPlaceholderFuncName {
790 return true
791 }
792 case ast.UnspecifiedExprKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind:
793 // appeasing the linter :)
794 }
795 return false
796 }
797
798 // isCELConcatCall tests whether the expression is a concat function (+) with string, placeholder, or
799 // other concat call arguments.
800 func isCELConcatCall(e ast.Expr) bool {
801 switch e.Kind() {
802 case ast.CallKind:
803 call := e.AsCall()
804 if call.Target().Kind() != ast.UnspecifiedExprKind {
805 return false
806 }
807 if call.FunctionName() != operators.Add {
808 return false
809 }
810 for _, arg := range call.Args() {
811 if !isCELStringExpr(arg) {
812 return false
813 }
814 }
815 return true
816 case ast.UnspecifiedExprKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind:
817 // appeasing the linter :)
818 }
819 return false
820 }
821
822 // isCELStringListLiteral returns whether the expression resolves to a list literal
823 // containing only string constants or a placeholder call.
824 func isCELStringListLiteral(e ast.Expr) bool {
825 switch e.Kind() {
826 case ast.ListKind:
827 list := e.AsList()
828 for _, elem := range list.Elements() {
829 if !isCELStringExpr(elem) {
830 return false
831 }
832 }
833 return true
834 case ast.UnspecifiedExprKind, ast.CallKind, ast.ComprehensionKind, ast.IdentKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind:
835 // appeasing the linter :)
836 }
837 return false
838 }
839
840 // Variables used for replacing Caddy placeholders in CEL
841 // expressions with a proper CEL function call; this is
842 // just for syntactic sugar.
843 var (
844 // The placeholder may not be preceded by a backslash; the expansion
845 // will include the preceding character if it is not a backslash.
846 placeholderRegexp = regexp.MustCompile(`([^\\]|^){([a-zA-Z][\w.-]+)}`)
847 placeholderExpansion = `${1}ph(req, "${2}")`
848
849 // As a second pass, we need to strip the escape character in front of
850 // the placeholder, if it exists.
851 escapedPlaceholderRegexp = regexp.MustCompile(`\\{([a-zA-Z][\w.-]+)}`)
852 escapedPlaceholderExpansion = `{${1}}`
853
854 CELTypeJSON = cel.MapType(cel.StringType, cel.DynType)
855 )
856
857 var httpRequestObjectType = cel.ObjectType("http.Request")
858
859 // The name of the CEL function which accesses Replacer values.
860 const CELPlaceholderFuncName = "ph"
861
862 // The name of the CEL request variable.
863 const CELRequestVarName = "req"
864
865 const MatcherNameCtxKey = "matcher_name"
866
867 // Interface guards
868 var (
869 _ caddy.Provisioner = (*MatchExpression)(nil)
870 _ RequestMatcherWithError = (*MatchExpression)(nil)
871 _ caddyfile.Unmarshaler = (*MatchExpression)(nil)
872 _ json.Marshaler = (*MatchExpression)(nil)
873 _ json.Unmarshaler = (*MatchExpression)(nil)
874 )