ADR-0020: Adopting Common Expression Language (CEL) for CascadingRule Matching
Status: | PROPOSED |
Date: | 2025-10-14 |
Author(s): | Jannik Hollenbach jannik.hollenbach@iteratec.com |
Context
CascadingRules in secureCodeBox currently use a custom matches
object syntax to define which findings should trigger subsequent scans. The current implementation uses a declarative YAML structure with anyOf
rules that perform partial deep comparison against finding fields:
spec:
matches:
anyOf:
- category: "Open Port"
attributes:
port: 22
state: open
- category: "Open Port"
attributes:
service: "ssh"
state: open
While this approach works well for simple matching scenarios, it has several limitations:
- Limited Expressiveness: The current syntax only supports exact matching and partial deep comparison. Complex conditions like range checks, regex patterns, logical combinations beyond
anyOf
, or computed values are not possible without extending the custom syntax. - Maintenance Burden: Every new matching requirement necessitates extending the custom matcher implementation. This creates ongoing maintenance overhead and increases the complexity of the codebase.
- Lack of Flexibility: Common use cases like checking if a port is within a range (e.g.,
port >= 8000 && port <= 9000
), matching against multiple patterns, or combining conditions with complex boolean logic require workarounds or are simply not possible.
Common Expression Language (CEL) is a non-Turing complete expression language designed for evaluating expressions in a safe, fast, and portable manner. It is already widely adopted in the Kubernetes ecosystem, particularly in:
- Kubernetes ValidatingAdmissionPolicy (since v1.26)
- Kubernetes Custom Resource Definitions (CRD validation rules)
- Istio authorization policies
- Various other cloud-native projects
CEL provides a familiar C-like syntax and is specifically designed for configuration and policy evaluation use cases, making it an ideal fit for CascadingRule matching logic.
Decision
We propose migrating the CascadingRule matches
specification from the current custom object syntax to use Common Expression Language (CEL) expressions.
Proposed Syntax
Instead of the current matches.anyOf
structure, users would write CEL expressions that evaluate to a boolean:
spec:
matches:
expression: |
(finding.category == "Open Port" && finding.attributes.port == 22 && finding.attributes.state == "open") ||
(finding.category == "Open Port" && finding.attributes.service == "ssh" && finding.attributes.state == "open")
Or more concisely:
spec:
matches:
expression: |
finding.category == "Open Port" &&
finding.attributes.state == "open" &&
(finding.attributes.port == 22 || finding.attributes.service == "ssh")
Advanced Use Cases Enabled by CEL
CEL would enable powerful matching scenarios that are currently impossible:
Range Checks:
expression: |
finding.category == "Open Port" &&
finding.attributes.port >= 8000 &&
finding.attributes.port <= 9000
Regex Matching:
expression: |
finding.category == "Subdomain" &&
finding.attributes.hostname.matches("^.*\\.example\\.com$")
Complex Boolean Logic:
expression: |
(finding.severity in ["HIGH", "CRITICAL"] && finding.category == "Vulnerability") ||
(finding.category == "Open Port" && finding.attributes.port in [22, 23, 3389])
Computed Values:
expression: |
finding.category == "Open Port" &&
has(finding.attributes.service) &&
finding.attributes.service.startsWith("http")
Migration Strategy
To ensure backward compatibility and smooth migration:
-
Dual Support Period: Support both the legacy
matches.anyOf
syntax and the newmatches.expression
syntax simultaneously for at least two major versions. -
Automatic Translation (Preferred Approach): Implement automatic runtime translation of
matches.anyOf
to CEL expressions:- When a CascadingRule contains only
matches.anyOf
, automatically translate it to an equivalent CEL expression at runtime - Log an informational message indicating the automatic translation occurred
- This approach eliminates the need for complex conflict resolution logic
- Users can gradually migrate at their own pace without breaking changes
- The translation logic can be removed in a future major version when
anyOf
support is dropped
If automatic translation is implemented, the conflict resolution below becomes unnecessary.
- When a CascadingRule contains only
-
Conflict Resolution (Alternative if no automatic translation): When both
matches.anyOf
andmatches.expression
are specified in the same CascadingRule:- Generally automatic translation would be prefered as it eliminates manual work and potential errors, but if that turns out to be hard to achieve, manual translation might be the only option.
- CEL takes precedence: The
matches.expression
will be evaluated andmatches.anyOf
will be ignored - Emit a warning: Log a warning message and add a Kubernetes event to the CascadingRule resource indicating the conflict
- Add status condition: Update the CascadingRule status with a condition indicating that both matchers were specified and CEL was used
Example warning message:
Warning: CascadingRule 'nmap-hostscan' specifies both 'matches.anyOf' and 'matches.expression'.
Using CEL expression and ignoring anyOf matcher. Please remove the deprecated 'matches.anyOf' field. -
Translation Documentation: Provide documentation to help users manually translate existing
anyOf
rules to CEL expressions to better help users understanding of the new syntax. -
(consider) Validation: Implement comprehensive validation of CEL expressions at CRD admission time to catch syntax errors early. We have avoided validating webhooks so far, as they have a overhead in terms of cert management, but might be worthwhile for this issue.
-
Documentation: Create extensive documentation with examples showing common patterns and migration guides.
-
Proposed Deprecation Path:
- Version 5.x: Introduce CEL support with automatic translation of
anyOf
(if implemented) or dual support, markanyOf
as deprecated with informational/warning messages - Version 6.0.0: Remove support for
anyOf
syntax and automatic translation logic
- Version 5.x: Introduce CEL support with automatic translation of
Consequences
Positive Consequences
- Increased Flexibility: Users can express arbitrarily complex matching logic without waiting for custom syntax extensions.
- Reduced Maintenance: The secureCodeBox team no longer needs to maintain and extend custom matching logic. CEL is maintained by Google and the broader community.
- Industry Standard: CEL is becoming the de facto standard for policy expressions in Kubernetes, making it familiar to many users.
- Better Tooling: CEL has existing tooling, documentation, and community support that users can leverage.
- Type Safety: CEL provides compile-time type checking, catching errors before runtime.
- Security: CEL is non-Turing complete and designed to be safe for user-provided expressions, preventing infinite loops or resource exhaustion.
Negative Consequences
- Breaking Change: Eventually removing the
anyOf
syntax will require users to migrate their existing CascadingRules. - Learning Curve: Users unfamiliar with CEL will need to learn a new expression syntax, though it's relatively simple and well-documented.
- Migration Effort: Existing CascadingRules will need to be updated, requiring effort from users and clear migration documentation.
- Increased Complexity: The cascading hook codebase will temporarily be more complex during the dual-support period.
- Dependency Addition: Adding the cel library increases the dependency footprint of the operator.
- Error Messages: CEL error messages may be less intuitive than custom validation errors, requiring careful wrapping and contextualization.
- Potential Confusion: During the dual-support period, users might accidentally specify both matchers, though the clear precedence rule and warnings mitigate this risk.
Mitigation Strategies
- If automatic translation is implemented: The migration becomes seamless with minimal user impact
- Provide comprehensive migration guides with side-by-side examples
- Create a validation tool or script to help users test their CEL expressions
- Maintain the dual-support period for sufficient time to allow gradual migration
- Offer community support and examples for common migration scenarios
- If no automatic translation: Implement clear conflict detection with actionable warning messages
- Add linting/validation in CI/CD pipelines to detect deprecated usage early
Alternatives Considered
1. Extend the Current Custom Syntax
We could continue extending the matches
object with new fields and operators (e.g., allOf
, noneOf
, etc.).
Rejected because: This would perpetuate the maintenance burden and still wouldn't provide the full flexibility of a proper expression language. Each new requirement would require code changes and releases.
2. Use JSONPath or JMESPath
These are query languages designed for JSON data extraction and filtering.
Rejected because: While powerful for data extraction, they are less intuitive for boolean logic and condition evaluation. CEL is specifically designed for policy evaluation use cases.
3. Use JavaScript or Lua
Embed a scripting language for maximum flexibility.
Rejected because: Full scripting languages are Turing complete and pose security risks when evaluating user-provided code. They also have higher performance overhead and complexity. CEL's non-Turing complete nature makes it safer and more appropriate for this use case.
4. Use Rego (Open Policy Agent)
Rego is the policy language used by Open Policy Agent.
Rejected because: While Rego is powerful, it has a steeper learning curve and is less widely adopted in the Kubernetes ecosystem compared to CEL. CEL's integration with Kubernetes CRDs and admission policies makes it a more natural fit.