mirror of
https://github.com/tiennm99/coolify.git
synced 2026-08-24 10:26:42 +00:00
feat: self-hosted GitLab Apps OAuth integration
Adds self-hosted GitLab OAuth sources so Coolify can connect to a self-managed GitLab instance, list private repositories, clone over an OAuth token, and deploy (the GitLab counterpart to GitHub Apps). Hardening: authenticated, one-time team-bound OAuth callback state; token redaction in deploy logs; custom host port/path kept in clone and ls-remote URLs; submodule OAuth auth; system-wide source selection. Covered by unit and feature tests. cosigned by OpenAI Codex at M1 Max
This commit is contained in:
committed by
Andras Bacsai
parent
9d341d0bb9
commit
a26091de0a
@@ -1378,11 +1378,11 @@ class Application extends BaseModel
|
||||
|
||||
if ($this->deploymentType() === 'source') {
|
||||
$source_html_url = data_get($this, 'source.html_url');
|
||||
$url = parse_url(filter_var($source_html_url, FILTER_SANITIZE_URL));
|
||||
$source_html_url_host = $url['host'];
|
||||
$source_html_url_scheme = $url['scheme'];
|
||||
|
||||
if ($this->source->getMorphClass() == 'App\Models\GithubApp') {
|
||||
$url = parse_url(filter_var($source_html_url, FILTER_SANITIZE_URL)) ?: [];
|
||||
$source_html_url_host = $url['host'] ?? '';
|
||||
$source_html_url_scheme = $url['scheme'] ?? '';
|
||||
$escapedCustomRepository = escapeshellarg($customRepository);
|
||||
if ($this->source->is_public) {
|
||||
$escapedRepoUrl = escapeshellarg("{$this->source->html_url}/{$customRepository}");
|
||||
@@ -1420,6 +1420,32 @@ class Application extends BaseModel
|
||||
|
||||
if ($this->source->getMorphClass() === GitlabApp::class) {
|
||||
$gitlabSource = $this->source;
|
||||
|
||||
if ($gitlabSource->isConnected()) {
|
||||
$url = parse_url(filter_var($source_html_url, FILTER_SANITIZE_URL)) ?: [];
|
||||
$source_html_url_host = $this->urlHostWithPort($url);
|
||||
$source_html_url_scheme = $url['scheme'] ?? '';
|
||||
$token = generateGitlabCloneToken($gitlabSource);
|
||||
$encodedToken = rawurlencode($token);
|
||||
$pathPrefix = rtrim($url['path'] ?? '', '/');
|
||||
$repoUrl = "{$source_html_url_scheme}://oauth2:{$encodedToken}@{$source_html_url_host}{$pathPrefix}/{$customRepository}.git";
|
||||
$escapedRepoUrl = escapeshellarg($repoUrl);
|
||||
$fullRepoUrl = $repoUrl;
|
||||
$base_command = "{$base_command} {$escapedRepoUrl}";
|
||||
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, $base_command));
|
||||
} else {
|
||||
$commands->push($base_command);
|
||||
}
|
||||
|
||||
return [
|
||||
'commands' => $commands->implode(' && '),
|
||||
'branch' => $branch,
|
||||
'fullRepoUrl' => $fullRepoUrl,
|
||||
];
|
||||
}
|
||||
|
||||
$private_key = data_get($gitlabSource, 'privateKey.private_key');
|
||||
|
||||
if ($private_key) {
|
||||
@@ -1444,7 +1470,6 @@ class Application extends BaseModel
|
||||
];
|
||||
}
|
||||
|
||||
// GitLab source without private key — use URL as-is (supports user-embedded basic auth)
|
||||
$fullRepoUrl = $customRepository;
|
||||
$escapedCustomRepository = escapeshellarg($customRepository);
|
||||
$base_command = "{$base_command} {$escapedCustomRepository}";
|
||||
@@ -1677,6 +1702,52 @@ class Application extends BaseModel
|
||||
|
||||
if ($this->source->getMorphClass() === GitlabApp::class) {
|
||||
$gitlabSource = $this->source;
|
||||
|
||||
if ($gitlabSource->isConnected()) {
|
||||
$token = generateGitlabCloneToken($gitlabSource);
|
||||
$encodedToken = rawurlencode($token);
|
||||
$pathPrefix = rtrim($url['path'] ?? '', '/');
|
||||
$source_html_url_host = $this->urlHostWithPort($url ?: []);
|
||||
|
||||
// Rewrite same-host HTTPS submodule URLs to auth with the OAuth token (mirrors the GitHub path) without persisting credentials.
|
||||
$gitConfigOption = '-c '.escapeshellarg("url.{$source_html_url_scheme}://oauth2:{$encodedToken}@{$source_html_url_host}{$pathPrefix}/.insteadOf={$source_html_url_scheme}://{$source_html_url_host}{$pathPrefix}/");
|
||||
$gitConfigOptions = $this->withGitHttpTransportConfig($gitConfigOption);
|
||||
|
||||
$repoUrl = "{$source_html_url_scheme}://oauth2:{$encodedToken}@{$source_html_url_host}{$pathPrefix}/{$customRepository}.git";
|
||||
$escapedRepoUrl = escapeshellarg($repoUrl);
|
||||
$fullRepoUrl = $repoUrl;
|
||||
$git_clone_command_base = $this->applyGitConfigOptionsToCloneCommand("{$git_clone_command} {$escapedRepoUrl} {$escapedBaseDir}", $gitConfigOptions);
|
||||
if ($only_checkout) {
|
||||
$git_clone_command = $git_clone_command_base;
|
||||
} else {
|
||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base, commit: $commit, gitConfigOptions: $gitConfigOptions);
|
||||
}
|
||||
|
||||
if ($pull_request_id !== 0) {
|
||||
$branch = "merge-requests/{$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_checkout_command = $this->buildGitCheckoutCommand($pr_branch_name, gitConfigOptions: $gitConfigOptions);
|
||||
$escapedPrBranch = escapeshellarg($branch);
|
||||
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && git {$gitConfigOptions} fetch origin {$escapedPrBranch} && {$git_checkout_command}";
|
||||
}
|
||||
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, $git_clone_command));
|
||||
} else {
|
||||
$commands->push($git_clone_command);
|
||||
}
|
||||
|
||||
return [
|
||||
'commands' => $commands->implode(' && '),
|
||||
'branch' => $branch,
|
||||
'fullRepoUrl' => $fullRepoUrl,
|
||||
];
|
||||
}
|
||||
|
||||
$private_key = data_get($gitlabSource, 'privateKey.private_key');
|
||||
|
||||
if ($private_key) {
|
||||
@@ -1717,7 +1788,6 @@ class Application extends BaseModel
|
||||
];
|
||||
}
|
||||
|
||||
// GitLab source without private key — use URL as-is (supports user-embedded basic auth)
|
||||
$fullRepoUrl = $customRepository;
|
||||
$escapedCustomRepository = escapeshellarg($customRepository);
|
||||
$git_clone_command = "{$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}";
|
||||
@@ -2382,6 +2452,14 @@ class Application extends BaseModel
|
||||
];
|
||||
}
|
||||
|
||||
private function urlHostWithPort(array $url): string
|
||||
{
|
||||
$host = $url['host'] ?? '';
|
||||
$port = isset($url['port']) ? ":{$url['port']}" : '';
|
||||
|
||||
return "{$host}{$port}";
|
||||
}
|
||||
|
||||
public function generateConfig($is_json = false)
|
||||
{
|
||||
$generator = new ConfigurationGenerator($this);
|
||||
|
||||
Reference in New Issue
Block a user