Skip to content

QA: E2E tests for SPIRE-439 SCC Hardening for SPIRE Agent (PR #105)#111

Draft
sujkini wants to merge 1 commit into
openshift:mainfrom
sujkini:qa/e2e-scc-hardening-spire-agent-pr-105
Draft

QA: E2E tests for SPIRE-439 SCC Hardening for SPIRE Agent (PR #105)#111
sujkini wants to merge 1 commit into
openshift:mainfrom
sujkini:qa/e2e-scc-hardening-spire-agent-pr-105

Conversation

@sujkini

@sujkini sujkini commented Apr 26, 2026

Copy link
Copy Markdown

E2E Test Generation

Source PR: #105
Jira: SPIRE-439
Generated by: Iterative Analysis Rule (automated QA E2E tooling)


What this PR adds

E2E test coverage for the security hardening introduced by PR #105
(pkg/controller/spire-agent/daemonset.go and pkg/controller/spire-agent/scc.go).

New Context blocks in test/e2e/e2e_test.go

SpireAgent DaemonSet security hardening

  • SPIRE Agent DaemonSet should have host networking disabled
    Asserts HostNetwork=false, DNSPolicy=ClusterFirst, HostPID=true (retained), and HostIPC=false (mirrors SCC AllowHostIPC: false).

  • SPIRE Agent DaemonSet container should have a hardened security context
    Asserts Privileged=false, AllowPrivilegeEscalation=false, ReadOnlyRootFilesystem=true, Capabilities.Drop=[ALL], and no container HostPorts (mirrors SCC AllowHostPorts: false).

SpireAgent SCC hardening

  • SPIRE Agent SCC should restrict host access and privileged execution
    Asserts all tightened SCC fields including AllowHostIPC=false (added), AllowHostNetwork=false, AllowHostPorts=false, AllowPrivilegedContainer=false, AllowPrivilegeEscalation=false, RunAsUser=RunAsAny, ReadOnlyRootFilesystem=true, AllowHostPID=true, RequiredDropCapabilities=[ALL].

  • SPIRE Agent pods should be bound to the hardened spire-agent SCC
    Verifies openshift.io/scc: spire-agent annotation on running pods.

  • SPIRE Agent replacement pod after deletion should be admitted under the hardened spire-agent SCC (new)
    Deletes one SPIRE Agent pod, waits for the DaemonSet replacement, then verifies the replacement pod is also admitted under spire-agent (not a privileged fallback).

  • spire-agent SCC should be reconciled back to hardened state if manually modified (new)
    Patches AllowHostNetwork=true on the SCC, then verifies the operator reconciles it back to false within the standard timeout — proving the SCC is under active reconciliation.

Suite file update (test/e2e/e2e_suite_test.go)

Registers securityv1 scheme in BeforeSuite so the typed k8sClient.Get for SecurityContextConstraints works correctly.


Test Coverage

  • AC-1 through AC-15 from SPIRE-439 acceptance criteria: ✅ all covered
  • Coverage score for PR-relevant journeys: 87/100 (threshold: 85)

Red Hat Certification Checklist

  • OLM install — covered in Installation context
  • SCC validation — covered in SpireAgent SCC hardening context
  • RBAC least-privilege — existing RBAC tests in place
  • Security context (runAsNonRoot, readOnly filesystem) — partially; runAsNonRoot deferred (not set by PR SPIRE-439: SCC Hardening for Spire Agent #105)
  • Workload attestation regression — covered in SpireAgent attestation context

Deferred (not actionable against PR #105 implementation)


Files Modified

  • test/e2e/e2e_test.go — new Context blocks and extended assertions
  • test/e2e/e2e_suite_test.gosecurityv1 scheme registration

How to Run

go test ./test/e2e/... -v -count=1 -run "SpireAgent"

Full analysis: output/pr-105/Analysis/pr-analysis-report.md and output/pr-105/Analysis/pr-gaps-analysis.md

Made with Cursor

Summary by CodeRabbit

  • Tests
    • Added comprehensive end-to-end test coverage for SPIRE Agent security hardening on OpenShift, validating pod security configurations, security policies, constraint enforcement, and drift correction to ensure hardened security settings are consistently maintained.

…enshift#105)

Add E2E coverage for the security hardening introduced by PR openshift#105:

- Assert HostIPC=false and HostPort=0 on SPIRE Agent DaemonSet pod spec
- Assert AllowHostIPC=false on the spire-agent SCC
- Verify replacement pods after deletion are still admitted under the
  hardened spire-agent SCC (not a privileged fallback)
- Verify the operator reconciles SCC drift back to AllowHostNetwork=false
- Register securityv1 scheme in BeforeSuite for typed SCC client access

Overall coverage score for PR-relevant journeys: 87/100 (>= 85 threshold).
Deferred: runAsNonRoot and seccompProfile assertions pending upstream
container SecurityContext additions.

Made-with: Cursor
@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Apr 26, 2026
@openshift-ci

openshift-ci Bot commented Apr 26, 2026

Copy link
Copy Markdown

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@coderabbitai

coderabbitai Bot commented Apr 26, 2026

Copy link
Copy Markdown

Walkthrough

These changes add OpenShift security hardening validation to the end-to-end test suite by registering the Security API with the runtime scheme and implementing comprehensive tests that verify SPIRE Agent DaemonSet configuration, Security Context Constraints (SCC) policies, container security contexts, pod admission, and SCC drift correction behavior.

Changes

Cohort / File(s) Summary
E2E Test Infrastructure
test/e2e/e2e_suite_test.go
Registers OpenShift Security API v1 with the controller-runtime scheme during test initialization to enable recognition and serialization of security objects.
E2E Test Coverage
test/e2e/e2e_test.go
Adds comprehensive test validating SPIRE Agent security hardening: DaemonSet pod template node isolation settings, hardened container security context (privileged/escalation disabled, read-only filesystem, capability drops), SCC policy constraints, pod admission under hardened SCC via annotation, DaemonSet reconciliation after pod deletion, and SCC drift correction by restoring AllowHostNetwork to false.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 9 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Test Structure And Quality ⚠️ Warning SCC drift reconciliation test lacks DeferCleanup, leaving cluster with AllowHostNetwork=true if operator fails, weakening security posture. Add DeferCleanup block after SCC drift introduction to restore AllowHostNetwork=false regardless of reconciliation outcome, ensuring test suite remains hermetic.
Microshift Test Compatibility ⚠️ Warning New e2e tests lack MicroShift skip mechanisms despite using only compatible APIs. Add [Skipped:MicroShift] label or [apigroup:security.openshift.io] tag to test names or implement runtime IsMicroShift() guard.
Single Node Openshift (Sno) Test Compatibility ❓ Inconclusive Cannot directly access test implementation to verify SNO compatibility assumptions in new e2e tests. Review actual test code in test/e2e/e2e_test.go to confirm no multi-node assumptions exist or add [Skipped:SingleReplicaTopology] labels.
✅ Passed checks (9 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding E2E tests for SPIRE-439 SCC hardening for SPIRE Agent. It directly matches the changeset which adds new E2E test coverage in two files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed All test names use static descriptive strings without dynamic values like pod names, timestamps, UUIDs, node names, or IP addresses.
Topology-Aware Scheduling Compatibility ✅ Passed PR adds only E2E test coverage validating security properties of existing objects; no deployment manifests, operator code, or topology-aware scheduling constraints are introduced.
Ote Binary Stdout Contract ✅ Passed Pull request complies with OTE Binary Stdout Contract. All fmt.Fprintf calls write to GinkgoWriter within test blocks; no stdout writes in process-level code.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed The new e2e tests for SPIRE Agent security hardening use only Kubernetes client APIs to validate cluster-internal resources without IPv4 assumptions or external connectivity requirements, ensuring compatibility with both IPv4 and IPv6 clusters and disconnected environments.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.11.4)

Error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions
The command is terminated due to an error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@openshift-ci

openshift-ci Bot commented Apr 26, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: sujkini
Once this PR has been reviewed and has the lgtm label, please assign bharath-b-rh for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
test/e2e/e2e_test.go (3)

1807-1811: Redundant BeforeEach blocks — outer BeforeEach already handles this.

The outer BeforeEach at lines 76‑80 already initializes testCtx with utils.TestContextTimeout and registers the cancel as DeferCleanup for every It in the suite. Re-doing it inside these Contexts shadows the outer setup with no behavioral difference and is dead code. Other Contexts in this file (e.g., "SpireAgent attestation", "Common configurations") deliberately rely on the outer BeforeEach and don't redeclare it.

♻️ Suggested cleanup
 	Context("SpireAgent DaemonSet security hardening", func() {
-		BeforeEach(func() {
-			var cancel context.CancelFunc
-			testCtx, cancel = context.WithTimeout(context.Background(), utils.TestContextTimeout)
-			DeferCleanup(cancel)
-		})
-
 		It("SPIRE Agent DaemonSet should have host networking disabled", ...

Apply the same removal in the "SpireAgent SCC hardening" Context.

Also applies to: 1879-1883

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/e2e_test.go` around lines 1807 - 1811, Remove the redundant
BeforeEach blocks that reinitialize testCtx and register the cancel via
DeferCleanup inside the "SpireAgent SCC hardening" Context (and the other
Context at the referenced location); these duplicate the outer BeforeEach
already setting testCtx with utils.TestContextTimeout. Locate the inner
BeforeEach functions that create a local cancel via
context.WithTimeout(context.Background(), utils.TestContextTimeout) and call
DeferCleanup(cancel) and delete those inner blocks so the suite relies on the
outer BeforeEach's testCtx initialization.

1929-1942: Pod list may include terminating/pending pods — consider filtering to active pods.

pods.Items returned from List includes pods in any phase (Pending, Succeeded, Failed) and pods with a DeletionTimestamp still in the API server. Earlier tests in this file (e.g., the OIDC resource test at line 1434) defensively use utils.FilterActivePods(...). If a previous test leaves a terminating agent pod transiently visible, this assertion can flake on the lingering pod's annotation. Suggest filtering to running, non-terminating pods before iterating:

♻️ Suggested filter
 		Expect(err).NotTo(HaveOccurred(), "failed to list SPIRE Agent pods")
 		Expect(pods.Items).NotTo(BeEmpty(), "expected at least one running SPIRE Agent pod")
+		activePods := utils.FilterActivePods(pods.Items)
+		Expect(activePods).NotTo(BeEmpty(), "expected at least one Running SPIRE Agent pod")

 		By("Verifying each pod is admitted under the spire-agent SCC")
-		for _, pod := range pods.Items {
+		for _, pod := range activePods {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/e2e_test.go` around lines 1929 - 1942, The loop that checks
pods.Items for the openshift.io/scc annotation can include terminating or
non-running pods and flake; before iterating use the existing utility to
restrict to active, non-terminating pods (e.g., call
utils.FilterActivePods(pods.Items) or an equivalent filter that excludes pods
with non-Running phase or a non-nil DeletionTimestamp) and then iterate over the
filtered slice when checking each pod.Annotate references: use pods.Items as
input, FilterActivePods helper, and still assert the "openshift.io/scc"
annotation and value "spire-agent" on each resulting pod.

1854-1863: Use value-aware matchers for *bool fields to avoid coupling to "explicitly set" semantics and surface clearer failures when nil.

Equal(ptr.To(false)) uses reflect.DeepEqual, which conflates two states:

  • the pointer is nil (field unset; default behavior applies), and
  • the pointer is set to false.

If code removes the explicit ptr.To(true) rather than replacing it with ptr.To(false), the assertion will fail with an opaque diff (<nil> != *false) even though runtime behavior is correct. Use HaveValue(BeFalse()) (or HaveValue(BeTrue())) instead, which explicitly asserts the pointer is non-nil and holds the expected value, with clearer failure messages.

Suggested changes
-Expect(sc.Privileged).To(Equal(ptr.To(false)),
+Expect(sc.Privileged).To(HaveValue(BeFalse()),
     "spire-agent container must not run as privileged after SCC hardening")

-Expect(sc.AllowPrivilegeEscalation).To(Equal(ptr.To(false)),
+Expect(sc.AllowPrivilegeEscalation).To(HaveValue(BeFalse()),
     "spire-agent container must not allow privilege escalation")

-Expect(sc.ReadOnlyRootFilesystem).To(Equal(ptr.To(true)),
+Expect(sc.ReadOnlyRootFilesystem).To(HaveValue(BeTrue()),
     "spire-agent container must use a read-only root filesystem")

Also applies to lines 1908‑1909 (scc.AllowPrivilegeEscalation).

If the intent is specifically to require the field be explicitly set (i.e., reject nil), keep Equal but add a preceding Expect(sc.Privileged).NotTo(BeNil()) for clarity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/e2e_test.go` around lines 1854 - 1863, Replace pointer equality
checks that use Equal(ptr.To(false/true)) on the securityContext fields with
value-aware matchers so nil vs set is clear: for sc.Privileged and
sc.AllowPrivilegeEscalation use Expect(sc.Privileged).To(HaveValue(BeFalse()))
and Expect(sc.AllowPrivilegeEscalation).To(HaveValue(BeFalse())); for
sc.ReadOnlyRootFilesystem use
Expect(sc.ReadOnlyRootFilesystem).To(HaveValue(BeTrue())). If you intentionally
require non-nil semantics instead, add explicit NotTo(BeNil()) checks (e.g.,
Expect(sc.Privileged).NotTo(BeNil())) before using Equal.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/e2e/e2e_test.go`:
- Around line 2002-2024: Capture the SCC's original AllowHostNetwork value
before calling UpdateCRWithRetry and register a Ginkgo DeferCleanup that
restores that original state (e.g., using the same utils.UpdateCRWithRetry or
k8sClient operations) so the scc named "spire-agent" is reset even if the
operator never reconciles; reference the existing variables/test helpers (scc,
UpdateCRWithRetry, testCtx, k8sClient) to implement the cleanup and ensure it
runs immediately after reading the original value and before mutating the SCC.

---

Nitpick comments:
In `@test/e2e/e2e_test.go`:
- Around line 1807-1811: Remove the redundant BeforeEach blocks that
reinitialize testCtx and register the cancel via DeferCleanup inside the
"SpireAgent SCC hardening" Context (and the other Context at the referenced
location); these duplicate the outer BeforeEach already setting testCtx with
utils.TestContextTimeout. Locate the inner BeforeEach functions that create a
local cancel via context.WithTimeout(context.Background(),
utils.TestContextTimeout) and call DeferCleanup(cancel) and delete those inner
blocks so the suite relies on the outer BeforeEach's testCtx initialization.
- Around line 1929-1942: The loop that checks pods.Items for the
openshift.io/scc annotation can include terminating or non-running pods and
flake; before iterating use the existing utility to restrict to active,
non-terminating pods (e.g., call utils.FilterActivePods(pods.Items) or an
equivalent filter that excludes pods with non-Running phase or a non-nil
DeletionTimestamp) and then iterate over the filtered slice when checking each
pod.Annotate references: use pods.Items as input, FilterActivePods helper, and
still assert the "openshift.io/scc" annotation and value "spire-agent" on each
resulting pod.
- Around line 1854-1863: Replace pointer equality checks that use
Equal(ptr.To(false/true)) on the securityContext fields with value-aware
matchers so nil vs set is clear: for sc.Privileged and
sc.AllowPrivilegeEscalation use Expect(sc.Privileged).To(HaveValue(BeFalse()))
and Expect(sc.AllowPrivilegeEscalation).To(HaveValue(BeFalse())); for
sc.ReadOnlyRootFilesystem use
Expect(sc.ReadOnlyRootFilesystem).To(HaveValue(BeTrue())). If you intentionally
require non-nil semantics instead, add explicit NotTo(BeNil()) checks (e.g.,
Expect(sc.Privileged).NotTo(BeNil())) before using Equal.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: cfdbabb6-f725-477a-8be2-3413b9457442

📥 Commits

Reviewing files that changed from the base of the PR and between b631460 and 828d3d1.

📒 Files selected for processing (2)
  • test/e2e/e2e_suite_test.go
  • test/e2e/e2e_test.go

Comment thread test/e2e/e2e_test.go
Comment on lines +2002 to +2024
By("Patching the SCC to reintroduce host network access (simulating manual drift)")
err = utils.UpdateCRWithRetry(testCtx, k8sClient, scc, func() {
scc.AllowHostNetwork = true
})
Expect(err).NotTo(HaveOccurred(), "failed to patch SCC to introduce drift")
fmt.Fprintf(GinkgoWriter, "introduced drift: SCC AllowHostNetwork set to true\n")

By("Verifying the operator reconciles the SCC back to AllowHostNetwork: false")
Eventually(func() bool {
current := &securityv1.SecurityContextConstraints{}
if err := k8sClient.Get(testCtx, types.NamespacedName{Name: "spire-agent"}, current); err != nil {
fmt.Fprintf(GinkgoWriter, "failed to get SCC: %v\n", err)
return false
}
if current.AllowHostNetwork {
fmt.Fprintf(GinkgoWriter, "SCC AllowHostNetwork is still true — drift not yet corrected\n")
return false
}
fmt.Fprintf(GinkgoWriter, "SCC drift corrected: AllowHostNetwork restored to false\n")
return true
}).WithTimeout(utils.DefaultTimeout).WithPolling(utils.ShortInterval).Should(BeTrue(),
"operator should reconcile SCC AllowHostNetwork back to false within %v", utils.DefaultTimeout)
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add a defensive DeferCleanup to restore the SCC if the reconciler fails to converge.

If the operator does not reconcile within DefaultTimeout (or the test fails for any other reason after introducing drift), the cluster is left with spire-agent SCC AllowHostNetwork=true. That weakens the security posture for the rest of the suite (and any subsequent re-run that polls "must already be in hardened state before drift test" at line 2000 will pass — masking the original failure). A best-effort cleanup keeps the suite hermetic regardless of reconciler behavior:

🛡️ Suggested defensive cleanup
 		By("Patching the SCC to reintroduce host network access (simulating manual drift)")
 		err = utils.UpdateCRWithRetry(testCtx, k8sClient, scc, func() {
 			scc.AllowHostNetwork = true
 		})
 		Expect(err).NotTo(HaveOccurred(), "failed to patch SCC to introduce drift")
+		DeferCleanup(func(ctx context.Context) {
+			current := &securityv1.SecurityContextConstraints{}
+			if err := k8sClient.Get(ctx, types.NamespacedName{Name: "spire-agent"}, current); err == nil && current.AllowHostNetwork {
+				_ = utils.UpdateCRWithRetry(ctx, k8sClient, current, func() {
+					current.AllowHostNetwork = false
+				})
+			}
+		})
 		fmt.Fprintf(GinkgoWriter, "introduced drift: SCC AllowHostNetwork set to true\n")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/e2e_test.go` around lines 2002 - 2024, Capture the SCC's original
AllowHostNetwork value before calling UpdateCRWithRetry and register a Ginkgo
DeferCleanup that restores that original state (e.g., using the same
utils.UpdateCRWithRetry or k8sClient operations) so the scc named "spire-agent"
is reset even if the operator never reconciles; reference the existing
variables/test helpers (scc, UpdateCRWithRetry, testCtx, k8sClient) to implement
the cleanup and ensure it runs immediately after reading the original value and
before mutating the SCC.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant