fix(git-import): ensure ssh key is used for fetch, submodule, and lfs operations (#8933)

This commit is contained in:
Andras Bacsai 2026-03-12 13:35:26 +01:00 committed by GitHub
commit 4f1fe824e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 73 additions and 9 deletions

View file

@ -1087,12 +1087,16 @@ public function dirOnServer()
return application_configuration_dir()."/{$this->uuid}"; return application_configuration_dir()."/{$this->uuid}";
} }
public function setGitImportSettings(string $deployment_uuid, string $git_clone_command, bool $public = false, ?string $commit = null) public function setGitImportSettings(string $deployment_uuid, string $git_clone_command, bool $public = false, ?string $commit = null, ?string $git_ssh_command = null)
{ {
$baseDir = $this->generateBaseDir($deployment_uuid); $baseDir = $this->generateBaseDir($deployment_uuid);
$escapedBaseDir = escapeshellarg($baseDir); $escapedBaseDir = escapeshellarg($baseDir);
$isShallowCloneEnabled = $this->settings?->is_git_shallow_clone_enabled ?? false; $isShallowCloneEnabled = $this->settings?->is_git_shallow_clone_enabled ?? false;
// Use the full GIT_SSH_COMMAND (including -i for SSH key and port options) when provided,
// so that git fetch, submodule update, and lfs pull can authenticate the same way as git clone.
$sshCommand = $git_ssh_command ?? 'GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"';
// Use the explicitly passed commit (e.g. from rollback), falling back to the application's git_commit_sha. // Use the explicitly passed commit (e.g. from rollback), falling back to the application's git_commit_sha.
// Invalid refs will cause the git checkout/fetch command to fail on the remote server. // Invalid refs will cause the git checkout/fetch command to fail on the remote server.
$commitToUse = $commit ?? $this->git_commit_sha; $commitToUse = $commit ?? $this->git_commit_sha;
@ -1102,9 +1106,9 @@ public function setGitImportSettings(string $deployment_uuid, string $git_clone_
// If shallow clone is enabled and we need a specific commit, // If shallow clone is enabled and we need a specific commit,
// we need to fetch that specific commit with depth=1 // we need to fetch that specific commit with depth=1
if ($isShallowCloneEnabled) { if ($isShallowCloneEnabled) {
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git fetch --depth=1 origin {$escapedCommit} && git -c advice.detachedHead=false checkout {$escapedCommit} >/dev/null 2>&1"; $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && {$sshCommand} git fetch --depth=1 origin {$escapedCommit} && git -c advice.detachedHead=false checkout {$escapedCommit} >/dev/null 2>&1";
} else { } else {
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git -c advice.detachedHead=false checkout {$escapedCommit} >/dev/null 2>&1"; $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && {$sshCommand} git -c advice.detachedHead=false checkout {$escapedCommit} >/dev/null 2>&1";
} }
} }
if ($this->settings->is_git_submodules_enabled) { if ($this->settings->is_git_submodules_enabled) {
@ -1115,10 +1119,10 @@ public function setGitImportSettings(string $deployment_uuid, string $git_clone_
} }
// Add shallow submodules flag if shallow clone is enabled // Add shallow submodules flag if shallow clone is enabled
$submoduleFlags = $isShallowCloneEnabled ? '--depth=1' : ''; $submoduleFlags = $isShallowCloneEnabled ? '--depth=1' : '';
$git_clone_command = "{$git_clone_command} git submodule sync && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git submodule update --init --recursive {$submoduleFlags}; fi"; $git_clone_command = "{$git_clone_command} git submodule sync && {$sshCommand} git submodule update --init --recursive {$submoduleFlags}; fi";
} }
if ($this->settings->is_git_lfs_enabled) { if ($this->settings->is_git_lfs_enabled) {
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git lfs pull"; $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && {$sshCommand} git lfs pull";
} }
return $git_clone_command; return $git_clone_command;
@ -1407,11 +1411,12 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req
$private_key = base64_encode($private_key); $private_key = base64_encode($private_key);
$gitlabPort = $gitlabSource->custom_port ?? 22; $gitlabPort = $gitlabSource->custom_port ?? 22;
$escapedCustomRepository = escapeshellarg($customRepository); $escapedCustomRepository = escapeshellarg($customRepository);
$git_clone_command_base = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$gitlabPort} -o Port={$gitlabPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}"; $gitlabSshCommand = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$gitlabPort} -o Port={$gitlabPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\"";
$git_clone_command_base = "{$gitlabSshCommand} {$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}";
if ($only_checkout) { if ($only_checkout) {
$git_clone_command = $git_clone_command_base; $git_clone_command = $git_clone_command_base;
} else { } else {
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base, commit: $commit); $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base, commit: $commit, git_ssh_command: $gitlabSshCommand);
} }
if ($exec_in_docker) { if ($exec_in_docker) {
$commands = collect([ $commands = collect([
@ -1477,11 +1482,12 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req
} }
$private_key = base64_encode($private_key); $private_key = base64_encode($private_key);
$escapedCustomRepository = escapeshellarg($customRepository); $escapedCustomRepository = escapeshellarg($customRepository);
$git_clone_command_base = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}"; $deployKeySshCommand = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\"";
$git_clone_command_base = "{$deployKeySshCommand} {$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}";
if ($only_checkout) { if ($only_checkout) {
$git_clone_command = $git_clone_command_base; $git_clone_command = $git_clone_command_base;
} else { } else {
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base, commit: $commit); $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base, commit: $commit, git_ssh_command: $deployKeySshCommand);
} }
if ($exec_in_docker) { if ($exec_in_docker) {
$commands = collect([ $commands = collect([

View file

@ -85,4 +85,62 @@
expect($result)->not->toContain('advice.detachedHead=false checkout'); expect($result)->not->toContain('advice.detachedHead=false checkout');
}); });
test('setGitImportSettings uses provided git_ssh_command for fetch', function () {
$this->application->settings->is_git_shallow_clone_enabled = true;
$rollbackCommit = 'abc123def456abc123def456abc123def456abc1';
$sshCommand = 'GIT_SSH_COMMAND="ssh -o ConnectTimeout=30 -p 22222 -o Port=22222 -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa"';
$result = $this->application->setGitImportSettings(
deployment_uuid: 'test-uuid',
git_clone_command: 'git clone',
commit: $rollbackCommit,
git_ssh_command: $sshCommand,
);
expect($result)
->toContain('-i /root/.ssh/id_rsa" git fetch --depth=1 origin')
->toContain($rollbackCommit);
});
test('setGitImportSettings uses provided git_ssh_command for submodule update', function () {
$this->application->settings->is_git_submodules_enabled = true;
$sshCommand = 'GIT_SSH_COMMAND="ssh -o ConnectTimeout=30 -p 22 -o Port=22 -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa"';
$result = $this->application->setGitImportSettings(
deployment_uuid: 'test-uuid',
git_clone_command: 'git clone',
git_ssh_command: $sshCommand,
);
expect($result)
->toContain('-i /root/.ssh/id_rsa" git submodule update --init --recursive');
});
test('setGitImportSettings uses provided git_ssh_command for lfs pull', function () {
$this->application->settings->is_git_lfs_enabled = true;
$sshCommand = 'GIT_SSH_COMMAND="ssh -o ConnectTimeout=30 -p 22 -o Port=22 -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa"';
$result = $this->application->setGitImportSettings(
deployment_uuid: 'test-uuid',
git_clone_command: 'git clone',
git_ssh_command: $sshCommand,
);
expect($result)->toContain('-i /root/.ssh/id_rsa" git lfs pull');
});
test('setGitImportSettings uses default ssh command when git_ssh_command not provided', function () {
$this->application->settings->is_git_lfs_enabled = true;
$result = $this->application->setGitImportSettings(
deployment_uuid: 'test-uuid',
git_clone_command: 'git clone',
public: true,
);
expect($result)
->toContain('GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" git lfs pull')
->not->toContain('-i /root/.ssh/id_rsa');
});
}); });