fix: give external SSH build repositories an operator identity surface - #32
Merged
Conversation
The schema-7 go-repository-v1 fetch runs in a private empty HOME with an empty PATH and no inherited agent socket, and pointed GIT_SSH at the bare operator ssh binary. Nothing selected an identity or an agent, so every private SSH build repository failed closed with build_repository_source_unavailable. SSHPolicy and exact_ssh_command implemented the reviewed option shape but were unreferenced outside tests. Add the operator surface and wire it to the real fetch: - capture_operator_ssh_credentials resolves --build-ssh-identity, --build-ssh-agent and --build-ssh-known-hosts (and the matching CSK_BUILD_SSH_* variables) once at process entry, next to the existing operator search path capture. --build-ssh-agent with no value adopts SSH_AUTH_SOCK; host keys default to the operator home's known_hosts and are copied into the private root, so a fetch cannot rewrite operator state. Operator paths are resolved rather than refused for being symbolic links, because a live agent socket is conventionally a stable symlink onto a per-session rendezvous point. - acquire_network materializes a private wrapper carrying the argv exact_ssh_command produced and points GIT_SSH_COMMAND at it. Git runs GIT_SSH_COMMAND through its compiled-in shell and appends the host and upload-pack arguments as real argv, so the wrapper observes exactly the invocation it was pinned to on both macOS and Windows; GIT_SSH would need a per-platform stub and a shebang the empty PATH cannot resolve. The wrapper refuses to exec on any other argv. - exact_ssh_command accepts an identity and an agent together. The agent holds the key and the identity pins which agent key is offered; otherwise a populated agent walks every loaded key and the server closes the connection on MaxAuthTries before reaching the one that authenticates. This is the only mode that works for a passphrase-protected key. An SSH source with no operator selection now fails closed with the actionable build_repository_ssh_credential_missing before any launcher, snapshot, or cache artifact is written. GitTool.ssh_wrapper becomes GitTool.ssh: it always held the operator ssh program, and the wrapper is now a distinct manager-generated artifact.
…ssh resolves The assertion only holds once _external_git_tool can freeze an ssh program; without one the run fails earlier on tool discovery, which is an environment gap rather than a regression.
Owner
Author
Coverage gap to flag at review
Windows qualification of the SSH wrapper is a separate task — nothing in this PR claims it is proven. |
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.
Problem
The schema-7
go-repository-v1fetch runs in a private emptyHOMEwith an emptyPATHand no inherited agent socket, and pointedGIT_SSHat the bare operatorsshbinary. No flag, config key, or environment variable selected an SSH identity or agent socket, so every private SSH build repository failed closed withbuild_repository_source_unavailable.SSHPolicy/exact_ssh_commandimplemented the reviewed operator identity/agent option shape but were unreferenced outside tests.Operator surface
--build-ssh-identity PATHCSK_BUILD_SSH_IDENTITY--build-ssh-agent [SOCKET]CSK_BUILD_SSH_AGENT--build-ssh-known-hosts PATHCSK_BUILD_SSH_KNOWN_HOSTSFlags win over the environment.
--build-ssh-agentwith no value (orauto) adopts the operator's liveSSH_AUTH_SOCK. Host keys default to the operator home's.ssh/known_hostsbecause the fetch pinsStrictHostKeyChecking=yes, and are copied into the private root so a fetch cannot rewrite operator state. Credentials are captured once at process entry, next to the existing operator search path capture.Wiring
acquire_networkmaterializes a private wrapper carrying the argvexact_ssh_commandproduced and pointsGIT_SSH_COMMANDat it. Git runsGIT_SSH_COMMANDthrough its compiled-in shell and appends the host and upload-pack arguments as real argv, so the wrapper observes exactly the invocation it was pinned to on both macOS and Windows —GIT_SSHwould need a per-platform executable stub and a shebang the emptyPATHcannot resolve. The wrapper refuses to exec on any other argv. The operator's ownsshonPATHis used unchanged and never has to be shadowed.Two findings this surfaced
Combined identity + agent mode.
exact_ssh_commandpreviously required exactly one of identity/agent. Agent-only setsIdentitiesOnly=no, which offers every loaded key in turn — against a real server this dies withReceived disconnect ... Too many authentication failuresbefore reaching the right key. Identity + agent (IdentitiesOnly=yes+IdentityAgent=<socket>+-i <pubkey>) pins which agent key is offered, and is the only mode that works for a passphrase-protected key.-o SendEnv=GIT_PROTOCOL. Git appends this option before the host wheneverprotocol.version > 0, which would never match the pinned three-element argv._strict_fetch_argsalready setsprotocol.version=0, so the real invocation is exactly(wrapper, host, "git-upload-pack '<path>'")— verified empirically against the live remote.Fail-closed
An SSH source with no operator selection now fails with the actionable
build_repository_ssh_credential_missingbefore any launcher, snapshot, or cache artifact is written.Verification
Live
csk installof a manifest lockinggit@gitlab.wildberries.ru:portals/agentic-infra/cli/sentry-cli.gitat3f6cff5bd76b1b4c95c4ef196f21fd04233e56fe, from a clean-room csk home with a non-editable install and stock/usr/bin/sshonPATH:Credential-free, same manifest, fresh home:
…with no
.agents/bin, noexternal-builds/artifacts, and noexternal-builds/snapshots.tests/test_git_admission_ssh.py(18 tests) drives the real fetch path through a stand-insshprogram: identity mode, agent+identity mode, agent-only mode, credential-free fail-closed, missing known_hosts, wrapper argv refusal, no ambient SSH state in the child environment, endpoint derivation for scp-like andssh://forms, and capture precedence/validation.mypystrict: clean.Note
GitTool.ssh_wrapperis renamed toGitTool.ssh— it always held the operatorsshprogram, and the wrapper is now a distinct manager-generated artifact.