Skip to content

fix(security): supply the C-0211 baseline context to descheduler - #3733

Merged
devantler merged 4 commits into
mainfrom
claude/descheduler-baseline-context-3239
Sep 12, 2026
Merged

devantler merged 4 commits into
mainfrom
claude/descheduler-baseline-context-3239

Conversation

@devantler

Copy link
Copy Markdown
Contributor

🤖 Generated by the Agentic Engineer

Motivation

Kubescape scores the C-0211 security-context controls against each workload's stored controller template. kube-system is excluded from the cluster-wide security-context mutation because the workloads there need elevated privileges — but two of the controls it suppresses, volume-ownership policy and the SELinux options object, are not privilege settings. The descheduler has been failing both for no reason anyone chose.

Description

Supplies the two fields through the descheduler chart's own values, which is the source-owned route the baseline rollout doc names for Helm-templated workloads. Both are inert for this workload: it has no group-ownership setting and its only volume is a configuration map, so the ownership policy governs nothing, and the empty SELinux object satisfies the control while leaving the runtime's own label selection untouched.

Rendering the real pinned chart shows the two additions are the entire change — every existing hardening setting on the container is preserved and the rendered resource list is identical. Removing the two values restores the previous output exactly, so the rollback is a one-line revert.

This is one workload of the remaining population; the issue stays open for the rest.

Part of #3239

🤖 Generated with Claude Code

kube-system is excluded from the add-security-context mutation, so the
descheduler's stored controller template — the surface Kubescape scores for
C-0211 — carries neither fsGroupChangePolicy nor container seLinuxOptions.
Supply both through the chart's own values, the source-owned route the
baseline rollout doc names for Helm-templated workloads.

Both fields are inert for this workload and neither relaxes the chart's
hardening. The pod has no fsGroup and its only volume is a configMap, so the
ownership policy governs nothing; the empty seLinuxOptions object satisfies
the control while leaving SELinux type and MCS category selection to the
runtime. Rendering the real pinned chart 0.36.0 shows the two additions as
the entire delta: allowPrivilegeEscalation, the dropped ALL capabilities,
privileged, readOnlyRootFilesystem, runAsNonRoot and runAsUser 1000 are all
preserved verbatim, and the rendered resource inventory is identical.

Part of #3239

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

🔐 Validate Production Authorization — expected, and the conservation evidence for re-approval

A HelmRelease is a controller-RBAC emitter, so the aggregate rendered-authorization fingerprint moves
on a pure values change even though nothing is granted. The constant needs re-approval in this PR.
Recording the evidence first; the digest itself is taken from CI, because the local renderer
(kubectl v1.36.1 / kustomize v5.8.1) is unapproved and the validator correctly fails closed rather
than producing a digest from it.

Render set — all five selected overlays, rendered from both trees under the same local renderer so
the delta is attributable to this change rather than to a renderer difference:

Overlay Result
providers/hetzner/apps byte-identical
providers/hetzner/infrastructure byte-identical
providers/hetzner/infrastructure/controllers +112 bytes
clusters/prod/bootstrap byte-identical
clusters/prod byte-identical

Complete hunk list — one hunk, four added lines, inside the descheduler HelmRelease's values:
podSecurityContext.fsGroupChangePolicy, securityContext.seLinuxOptions. No other hunk anywhere.

Grant-bearing membership, both directions, by two independent extractions:

  • structural selection of ClusterRole / Role / ClusterRoleBinding / RoleBinding /
    ServiceAccount identities — 86 either side, identical;
  • digest over the ClusterRole/Role rule bodies — 6a52babc727cc081… identical either side.

A first pass of the structural extraction returned 171 per side; 85 of those were document-separator
artifacts rather than identities. Both sides carried them symmetrically so the comparison held, but
the count was wrong, so the filtered form above is what is reported.

Negative control — injecting one synthetic ClusterRole into the green surface makes both
extractions report the difference, so "identical" here is a finding rather than a blind read.

