Skip to content

fix(csrapprover): validate CSR requester username matches CommonName for node certificates - #2734

Open
nishantbkl3345-ship-it wants to merge 4 commits into
openyurtio:masterfrom
nishantbkl3345-ship-it:fix/csrapprover-verify-requester-username
Open

fix(csrapprover): validate CSR requester username matches CommonName for node certificates#2734
nishantbkl3345-ship-it wants to merge 4 commits into
openyurtio:masterfrom
nishantbkl3345-ship-it:fix/csrapprover-verify-requester-username

Conversation

@nishantbkl3345-ship-it

Copy link
Copy Markdown

What this PR does / why we need it

Fixes a security vulnerability in csrapprover where node client and serving CSRs were auto-approved without verifying that csr.Spec.Username matched the requested CommonName.

Previously, isYurtHubNodeCert and isYurtTLSServerCert only checked that the CertificateRequest CommonName had prefix system:node:. They never examined csr.Spec.Username. If an authenticated node (system:node:node-a) submitted a CSR requesting a certificate for system:node:node-b, csrapprover auto-approved it — allowing any node to mint client certificates for any other node's identity and perform lateral movement to read secrets/pods.

This PR adds a validation check requiring that when csr.Spec.Username starts with system:node:, it must match x509cr.Subject.CommonName, mirroring upstream Kubernetes node CSR approver behavior.

Which issue(s) this PR fixes

N/A

Special notes for your reviewer

  • Added unit test case yurthub node client CSR with mismatched node username and commonName in pkg/yurtmanager/controller/csrapprover/csr_approver_controller_test.go.
  • Verified all unit tests and go test -race pass cleanly.

APPLE added 4 commits August 3, 2026 00:26
…key mismatch in enhancement mode

In gcPodsWhenRestart, KeyBuildInfo for pods fetched from the apiserver
was missing the Version field. In enhancement mode, this produced keys
like 'pods..core' instead of 'pods.v1.core', causing them to never
match the cached keys from ListResourceKeysOfComponent. As a result,
all cached pods appeared deleted, triggering the safety guard that
skips GC entirely — making pod GC silently non-functional on restart.

Added Version: 'v1' to the KeyBuildInfo and created gc_test.go with
unit tests that prove the mismatch and verify the fix.
…bjWithObjs to prevent overflow

In completeListObjWithObjs, resourceVersion was parsed using strconv.Atoi into a signed Go int, ignoring parsing errors. On 32-bit platforms (such as ARM32 linux/arm/v7 edge devices), signed int is 32-bit (max 2147483647). Any resourceVersion above 2^31-1 caused strconv.Atoi to return ErrRange and clamp to 2147483647.

This produced corrupted resourceVersion strings on synthesized list responses, causing relist storms and 410 Gone errors on edge devices.

Switched to strconv.ParseUint(rvStr, 10, 64) with uint64 listRv and added unit tests covering 64-bit uint64 resourceVersions and edge cases.
@nishantbkl3345-ship-it
nishantbkl3345-ship-it requested a review from a team as a code owner August 5, 2026 11:40
Copilot AI lite review requested due to automatic review settings August 5, 2026 11:40
@sonarqubecloud

sonarqubecloud Bot commented Aug 5, 2026

Copy link
Copy Markdown

Copilot AI 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.

Pull request overview

This PR hardens several controller/cache paths, primarily aiming to close a privilege-escalation risk in csrapprover by validating node CSR requester identity, while also addressing correctness issues in hub leader selection and yurthub cache/GC behaviors.

Changes:

  • csrapprover: add a Username vs CommonName validation gate for node client/serving CSRs.
  • hubleader: avoid panics when LeaderNodeLabelSelector is nil under mark election strategy, and add a regression test.
  • yurthub: fix pod GC key construction to include API version; improve list resourceVersion aggregation to handle large RVs safely and add unit tests.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pkg/yurtmanager/controller/hubleader/hubleader_controller.go Avoid overwriting matchLabels with a potentially nil selector map.
pkg/yurtmanager/controller/hubleader/hubleader_controller_test.go Add a test case for mark strategy with nil LeaderNodeLabelSelector.
pkg/yurtmanager/controller/csrapprover/csr_approver_controller.go Add Username/CommonName validation in node cert recognizers.
pkg/yurtmanager/controller/csrapprover/csr_approver_controller_test.go Add unit test for mismatched node Username vs CommonName CSR.
pkg/yurthub/gc/gc.go Include Version: "v1" when building pod storage keys during restart GC.
pkg/yurthub/gc/gc_test.go New tests intended to validate pod GC key consistency.
pkg/yurthub/cachemanager/cache_manager.go Parse list resourceVersions as uint64 and handle invalid/empty RVs safely.
pkg/yurthub/cachemanager/cache_manager_test.go Add tests for completeListObjWithObjs RV aggregation behavior.
Suppressed comments (2)

pkg/yurtmanager/controller/csrapprover/csr_approver_controller.go:369

  • This check only enforces Username==CommonName when Username already has the "system:node:" prefix. If Username is non-node (or empty), a crafted CSR with CN "system:node:..." and the expected orgs can still be auto-approved, because approval is based largely on CSR contents. Tighten this recognizer to require csr.Spec.Username to be a node identity and to exactly match x509cr.Subject.CommonName; otherwise return false.
	if strings.HasPrefix(csr.Spec.Username, "system:node:") && csr.Spec.Username != x509cr.Subject.CommonName {
		return false
	}

pkg/yurthub/gc/gc_test.go:153

  • In this test, errors from KeyFunc are ignored and the “buggy lookup should MISS” path only logs instead of asserting, so the test can pass without actually validating the expected failure mode. Consider failing when the buggy lookup finds the cached key in enhancement mode, and handle KeyFunc errors explicitly.
	buggyKey, _ := store.KeyFunc(storage.KeyBuildInfo{
		Component: "kubelet",
		Namespace: "kube-system",
		Name:      "coredns-abc123",
		Resources: "pods",
	})
	currentPodKeysBuggy[buggyKey] = struct{}{}

	// FIXED: with Version
	currentPodKeysFixed := make(map[storage.Key]struct{})
	fixedKey, _ := store.KeyFunc(storage.KeyBuildInfo{
		Component: "kubelet",
		Namespace: "kube-system",
		Name:      "coredns-abc123",
		Resources: "pods",
		Version:   "v1",
	})
	currentPodKeysFixed[fixedKey] = struct{}{}

	// Check buggy lookup — this should MISS (the bug)
	if _, found := currentPodKeysBuggy[cachedKey]; found {
		t.Logf("Buggy code found the cached key (storage in legacy mode)")
	} else {
		t.Logf("CONFIRMED BUG: Buggy code did NOT find cached key %q in currentPodKeys — pod would be incorrectly marked for GC", cachedKey.Key())
	}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/yurthub/gc/gc_test.go
Comment on lines +87 to +92
// The buggy key should NOT match the list key (proving the bug exists)
if buggyKey.Key() == listKey.Key() {
t.Logf("Keys match even without Version — storage may be in legacy mode, bug is not applicable in this mode")
} else {
t.Logf("CONFIRMED: Buggy key %q != list key %q — pod GC key mismatch exists in enhancement mode", buggyKey.Key(), listKey.Key())
}
Comment on lines +336 to +338
if strings.HasPrefix(csr.Spec.Username, "system:node:") && csr.Spec.Username != x509cr.Subject.CommonName {
return false
}
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