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
| 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 | ) |