Skip to content

OCPBUGS-85280: encryption: detect orphaned namespace objects during key migration - #2364

Open
sanchezl wants to merge 1 commit into
openshift:masterfrom
sanchezl:fix-encryption-migration-orphaned-namespace
Open

OCPBUGS-85280: encryption: detect orphaned namespace objects during key migration#2364
sanchezl wants to merge 1 commit into
openshift:masterfrom
sanchezl:fix-encryption-migration-orphaned-namespace

Conversation

@sanchezl

@sanchezl sanchezl commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

When a namespace is fully deleted from etcd but child objects remain (orphans), the NamespaceLifecycle admission plugin rejects Update requests with NotFound. The in-process encryption key migrator previously treated all NotFound errors as "object deleted, safe to skip," causing migration to report success. The encryption state machine then pruned the old encryption key, making the orphaned objects permanently undecryptable and crashing the kube-apiserver with no matching key was found for the provided AES transformer.

This change adds a GET probe after NotFound on Update. GET bypasses admission and reads directly from etcd storage. If the object still exists, it is an orphan that cannot be re-encrypted. The migration is failed with an actionable error message pointing to KCS 6769801, preventing the state machine from pruning the old encryption key.

Details

  • NamespaceLifecycle allows Updates in Terminating namespaces but rejects them with NotFound when the namespace is fully deleted from etcd
  • GET requests bypass admission entirely and read directly from the storage layer
  • Delete requests are also allowed by NamespaceLifecycle regardless of namespace state
  • The upstream kube-storage-version-migrator and the in-tree KCM migrator (KEP-4192) have the same NotFound handling pattern
  • Kubernetes issue #49027 (open since 2017) documents that namespace deletion can leave orphaned objects in etcd

Related

Test plan

  • New test: TestInProcessMigratorOrphanedNamespace/orphaned_object_in_deleted_namespace_blocks_migration verifies migration fails when Update returns NotFound but GET succeeds (orphaned object)
  • New test: TestInProcessMigratorOrphanedNamespace/genuinely_deleted_object_is_skipped verifies existing behavior is preserved when both Update and GET return NotFound (object truly deleted)
  • Existing TestInProcessMigrator passes unchanged (no regression)
  • Full go test ./pkg/operator/encryption/... passes (14 packages, 0 failures)

/verified by "TestInProcessMigratorOrphanedNamespace"

When a namespace is fully deleted from etcd but child objects remain,
the NamespaceLifecycle admission plugin rejects Update requests with
NotFound. The in-process migrator previously treated all NotFound
errors as "object deleted, safe to skip," causing migration to report
success. The encryption state machine then pruned the old encryption
key, making the orphaned objects permanently undecryptable and
crashing the kube-apiserver.

After receiving NotFound on Update, the migrator now issues a GET
(which bypasses admission and reads directly from etcd storage) to
check whether the object still exists. If it does, the object is an
orphan in a deleted namespace that cannot be re-encrypted. The
migration is failed with an actionable error message, preventing the
state machine from pruning the old encryption key.
@openshift-ci-robot openshift-ci-robot added jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. labels Jul 15, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@sanchezl: This pull request references Jira Issue OCPBUGS-85280, which is invalid:

  • expected the bug to target the "5.0.0" version, but no target version was set

Comment /jira refresh to re-evaluate validity if changes to the Jira bug are made, or edit the title of this pull request to link to a different bug.

The bug has been updated to refer to the pull request using the external bug tracker.

Details

In response to this:

Summary

When a namespace is fully deleted from etcd but child objects remain (orphans), the NamespaceLifecycle admission plugin rejects Update requests with NotFound. The in-process encryption key migrator previously treated all NotFound errors as "object deleted, safe to skip," causing migration to report success. The encryption state machine then pruned the old encryption key, making the orphaned objects permanently undecryptable and crashing the kube-apiserver with no matching key was found for the provided AES transformer.

This change adds a GET probe after NotFound on Update. GET bypasses admission and reads directly from etcd storage. If the object still exists, it is an orphan that cannot be re-encrypted. The migration is failed with an actionable error message pointing to KCS 6769801, preventing the state machine from pruning the old encryption key.

Details

  • NamespaceLifecycle allows Updates in Terminating namespaces but rejects them with NotFound when the namespace is fully deleted from etcd
  • GET requests bypass admission entirely and read directly from the storage layer
  • Delete requests are also allowed by NamespaceLifecycle regardless of namespace state
  • The upstream kube-storage-version-migrator and the in-tree KCM migrator (KEP-4192) have the same NotFound handling pattern
  • Kubernetes issue #49027 (open since 2017) documents that namespace deletion can leave orphaned objects in etcd

