mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(servers): support IPv6 server addresses everywhere
Store server IPs in one form (no brackets, short lower-case IPv6) and write each address in the form its context needs: - URLs and host:port (Sentinel, database URLs, SFTP backup download, DNS servers, SMTP, webhook base URLs, links, health checks) get brackets through formatHostForUrl(). - Traefik Host() rules use the address without brackets, so IPv6 domains no longer return 404; saved labels are fixed at deploy. Caddy keeps the brackets. - SSH keeps the plain address. - Hetzner gives an IPv6 /64 network; IPv6-only servers now get the server address (prefix::1) instead of the unusable prefix::64. - sslip labels, registry image names with an IPv6 host, the API domain list, allowlist matching and duplicate checks handle IPv6. A migration normalizes stored IPv6 server addresses, repairs Hetzner servers saved as ::64 that are not reachable, and adds brackets to saved Sentinel URLs.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Jobs\ApplicationDeploymentJob;
|
||||
use App\Livewire\Project\Shared\HealthChecks;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\ApplicationSetting;
|
||||
@@ -89,6 +90,56 @@ it('uses escapeshellarg on the constructed URL', function () {
|
||||
expect($result)->toContain("'http://my-app.local:80/api/health'");
|
||||
});
|
||||
|
||||
it('builds the healthcheck URL with brackets for an IPv6 host', function (string $host, string $expectedUrl) {
|
||||
$result = callGenerateHealthcheckCommands([
|
||||
'health_check_host' => $host,
|
||||
'health_check_port' => '8080',
|
||||
'health_check_path' => '/health',
|
||||
]);
|
||||
|
||||
expect($result)->toContain("'{$expectedUrl}'")
|
||||
->and($result)->not->toContain('localhost');
|
||||
})->with([
|
||||
'loopback' => ['::1', 'http://[::1]:8080/health'],
|
||||
'bracketed loopback' => ['[::1]', 'http://[::1]:8080/health'],
|
||||
'global address' => ['2A01:4F8:0::1', 'http://[2a01:4f8::1]:8080/health'],
|
||||
]);
|
||||
|
||||
it('falls back to localhost for an invalid IPv6-like healthcheck host', function (string $host) {
|
||||
$result = callGenerateHealthcheckCommands(['health_check_host' => $host]);
|
||||
|
||||
expect($result)->toContain("'http://localhost:80/'");
|
||||
})->with([
|
||||
'too many colons' => [':::1'],
|
||||
'shell injection in brackets' => ['[::1];id'],
|
||||
'unclosed bracket' => ['[::1'],
|
||||
]);
|
||||
|
||||
it('keeps IPv4 and hostname healthcheck hosts unchanged', function (string $host) {
|
||||
$result = callGenerateHealthcheckCommands(['health_check_host' => $host, 'health_check_port' => '3000']);
|
||||
|
||||
expect($result)->toContain("'http://{$host}:3000/'");
|
||||
})->with(['localhost', '127.0.0.1', 'my-app.local']);
|
||||
|
||||
it('validates the health check host in the health checks form', function (string $host, bool $passes) {
|
||||
$rules = (new ReflectionMethod(HealthChecks::class, 'rules'))->invoke(new HealthChecks);
|
||||
|
||||
$validator = Validator::make(['healthCheckHost' => $host], ['healthCheckHost' => $rules['healthCheckHost']]);
|
||||
|
||||
expect($validator->passes())->toBe($passes);
|
||||
})->with([
|
||||
'localhost' => ['localhost', true],
|
||||
'hostname' => ['my-app.local', true],
|
||||
'IPv4' => ['127.0.0.1', true],
|
||||
'IPv6 loopback' => ['::1', true],
|
||||
'bracketed IPv6 loopback' => ['[::1]', true],
|
||||
'IPv6 address' => ['2a01:4f8::1', true],
|
||||
'invalid IPv6' => [':::1', false],
|
||||
'unclosed bracket' => ['[::1', false],
|
||||
'shell metacharacters' => ['localhost; id #', false],
|
||||
'IPv6 with shell metacharacters' => ['[::1];id', false],
|
||||
]);
|
||||
|
||||
it('validates health_check_host rejects shell metacharacters via API rules', function () {
|
||||
$rules = sharedDataApplications();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user