mirror of https://github.com/dexidp/dex.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
105 lines
3.4 KiB
105 lines
3.4 KiB
package cel |
|
|
|
import ( |
|
"fmt" |
|
|
|
"github.com/google/cel-go/checker" |
|
) |
|
|
|
// DefaultCostBudget is the default cost budget for a single expression |
|
// evaluation. Aligned with Kubernetes defaults: enough for typical identity |
|
// operations but prevents runaway expressions. |
|
const DefaultCostBudget uint64 = 10_000_000 |
|
|
|
// MaxExpressionLength is the maximum length of a CEL expression string. |
|
const MaxExpressionLength = 10_240 |
|
|
|
// DefaultStringMaxLength is the estimated max length of string values |
|
// (emails, usernames, group names, etc.) used for compile-time cost estimation. |
|
const DefaultStringMaxLength = 256 |
|
|
|
// DefaultListMaxLength is the estimated max length of list values |
|
// (groups, scopes) used for compile-time cost estimation. |
|
const DefaultListMaxLength = 100 |
|
|
|
// CostEstimate holds the estimated cost range for a compiled expression. |
|
type CostEstimate struct { |
|
Min uint64 |
|
Max uint64 |
|
} |
|
|
|
// EstimateCost returns the estimated cost range for a compiled expression. |
|
// This is computed statically at compile time without evaluating the expression. |
|
func (c *Compiler) EstimateCost(result *CompilationResult) (CostEstimate, error) { |
|
costEst, err := c.env.EstimateCost(result.ast, &defaultCostEstimator{}) |
|
if err != nil { |
|
return CostEstimate{}, fmt.Errorf("CEL cost estimation failed: %w", err) |
|
} |
|
|
|
return CostEstimate{Min: costEst.Min, Max: costEst.Max}, nil |
|
} |
|
|
|
// defaultCostEstimator provides size hints for compile-time cost estimation. |
|
// Without these hints, the CEL cost estimator assumes unbounded sizes for |
|
// variables, leading to wildly overestimated max costs. |
|
type defaultCostEstimator struct{} |
|
|
|
func (defaultCostEstimator) EstimateSize(element checker.AstNode) *checker.SizeEstimate { |
|
// Provide size hints for map(string, dyn) variables: identity, request, claims. |
|
// Without these, the estimator assumes lists/strings can be infinitely large. |
|
if element.Path() == nil { |
|
return nil |
|
} |
|
|
|
path := element.Path() |
|
if len(path) == 0 { |
|
return nil |
|
} |
|
|
|
root := path[0] |
|
|
|
switch root { |
|
case "identity", "request", "claims": |
|
// Nested field access (e.g. identity.email, identity.groups) |
|
if len(path) >= 2 { |
|
field := path[1] |
|
switch field { |
|
case "groups", "scopes": |
|
// list(string) fields |
|
return &checker.SizeEstimate{Min: 0, Max: DefaultListMaxLength} |
|
case "email_verified": |
|
// bool field — size is always 1 |
|
return &checker.SizeEstimate{Min: 1, Max: 1} |
|
default: |
|
// string fields (email, username, user_id, client_id, etc.) |
|
return &checker.SizeEstimate{Min: 0, Max: DefaultStringMaxLength} |
|
} |
|
} |
|
// The map itself: number of keys |
|
return &checker.SizeEstimate{Min: 0, Max: 20} |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func (defaultCostEstimator) EstimateCallCost(function, overloadID string, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { |
|
switch function { |
|
case "dex.emailDomain", "dex.emailLocalPart": |
|
// Simple string split — O(n) where n is string length, bounded. |
|
return &checker.CallEstimate{ |
|
CostEstimate: checker.CostEstimate{Min: 1, Max: 2}, |
|
} |
|
case "dex.groupMatches": |
|
// Iterates over groups list and matches each against a pattern. |
|
return &checker.CallEstimate{ |
|
CostEstimate: checker.CostEstimate{Min: 1, Max: DefaultListMaxLength}, |
|
} |
|
case "dex.groupFilter": |
|
// Builds a set from allowed list, then iterates groups. |
|
return &checker.CallEstimate{ |
|
CostEstimate: checker.CostEstimate{Min: 1, Max: 2 * DefaultListMaxLength}, |
|
} |
|
} |
|
|
|
return nil |
|
}
|
|
|