summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--go.mod2
-rw-r--r--go.sum5
-rw-r--r--modules/caddyhttp/celmatcher.go221
-rw-r--r--modules/caddyhttp/matchers.go3
4 files changed, 228 insertions, 3 deletions
diff --git a/go.mod b/go.mod
index 3a898fe..c5fc336 100644
--- a/go.mod
+++ b/go.mod
@@ -10,6 +10,7 @@ require (
github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac
github.com/go-acme/lego/v3 v3.5.0
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
+ github.com/google/cel-go v0.3.2
github.com/ilibs/json5 v1.0.1
github.com/jsternberg/zap-logfmt v1.2.0
github.com/klauspost/compress v1.10.3
@@ -29,6 +30,7 @@ require (
go.uber.org/zap v1.14.1
golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
+ google.golang.org/genproto v0.0.0-20200305110556-506484158171
gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/square/go-jose.v2 v2.4.1 // indirect
gopkg.in/yaml.v2 v2.2.8
diff --git a/go.sum b/go.sum
index f116fb7..20c5b82 100644
--- a/go.sum
+++ b/go.sum
@@ -104,6 +104,8 @@ github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDa
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
+github.com/antlr/antlr4 v0.0.0-20190819145818-b43a4c3a8015 h1:StuiJFxQUsxSCzcby6NFZRdEhPkXD5vxN7TZ4MD6T84=
+github.com/antlr/antlr4 v0.0.0-20190819145818-b43a4c3a8015/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
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
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/cel-go v0.3.2 h1:72Lj/nrfpWSJkuXdeEGB/7jfdwVFtV8kPJSL2Mt9rog=
+github.com/google/cel-go v0.3.2/go.mod h1:DoRSdzaJzNiP1lVuWhp/RjSnHLDQr/aNPlyqSBasBqA=
+github.com/google/cel-spec v0.3.0/go.mod h1:MjQm800JAGhOZXI7vatnVpmIaFTR6L8FHcKk+piiKpI=
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/certificate-transparency-go v1.1.0/go.mod h1:i+Q7XY+ArBveOUT36jiHGfuSK1fHICIg6sUkRxPAbCs=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
diff --git a/modules/caddyhttp/celmatcher.go b/modules/caddyhttp/celmatcher.go
new file mode 100644
index 0000000..fcedea4
--- /dev/null
+++ b/modules/caddyhttp/celmatcher.go
@@ -0,0 +1,221 @@
+// Copyright 2015 Matthew Holt and The Caddy Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package caddyhttp
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "reflect"
+ "regexp"
+ "strings"
+
+ "github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
+ "github.com/google/cel-go/cel"
+ "github.com/google/cel-go/checker/decls"
+ "github.com/google/cel-go/common/types"
+ "github.com/google/cel-go/common/types/ref"
+ "github.com/google/cel-go/common/types/traits"
+ "github.com/google/cel-go/interpreter/functions"
+ exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
+)
+
+func init() {
+ caddy.RegisterModule(MatchExpression{})
+}
+
+// MatchExpression matches requests by evaluating a
+// [CEL](https://github.com/google/cel-spec) expression.
+// This enables complex logic to be expressed using a comfortable,
+// familiar syntax.
+//
+// COMPATIBILITY NOTE: This module is still experimental and is not
+// subject to Caddy's compatibility guarantee.
+type MatchExpression struct {
+ // The CEL expression to evaluate. Any Caddy placeholders
+ // will be expanded and situated into proper CEL function
+ // calls before evaluating.
+ Expr string
+
+ expandedExpr string
+ prg cel.Program
+}
+
+// CaddyModule returns the Caddy module information.
+func (MatchExpression) CaddyModule() caddy.ModuleInfo {
+ return caddy.ModuleInfo{
+ ID: "http.matchers.expression",
+ New: func() caddy.Module { return new(MatchExpression) },
+ }
+}
+
+// MarshalJSON marshals m's expression.
+func (m MatchExpression) MarshalJSON() ([]byte, error) {
+ return json.Marshal(m.Expr)
+}
+
+// UnmarshalJSON unmarshals m's expression.
+func (m *MatchExpression) UnmarshalJSON(data []byte) error {
+ return json.Unmarshal(data, &m.Expr)
+}
+
+// Provision sets ups m.
+func (m *MatchExpression) Provision(_ caddy.Context) error {
+ // replace placeholders with a function call - this is just some
+ // light (and possibly naïve) syntactic sugar
+ m.expandedExpr = placeholderRegexp.ReplaceAllString(m.Expr, placeholderExpansion)
+
+ // create the CEL environment
+ env, err := cel.NewEnv(
+ cel.Declarations(
+ decls.NewIdent("request", httpRequestObjectType, nil),
+ decls.NewFunction(placeholderFuncName,
+ decls.NewOverload(placeholderFuncName+"_httpRequest_string",
+ []*exprpb.Type{httpRequestObjectType, decls.String},
+ decls.String)),
+ ),
+ cel.CustomTypeAdapter(celHTTPRequestTypeAdapter{}),
+ )
+ if err != nil {
+ return fmt.Errorf("setting up CEL environment: %v", err)
+ }
+
+ // parse the expression
+ parsed, issues := env.Parse(m.expandedExpr)
+ if issues != nil && issues.Err() != nil {
+ return fmt.Errorf("parsing CEL program: %s", issues.Err())
+ }
+
+ // type-check it
+ checked, issues := env.Check(parsed)
+ if issues != nil && issues.Err() != nil {
+ return fmt.Errorf("type-checking CEL program: %s", issues.Err())
+ }
+
+ // compile the "program"
+ m.prg, err = env.Program(checked,
+ cel.Functions(
+ &functions.Overload{
+ Operator: placeholderFuncName,
+ Binary: caddyPlaceholderFunc,
+ },
+ ),
+ )
+
+ if err != nil {
+ return fmt.Errorf("compiling CEL program: %s", err)
+ }
+ return nil
+}
+
+// Match returns true if r matches m.
+func (m MatchExpression) Match(r *http.Request) bool {
+ out, _, _ := m.prg.Eval(map[string]interface{}{
+ "request": celHTTPRequest{r},
+ })
+ if outBool, ok := out.Value().(bool); ok {
+ return outBool
+ }
+ return false
+
+}
+
+// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
+func (m *MatchExpression) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ m.Expr = strings.Join(d.RemainingArgs(), " ")
+ }
+ return nil
+}
+
+// httpRequestCELType is the type representation of a native HTTP request.
+var httpRequestCELType = types.NewTypeValue("http.Request", traits.ReceiverType)
+
+// cellHTTPRequest wraps an http.Request with
+// methods to satisfy the ref.Val interface.
+type celHTTPRequest struct {
+ *http.Request
+}
+
+func (cr celHTTPRequest) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
+ return cr.Request, nil
+}
+func (celHTTPRequest) ConvertToType(typeVal ref.Type) ref.Val {
+ panic("not implemented")
+}
+func (cr celHTTPRequest) Equal(other ref.Val) ref.Val {
+ if o, ok := other.Value().(celHTTPRequest); ok {
+ return types.Bool(o.Request == cr.Request)
+ }
+ return types.ValOrErr(other, "%v is not comparable type", other)
+}
+func (celHTTPRequest) Type() ref.Type { return httpRequestCELType }
+func (cr celHTTPRequest) Value() interface{} { return cr }
+
+// celHTTPRequestTypeAdapter can adapt a
+// celHTTPRequest to a CEL value.
+type celHTTPRequestTypeAdapter struct{}
+
+func (celHTTPRequestTypeAdapter) NativeToValue(value interface{}) ref.Val {
+ if celReq, ok := value.(celHTTPRequest); ok {
+ return celReq
+ }
+ return types.DefaultTypeAdapter.NativeToValue(value)
+}
+
+// Variables used for replacing Caddy placeholders in CEL
+// expressions with a proper CEL function call; this is
+// just for syntactic sugar.
+var (
+ placeholderRegexp = regexp.MustCompile(`{([\w.-]+)}`)
+ placeholderExpansion = `caddyPlaceholder(request, "${1}")`
+)
+
+var httpRequestObjectType = decls.NewObjectType("http.Request")
+
+// The name of the CEL function which accesses Replacer values.
+const placeholderFuncName = "caddyPlaceholder"
+
+// caddyPlaceholderFunc implements the custom CEL function that
+// accesses the Replacer on a request and gets values from it.
+func caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val {
+ celReq, ok := lhs.(celHTTPRequest)
+ if !ok {
+ return types.NewErr(
+ "invalid request of type '%v' to "+placeholderFuncName+"(request, placeholderVarName)",
+ lhs.Type())
+ }
+ phStr, ok := rhs.(types.String)
+ if !ok {
+ return types.NewErr(
+ "invalid placeholder variable name of type '%v' to "+placeholderFuncName+"(request, placeholderVarName)",
+ rhs.Type())
+ }
+
+ repl := celReq.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
+ val, _ := repl.Get(string(phStr))
+
+ return types.String(val)
+}
+
+// Interface guards
+var (
+ _ caddy.Provisioner = (*MatchExpression)(nil)
+ _ RequestMatcher = (*MatchExpression)(nil)
+ _ caddyfile.Unmarshaler = (*MatchExpression)(nil)
+ _ json.Marshaler = (*MatchExpression)(nil)
+ _ json.Unmarshaler = (*MatchExpression)(nil)
+)
diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go
index 6bfb31e..043831f 100644
--- a/modules/caddyhttp/matchers.go
+++ b/modules/caddyhttp/matchers.go
@@ -107,9 +107,6 @@ type (
Matchers MatcherSet `json:"-"`
}
-
- // MatchTable matches requests by values in the table.
- MatchTable string // TODO: finish implementing
)
func init() {