-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
fix(git): ensure ssh credentials are propagated to submodule operations #8900
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1087,12 +1087,14 @@ public function dirOnServer() | |
| 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 $gitSshCommand = null) | ||
| { | ||
| $baseDir = $this->generateBaseDir($deployment_uuid); | ||
| $escapedBaseDir = escapeshellarg($baseDir); | ||
| $isShallowCloneEnabled = $this->settings?->is_git_shallow_clone_enabled ?? false; | ||
|
|
||
| $sshCommand = $gitSshCommand ?? '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. | ||
| // Invalid refs will cause the git checkout/fetch command to fail on the remote server. | ||
| $commitToUse = $commit ?? $this->git_commit_sha; | ||
|
|
@@ -1102,9 +1104,9 @@ public function setGitImportSettings(string $deployment_uuid, string $git_clone_ | |
| // If shallow clone is enabled and we need a specific commit, | ||
| // we need to fetch that specific commit with depth=1 | ||
| 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} && GIT_SSH_COMMAND=\"{$sshCommand}\" git fetch --depth=1 origin {$escapedCommit} && git -c advice.detachedHead=false checkout {$escapedCommit} >/dev/null 2>&1"; | ||
| } 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} && GIT_SSH_COMMAND=\"{$sshCommand}\" git -c advice.detachedHead=false checkout {$escapedCommit} >/dev/null 2>&1"; | ||
| } | ||
| } | ||
| if ($this->settings->is_git_submodules_enabled) { | ||
|
|
@@ -1115,10 +1117,10 @@ public function setGitImportSettings(string $deployment_uuid, string $git_clone_ | |
| } | ||
| // Add shallow submodules flag if shallow clone is enabled | ||
| $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 && GIT_SSH_COMMAND=\"{$sshCommand}\" git submodule update --init --recursive {$submoduleFlags}; fi"; | ||
| } | ||
| 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} && GIT_SSH_COMMAND=\"{$sshCommand}\" git lfs pull"; | ||
| } | ||
|
|
||
| return $git_clone_command; | ||
|
|
@@ -1301,12 +1303,18 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req | |
| } | ||
| } else { | ||
| $github_access_token = generateGithubInstallationToken($this->source); | ||
| // Configure git to rewrite URLs with the token so submodules on the same host authenticate correctly | ||
| $escapedTokenUrl = escapeshellarg("{$source_html_url_scheme}://x-access-token:{$github_access_token}@{$source_html_url_host}/"); | ||
| $escapedHostUrl = escapeshellarg("{$source_html_url_scheme}://{$source_html_url_host}/"); | ||
| $gitConfigCommand = "git config --global url.{$escapedTokenUrl}.insteadOf {$escapedHostUrl}"; | ||
| if ($exec_in_docker) { | ||
| $commands->push(executeInDocker($deployment_uuid, $gitConfigCommand)); | ||
| $repoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git"; | ||
| $escapedRepoUrl = escapeshellarg($repoUrl); | ||
| $git_clone_command = "{$git_clone_command} {$escapedRepoUrl} {$escapedBaseDir}"; | ||
| $fullRepoUrl = $repoUrl; | ||
| } else { | ||
| $commands->push($gitConfigCommand); | ||
|
Comment on lines
1305
to
+1317
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Read the file and examine the specific lines and surrounding context
sed -n '1300,1320p' app/Models/Application.phpRepository: coollabsio/coolify Length of output: 1790 🏁 Script executed: # Check if there's any cleanup of global git config
rg 'git.*config.*--global.*--unset' app/Models/Application.phpRepository: coollabsio/coolify Length of output: 44 🏁 Script executed: # Look at the loadComposeFile function and how it calls generateGitImportCommands
sed -n '1540,1555p' app/Models/Application.phpRepository: coollabsio/coolify Length of output: 1105 🏁 Script executed: # Check the full generateGitImportCommands function signature and context
sed -n '1200,1250p' app/Models/Application.phpRepository: coollabsio/coolify Length of output: 2483 🏁 Script executed: # Search for any cleanup mechanisms after git config is set
rg -A 10 'git config --global url' app/Models/Application.phpRepository: coollabsio/coolify Length of output: 980 Remove Line 1309 writes the GitHub installation token into global git config, and this branch executes on the actual host machine (called from 🧰 Tools🪛 PHPMD (2.15.0)[error] 1305-1305: The variable $github_access_token is not named in camelCase. (undefined) (CamelCaseVariableName) 🤖 Prompt for AI Agents |
||
| $repoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}"; | ||
| $escapedRepoUrl = escapeshellarg($repoUrl); | ||
| $git_clone_command = "{$git_clone_command} {$escapedRepoUrl} {$escapedBaseDir}"; | ||
|
|
@@ -1348,11 +1356,12 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req | |
| } | ||
| $private_key = base64_encode($private_key); | ||
| $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 = "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 = "GIT_SSH_COMMAND=\"{$deployKeySshCommand}\" {$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}"; | ||
| if ($only_checkout) { | ||
| $git_clone_command = $git_clone_command_base; | ||
| } 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, gitSshCommand: $deployKeySshCommand); | ||
| } | ||
| if ($exec_in_docker) { | ||
| $commands = collect([ | ||
|
|
@@ -1375,22 +1384,22 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req | |
| } else { | ||
| $commands->push("echo 'Checking out $branch'"); | ||
| } | ||
| $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && 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 fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name); | ||
| $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"{$deployKeySshCommand}\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name, $deployKeySshCommand); | ||
| } elseif ($git_type === 'github' || $git_type === 'gitea') { | ||
| $branch = "pull/{$pull_request_id}/head:$pr_branch_name"; | ||
| if ($exec_in_docker) { | ||
| $commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'")); | ||
| } else { | ||
| $commands->push("echo 'Checking out $branch'"); | ||
| } | ||
| $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && 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 fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name); | ||
| $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"{$deployKeySshCommand}\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name, $deployKeySshCommand); | ||
| } elseif ($git_type === 'bitbucket') { | ||
| if ($exec_in_docker) { | ||
| $commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'")); | ||
| } else { | ||
| $commands->push("echo 'Checking out $branch'"); | ||
| } | ||
| $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && 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\" ".$this->buildGitCheckoutCommand($commit); | ||
| $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"{$deployKeySshCommand}\" ".$this->buildGitCheckoutCommand($commit, $deployKeySshCommand); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -1411,6 +1420,7 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req | |
| $escapedCustomRepository = escapeshellarg($customRepository); | ||
| $git_clone_command = "{$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}"; | ||
| $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true, commit: $commit); | ||
| $otherSshCommand = "ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa"; | ||
|
|
||
| if ($pull_request_id !== 0) { | ||
| if ($git_type === 'gitlab') { | ||
|
|
@@ -1420,22 +1430,22 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req | |
| } else { | ||
| $commands->push("echo 'Checking out $branch'"); | ||
| } | ||
| $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && 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 fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name); | ||
| $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"{$otherSshCommand}\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name, $otherSshCommand); | ||
| } elseif ($git_type === 'github' || $git_type === 'gitea') { | ||
| $branch = "pull/{$pull_request_id}/head:$pr_branch_name"; | ||
| if ($exec_in_docker) { | ||
| $commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'")); | ||
| } else { | ||
| $commands->push("echo 'Checking out $branch'"); | ||
| } | ||
| $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && 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 fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name); | ||
| $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"{$otherSshCommand}\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name, $otherSshCommand); | ||
| } elseif ($git_type === 'bitbucket') { | ||
| if ($exec_in_docker) { | ||
| $commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'")); | ||
| } else { | ||
| $commands->push("echo 'Checking out $branch'"); | ||
| } | ||
| $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && 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\" ".$this->buildGitCheckoutCommand($commit); | ||
| $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"{$otherSshCommand}\" ".$this->buildGitCheckoutCommand($commit, $otherSshCommand); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -1684,13 +1694,14 @@ public function fqdns(): Attribute | |
| ); | ||
| } | ||
|
|
||
| protected function buildGitCheckoutCommand($target): string | ||
| protected function buildGitCheckoutCommand($target, ?string $gitSshCommand = null): string | ||
| { | ||
| $escapedTarget = escapeshellarg($target); | ||
| $command = "git checkout {$escapedTarget}"; | ||
|
|
||
| if ($this->settings->is_git_submodules_enabled) { | ||
| $command .= ' && git submodule update --init --recursive'; | ||
| $sshCommand = $gitSshCommand ?? 'ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'; | ||
| $command .= " && GIT_SSH_COMMAND=\"{$sshCommand}\" git submodule update --init --recursive"; | ||
| } | ||
|
|
||
| return $command; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: coollabsio/coolify
Length of output: 6435
🏁 Script executed:
Repository: coollabsio/coolify
Length of output: 7173
🏁 Script executed:
Repository: coollabsio/coolify
Length of output: 3290
🏁 Script executed:
Repository: coollabsio/coolify
Length of output: 2600
SSH submodules will skip the GitHub App token authentication.
The
git config url.{token}.insteadOfrewrite only matcheshttps://{host}/...URLs. Any submodules declared asgit@{host}:owner/repo.gitorssh://git@{host}/owner/repo.gitin.gitmoduleswill use SSH duringgit submodule updateand bypass this token-based authentication entirely.The codebase already handles this for public repos (line 1116 converts
git@...tohttps://in.gitmodules), but private GitHub App repos lack the equivalent SSH URL normalization. Either add the same.gitmodulessed rewrite for private repos, or normalize SSH URLs before submodule init.🤖 Prompt for AI Agents