Skip to content

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

Open
nachandr wants to merge 1 commit into
migtools:mainfrom
nachandr:strip-security-context
Open

Remove SCC-injected security contexts for cross-cluster migration#24
nachandr wants to merge 1 commit into
migtools:mainfrom
nachandr:strip-security-context

Conversation

@nachandr
Copy link
Copy Markdown

@nachandr nachandr commented Apr 19, 2026

Fixes issue

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.

Summary by CodeRabbit

  • Bug Fixes
    • OpenShift SCC-injected security context fields are now automatically removed from Pod and workload resources (Deployment, StatefulSet, DaemonSet, Job, CronJob, ReplicaSet, ReplicationController), preventing conflicts and ensuring consistent security configuration management.

Signed-off-by: Nandini Chandra <nachandr@redhat.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 19, 2026

📝 Walkthrough

Walkthrough

Added a new stripSecurityContext helper function to remove SCC-injected security context fields from OpenShift workload resources via JSON patches. Modified cmd.go to invoke this function for Pod resources and workload controller kinds (Deployment, StatefulSet, DaemonSet, Job, CronJob, ReplicaSet, ReplicationController), appending resulting patches.

Changes

Cohort / File(s) Summary
Security Context Stripping Implementation
openshift.go
Added new stripSecurityContext function that deep-copies resources, removes spec.securityContext from Pods and controller templates, strips securityContext from containers/initContainers/ephemeralContainers, and generates JSON Patch via diff. Includes error handling for JSON marshaling and patch creation.
Integration with Patch Operations
cmd.go
Integrated stripSecurityContext calls for Pod resources (with error propagation) and workload controller kinds (unconditional invocation with logging), appending resulting patches to existing patch operations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Poem