Test plan

  • New test: TestInProcessMigratorOrphanedNamespace/orphaned_object_in_deleted_namespace_blocks_migration verifies migration fails when Update returns NotFound but GET succeeds (orphaned object)
  • New test: TestInProcessMigratorOrphanedNamespace/genuinely_deleted_object_is_skipped verifies existing behavior is preserved when both Update and GET return NotFound (object truly deleted)
  • Existing TestInProcessMigrator passes unchanged (no regression)
  • Full go test ./pkg/operator/encryption/... passes (14 packages, 0 failures)

/verified by "TestInProcessMigratorOrphanedNamespace"

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Walkthrough

The in-process encryption migrator now verifies objects after NotFound update failures, blocking migration for retained orphaned objects while ignoring confirmed deletions. Tests cover both deleted-namespace outcomes.

Changes

Orphaned namespace migration handling

Layer / File(s) Summary
Verify NotFound update failures
pkg/operator/encryption/controllers/migrators/inprocess.go, pkg/operator/encryption/controllers/migrators/inprocess_test.go
runMigration performs a follow-up Get after NotFound updates, returning an error when the object remains and continuing when it is confirmed absent. Table-driven tests simulate both cases.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant InProcessMigrator
  participant DynamicClient
  participant ObjectStorage
  InProcessMigrator->>DynamicClient: Update encrypted object
  DynamicClient-->>InProcessMigrator: NotFound error
  InProcessMigrator->>DynamicClient: Get encrypted object
  DynamicClient->>ObjectStorage: Read object
  ObjectStorage-->>DynamicClient: Object or NotFound
  DynamicClient-->>InProcessMigrator: Existence result
  InProcessMigrator-->>InProcessMigrator: Block migration or ignore error
Loading
🚥 Pre-merge checks | ✅ 15
✅ Passed checks (15 passed)
Check name Status Explanation
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 Added subtest titles are static and descriptive; no dynamic values or generated identifiers appear in any test names.
Test Structure And Quality ✅ Passed PASS: the new table-driven subtests each cover one behavior, use a 30s PollImmediate timeout, and follow existing package test patterns; no cleanup issues.
Microshift Test Compatibility ✅ Passed The new test is a plain Go unit test, not Ginkgo/e2e, and it uses only core K8s APIs (ConfigMap, fake clients); no MicroShift-unsupported OpenShift APIs found.
Single Node Openshift (Sno) Test Compatibility ✅ Passed The added test is a plain Go unit test using t.Run, not Ginkgo e2e, and it contains no SNO/multi-node assumptions or topology checks.
Topology-Aware Scheduling Compatibility ✅ Passed PASS: The patch only changes encryption migration logic and tests; no replicas, node selectors, affinities, tolerations, PDBs, or topology assumptions were added.
Ote Binary Stdout Contract ✅ Passed No stdout writes or process-level entrypoints were added; the changes are confined to library/test code, and only t.Logf/klog usage appears.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No Ginkgo e2e tests were added; the new test is a unit test using fake clients, with no IPv4-only assumptions or external connectivity.
No-Weak-Crypto ✅ Passed Changed files only adjust migrator NotFound handling and tests; no MD5/SHA1/DES/RC4/3DES/Blowfish/ECB, custom crypto, or secret comparisons found.
Container-Privileges ✅ Passed PR only changes Go files; no container/K8s manifests were modified and no privilege/security-context settings appear in touched files.
No-Sensitive-Data-In-Logs ✅ Passed No passwords/tokens/PII/customer data are logged; the new messages only include resource names/namespace and a public remediation URL.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: detecting orphaned namespace objects during encryption key migration.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@openshift-ci
openshift-ci Bot requested review from ardaguclu and p0lyn0mial July 15, 2026 00:59
@openshift-ci

openshift-ci Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: sanchezl
Once this PR has been reviewed and has the lgtm label, please assign dgrisonnet 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.

🧹 Nitpick comments (1)
pkg/operator/encryption/controllers/migrators/inprocess_test.go (1)

156-171: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Strengthen assertions to verify the specific branch, and add a case for the "verification failed" path.

Both subtests only check result == nil vs result != nil (lines 234-242), not the actual error content. This means the "orphaned object" case would also pass if some unrelated bug made Get itself error out (hitting the "failed to verify existence" branch in inprocess.go at lines 154-157) rather than the intended "object confirmed to exist" branch (lines 158-161) — the test can't distinguish which of the two error paths was actually exercised. Consider asserting on error content (e.g., strings.Contains for the KCS reference or "blocking migration") and adding a third case where the Get reactor itself returns a non-NotFound error, to cover that branch too.