Document census across the whole surface is unchanged: 560 documents over 63 kinds, identical.

A HelmRelease is a controller-RBAC emitter, so supplying the descheduler's
baseline security context through the chart's values moves the aggregate
rendered-authorization fingerprint even though nothing is granted.

The conservation evidence is recorded beside the constant: all five
authorization roots rendered from both trees under one renderer, 86
grant-bearing identities identical in both directions, identical ClusterRole
and Role rule bodies, 560 documents over 63 kinds unchanged, and a complete
hunk list of one hunk of four added lines inside the descheduler HelmRelease's
values. A synthetic ClusterRole injected into the rendered surface makes both
independent extractions report the difference, so that identity result is a
finding rather than a blind read.

The digest itself is taken from CI, because this host's renderer is not the
approved one and the validator fails closed rather than producing a local
value. The previous aggregate is preserved in the comment history.

Part of #3239

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

All required checks are green at this head, including 🔐 Validate Production Authorization after the fingerprint re-approval. Requesting review.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

@devantler I will review the changes and the authorization validation update.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Warning

Review limit reached

Next included review available in 26 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Advanced

Run ID: a626276b-5438-4689-b87f-b3d4e61a802d

📥 Commits

Reviewing files that changed from the base of the PR and between a067e2d and 87ecc80.

📒 Files selected for processing (1)
  • scripts/validate-eks-ci-role-policy/main.go

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 96e6058f-c93e-4c0a-ad01-cf2c5c51fa11

📥 Commits

Reviewing files that changed from the base of the PR and between 0fd0ee0 and a067e2d.

📒 Files selected for processing (2)
  • k8s/providers/hetzner/infrastructure/controllers/descheduler/helm-release.yaml
  • scripts/validate-eks-ci-role-policy/main.go

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

📜 Recent review details
🔇 Additional comments (2)
k8s/providers/hetzner/infrastructure/controllers/descheduler/helm-release.yaml (1)

34-44: LGTM!

scripts/validate-eks-ci-role-policy/main.go (1)

1897-1937: LGTM!


📝 Walkthrough

Walkthrough

The descheduler HelmRelease now sets fsGroupChangePolicy to OnRootMismatch and adds an empty seLinuxOptions object. The expected rendered policy fingerprint is updated to match the resulting rendered surface. A comment records the baseline context, preserved document identities, render changes, CI source, and previous fingerprint.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to a067e

The security-context values and corresponding rendered-surface fingerprint update are ready to merge.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the security change for the descheduler and the C-0211 baseline context.
Description check ✅ Passed The description directly explains the motivation, implementation, scope, and validation for the descheduler security-context changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1…
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.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch claude/descheduler-baseline-context-3239

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.

devantler and others added 2 commits September 12, 2026 16:08
…seline-context-3239