🐰 Fields of security, away they go,
SCC context stripped, a cleaner flow,
Patch by patch, the resources are freed,
From OpenShift's injected seed! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 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 summarizes the main change: removing SCC-injected security contexts for cross-cluster migration, which directly matches the PR's primary objective and file changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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.go
Comment on lines +428 to +430
if kind != "Pod" && kind != "Deployment" && kind != "StatefulSet" &&
kind != "DaemonSet" && kind != "Job" && kind != "CronJob" &&
kind != "ReplicaSet" && kind != "ReplicationController" {
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 | 🟠 Major

Include DeploymentConfig in security-context stripping.

DeploymentConfig has a pod template at spec.template.spec, but this allow-list excludes it. Migrated OpenShift apps using DeploymentConfigs can still retain source-cluster SCC UID/GID/SELinux values and hit the validation failure this PR is intended to prevent.

Suggested fix direction
 	if kind != "Pod" && kind != "Deployment" && kind != "StatefulSet" &&
 		kind != "DaemonSet" && kind != "Job" && kind != "CronJob" &&
-		kind != "ReplicaSet" && kind != "ReplicationController" {
+		kind != "ReplicaSet" && kind != "ReplicationController" &&
+		kind != "DeploymentConfig" {
 		return jsonpatch.Patch{}, nil
 	}

Also append this patch from the existing DeploymentConfig branch in cmd.go so PVC renames are preserved.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if kind != "Pod" && kind != "Deployment" && kind != "StatefulSet" &&
kind != "DaemonSet" && kind != "Job" && kind != "CronJob" &&
kind != "ReplicaSet" && kind != "ReplicationController" {
if kind != "Pod" && kind != "Deployment" && kind != "StatefulSet" &&
kind != "DaemonSet" && kind != "Job" && kind != "CronJob" &&
kind != "ReplicaSet" && kind != "ReplicationController" &&
kind != "DeploymentConfig" {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openshift.go` around lines 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).

Comment thread openshift.go
Comment on lines +437 to +485
// Remove pod-level spec.securityContext
unstructured.RemoveNestedField(modified.Object, "spec", "securityContext")

// For workload controllers, remove spec.template.spec.securityContext
if kind != "Pod" {
if kind == "CronJob" {
// CronJob has spec.jobTemplate.spec.template.spec
unstructured.RemoveNestedField(modified.Object, "spec", "jobTemplate", "spec", "template", "spec", "securityContext")
} else {
// Deployment/StatefulSet/DaemonSet/Job/ReplicaSet/ReplicationController have spec.template.spec
unstructured.RemoveNestedField(modified.Object, "spec", "template", "spec", "securityContext")
}
}

// Helper function to strip container securityContext
stripContainerSecurityContext := func(containersPath ...string) {
containers, found, _ := unstructured.NestedSlice(modified.Object, containersPath...)
if found {
for i, c := range containers {
if container, ok := c.(map[string]interface{}); ok {
delete(container, "securityContext")
containers[i] = container
}
}
unstructured.SetNestedSlice(modified.Object, containers, containersPath...)
}
}

// Determine base path for containers
var basePath []string
if kind == "Pod" {
basePath = []string{"spec"}
} else if kind == "CronJob" {
basePath = []string{"spec", "jobTemplate", "spec", "template", "spec"}
} else {
basePath = []string{"spec", "template", "spec"}
}

// Remove container-level securityContext
containersPath := append(basePath, "containers")
stripContainerSecurityContext(containersPath...)

// Remove initContainers securityContext
initContainersPath := append(basePath, "initContainers")
stripContainerSecurityContext(initContainersPath...)

// Remove ephemeralContainers securityContext (if present)
ephemeralContainersPath := append(basePath, "ephemeralContainers")
stripContainerSecurityContext(ephemeralContainersPath...)
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 | 🟠 Major

Preserve user-authored security settings instead of deleting the whole securityContext.

This removes fields such as privileged, capabilities, allowPrivilegeEscalation, readOnlyRootFilesystem, and seccompProfile along with SCC-injected UID/GID/SELinux values. That can weaken migrated workloads or change their runtime behavior. Prefer deleting only cluster-specific fields like runAsUser, fsGroup, supplementalGroups, and seLinuxOptions, or make the destructive behavior explicit via an option.

Safer direction
-	unstructured.RemoveNestedField(modified.Object, "spec", "securityContext")
+	removeSCCInjectedPodSecurityContextFields(modified.Object, "spec", "securityContext")
...
-					delete(container, "securityContext")
+					removeSCCInjectedContainerSecurityContextFields(container, "securityContext")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openshift.go` around lines 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").

Comment thread openshift.go
return nil, err
}

patch, err := jsonpatch.CreatePatch(originalJSON, modifiedJSON)
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the imported json-patch module and all CreatePatch references.
rg -n 'github\.com/evanphx/json-patch|CreatePatch\s*\(' go.mod go.sum .

Repository: migtools/crane-plugin-openshift

Length of output: 1500


🌐 Web query:

Does github.com/evanphx/json-patch provide a CreatePatch function in the version imported by a Go module?

💡 Result:

No, github.com/evanphx/json-patch does not provide a CreatePatch function in any version imported by a Go module. The library supports creating RFC 7396 JSON Merge Patches using CreateMergePatch(originalJSON, modifiedJSON []byte) ([]byte, error), but there is no equivalent function to automatically generate RFC 6902 JSON Patches (arrays of operations) from two documents. RFC 6902 patches must be manually constructed as JSON arrays of operations and decoded using DecodePatch(buf []byte) (Patch, error).

Citations:


Replace jsonpatch.CreatePatch with correct library API or switch libraries.

github.com/evanphx/json-patch v4.11.0+incompatible does not expose a CreatePatch function. The library provides CreateMergePatch() for RFC 7396 JSON Merge Patches only; RFC 6902 JSON Patches must be manually constructed as operation arrays. Either build patch operations directly or use a diff library that provides JSON Patch generation.

🧰 Tools
🪛 golangci-lint (2.11.4)

[error] 498-498: undefined: jsonpatch.CreatePatch

(typecheck)

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

In `@openshift.go` at 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.

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.

Strip Runtime Security Context from Exported Pods/Deployments

1 participant