caddyhttp: Use new CEL APIs (fix #4915)

Hahaha this is the ultimate "I have no idea what I'm doing" commit but it
compiles and the tests pass and I declare victory!

... probably broke something, should be tested more.

It is nice that the protobuf dependency becomes indirect now.
This commit is contained in:
Matthew Holt 2022-07-28 14:50:28 -06:00
parent c833e3b249
commit ea8df6ff11
No known key found for this signature in database
GPG key ID: 2A349DD577D586A5
4 changed files with 43 additions and 109 deletions

View file

@ -28,7 +28,6 @@ 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"
"github.com/google/cel-go/common/operators"
"github.com/google/cel-go/common/types"
@ -40,7 +39,6 @@ import (
"github.com/google/cel-go/parser"
"go.uber.org/zap"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
"google.golang.org/protobuf/proto"
)
func init() {
@ -126,13 +124,12 @@ func (m *MatchExpression) Provision(ctx caddy.Context) error {
// create the CEL environment
env, err := cel.NewEnv(
cel.Declarations(
decls.NewVar("request", httpRequestObjectType),
decls.NewFunction(placeholderFuncName,
decls.NewOverload(placeholderFuncName+"_httpRequest_string",
[]*exprpb.Type{httpRequestObjectType, decls.String},
decls.Any)),
),
cel.Function(placeholderFuncName, cel.SingletonBinaryImpl(m.caddyPlaceholderFunc), cel.Overload(
placeholderFuncName+"_httpRequest_string",
[]*cel.Type{httpRequestObjectType, cel.StringType},
cel.AnyType,
)),
cel.Variable("request", httpRequestObjectType),
cel.CustomTypeAdapter(m.ta),
ext.Strings(),
matcherLib,
@ -149,20 +146,12 @@ func (m *MatchExpression) Provision(ctx caddy.Context) error {
// request matching is a boolean operation, so we don't really know
// what to do if the expression returns a non-boolean type
if !proto.Equal(checked.ResultType(), decls.Bool) {
return fmt.Errorf("CEL request matcher expects return type of bool, not %s", checked.ResultType())
if checked.OutputType() != cel.BoolType {
return fmt.Errorf("CEL request matcher expects return type of bool, not %s", checked.OutputType())
}
// compile the "program"
m.prg, err = env.Program(checked,
cel.EvalOptions(cel.OptOptimize),
cel.Functions(
&functions.Overload{
Operator: placeholderFuncName,
Binary: m.caddyPlaceholderFunc,
},
),
)
m.prg, err = env.Program(checked, cel.EvalOptions(cel.OptOptimize))
if err != nil {
return fmt.Errorf("compiling CEL program: %s", err)
}
@ -321,62 +310,46 @@ type CELLibraryProducer interface {
// limited set of function signatures. For strong type validation you may need
// to provide a custom macro which does a more detailed analysis of the CEL
// literal provided to the macro as an argument.
func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*exprpb.Type, fac CELMatcherFactory) (cel.Library, error) {
requestType := decls.NewObjectType("http.Request")
func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*cel.Type, fac CELMatcherFactory) (cel.Library, error) {
requestType := cel.ObjectType("http.Request")
var macro parser.Macro
switch len(matcherDataTypes) {
case 1:
matcherDataType := matcherDataTypes[0]
if isCELStringListType(matcherDataType) {
switch matcherDataType.String() {
case "list(string)":
macro = parser.NewGlobalVarArgMacro(macroName, celMatcherStringListMacroExpander(funcName))
} else if isCELStringType(matcherDataType) {
case cel.StringType.String():
macro = parser.NewGlobalMacro(macroName, 1, celMatcherStringMacroExpander(funcName))
} else if isCELJSONType(matcherDataType) {
case CELTypeJSON.String():
macro = parser.NewGlobalMacro(macroName, 1, celMatcherJSONMacroExpander(funcName))
} else {
return nil, fmt.Errorf("unsupported matcher data type: %s", cel.FormatType(matcherDataType))
default:
return nil, fmt.Errorf("unsupported matcher data type: %s", matcherDataType)
}
case 2:
if isCELStringType(matcherDataTypes[0]) && isCELStringType(matcherDataTypes[1]) {
if matcherDataTypes[0] == cel.StringType && matcherDataTypes[1] == cel.StringType {
macro = parser.NewGlobalMacro(macroName, 2, celMatcherStringListMacroExpander(funcName))
matcherDataTypes = []*exprpb.Type{CelTypeListString}
matcherDataTypes = []*cel.Type{cel.ListType(cel.StringType)}
} else {
return nil, fmt.Errorf(
"unsupported matcher data type: %s, %s",
cel.FormatType(matcherDataTypes[0]), cel.FormatType(matcherDataTypes[1]),
)
return nil, fmt.Errorf("unsupported matcher data type: %s, %s", matcherDataTypes[0], matcherDataTypes[1])
}
case 3:
if isCELStringType(matcherDataTypes[0]) && isCELStringType(matcherDataTypes[1]) && isCELStringType(matcherDataTypes[2]) {
if matcherDataTypes[0] == cel.StringType && matcherDataTypes[1] == cel.StringType && matcherDataTypes[2] == cel.StringType {
macro = parser.NewGlobalMacro(macroName, 3, celMatcherStringListMacroExpander(funcName))
matcherDataTypes = []*exprpb.Type{CelTypeListString}
matcherDataTypes = []*cel.Type{cel.ListType(cel.StringType)}
} else {
return nil, fmt.Errorf(
"unsupported matcher data type: %s, %s, %s",
cel.FormatType(matcherDataTypes[0]), cel.FormatType(matcherDataTypes[1]), cel.FormatType(matcherDataTypes[2]),
)
return nil, fmt.Errorf("unsupported matcher data type: %s, %s, %s", matcherDataTypes[0], matcherDataTypes[1], matcherDataTypes[2])
}
}
envOptions := []cel.EnvOption{
cel.Macros(macro),
cel.Declarations(
decls.NewFunction(funcName,
decls.NewOverload(
funcName,
append([]*exprpb.Type{requestType}, matcherDataTypes...),
decls.Bool,
),
),
),
cel.Function(funcName,
cel.Overload(funcName, append([]*cel.Type{requestType}, matcherDataTypes...), cel.BoolType),
cel.SingletonBinaryImpl(CELMatcherRuntimeFunction(funcName, fac))),
}
programOptions := []cel.ProgramOption{
cel.CustomDecorator(CELMatcherDecorator(funcName, fac)),
cel.Functions(
&functions.Overload{
Operator: funcName,
Binary: CELMatcherRuntimeFunction(funcName, fac),
},
),
}
return NewMatcherCELLibrary(envOptions, programOptions), nil
}
@ -610,25 +583,6 @@ func CELValueToMapStrList(data ref.Val) (map[string][]string, error) {
return mapStrListStr, nil
}
// isCELJSONType returns whether the type corresponds to JSON input.
func isCELJSONType(t *exprpb.Type) bool {
switch t.GetTypeKind().(type) {
case *exprpb.Type_MapType_:
mapType := t.GetMapType()
return isCELStringType(mapType.GetKeyType()) && mapType.GetValueType().GetDyn() != nil
}
return false
}
// isCELStringType returns whether the type corresponds to a string.
func isCELStringType(t *exprpb.Type) bool {
switch t.GetTypeKind().(type) {
case *exprpb.Type_Primitive:
return t.GetPrimitive() == exprpb.Type_STRING
}
return false
}
// isCELStringExpr indicates whether the expression is a supported string expression
func isCELStringExpr(e *exprpb.Expr) bool {
return isCELStringLiteral(e) || isCELCaddyPlaceholderCall(e) || isCELConcatCall(e)
@ -681,15 +635,6 @@ func isCELConcatCall(e *exprpb.Expr) bool {
return false
}
// isCELStringListType returns whether the type corresponds to a list of strings.
func isCELStringListType(t *exprpb.Type) bool {
switch t.GetTypeKind().(type) {
case *exprpb.Type_ListType_:
return isCELStringType(t.GetListType().GetElemType())
}
return false
}
// isCELStringListLiteral returns whether the expression resolves to a list literal
// containing only string constants or a placeholder call.
func isCELStringListLiteral(e *exprpb.Expr) bool {
@ -713,11 +658,10 @@ var (
placeholderRegexp = regexp.MustCompile(`{([a-zA-Z][\w.-]+)}`)
placeholderExpansion = `caddyPlaceholder(request, "${1}")`
CelTypeListString = decls.NewListType(decls.String)
CelTypeJson = decls.NewMapType(decls.String, decls.Dyn)
CELTypeJSON = cel.MapType(cel.StringType, cel.DynType)
)
var httpRequestObjectType = decls.NewObjectType("http.Request")
var httpRequestObjectType = cel.ObjectType("http.Request")
// The name of the CEL function which accesses Replacer values.
const placeholderFuncName = "caddyPlaceholder"