# Conflicts:
#	scripts/validate-eks-ci-role-policy/main.go
Merging main brought in the Kubescape 1.40.4 repair (#3731), which had already
moved this aggregate to bc95f7ee. Stacking the descheduler values change on top
moves it again, so the constant is re-read from CI's own failure on run
34698439645 under the approved SHA256-verified toolchain.

Both predecessors stay recorded in the comment history above. The conservation
evidence for the descheduler change is unchanged by the merge: it compares two
trees under one renderer, so it does not depend on which aggregate precedes it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Head advanced to 87ecc800: origin/main merged in (the Kubescape 1.40.4 repair had moved the
authorization fingerprint) and the aggregate re-approved from CI's own run 34698439645. All 26
checks are green at this head, including 🔐 Validate Production Authorization. The earlier
CodeRabbit pass at a067e2d4 is stale, so requesting a fresh one.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

@devantler I will review the updated changes at 87ecc800.

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

CodeRabbit was requested first at this head and refused: its reply (5646556405) carries the
acknowledgement and ⚠️ Action not completed — Review rate limited in the same body, and the head
status moved to Review rate limited at 14:37:56Z, after this round's request marker at 14:37:32Z.
No retry window was given, so recording cr:no-gate@87ecc800 and advancing one lane rather than
waiting on it.

All 26 checks are green at 87ecc800, which merges origin/main and re-approves the rendered
authorization fingerprint from CI run 34698439645.

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 12, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-12T14:42:08.513733Z 87ecc80 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: 87ecc800e5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Readiness at 87ecc800

Programmatically tested — all 26 checks green, including 🔐 Validate Production Authorization after the fingerprint was re-read from CI run 34698439645. Hygiene pentad clear:
green checks, 0 review threads (0 of 0, paginated read verified complete), no non-thread
review findings, mergeStateStatus: CLEAN.

Reviewed — Codex green at this exact head: "Didn't find any major issues",
**Reviewed commit:** 87ecc800e5, matching headRefOid. Zero review objects and no
## Review finding comment
, checked explicitly because a Codex green can otherwise coexist
with an open P2. CodeRabbit was requested first per lane priority and refused
(Review rate limited, recorded cr:no-gate@87ecc800).

Exercised and evaluated — this is a rendered-manifest change, so the render is the surface
that actually shows the effect, and it was rendered rather than reasoned about:

  • the delta against the pinned chart is exactly the two fields —
    podSecurityContext.fsGroupChangePolicy: OnRootMismatch and securityContext.seLinuxOptions: {};
  • all seven container hardening fields are preserved verbatim, and the resource inventory is
    identical either side (5 documents both ways) — securityContext in values merges rather
    than replaces, proven by render, since the opposite would have been a silent hardening
    regression;
  • the authorization-surface conservation proof is unchanged by the merge (it compares two trees
    under one renderer): 86 grant-bearing identities identical in both directions, RBAC
    rule-body digest identical, and the negative control fired on both extractions.

Both fields are inert for this workload — the pod has no fsGroup and its only volume is a
configMap — so this supplies the C-0211 baseline context without changing what the descheduler
can do.

Post-merge check to run once deployed: confirm Kubescape now scores C-0211 as passing for the
descheduler's stored controller template. Shipping is the start of that loop, not proof of it.

@devantler
devantler marked this pull request as ready for review September 12, 2026 14:43
@devantler
devantler added this pull request to the merge queue Sep 12, 2026
Merged via the queue into main with commit b2f2f59 Sep 12, 2026
29 checks passed
@devantler
devantler deleted the claude/descheduler-baseline-context-3239 branch September 12, 2026 15:07
@github-project-automation github-project-automation Bot moved this from 🫴 Ready to ✅ Done in 🌊 Project Board Sep 12, 2026
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Post-merge verification: C-0211 satisfied on the deployed descheduler

This is the verification I committed to when this merged. Checked against the live cluster rather
than inferred from the merged diff.

The fix is enacted. The descheduler's stored controller template now carries the field:

kube-system/deployment/descheduler → containers[descheduler].securityContext:
  allowPrivilegeEscalation: false, capabilities.drop: [ALL], privileged: false,
  readOnlyRootFilesystem: true, runAsNonRoot: true, runAsUser: 1000,
  seLinuxOptions: {}

seLinuxOptions: {} is what C-0211 asks for — the control accepts any non-absent
seLinuxOptions, as decided on #3465. So the stored template satisfies it.

One thing worth recording, because it looks like a gap and is not

There is no workloadconfigurationscan object for this workload, and that is by design, not a
broken scanner: the Kubescape operator's excludeNamespaces lists
kubescape,kube-system,kube-public,kube-node-lease,kubeconfig,gmp-system,gmp-public. So
kube-system workloads are never config-scanned, and a future run looking for a descheduler scan
row will correctly find nothing.

The scanner itself is alive (kubescape-scheduler completed today at 09:12:18Z), so this absence is
an exclusion, not the silent-dead-scanner failure mode. Verification for anything in kube-system
has to read the live controller template directly, as done here.

Carry-forward discharged; no follow-up needed.

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

Labels

None yet

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

1 participant