♻️ Example assertion tightening
 				if tc.expectError {
 					if result == nil {
 						return false, fmt.Errorf("expected migration to fail for orphaned namespace object, but it succeeded")
 					}
+					if !strings.Contains(result.Error(), "6769801") {
+						return false, fmt.Errorf("expected orphaned-namespace error, got: %v", result)
+					}
 					t.Logf("migration correctly failed: %v", result)

Also applies to: 226-244

🤖 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 `@pkg/operator/encryption/controllers/migrators/inprocess_test.go` around lines
156 - 171, Strengthen the assertions in the migration test cases around the
existing result checks by verifying the expected error content for the
orphaned-object branch, such as the KCS reference or “blocking migration,” while
preserving the successful deleted-object case. Extend the test table and Get
reactor setup to include a non-NotFound Get error, then assert that this case
exercises the verification-failed path with its specific error.
🤖 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.

Nitpick comments:
In `@pkg/operator/encryption/controllers/migrators/inprocess_test.go`:
- Around line 156-171: Strengthen the assertions in the migration test cases
around the existing result checks by verifying the expected error content for
the orphaned-object branch, such as the KCS reference or “blocking migration,”
while preserving the successful deleted-object case. Extend the test table and
Get reactor setup to include a non-NotFound Get error, then assert that this
case exercises the verification-failed path with its specific error.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Enterprise

Run ID: bf45d2f0-1a84-483a-8c15-f097774774fc

📥 Commits

Reviewing files that changed from the base of the PR and between 795ac1a and 16d4c3f.

📒 Files selected for processing (2)
  • pkg/operator/encryption/controllers/migrators/inprocess.go
  • pkg/operator/encryption/controllers/migrators/inprocess_test.go

@openshift-ci

openshift-ci Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

@sanchezl: all tests passed!

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@ardaguclu

Copy link
Copy Markdown
Member

This looks good to me but I think it is better that @p0lyn0mial also looks at this PR

@sanchezl

Copy link
Copy Markdown
Contributor Author

/jira refresh

@openshift-ci-robot openshift-ci-robot added jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. and removed jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. labels Jul 20, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@sanchezl: This pull request references Jira Issue OCPBUGS-85280, which is valid. The bug has been moved to the POST state.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (5.0.0) matches configured target version for branch (5.0.0)
  • bug is in the state New, which is one of the valid states (NEW, ASSIGNED, POST)
Details

In response to this:

/jira refresh

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci-robot

Copy link
Copy Markdown

@sanchezl: This pull request references Jira Issue OCPBUGS-85280, which is valid.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (5.0.0) matches configured target version for branch (5.0.0)
  • bug is in the state POST, which is one of the valid states (NEW, ASSIGNED, POST)
Details

In response to this:

Summary

When a namespace is fully deleted from etcd but child objects remain (orphans), the NamespaceLifecycle admission plugin rejects Update requests with NotFound. The in-process encryption key migrator previously treated all NotFound errors as "object deleted, safe to skip," causing migration to report success. The encryption state machine then pruned the old encryption key, making the orphaned objects permanently undecryptable and crashing the kube-apiserver with no matching key was found for the provided AES transformer.

This change adds a GET probe after NotFound on Update. GET bypasses admission and reads directly from etcd storage. If the object still exists, it is an orphan that cannot be re-encrypted. The migration is failed with an actionable error message pointing to KCS 6769801, preventing the state machine from pruning the old encryption key.

Details

  • NamespaceLifecycle allows Updates in Terminating namespaces but rejects them with NotFound when the namespace is fully deleted from etcd
  • GET requests bypass admission entirely and read directly from the storage layer
  • Delete requests are also allowed by NamespaceLifecycle regardless of namespace state
  • The upstream kube-storage-version-migrator and the in-tree KCM migrator (KEP-4192) have the same NotFound handling pattern
  • Kubernetes issue #49027 (open since 2017) documents that namespace deletion can leave orphaned objects in etcd

Related

Test plan

  • New test: TestInProcessMigratorOrphanedNamespace/orphaned_object_in_deleted_namespace_blocks_migration verifies migration fails when Update returns NotFound but GET succeeds (orphaned object)
  • New test: TestInProcessMigratorOrphanedNamespace/genuinely_deleted_object_is_skipped verifies existing behavior is preserved when both Update and GET return NotFound (object truly deleted)
  • Existing TestInProcessMigrator passes unchanged (no regression)
  • Full go test ./pkg/operator/encryption/... passes (14 packages, 0 failures)

/verified by "TestInProcessMigratorOrphanedNamespace"

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

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

Labels

jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants