Add Workspace options to restrict the checkout and agent credentials
What type of PR is this?
/kind api
What this PR does / why we need it:
A Workspace with a secretRef always hands the agent container the GitHub token (GITHUB_TOKEN/GH_TOKEN, the mounted token file, and the credential helper written into .git/config), and always checks out the whole repository with its .git directory. There is no way to give an agent part of a private repository without also giving it the means to fetch the rest, and even a depth-1 clone keeps every path reachable through .git.
This adds three optional, backward-compatible fields to Workspace (v1alpha2 only):
spec.clone.paths: check out only the listed repository paths (directories or files). git-clone uses a partial clone (--filter=blob:none --no-checkout) and a non-cone sparse checkout with root-anchored patterns, so root files such asCLAUDE.mdare not checked out unless listed (cone mode always includes them). Works for branch, tag and full-SHA refs, keeping the existing depth and branch behavior. Entries are validated by CEL (relative, no empty/./..segments, no trailing space before or after an optional trailing slash, no*?[\or control characters, 1-64 entries of at most 256 characters) and again when the Job is built.spec.clone.removeGitDir: delete/workspace/repo/.gitas the last step of git-clone. Features that need.gitare rejected rather than silently broken: CEL rejects it together withspec.remotes, and a Task withspec.branchor a Session withspec.initialBranchfails at Job build with a message naming the conflict (JobBuildFailed/StatefulSetBuildFailed).spec.agentCredentials(Token, the default, orNone): withNonethe token reaches only git-clone and branch-setup. The agent container gets noGITHUB_TOKEN,GH_TOKEN,GH_ENTERPRISE_TOKEN,GH_CONFIG_DIR,KELOS_GITHUB_TOKEN_FILEor token volume mount, and the credential helper is not persisted into.git/config. Session restarts on a persistent volume no longer re-persist it either. branch-setup passes the helper inline to its checkout commands, because a partial clone fetches blobs there.
Sessions with a volumeClaimTemplate reject clone.paths and clone.removeGitDir (the Session fails with StatefulSetBuildFailed and a message naming the conflict). Their workspace keeps the checkout it was created with, so a restriction added later could not take effect, and any record of it on that volume could be rewritten by the agent. Sessions without persistent storage clone again on every Pod start, so they always match the current Workspace. agentCredentials: None works with both.
Together they let a Workspace produce a checkout the agent can read but cannot widen.
The new fields are carried through a v1alpha1 round trip in a kelos.dev/v1alpha2-workspace-checkout annotation, the same way AgentConfig and TaskSpawner preserve their v1alpha2-only fields, so an update through v1alpha1 cannot silently turn agentCredentials: None back into Token. The path limits keep that annotation under about 96 KiB in the worst case.
Which issue(s) this PR is related to:
N/A
Special notes for your reviewer:
- #1809 also introduces
Workspace.spec.clone(withbranches). The two structs merge without overlap; whichever PR lands second needs a small rebase ofWorkspaceClone. - WorkerPools have their own pod builder and a long-lived checkout, and they do not implement these fields. Rather than build worker pods that are less restricted than the Workspace asks for, the WorkerPool reconciler rejects such a Workspace right after fetching it, before minting a token or touching the Service or StatefulSet, and records an
UnsupportedWorkspaceWarning event. The reconcile returns without an error so nothing retries with backoff; the Workspace watch re-enqueues the pool when it changes. Existing worker pods are left in place, but Tasks sent to the pool fail instead of being assigned to them, since they were built with the full checkout and token, and while the pool's Workspace is missing its Tasks wait instead of running on those pods. This is documented in the reference. - The token re-mint path for GitHub App secrets only rewrites the derived Secret's data. With
Nonethe agent container never mounts that Secret, so refreshes do not reach it. - The commit-SHA path sets
remote.origin.promisorandremote.origin.partialclonefilterexplicitly before the filtered fetch, asgit clone --filterwould, and runs the sparse checkout beforecheckout --detachso only the listed blobs are fetched. Both checkouts that fetch blobs lazily run with the credential helper. agentCredentialsis named after what it controls in the agent container. It does not limit network access, andpodOverridesentries that reference the token Secret are applied as written; the reference says so.
Testing:
- Unit: table tests for the rendered git-clone args and script (sparse, filter and
.gitremoval only when set; credential helper persisted only forTokenwithoutremoveGitDir), agent env and mounts for omitted/Token/Noneon github.com and GHE, validation of paths and ofremoveGitDirwith a branch or remotes, Session restart handling, WorkerPool rejection and the WorkerPool task-assignment guard.TestSessionPodRestrictedCheckoutNeedsEphemeralStoragecovers the persistent-Session rejection.TestBranchSetupFetchesBlobsWithoutSavedCredentialsserves the repository throughgit http-backendbehind basic auth and checks that branch-setup can check out a branch whose blobs the partial clone has not downloaded, with no helper saved in.git/config.TestWorkspaceCloneScriptProducesRestrictedCheckoutruns the rendered git-clone command with real git against a local repository for branch and SHA refs and checks the files on disk, that.gitis gone, and that no credential helper is left in.git/configunderNone. - Integration (envtest): CEL accepts and rejects the expected Workspaces, a Task with a restricted Workspace gets the expected Job, a Task combining
removeGitDirwith a branch fails with the conflict message, and a restricted Workspace updated through v1alpha1 keeps its restrictions (this spec fails without the conversion change). - e2e: a Task whose Workspace sets all three fields checks from
setupCommand(which runs in the agent container) that only the listed paths exist,.gitis gone and no token is present. make update,make verify,make test,make test-integration,make buildpass locally. e2e was not run locally.
Does this PR introduce a user-facing change?
Workspaces gain three optional fields for restricted checkouts: `spec.clone.paths` checks out only the listed repository paths using a partial clone and sparse checkout, `spec.clone.removeGitDir` deletes `.git` after the clone, and `spec.agentCredentials: None` keeps the `secretRef` GitHub token out of the agent container while still using it to clone. `removeGitDir` cannot be combined with `spec.remotes`, a Task `spec.branch` or a Session `spec.initialBranch`. Sessions with a `volumeClaimTemplate` do not support `clone.paths` or `clone.removeGitDir`, and WorkerPools do not support any of the three fields.