# CEL Content ## Introduction CEL (Common Expression Language) is an alternative scanning mechanism to OVAL that provides native Kubernetes resource evaluation. CEL rules are used by the [compliance-operator](https://github.com/ComplianceAsCode/compliance-operator) to perform compliance checks on Kubernetes and OpenShift resources without requiring shell access or OVAL evaluation. This document describes how to create CEL rules and profiles, and how the build system generates CEL content. ## When to Use CEL Rules Use CEL rules when: - Checking Kubernetes or OpenShift API resources (Pods, Deployments, ConfigMaps, etc.) - Evaluating Custom Resource Definitions (CRDs) - Performing compliance checks that don't require shell access to nodes - Building platform-level compliance checks for container orchestration systems Continue using OVAL/template-based rules for: - File system checks - Process checks - Package installation verification - Traditional operating system compliance checks ## CEL Rule Format CEL rules use a **split-file structure** to separate metadata from CEL-specific content: - **`rule.yml`** - Contains metadata (title, description, rationale, severity, references, etc.) - **`cel/shared.yml`** - Contains CEL-specific fields (check_type, failure_reason, inputs, expression) This allows rules to support **both CEL and OVAL** checks during migration from OVAL to CEL. ### rule.yml Fields ```yaml documentation_complete: true title: 'Short descriptive title' description: |- Full description of what the rule checks. rationale: |- Why this rule matters for security/compliance. severity: medium # low, medium, high ocil: |- # Optional: Manual verification instructions Run the following command to verify:
$ oc get podsreferences: # Optional cis@ocp4: 1.2.3 nist: CM-6,CM-6(1) srg: SRG-APP-000516-CTR-001325 ``` ### cel/shared.yml Fields ```yaml check_type: Platform # Type of check (usually Platform for K8s checks) failure_reason: |- # Optional: Custom message displayed when the check fails The resource is not properly configured. expression: |- # REQUIRED: CEL expression that evaluates to boolean resource.spec.enabled == true inputs: # REQUIRED: List of Kubernetes resources to evaluate - name: resource kubernetes_input_spec: api_version: v1 resource: pods resource_name: my-pod # Optional: specific resource name resource_namespace: default # Optional: specific namespace ``` ### CEL Expression The `expression` field contains a CEL expression that evaluates to a boolean value: - `true` means the check passes (compliant) - `false` means the check fails (non-compliant) CEL expressions can reference inputs by name and use standard CEL operators and functions. #### Example Expressions Simple boolean check: ```yaml expression: resource.spec.enabled == true ``` Checking for absence: ```yaml expression: !has(hco.spec.storageImport) || hco.spec.storageImport.insecureRegistries.size() == 0 ``` Multiple conditions: ```yaml expression: |- resource.spec.replicas >= 3 && has(resource.spec.template.spec.securityContext) && resource.spec.template.spec.securityContext.runAsNonRoot == true ``` ### Input Specifications The `inputs` field lists Kubernetes resources that the CEL expression can reference. #### Kubernetes Input Spec ```yaml inputs: - name: deployment # Name used in the expression kubernetes_input_spec: api_version: apps/v1 # Kubernetes API version resource: deployments # Resource type (plural form) resource_name: my-app # Optional: specific resource name resource_namespace: kube-system # Optional: specific namespace ``` If `resourceName` is omitted, the check applies to all resources of that type. If `resourceNamespace` is omitted, the check applies across all namespaces. ## CEL Profile Format CEL profiles use the standard profile format with one additional field: ```yaml documentation_complete: true title: 'CIS Red Hat OpenShift Virtual Machine Extension Benchmark' description: |- Profile description text. scanner_type: CEL # REQUIRED: Marks this as a CEL profile selections: - kubevirt-nonroot-feature-gate-is-enabled - kubevirt-no-permitted-host-devices - kubevirt-persistent-reservation-disabled ``` **Important:** CEL profiles can only select CEL rules. If a profile includes both CEL and OVAL rules, only the CEL rules will be included in the generated CEL content file. ## Creating a CEL Rule ### 1. Choose the Correct Directory CEL rules for Kubernetes/OpenShift should go under `applications/openshift/` or `applications/openshift-virtualization/`, organized by component: ``` applications/openshift-virtualization/ ├── group.yml ├── kubevirt-nonroot-feature-gate-is-enabled/ │ └── rule.yml ├── kubevirt-no-permitted-host-devices/ │ └── rule.yml └── kubevirt-enforce-trusted-tls-registries/ └── rule.yml ``` **Important:** Directory names should use hyphens (e.g., `kubevirt-nonroot-enabled`), not underscores. This follows Kubernetes naming conventions. ### 2. Create the group.yml Each component directory requires a `group.yml` file: ```yaml documentation_complete: true title: 'OpenShift Virtualization' description: |- Security recommendations for OpenShift Virtualization (KubeVirt). ``` ### 3. Create the rule.yml and cel/shared.yml Follow the CEL rule format described above. Example: **rule.yml:** ```yaml documentation_complete: true title: 'Ensure NonRoot Feature Gate is Enabled' description: |- The NonRoot feature gate restricts containers from running as root, reducing the attack surface. rationale: |- Running containers as non-root users is a security best practice that limits the impact of container breakout vulnerabilities. severity: medium ocil: |- Run the following command to verify the NonRoot feature gate:
oc get hyperconverged -n openshift-cnv kubevirt-hyperconverged -o jsonpath='{.spec.featureGates.nonRoot}'
The output should be true.
references:
cis@ocp4: 5.7.1
```
**cel/shared.yml:**
```yaml
check_type: Platform
failure_reason: |-
The NonRoot feature gate is not enabled in the kubevirt-hyperconverged resource.
expression: |-
hco.spec.featureGates.nonRoot == true
inputs:
- name: hco
kubernetes_input_spec:
api_version: hco.kubevirt.io/v1beta1
resource: hyperconvergeds
resource_name: kubevirt-hyperconverged
resource_namespace: openshift-cnv
```
## Build System Integration
### Enabling CEL Content for a Product
CEL content generation is enabled per-product in the product's `CMakeLists.txt`:
```cmake
set(PRODUCT "ocp4")
set(PRODUCT_REMEDIATION_LANGUAGES "ignition;kubernetes")
set(PRODUCT_CEL_ENABLED TRUE) # Enable CEL content generation
ssg_build_product(${PRODUCT})
```
### Build Process
When `PRODUCT_CEL_ENABLED` is set to `TRUE`, the build system:
1. **Compiles all rules** (including CEL rules) using `compile_all.py`
2. **Filters CEL rules** - Rules with CEL checks are identified by the presence of `cel/shared.yml` containing `expression` and `inputs` fields
3. **Filters CEL profiles** - Profiles with `scanner_type: CEL` are identified
4. **Validates CEL content**:
- CEL rules must have `expression` field (non-empty)
- CEL rules must have `inputs` field (non-empty list)
- CEL profiles must have rules in `selected` field
- No duplicate rule names after conversion to hyphens
- Profiles can only reference existing CEL rules
5. **Generates CEL content YAML** at `build/${PRODUCT}-cel-content.yaml`
### Build Script: build_cel_content.py
The `build_cel_content.py` script is located in `build-scripts/` and performs the following:
#### Input
- Resolved rules directory: `build/${PRODUCT}/rules/`
- Resolved profiles directory: `build/${PRODUCT}/profiles/`
- Product YAML: `build/${PRODUCT}/product.yml`
#### Processing
1. Loads all rules with CEL checks (identified by having both `expression` and `inputs` fields from `cel/shared.yml`)
2. Validates required CEL fields (`expression`, `inputs`)
3. Loads all profiles with `scanner_type: CEL`
4. Validates profile rules are non-empty
5. Converts rule IDs (underscores) to rule names (hyphens)
6. Maps `ocil` field to `instructions` in output
7. Preserves reference keys like `cis@ocp4`, `nist`, etc. as `controls`
8. Validates no duplicate rule names
9. Validates all profile rule references exist
#### Output
YAML file at `build/${PRODUCT}-cel-content.yaml` with structure:
```yaml
profiles:
- id: cis_vm_extension
name: cis-vm-extension
title: CIS Red Hat OpenShift Virtual Machine Extension Benchmark
description: Profile description text
productType: Platform
rules:
- kubevirt-nonroot-feature-gate-is-enabled
- kubevirt-no-permitted-host-devices
rules:
- id: kubevirt_nonroot_feature_gate_is_enabled
name: kubevirt-nonroot-feature-gate-is-enabled
title: Ensure NonRoot Feature Gate is Enabled
description: The NonRoot feature gate restricts containers...
rationale: Running containers as non-root...
severity: medium
checkType: Platform
expression: hco.spec.featureGates.nonRoot == true
inputs:
- name: hco
kubernetesInputSpec:
apiVersion: hco.kubevirt.io/v1beta1
resource: hyperconvergeds
resourceName: kubevirt-hyperconverged
resourceNamespace: openshift-cnv
instructions: Run the following command...
controls:
cis@ocp4:
- 5.7.1
```
### Build Targets
```bash
# Build all content including CEL content (for products with PRODUCT_CEL_ENABLED)
./build_product ocp4
# Build data stream only (faster, excludes CEL content)
./build_product ocp4 --datastream
# Short form (only builds datastream):
./build_product ocp4 -d
# Legacy form (still supported):
./build_product ocp4 --datastream-only
# Build data stream and CEL content
./build_product ocp4 --datastream --cel-content=ocp4
# Build only CEL content (no data stream)
./build_product --cel-content=ocp4
# Build CEL content for multiple products
./build_product --cel-content=ocp4,rhel9
# CEL content is generated as build/ocp4-cel-content.yaml
```
## Validation
### Build-Time Validation
The build system validates CEL content automatically:
**Rule Validation:**
- `cel/shared.yml` file must exist for CEL rules
- `expression` field must be present and non-empty in `cel/shared.yml`
- `inputs` field must be present and non-empty list in `cel/shared.yml`
- Rule directory names must match rule IDs (with hyphens)
**Profile Validation:**
- `selected` field must contain at least one rule
- All selected rules must exist in CEL rules
- Profile cannot reference OVAL rules
**Content Validation:**
- No duplicate rule names (after underscore-to-hyphen conversion)
- All profile rule references must exist
### Manual Validation
Test CEL expressions using the CEL evaluator:
```bash
# Using cel-go
cel-spec '{"resource": {"spec": {"enabled": true}}}' 'resource.spec.enabled == true'
```
## CEL vs OVAL Comparison
| Aspect | CEL | OVAL |
|--------|-----|------|
| **Scope** | Kubernetes API resources | File system, processes, packages |
| **Access Required** | API server access | Node shell access |
| **Syntax** | CEL expressions (C-like) | XML definitions |
| **Performance** | Fast, API-level | Slower, requires node scanning |
| **Use Case** | Platform compliance | OS compliance |
| **Scanner** | compliance-operator | OpenSCAP |
| **Output Format** | YAML (cel-content.yaml) | XML (DataStream) |
## Best Practices
### Writing CEL Rules
1. **Use specific resource names when possible**
```yaml
inputs:
- name: config
kubernetes_input_spec:
api_version: v1
resource: configmaps
resource_name: cluster-config # Specific resource
resource_namespace: openshift-config
```
2. **Check for field existence before accessing**
```yaml
expression: |-
!has(resource.spec.field) || resource.spec.field == "expected"
```
3. **Keep expressions simple and readable**
- Break complex checks into multiple rules
- Use clear variable names in inputs
- Add comments for non-obvious logic
4. **Test expressions thoroughly**
- Verify both pass and fail cases
- Test with missing fields
- Test with unexpected values
### Organizing CEL Rules
1. **Group related rules by component**
- Use meaningful directory names (e.g., `api-server/`, `kubelet/`)
- Create a `group.yml` for each component
2. **Follow Kubernetes naming conventions**
- Use hyphens in directory names
- Keep names descriptive but concise
3. **Document OCIL instructions**
- Provide manual verification commands
- Include expected output
- Use proper formatting with `` and `` tags
## Troubleshooting
### Build Errors
**Error: `CEL rule 'rule-name' has no expression`**
- Add the `expression` field to your rule.yml
**Error: `CEL rule 'rule-name' has no inputs`**
- Add the `inputs` field with at least one Kubernetes input
**Error: `CEL profile 'profile-name' has no rules`**
- Add rules to the `selections` field in the profile
**Error: `profile 'profile-name' references unknown rule 'rule-name'`**
- Verify the rule exists and has CEL checks (has `cel/shared.yml` with `expression` and `inputs`)
- Check the rule ID matches the profile selection
### CEL Content Not Generated
1. Verify `PRODUCT_CEL_ENABLED TRUE` is set in `products/${PRODUCT}/CMakeLists.txt`
2. Check that rules have a `cel/shared.yml` file with `expression` and `inputs` fields
3. Check that profiles have `scanner_type: CEL`
4. Review build logs for validation errors
## References
- [CEL Language Specification](https://github.com/google/cel-spec)
- [CEL Go Implementation](https://github.com/google/cel-go)
- [Compliance Operator](https://github.com/ComplianceAsCode/compliance-operator)
- [Kubernetes API Conventions](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md)