Skip to content

Commit 85004fc

Browse files
authored
Merge pull request #14597 from yuumasato/add-cel-rules
CMP-4040, CMP-4041: Add support for CEL based rules and profiles
2 parents f242378 + c9f98a0 commit 85004fc

36 files changed

Lines changed: 2554 additions & 32 deletions

File tree

.claude/CLAUDE.md

Lines changed: 135 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,122 @@ template:
158158
pkgname@ubuntu2204: avahi-daemon # Platform-specific overrides
159159
```
160160

161+
## CEL Checking Engine (Kubernetes/OpenShift)
162+
163+
CEL (Common Expression Language) provides native Kubernetes resource evaluation without requiring shell access or OVAL checks. Rules using the CEL checking engine are used by the compliance-operator for Kubernetes and OpenShift compliance scanning.
164+
165+
**Important:** Rules with CEL checks are **excluded** from SCAP data streams and are generated as a separate `${PRODUCT}-cel-content.yaml` file.
166+
167+
### Rule Structure for CEL Checks
168+
169+
Rules using CEL checks use a **split-file structure** to separate metadata from CEL-specific content:
170+
171+
- **`rule.yml`** - Contains metadata (title, description, rationale, severity, references, etc.)
172+
- **`cel/shared.yml`** - Contains CEL-specific fields (check_type, inputs, expression)
173+
174+
This allows rules to support **both CEL and OVAL** checks during migration from OVAL to CEL.
175+
176+
**Example rule.yml:**
177+
```yaml
178+
documentation_complete: true
179+
180+
title: 'Rule Title'
181+
182+
description: |-
183+
Description of what the rule checks.
184+
185+
rationale: |-
186+
Why this rule matters.
187+
188+
severity: medium
189+
190+
ocil: |- # Optional: Manual check instructions
191+
Run the following command:
192+
<pre>$ oc get pods</pre>
193+
194+
references: # Optional: Same as regular rules
195+
cis@ocp4: 1.2.3
196+
nist: CM-6
197+
```
198+
199+
**Example cel/shared.yml:**
200+
```yaml
201+
check_type: Platform # Usually Platform for K8s checks
202+
203+
failure_reason: |- # Optional: Custom failure message
204+
The resource is not properly configured.
205+
206+
expression: |- # REQUIRED: CEL expression (must evaluate to boolean)
207+
resource.spec.enabled == true
208+
209+
inputs: # REQUIRED: Kubernetes resources to evaluate
210+
- name: resource
211+
kubernetes_input_spec:
212+
api_version: v1
213+
resource: pods
214+
resource_name: my-pod # Optional: specific resource
215+
resource_namespace: default # Optional: specific namespace
216+
```
217+
218+
**Note:** The build system automatically detects rules with CEL checks by the presence of the `cel/` directory.
219+
220+
### CEL Expression Examples
221+
222+
Simple boolean check:
223+
```yaml
224+
expression: resource.spec.enabled == true
225+
```
226+
227+
Check for field absence:
228+
```yaml
229+
expression: !has(resource.spec.insecureField)
230+
```
231+
232+
Multiple conditions:
233+
```yaml
234+
expression: |-
235+
resource.spec.replicas >= 3 &&
236+
has(resource.spec.securityContext) &&
237+
resource.spec.securityContext.runAsNonRoot == true
238+
```
239+
240+
### CEL Profile Format
241+
242+
Profiles that select rules using CEL checks must have `scanner_type: CEL`:
243+
244+
```yaml
245+
documentation_complete: true
246+
247+
title: 'CIS VM Extension Benchmark'
248+
249+
description: |-
250+
Profile description.
251+
252+
scanner_type: CEL # REQUIRED: Marks this as a CEL profile (excluded from XCCDF)
253+
254+
selections:
255+
- kubevirt-nonroot-feature-gate-is-enabled
256+
- kubevirt-no-permitted-host-devices
257+
```
258+
259+
**Note:**
260+
- Profiles use `scanner_type: CEL` to indicate they target the CEL checking engine and should be excluded from XCCDF/datastream builds.
261+
- A rule can have both `cel/` (for CEL checks) and `template:` (for OVAL checks) to support both checking engines during migration.
262+
263+
**Important:** Use hyphens rule IDs (Kubernetes naming convention), not underscores.
264+
265+
### Enabling CEL Content for a Product
266+
267+
In `products/${PRODUCT}/CMakeLists.txt`:
268+
269+
```cmake
270+
set(PRODUCT "ocp4")
271+
set(PRODUCT_CEL_ENABLED TRUE) # Enable CEL content generation
272+
ssg_build_product(${PRODUCT})
273+
```
274+
275+
See `docs/manual/developer/13_cel_content.md` for complete CEL documentation.
276+
161277
## Common Jinja2 Macros
162278

163279
Used in rule descriptions, OCIL, fixtext, and warnings fields:
@@ -279,19 +395,35 @@ Common selection patterns:
279395
## Build Instructions
280396

281397
```bash
282-
# Build a single product (full build)
398+
# Build a single product (full build, includes CEL content if PRODUCT_CEL_ENABLED)
283399
./build_product ocp4
284400
285-
# Build data stream only (faster, skips guides and tables)
401+
# Build data stream only (faster, skips guides, tables, and CEL content)
402+
./build_product ocp4 --datastream
403+
# Short form (only builds datastream):
404+
./build_product ocp4 -d
405+
# Legacy form (still supported):
286406
./build_product ocp4 --datastream-only
287407
408+
# Build data stream and CEL content
409+
./build_product ocp4 --datastream --cel-content=ocp4
410+
411+
# Build only CEL content (no data stream)
412+
./build_product --cel-content=ocp4
413+
414+
# Build CEL content for multiple products
415+
./build_product --cel-content=ocp4,rhel9
416+
288417
# Build with only specific rules (fastest, for testing individual rules)
289-
./build_product ocp4 --datastream-only --rule-id api_server_tls_security_profile
418+
./build_product ocp4 --datastream --rule-id api_server_tls_security_profile
290419
```
291420

292421
Build output goes to `build/`. The data stream file is at:
293422
`build/ssg-<product>-ds.xml`
294423

424+
For products with CEL content enabled, the CEL content file is at:
425+
`build/<product>-cel-content.yaml`
426+
295427
## Guidelines for Claude
296428

297429
1. **Always show proposals before making changes.** Present the full content of any new or modified file and wait for explicit approval.

.github/workflows/ocp-test-profiles.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ jobs:
5858

5959
- name: Build product OCP and RHCOS content
6060
if: ${{ steps.ctf.outputs.CTF_OUTPUT_SIZE != '0' && (contains(steps.product.outputs.prop, 'ocp4') || contains(steps.product.outputs.prop, 'rhcos4')) }}
61-
run: ./build_product -d ocp4 rhcos4
61+
run: ./build_product --datastream ocp4 rhcos4 --cel-content=ocp4
6262

6363
- name: Process list of rules into a list of product-profiles to test
6464
if: ${{ steps.ctf.outputs.CTF_OUTPUT_SIZE != '0' && (contains(steps.product.outputs.prop, 'ocp4') || contains(steps.product.outputs.prop, 'rhcos4')) }}

Dockerfiles/compliance-operator-content-konflux.Containerfile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ RUN grep -lr 'documentation_complete: false' ./products | xargs -I '{}' \
8484
# Build the OpenShift and RHCOS content for x86, aarch64 and ppc64le architectures.
8585
# Only build OpenShift content for s390x architectures.
8686
RUN if [ "$(uname -m)" = "x86_64" ] || [ "$(uname -m)" = "aarch64" ] || [ "$(uname -m)" = "ppc64le" ]; then \
87-
./build_product ocp4 rhcos4 --datastream-only; \
88-
else ./build_product ocp4 --datastream-only; \
87+
./build_product ocp4 rhcos4 --datastream --cel-content=ocp4; \
88+
else ./build_product ocp4 --datastream --cel-content=ocp4; \
8989
fi
9090

9191
FROM registry.redhat.io/ubi9/ubi-minimal:latest
@@ -110,3 +110,4 @@ LABEL \
110110
WORKDIR /
111111
COPY --from=builder /go/src/github.com/ComplianceAsCode/content/LICENSE /licenses/LICENSE
112112
COPY --from=builder /go/src/github.com/ComplianceAsCode/content/build/ssg-*-ds.xml .
113+
COPY --from=builder /go/src/github.com/ComplianceAsCode/content/build/*-cel-content.yaml .

Dockerfiles/ocp4_content

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ RUN if [ "$(uname -m)" == "x86_64" ] || [ "$(uname -m)" == "aarch64" ]; then \
2020
products/ocp4/profiles/pci-dss-node.profile \
2121
products/ocp4/profiles/pci-dss.profile \
2222
products/ocp4/profiles/cis-node.profile \
23+
products/ocp4/profiles/cis-vm-extension.profile \
2324
products/ocp4/profiles/cis.profile \
2425
products/ocp4/profiles/moderate-node.profile \
2526
products/ocp4/profiles/moderate.profile \
@@ -41,13 +42,14 @@ RUN if [ "$(uname -m)" == "x86_64" ] || [ "$(uname -m)" == "aarch64" ]; then \
4142
# OpenShift content for ppc64le and s390x architectures since we're not
4243
# including any RHCOS profiles on those architectures right now anyway.
4344
RUN if [ "$(uname -m)" = "x86_64" ] || [ "$(uname -m)" == "aarch64" ]; then \
44-
./build_product ocp4 rhcos4 eks --datastream-only; \
45+
./build_product ocp4 rhcos4 eks --datastream --cel-content=ocp4; \
4546
elif [ "$(uname -m)" = "ppc64le" ]; then \
46-
./build_product ocp4 rhcos4 --datastream-only; \
47-
else ./build_product ocp4 --datastream-only; \
47+
./build_product ocp4 rhcos4 --datastream --cel-content=ocp4; \
48+
else ./build_product ocp4 --datastream --cel-content=ocp4; \
4849
fi
4950

5051
FROM registry.access.redhat.com/ubi8/ubi-micro:latest
5152

5253
WORKDIR /
5354
COPY --from=builder /content/build/ssg-*-ds.xml .
55+
COPY --from=builder /content/build/*-cel-content.yaml .

Dockerfiles/quay_publish

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ FROM fedora:38 as builder
33
RUN dnf -y install cmake make git /usr/bin/python3 python3-pyyaml python3-jinja2 openscap-utils
44
RUN git clone --depth 1 https://github.com/ComplianceAsCode/content
55
WORKDIR /content
6-
RUN ./build_product --datastream-only --debug ocp4 rhcos4 eks
6+
RUN ./build_product --datastream --debug ocp4 rhcos4 eks --cel-content=ocp4
77

88
FROM registry.access.redhat.com/ubi8/ubi-minimal
99
WORKDIR /
1010
COPY --from=builder /content/build/ssg-ocp4-ds.xml .
1111
COPY --from=builder /content/build/ssg-rhcos4-ds.xml .
1212
COPY --from=builder /content/build/ssg-eks-ds.xml .
13+
COPY --from=builder /content/build/ocp4-cel-content.yaml .

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ profiles. These are meant to be run on machines to put them into
4141
compliance. We recommend using other formats but understand that for
4242
some deployment scenarios bash is the only option.
4343

44+
*"CEL content"* refers to compliance content using the Common Expression Language (CEL)
45+
for Kubernetes and OpenShift platforms. CEL content is generated as YAML files and is
46+
designed for native Kubernetes resource evaluation through the compliance-operator,
47+
without requiring shell access to nodes. This format is used for platform-level
48+
compliance checks on container orchestration systems.
49+
4450
### Why?
4551

4652
We want multiple organizations to be able to efficiently develop security
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
documentation_complete: true
2+
3+
title: 'OpenShift Virtualization'
4+
5+
description: |-
6+
This section contains security recommendations for OpenShift Virtualization
7+
(KubeVirt) configuration and virtual machine management.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
check_type: Platform
2+
3+
failure_reason: |-
4+
There are registries not using TLS in '.spec.storageImport.insecureRegistries' in
5+
the 'kubevirt-hyperconverged' resource.
6+
7+
inputs:
8+
- name: hco
9+
kubernetes_input_spec:
10+
api_version: hco.kubevirt.io/v1beta1
11+
resource: hyperconvergeds
12+
resource_name: kubevirt-hyperconverged
13+
resource_namespace: openshift-cnv
14+
15+
expression: |-
16+
!has(hco.spec.storageImport) ||
17+
hco.spec.storageImport.insecureRegistries.size() == 0
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
documentation_complete: true
2+
3+
title: 'Only Trusted Registries Using TLS Can Be Used'
4+
5+
description: |-
6+
By only pulling container images from trusted registries using TLS, organizations
7+
can reduce the risk of introducing unknown vulnerabilities or malicious
8+
software into their systems. This helps ensure that their applications and systems
9+
remain secure and stable. All container image registries used by KubeVirt should
10+
require TLS connections to protect the integrity and authenticity of images.
11+
12+
rationale: |-
13+
When the <tt>.spec.storageImport.insecureRegistries</tt> field contains entries in
14+
the <tt>kubevirt-hyperconverged</tt> resource, KubeVirt is configured to allow
15+
connections to container registries that do not use TLS encryption. This creates
16+
a significant security risk as images could be intercepted or tampered with during
17+
transit. Man-in-the-middle attacks could result in malicious images being pulled
18+
and executed within virtual machines. To maintain security, only registries using
19+
TLS should be permitted, and the insecureRegistries list should be empty.
20+
21+
severity: medium
22+
23+
ocil_clause: 'insecure registries are configured'
24+
25+
ocil: |-
26+
Run the following command to check for insecure registries:
27+
<pre>$ oc get hyperconverged kubevirt-hyperconverged -n openshift-cnv -o jsonpath='{.spec.storageImport.insecureRegistries}'</pre>
28+
The output should be empty or the field should not exist.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
check_type: Platform
2+
3+
failure_reason: |-
4+
The '.spec.permittedHostDevices' field is set in the 'kubevirt-hyperconverged'
5+
resource, allowing host devices to be used by virtualization workloads.
6+
7+
inputs:
8+
- name: hcoList
9+
kubernetes_input_spec:
10+
api_version: hco.kubevirt.io/v1beta1
11+
resource: hyperconvergeds
12+
13+
expression: |
14+
hcoList.items.filter(h,
15+
h.metadata.name == 'kubevirt-hyperconverged' &&
16+
h.metadata.namespace == 'openshift-cnv'
17+
).size() == 1 &&
18+
hcoList.items.filter(h,
19+
h.metadata.name == 'kubevirt-hyperconverged' &&
20+
h.metadata.namespace == 'openshift-cnv'
21+
).all(h,
22+
!has(h.spec.permittedHostDevices) ||
23+
h.spec.permittedHostDevices == null ||
24+
(has(h.spec.permittedHostDevices.pciHostDevices) && size(h.spec.permittedHostDevices.pciHostDevices) == 0) &&
25+
(has(h.spec.permittedHostDevices.mediatedDevices) && size(h.spec.permittedHostDevices.mediatedDevices) == 0)
26+
)

0 commit comments

Comments
 (0)