Skip to content

Remove SCC-injected security contexts for cross-cluster migration#24

Merged
stillalearner merged 5 commits into
migtools:mainfrom
nachandr:strip-security-context
Jun 26, 2026
Merged

Remove SCC-injected security contexts for cross-cluster migration#24
stillalearner merged 5 commits into
migtools:mainfrom
nachandr:strip-security-context

Conversation

@nachandr

@nachandr nachandr commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

Fixes [#22 )

The changes add a stripSecurityContext() function that removes pod-level and container-level security contexts from workload resources (Pods, Deployments, StatefulSets, etc.) to prevent SCC validation failures when migrating between OpenShift clusters with different namespace UID ranges.

The stripSecurityContext() fn only strips the following and preserves all other security context values (capabilities, readOnlyRootFilesystem, etc.)

  • runAsUser when >= SCCNamespaceUIDMin (SCC-injected namespace UID range)
  • fsGroup when >= SCCNamespaceUIDMin (SCC-injected namespace UID range
  • seLinuxOptions.level (always SCC-injected)
// Example:
//
//	Before fix:
//	  securityContext:
//	    runAsUser: 1000560000          # SCC-injected (>= SCCNamespaceUIDMin)
//	    fsGroup: 1000560000            # SCC-injected
//	    runAsNonRoot: true             # User-configured
//	    seLinuxOptions:
//	      level: s0:c26,c5             # SCC-injected
//	      type: spc_t                  # User-configured
//
//	After fix:
//	  securityContext:
//	    runAsNonRoot: true             # Preserved
//	    seLinuxOptions:
//	      type: spc_t                  # Preserved

Summary by CodeRabbit

  • New Features

    • Added OpenShift SCC security-context sanitization for multiple workload types (Pods, Deployments, StatefulSets, DaemonSets, Jobs, CronJobs, ReplicaSets, and ReplicationControllers), removing SCC-injected runAsUser, fsGroup, and seLinuxOptions.level at both pod and container levels while preserving user-configured securityContext settings.
  • Tests

    • Added comprehensive automated coverage to verify SCC-injected fields are stripped (including ephemeralContainers when present) and that other securityContext values remain unchanged.

@coderabbitai

coderabbitai Bot commented Apr 19, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Exports OpenshiftOptionalFields type and ParseOptionalFields function with SCCNamespaceUIDMin constant. Adds StripSecurityContext function generating JSON patches removing SCC-injected securityContext fields from pod and container specs. Integrates it into Run for Pod and workload kinds. Adds 552 lines of tests validating patch application and field preservation.

Changes

Security Context Stripping and Type Exports

Layer / File(s) Summary
Export types, constant, and propagate signature updates
openshift/plugin.go, openshift/openshift.go
Renames openshiftOptionalFieldsOpenshiftOptionalFields and parseOptionalFieldsParseOptionalFields, adds exported SCCNamespaceUIDMin constant (1000000000), and updates all internal function signatures to use the exported type: updateBuildConfigImageReference, UpdateDefaultPullSecrets, updateSecretsForSlice, updateSecret, UpdateBuildConfig, UpdateDeploymentConfig.
StripSecurityContext implementation
openshift/openshift.go
Adds exported StripSecurityContext (lines 424–705) that validates workload kind, deep-copies the object, derives kind-specific securityContext base paths, removes runAsUser/fsGroup only when ≥ SCCNamespaceUIDMin, strips seLinuxOptions.level unconditionally and prunes empty maps, processes containers/initContainers/ephemeralContainers, and returns a JSON patch built from remove operations by comparing original vs modified fields.
Run integration and test coverage
openshift/plugin.go, openshift/plugin_test.go
Updates Run to call ParseOptionalFields, concatenate UpdateDefaultPullSecrets and StripSecurityContext patches for Pod, and apply StripSecurityContext with info logging for workload kinds. Adds TestStripSecurityContext with table-driven cases for Pod, Deployment, StatefulSet, CronJob, and Service; applies patches and validates removal of runAsUser, fsGroup, and seLinuxOptions.level at pod and container levels, plus verification that user-configured fields are preserved.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested reviewers

  • msajidmansoori12
  • midays
  • stillalearner

Poem

🐇 A constant guards the threshold line,
A thousand million kept in mind,
StripSecurityContext hops with care,
Removing SCC's injected air.
Patches test each workload kind—
Security clean, no traces left behind! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Remove SCC-injected security contexts for cross-cluster migration' accurately and specifically summarizes the main change in the pull request—adding functionality to strip SecurityContextConstraint-injected security context values from workload resources during cluster migration.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

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

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.

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openshift.go`:
- Around line 428-430: The allow-list that skips security-context stripping is
missing "DeploymentConfig", so update the conditional that checks kind (the if
in openshift.go comparing kind to "Pod","Deployment", etc.) to also include
"DeploymentConfig" so its spec.template.spec pod template has SCC
UID/GID/SELinux cleared; additionally, apply the corresponding patch from the
existing DeploymentConfig branch in cmd.go so PVC rename logic for
DeploymentConfig is preserved (ensure the cmd.go changes that handle PVC
renaming for DeploymentConfig are appended/merged into the current cmd.go
implementation).
- Around line 437-485: The current code deletes entire securityContext objects
(via unstructured.RemoveNestedField and delete(container, "securityContext")),
which loses user-authored settings; instead, modify logic in RemoveNestedField
calls and in stripContainerSecurityContext to only remove cluster-injected
fields: runAsUser, fsGroup, supplementalGroups, seLinuxOptions (and any
cluster-specific UID/GID fields), leaving other keys (privileged, capabilities,
allowPrivilegeEscalation, readOnlyRootFilesystem, seccompProfile) intact;
implement this by reading the securityContext map (use unstructured.NestedMap or
container["securityContext"]), deleting those specific keys, writing the map
back (and only removing the whole securityContext if the map becomes empty), and
optionally gate the existing destructive behavior behind an explicit option/flag
if full removal is desired; update references: modified.Object,
unstructured.RemoveNestedField usage, and the stripContainerSecurityContext
closure to perform targeted-key removal rather than delete(container,
"securityContext").
- Line 498: The call to jsonpatch.CreatePatch does not exist in the
evanphx/json-patch API; replace it by using
jsonpatch.CreateMergePatch(originalJSON, modifiedJSON) if an RFC7396 merge patch
is acceptable (rename patch variable to mergePatch and use jsonpatch.MergePatch
APIs accordingly), or else implement RFC6902 operations yourself by constructing
the JSON Patch operation array (add/replace/remove objects) from originalJSON
and modifiedJSON and use that array as the patch payload instead of calling
CreatePatch; update references to the variable created from CreatePatch and any
apply/HTTP patch code to match the chosen approach.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 99cbc0dd-3370-4cee-aa04-8e9463f9a769

📥 Commits

Reviewing files that changed from the base of the PR and between 95eb3ef and 189c2e7.

📒 Files selected for processing (2)
  • cmd.go
  • openshift.go

Comment thread openshift/openshift.go
Comment thread openshift/openshift.go Outdated
Comment thread openshift/openshift.go Outdated
@stillalearner

Copy link
Copy Markdown
Contributor

@nachandr

So there can be some security context values set by the user intentionally.
Like runAsUser, or fsGroup, or readOnlyFileSystem, etc.

Here we would only like to remove the values that are injected by SCC, basically these 3:

  1. runAsUser -> pod and container level both, STRIP ONLY WHEN IT FALLS IN namespace UID range , >=1000000000

  2. fsGroup -> pod and container level both, STRIP ONLY WHEN IT FALLS IN namespace UID range , >=1000000000

  3. seLinuxOptions.level -> this we can strip always.

Other values we can always keep.

Signed-off-by: Nandini Chandra <nachandr@redhat.com>
@nachandr nachandr force-pushed the strip-security-context branch from 189c2e7 to e59928b Compare June 22, 2026 16:32

@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: 5

🧹 Nitpick comments (1)
openshift/plugin_test.go (1)

939-957: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add ephemeralContainers verification to match runtime behavior.

StripSecurityContext handles ephemeralContainers, but this test only validates containers and initContainers. Add at least one case and assertion loop for ephemeralContainers to close the coverage gap.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openshift/plugin_test.go` around lines 939 - 957, The test currently verifies
security contexts for containers and initContainers by calling
verifySecurityContext in similar loops, but does not include verification for
ephemeralContainers even though StripSecurityContext handles them. Add a new
verification block for ephemeralContainers following the same pattern as the
existing containers and initContainers loops, where you check if
ephemeralContainers exists in the spec, iterate through each ephemeral
container, extract its securityContext, and call verifySecurityContext with an
appropriately formatted path string like
"spec.ephemeralContainers[%d].securityContext".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openshift/openshift.go`:
- Around line 512-526: The code currently strips runAsUser when it is >=
1000000000, but it does not handle fsGroup with the same threshold. Add a
similar check after the runAsUser deletion logic to also strip fsGroup from the
security context when its value is >= 1000000000. Cast fsGroup to int64, verify
it exists and meets the threshold condition, and delete it from the security
context map (sc) using the same pattern as the runAsUser check. Apply this same
fix to any other locations mentioned where similar security context stripping
occurs.
- Around line 482-487: The calls to unstructured.SetNestedMap and
unstructured.RemoveNestedField are ignoring their error return values, which
golangci-lint is flagging. Update the closure containing the security context
removal logic to return an error type, then check the error returned by both the
unstructured.RemoveNestedField call (when removing empty securityContext) and
the unstructured.SetNestedMap call (when setting the modified securityContext),
propagating any errors back through the closure's return. Apply the same error
handling pattern to the unstructured.SetNestedSlice calls also mentioned in the
StripSecurityContext function to ensure all nested-set operations properly
propagate errors.
- Around line 615-616: The variable ok1 is being assigned on line 615 during the
type assertion for origSC but is never used in the code that follows, only ok2
is read. Remove the unused ok1 assignment by replacing it with the blank
identifier underscore on line 615 where origSC is being assigned from the
securityContext type assertion to eliminate the ineffectual-assignment lint
error.

In `@openshift/plugin_test.go`:
- Around line 917-933: The test code currently allows nil spec values to pass
silently without validation, which can hide patch-related failures. After the
multi-level map resolution that sets the spec variable (based on the resource
kind check using tt.resource.GetKind()), add a validation check that calls
t.Fatalf with descriptive error details when spec is nil and tt.expectPatch is
true, including the resolved kind and the expected path structure (e.g., "spec",
"spec.jobTemplate.spec.template.spec", etc.) so that accidental removal or
movement of the pod spec is properly caught instead of producing a
false-positive pass.
- Around line 586-593: The TestStripSecurityContext test struct defines an
expectedSecurityCtx field to specify which security context fields should be
preserved after patching, but the test implementation does not verify these
preserved fields are actually present in the output. Locate the test loop where
each test case is executed and add positive assertions that check the
expectedSecurityCtx values are preserved in the patched resource. This will
ensure regressions that remove fields like seLinuxOptions.type/user/role,
readOnlyRootFilesystem, or capabilities would be caught by the tests, not just
verify that SCC fields are removed.

---

Nitpick comments:
In `@openshift/plugin_test.go`:
- Around line 939-957: The test currently verifies security contexts for
containers and initContainers by calling verifySecurityContext in similar loops,
but does not include verification for ephemeralContainers even though
StripSecurityContext handles them. Add a new verification block for
ephemeralContainers following the same pattern as the existing containers and
initContainers loops, where you check if ephemeralContainers exists in the spec,
iterate through each ephemeral container, extract its securityContext, and call
verifySecurityContext with an appropriately formatted path string like
"spec.ephemeralContainers[%d].securityContext".
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: c679f71e-5ce0-48f8-9195-b4749ff7dd39

📥 Commits

Reviewing files that changed from the base of the PR and between e59928b and 731af1a.

📒 Files selected for processing (3)
  • openshift/openshift.go
  • openshift/plugin.go
  • openshift/plugin_test.go

Comment thread openshift/openshift.go
Comment thread openshift/openshift.go
Comment thread openshift/openshift.go Outdated
Comment thread openshift/plugin_test.go
Comment thread openshift/plugin_test.go
@nachandr nachandr force-pushed the strip-security-context branch from 731af1a to 3660508 Compare June 23, 2026 03:48

@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 (1)
openshift/plugin_test.go (1)

890-903: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Decouple this test from UpdateDefaultPullSecrets side effects.

This suite is validating security-context stripping, but Pod Run also executes pull-secret mutation. Setting extras to disable pull-secret stripping in this test will keep expectPatch assertions focused and future-proof.

Suggested tweak
 request := transform.PluginRequest{
   Unstructured: *tt.resource,
-  Extras:       map[string]string{},
+  Extras: map[string]string{
+    StripDefaultPullSecretsFlag: "false",
+  },
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openshift/plugin_test.go` around lines 890 - 903, The test's PluginRequest
currently has an empty Extras map, which means the Pod Run method executes both
security-context stripping and pull-secret mutation, causing the expectPatch
assertions to be affected by unwanted side effects. Populate the Extras field in
the PluginRequest initialization (where it currently shows Extras:
map[string]string{}) with the appropriate configuration values needed to disable
pull-secret stripping, so the test assertions focus exclusively on validating
security-context stripping behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openshift/plugin_test.go`:
- Around line 1020-1108: The checkPreserved helper function has a logic bug for
seLinuxOptions validation where it attempts to cast modVal (which is nil when
the field was removed) to a map, causing the check to silently pass instead of
detecting removed fields. Additionally, in the container, initContainer, and
ephemeralContainer security context checks (starting around lines 1058, 1070,
and 1083), if modSC is missing entirely, the validation is skipped instead of
treating it as an empty map to properly catch removed fields. Fix this by
ensuring that when modVal is nil in the seLinuxOptions section, the modOpts is
initialized to an empty map, and apply the same fallback pattern to all
container type checks so that missing modSC is treated as an empty map rather
than skipping validation entirely.

---

Nitpick comments:
In `@openshift/plugin_test.go`:
- Around line 890-903: The test's PluginRequest currently has an empty Extras
map, which means the Pod Run method executes both security-context stripping and
pull-secret mutation, causing the expectPatch assertions to be affected by
unwanted side effects. Populate the Extras field in the PluginRequest
initialization (where it currently shows Extras: map[string]string{}) with the
appropriate configuration values needed to disable pull-secret stripping, so the
test assertions focus exclusively on validating security-context stripping
behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3789875c-ae95-4245-a66c-e5e7155b1300

📥 Commits

Reviewing files that changed from the base of the PR and between 731af1a and 3660508.

📒 Files selected for processing (3)
  • openshift/openshift.go
  • openshift/plugin.go
  • openshift/plugin_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • openshift/plugin.go

Comment thread openshift/plugin_test.go
@nachandr nachandr force-pushed the strip-security-context branch from 3660508 to 24c0152 Compare June 23, 2026 04:15
Signed-off-by: Nandini Chandra <nachandr@redhat.com>
@nachandr nachandr force-pushed the strip-security-context branch from 24c0152 to 171a9d7 Compare June 23, 2026 04:30
@nachandr

Copy link
Copy Markdown
Contributor Author

/coderabbit review

Signed-off-by: Nandini Chandra <nachandr@redhat.com>

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openshift/openshift.go`:
- Around line 482-490: The code currently only handles int64 type assertions for
runAsUser and fsGroup, but JSON unmarshaling of unstructured.Unstructured
produces float64 for numeric values, causing the type checks to fail silently
and leave SCC-injected UIDs in place. Create a helper function called sccUID
that accepts an interface and safely handles both int64 and float64 types by
returning the value as int64 and a boolean indicating success. Then replace all
occurrences of the manual type assertions for runAsUser and fsGroup (both in the
current block and in the container-level block mentioned at lines 539 and 544)
with calls to the sccUID helper function, using the returned int64 value to
compare against SCCNamespaceUIDMin before deleting the field.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: ef027c36-af67-40de-a55a-ea4f9d8cd26a

📥 Commits

Reviewing files that changed from the base of the PR and between 3660508 and 88c708e.

📒 Files selected for processing (3)
  • openshift/openshift.go
  • openshift/plugin.go
  • openshift/plugin_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • openshift/plugin.go
  • openshift/plugin_test.go

Comment thread openshift/openshift.go
Signed-off-by: Nandini Chandra <nachandr@redhat.com>
Comment thread openshift/openshift.go
Comment thread openshift/openshift.go Outdated
Signed-off-by: Nandini Chandra <nachandr@redhat.com>
@nachandr

Copy link
Copy Markdown
Contributor Author

TestStripSecurityContext results:

crane_plugin_pr24

@stillalearner stillalearner left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm

@stillalearner stillalearner merged commit fcf91dd into migtools:main Jun 26, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants