fix: authorize the runner ServiceAccount override at admission - #69
Open
amaydixit11 wants to merge 2 commits into
Open
fix: authorize the runner ServiceAccount override at admission#69amaydixit11 wants to merge 2 commits into
amaydixit11 wants to merge 2 commits into
Conversation
Closes nirmata#26. Scoping the runner Job to a least-privilege ClusterRole left one way around it. A WorkflowRun can set spec.execution.job.serviceAccountName to any account in its namespace, buildWorkflowRunnerJob honours it, and ensureRunnerAccess skips runs that name one. The Job is then launched with that account's token, so anyone who can create a WorkflowRun can pick a privileged ServiceAccount and get its credentials. The WorkflowRun webhook now issues a SubjectAccessReview for the submitter, asking for use on the named ServiceAccount in the run's namespace, and rejects the run when the answer is no. Operators grant it by naming the accounts a subject may borrow: rules: - apiGroups: [""] resources: ["serviceaccounts"] resourceNames: ["build-runner"] verbs: ["use"] The issue expected this to need a raw admission handler, since the validator does not receive UserInfo. It does not: controller-runtime puts the AdmissionRequest in the context before calling the validator (admission/validator_custom.go), so admission.RequestFromContext reaches the user without changing the handler shape. Checked on update as well as create, because the field is mutable and authorizing only creates would let a run be submitted clean and then patched. Everything that stops the check reaching a verdict rejects: a review that errors, a request with no user, a validator with no authorizer. subjectaccessreviews create moves from the MCP fragment, which is enabled only with the MCP server, into the core controller role, since the webhook needs it whenever it runs. This is a breaking change for anyone already setting serviceAccountName: those runs are rejected until someone grants use on that account. Verified against a real API server that the grant works as intended: with a Role granting use on serviceaccounts/build-runner, the bound subject gets yes for that account, no for another, and an unbound subject gets no. Signed-off-by: Amay Dixit <amaydixit11@gmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 197c09135c
ℹ️ 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".
…e it is used Review caught that this rejected every cron run. The scheduler copies spec.execution from the Workflow into each run it creates and creates it as the controller, so reviewing the submitter asked whether the controller may use the account. The chart grants no use verb, so a default install would reject them all. Authorizing the submitter of a run is the wrong place on its own. The account is chosen in two places, and each is now authorized where the choice is made: - Workflow.spec.execution.job.serviceAccountName, checked when the Workflow is admitted, against its author. - WorkflowRun.spec.execution.job.serviceAccountName, checked when the run is admitted, against whoever submitted it. A run whose account matches the one its Workflow declares is inheriting rather than choosing, so it is not reviewed again: the declaration was authorized at Workflow admission and every run of that Workflow uses that account anyway. Same namespace only, since the account is used in the run's namespace and a Workflow elsewhere was authorized against its own. A run naming an account its Workflow does not declare is still reviewed. Signed-off-by: Amay Dixit <amaydixit11@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #26.
A WorkflowRun can set
spec.execution.job.serviceAccountNameto any account in its namespace,buildWorkflowRunnerJobhonours it, andensureRunnerAccessskips runs that name one. The Job then runs with that account's token — so anyone who can create a WorkflowRun can pick a privileged ServiceAccount and get its credentials, which is the escalation scoping the runner to its own role set out to close.The account is chosen in two places, and each is now authorized where the choice is made:
Workflow.spec.execution.job.serviceAccountName— against the Workflow author, at Workflow admission.WorkflowRun.spec.execution.job.serviceAccountName— against whoever submitted the run.Either way the check is a
SubjectAccessReviewforuseon that ServiceAccount in that namespace, and the object is rejected if the answer is no:Operators grant it by naming the accounts a subject may borrow:
Smaller than the issue expected
The issue says the validator would have to become a raw admission handler, since it does not receive
UserInfo. It does not — controller-runtime puts theAdmissionRequestinto the context before calling the validator (admission/validator_custom.go:104), soadmission.RequestFromContextreaches the user without changing the handler shape. The change is one new file plus a field on the validator.A run whose account matches the one its Workflow declares is inheriting rather than choosing, so it is not reviewed again — this is what keeps cron runs working, since the scheduler copies
spec.executioninto every run it creates and creates it as the controller. Same namespace only: the account is used in the run's namespace, and a Workflow elsewhere was authorized against its own. A run naming an account its Workflow does not declare is still reviewed.Two other things worth noting:
subjectaccessreviewscreate moves out of the MCP fragment — which only renders when the MCP server is enabled — into the core controller role, since the webhook needs it whenever it runs.Breaking
Anyone already setting
serviceAccountNamehas their runs rejected until someone grantsuseon that account. That is the point of the fix, but it is a real migration step and worth a release note. Say if you would rather have a flag for a transition period; I left one out deliberately, since a switch that turns off an authorization check tends to stay on.Verified
Against a real API server, that the grant behaves as designed:
Unit tests cover the review contents (user, UID, groups, extra, and the resource attributes), the allow and deny paths on both objects, update, no-review-when-unset, the three fail-closed cases, and inheritance: admitted with no review issued, still reviewed when the account differs, and not honoured across namespaces.
make build,make verify-codegen,go test,helm lint, chart RBAC tests pass.