summaryrefslogtreecommitdiff
path: root/modules/caddyhttp/matchers.go
diff options
context:
space:
mode:
authorTristan Swadell <tswadell@google.com>2022-06-22 15:53:46 -0700
committerGitHub <noreply@github.com>2022-06-22 18:53:46 -0400
commit10f85558ead15e119f8e9abd81c8ad55eb865f8b (patch)
treeeac30b8c4d91b8a9f8332d4733038d6d2e7235cd /modules/caddyhttp/matchers.go
parent98468af8b6224d29431576fe30a7d92a8676030d (diff)
Expose several Caddy HTTP Matchers to the CEL Matcher (#4715)
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
Diffstat (limited to 'modules/caddyhttp/matchers.go')
-rw-r--r--modules/caddyhttp/matchers.go290
1 files changed, 289 insertions, 1 deletions
diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go
index f8953ef..268b936 100644
--- a/modules/caddyhttp/matchers.go
+++ b/modules/caddyhttp/matchers.go
@@ -16,6 +16,7 @@ package caddyhttp
import (
"encoding/json"
+ "errors"
"fmt"
"net"
"net/http"
@@ -23,6 +24,7 @@ import (
"net/url"
"path"
"path/filepath"
+ "reflect"
"regexp"
"sort"
"strconv"
@@ -30,7 +32,12 @@ import (
"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"
"go.uber.org/zap"
+ exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
)
type (
@@ -291,6 +298,29 @@ outer:
return false
}
+// CELLibrary produces options that expose this matcher for use in CEL
+// expression matchers.
+//
+// Example:
+// expression host('localhost')
+func (MatchHost) CELLibrary(ctx caddy.Context) (cel.Library, error) {
+ return CELMatcherImpl(
+ "host",
+ "host_match_request_list",
+ []*exprpb.Type{CelTypeListString},
+ func(data ref.Val) (RequestMatcher, error) {
+ refStringList := reflect.TypeOf([]string{})
+ strList, err := data.ConvertToNative(refStringList)
+ if err != nil {
+ return nil, err
+ }
+ matcher := MatchHost(strList.([]string))
+ err = matcher.Provision(ctx)
+ return matcher, err
+ },
+ )
+}
+
// fuzzy returns true if the given hostname h is not a specific
// hostname, e.g. has placeholders or wildcards.
func (MatchHost) fuzzy(h string) bool { return strings.ContainsAny(h, "{*") }
@@ -396,6 +426,33 @@ func (m MatchPath) Match(r *http.Request) bool {
return false
}
+// CELLibrary produces options that expose this matcher for use in CEL
+// expression matchers.
+//
+// Example:
+// expression path('*substring*', '*suffix')
+func (MatchPath) CELLibrary(ctx caddy.Context) (cel.Library, error) {
+ return CELMatcherImpl(
+ // name of the macro, this is the function name that users see when writing expressions.
+ "path",
+ // name of the function that the macro will be rewritten to call.
+ "path_match_request_list",
+ // internal data type of the MatchPath value.
+ []*exprpb.Type{CelTypeListString},
+ // function to convert a constant list of strings to a MatchPath instance.
+ func(data ref.Val) (RequestMatcher, error) {
+ refStringList := reflect.TypeOf([]string{})
+ strList, err := data.ConvertToNative(refStringList)
+ if err != nil {
+ return nil, err
+ }
+ matcher := MatchPath(strList.([]string))
+ err = matcher.Provision(ctx)
+ return matcher, err
+ },
+ )
+}
+
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (m *MatchPath) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
@@ -440,6 +497,50 @@ func (m MatchPathRE) Match(r *http.Request) bool {
return m.MatchRegexp.Match(cleanedPath, repl)
}
+// CELLibrary produces options that expose this matcher for use in CEL
+// expression matchers.
+//
+// Example:
+// expression path_regexp('^/bar')
+func (MatchPathRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
+ unnamedPattern, err := CELMatcherImpl(
+ "path_regexp",
+ "path_regexp_request_string",
+ []*exprpb.Type{decls.String},
+ func(data ref.Val) (RequestMatcher, error) {
+ pattern := data.(types.String)
+ matcher := MatchPathRE{MatchRegexp{Pattern: string(pattern)}}
+ err := matcher.Provision(ctx)
+ return matcher, err
+ },
+ )
+ if err != nil {
+ return nil, err
+ }
+ namedPattern, err := CELMatcherImpl(
+ "path_regexp",
+ "path_regexp_request_string_string",
+ []*exprpb.Type{decls.String, decls.String},
+ func(data ref.Val) (RequestMatcher, error) {
+ refStringList := reflect.TypeOf([]string{})
+ params, err := data.ConvertToNative(refStringList)
+ if err != nil {
+ return nil, err
+ }
+ strParams := params.([]string)
+ matcher := MatchPathRE{MatchRegexp{Name: strParams[0], Pattern: strParams[1]}}
+ err = matcher.Provision(ctx)
+ return matcher, err
+ },
+ )
+ if err != nil {
+ return nil, err
+ }
+ envOpts := append(unnamedPattern.CompileOptions(), namedPattern.CompileOptions()...)
+ prgOpts := append(unnamedPattern.ProgramOptions(), namedPattern.ProgramOptions()...)
+ return NewMatcherCELLibrary(envOpts, prgOpts), nil
+}
+
// CaddyModule returns the Caddy module information.
func (MatchMethod) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
@@ -469,6 +570,27 @@ func (m MatchMethod) Match(r *http.Request) bool {
return false
}
+// CELLibrary produces options that expose this matcher for use in CEL
+// expression matchers.
+//
+// Example:
+// expression method('PUT', 'POST')
+func (MatchMethod) CELLibrary(_ caddy.Context) (cel.Library, error) {
+ return CELMatcherImpl(
+ "method",
+ "method_request_list",
+ []*exprpb.Type{CelTypeListString},
+ func(data ref.Val) (RequestMatcher, error) {
+ refStringList := reflect.TypeOf([]string{})
+ strList, err := data.ConvertToNative(refStringList)
+ if err != nil {
+ return nil, err
+ }
+ return MatchMethod(strList.([]string)), nil
+ },
+ )
+}
+
// CaddyModule returns the Caddy module information.
func (MatchQuery) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
@@ -518,6 +640,26 @@ func (m MatchQuery) Match(r *http.Request) bool {
return len(m) == 0 && len(r.URL.Query()) == 0
}
+// CELLibrary produces options that expose this matcher for use in CEL
+// expression matchers.
+//
+// Example:
+// expression query({'sort': 'asc'}) || query({'foo': ['*bar*', 'baz']})
+func (MatchQuery) CELLibrary(_ caddy.Context) (cel.Library, error) {
+ return CELMatcherImpl(
+ "query",
+ "query_matcher_request_map",
+ []*exprpb.Type{CelTypeJson},
+ func(data ref.Val) (RequestMatcher, error) {
+ mapStrListStr, err := CELValueToMapStrList(data)
+ if err != nil {
+ return nil, err
+ }
+ return MatchQuery(url.Values(mapStrListStr)), nil
+ },
+ )
+}
+
// CaddyModule returns the Caddy module information.
func (MatchHeader) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
@@ -573,6 +715,27 @@ func (m MatchHeader) Match(r *http.Request) bool {
return matchHeaders(r.Header, http.Header(m), r.Host, repl)
}
+// CELLibrary produces options that expose this matcher for use in CEL
+// expression matchers.
+//
+// Example:
+// expression header({'content-type': 'image/png'})
+// expression header({'foo': ['bar', 'baz']}) // match bar or baz
+func (MatchHeader) CELLibrary(_ caddy.Context) (cel.Library, error) {
+ return CELMatcherImpl(
+ "header",
+ "header_matcher_request_map",
+ []*exprpb.Type{CelTypeJson},
+ func(data ref.Val) (RequestMatcher, error) {
+ mapStrListStr, err := CELValueToMapStrList(data)
+ if err != nil {
+ return nil, err
+ }
+ return MatchHeader(http.Header(mapStrListStr)), nil
+ },
+ )
+}
+
// getHeaderFieldVals returns the field values for the given fieldName from input.
// The host parameter should be obtained from the http.Request.Host field since
// net/http removes it from the header map.
@@ -710,6 +873,57 @@ func (m MatchHeaderRE) Validate() error {
return nil
}
+// CELLibrary produces options that expose this matcher for use in CEL
+// expression matchers.
+//
+// Example:
+// expression header_regexp('foo', 'Field', 'fo+')
+func (MatchHeaderRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
+ unnamedPattern, err := CELMatcherImpl(
+ "header_regexp",
+ "header_regexp_request_string_string",
+ []*exprpb.Type{decls.String, decls.String},
+ func(data ref.Val) (RequestMatcher, error) {
+ refStringList := reflect.TypeOf([]string{})
+ params, err := data.ConvertToNative(refStringList)
+ if err != nil {
+ return nil, err
+ }
+ strParams := params.([]string)
+ matcher := MatchHeaderRE{}
+ matcher[strParams[0]] = &MatchRegexp{Pattern: strParams[1], Name: ""}
+ err = matcher.Provision(ctx)
+ return matcher, err
+ },
+ )
+ if err != nil {
+ return nil, err
+ }
+ namedPattern, err := CELMatcherImpl(
+ "header_regexp",
+ "header_regexp_request_string_string_string",
+ []*exprpb.Type{decls.String, decls.String, decls.String},
+ func(data ref.Val) (RequestMatcher, error) {
+ refStringList := reflect.TypeOf([]string{})
+ params, err := data.ConvertToNative(refStringList)
+ if err != nil {
+ return nil, err
+ }
+ strParams := params.([]string)
+ matcher := MatchHeaderRE{}
+ matcher[strParams[1]] = &MatchRegexp{Pattern: strParams[2], Name: strParams[0]}
+ err = matcher.Provision(ctx)
+ return matcher, err
+ },
+ )
+ if err != nil {
+ return nil, err
+ }
+ envOpts := append(unnamedPattern.CompileOptions(), namedPattern.CompileOptions()...)
+ prgOpts := append(unnamedPattern.ProgramOptions(), namedPattern.ProgramOptions()...)
+ return NewMatcherCELLibrary(envOpts, prgOpts), nil
+}
+
// CaddyModule returns the Caddy module information.
func (MatchProtocol) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
@@ -743,6 +957,26 @@ func (m *MatchProtocol) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return nil
}
+// CELLibrary produces options that expose this matcher for use in CEL
+// expression matchers.
+//
+// Example:
+// expression protocol('https')
+func (MatchProtocol) CELLibrary(_ caddy.Context) (cel.Library, error) {
+ return CELMatcherImpl(
+ "protocol",
+ "protocol_request_string",
+ []*exprpb.Type{decls.String},
+ func(data ref.Val) (RequestMatcher, error) {
+ protocolStr, ok := data.(types.String)
+ if !ok {
+ return nil, errors.New("protocol argument was not a string")
+ }
+ return MatchProtocol(strings.ToLower(string(protocolStr))), nil
+ },
+ )
+}
+
// CaddyModule returns the Caddy module information.
func (MatchNot) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
@@ -887,6 +1121,46 @@ func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return nil
}
+// CELLibrary produces options that expose this matcher for use in CEL
+// expression matchers.
+//
+// Example:
+// expression remote_ip('forwarded', '192.168.0.0/16', '172.16.0.0/12', '10.0.0.0/8')
+func (MatchRemoteIP) CELLibrary(ctx caddy.Context) (cel.Library, error) {
+ return CELMatcherImpl(
+ // name of the macro, this is the function name that users see when writing expressions.
+ "remote_ip",
+ // name of the function that the macro will be rewritten to call.
+ "remote_ip_match_request_list",
+ // internal data type of the MatchPath value.
+ []*exprpb.Type{CelTypeListString},
+ // function to convert a constant list of strings to a MatchPath instance.
+ func(data ref.Val) (RequestMatcher, error) {
+ refStringList := reflect.TypeOf([]string{})
+ strList, err := data.ConvertToNative(refStringList)
+ if err != nil {
+ return nil, err
+ }
+
+ m := MatchRemoteIP{}
+
+ for _, input := range strList.([]string) {
+ if input == "forwarded" {
+ if len(m.Ranges) > 0 {
+ return nil, errors.New("if used, 'forwarded' must be first argument")
+ }
+ m.Forwarded = true
+ continue
+ }
+ m.Ranges = append(m.Ranges, input)
+ }
+
+ err = m.Provision(ctx)
+ return m, err
+ },
+ )
+}
+
// Provision parses m's IP ranges, either from IP or CIDR expressions.
func (m *MatchRemoteIP) Provision(ctx caddy.Context) error {
m.logger = ctx.Logger(m)
@@ -1062,7 +1336,9 @@ func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return nil
}
-var wordRE = regexp.MustCompile(`\w+`)
+var (
+ wordRE = regexp.MustCompile(`\w+`)
+)
const regexpPlaceholderPrefix = "http.regexp"
@@ -1103,6 +1379,18 @@ var (
_ caddyfile.Unmarshaler = (*VarsMatcher)(nil)
_ caddyfile.Unmarshaler = (*MatchVarsRE)(nil)
+ _ CELLibraryProducer = (*MatchHost)(nil)
+ _ CELLibraryProducer = (*MatchPath)(nil)
+ _ CELLibraryProducer = (*MatchPathRE)(nil)
+ _ CELLibraryProducer = (*MatchMethod)(nil)
+ _ CELLibraryProducer = (*MatchQuery)(nil)
+ _ CELLibraryProducer = (*MatchHeader)(nil)
+ _ CELLibraryProducer = (*MatchHeaderRE)(nil)
+ _ CELLibraryProducer = (*MatchProtocol)(nil)
+ _ CELLibraryProducer = (*MatchRemoteIP)(nil)
+ // _ CELLibraryProducer = (*VarsMatcher)(nil)
+ // _ CELLibraryProducer = (*MatchVarsRE)(nil)
+
_ json.Marshaler = (*MatchNot)(nil)
_ json.Unmarshaler = (*MatchNot)(nil)
)