mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-12 15:53:46 -04:00
fix(dns): send only a public server IP to Cloudflare Domain Connect
Domain Connect used the unfiltered server IPs, so a private address could be published. It now uses the first public IP and refuses with the no public IP message when there is none, like automatic DNS.
This commit is contained in:
@@ -33,6 +33,12 @@ trait InteractsWithCloudflareDomainConnect
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->publicServerIpForDomainConnect() === null) {
|
||||
$this->dispatch('error', DnsRecordHints::NO_PUBLIC_ADDRESS_MESSAGE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->showCloudflareAutoconfigureModal = true;
|
||||
}
|
||||
|
||||
@@ -66,6 +72,14 @@ trait InteractsWithCloudflareDomainConnect
|
||||
return;
|
||||
}
|
||||
|
||||
$ip = $this->publicServerIpForDomainConnect();
|
||||
if ($ip === null) {
|
||||
$this->showCloudflareAutoconfigureModal = false;
|
||||
$this->dispatch('error', DnsRecordHints::NO_PUBLIC_ADDRESS_MESSAGE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$hostnames = $this->allDomainHostnames();
|
||||
if ($hostnames === []) {
|
||||
$this->dispatch('error', 'Add at least one domain before configuring DNS on Cloudflare.');
|
||||
@@ -245,6 +259,21 @@ trait InteractsWithCloudflareDomainConnect
|
||||
return rtrim($hostname, '.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Address that Domain Connect may publish at Cloudflare: the first public server address (IPv4 first).
|
||||
* Private, reserved, CGNAT, and link-local addresses are never sent to Cloudflare.
|
||||
*/
|
||||
protected function publicServerIpForDomainConnect(): ?string
|
||||
{
|
||||
foreach ($this->serverIpsForDnsHints() as $address) {
|
||||
if (is_string($address) && DnsRecordHints::isPublicAddress($address)) {
|
||||
return $address;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function serverIpForDomainConnect(): ?string
|
||||
{
|
||||
if (filled($this->serverIp) && filter_var($this->serverIp, FILTER_VALIDATE_IP) !== false) {
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
</div>
|
||||
|
||||
<x-forms.input label="Server IP (A record target)"
|
||||
value="{{ $serverIp ?: 'Unavailable' }}" readonly
|
||||
value="{{ $this->publicServerIpForDomainConnect() ?: 'Unavailable' }}" readonly
|
||||
helper="This IP is taken from the destination server." />
|
||||
|
||||
<div class="flex flex-wrap items-center justify-end gap-2 pt-2">
|
||||
|
||||
@@ -17,6 +17,7 @@ use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use App\Support\DnsRecordHints;
|
||||
use App\Support\ValidationPatterns;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
@@ -835,6 +836,83 @@ it('shows cloudflare domain connect only on cloud with a key', function () {
|
||||
->assertSet('showCloudflareAutoconfigureModal', false);
|
||||
});
|
||||
|
||||
function enableCloudflareDomainConnectForTest(): void
|
||||
{
|
||||
$key = openssl_pkey_new([
|
||||
'private_key_bits' => 2048,
|
||||
'private_key_type' => OPENSSL_KEYTYPE_RSA,
|
||||
]);
|
||||
openssl_pkey_export($key, $privateKey);
|
||||
|
||||
config([
|
||||
'constants.coolify.self_hosted' => false,
|
||||
'services.domain_connect.provider_id' => 'coolify.io',
|
||||
'services.domain_connect.service_id' => 'hosting',
|
||||
'services.domain_connect.key_id' => '_dcpubkeyv1',
|
||||
'services.domain_connect.private_key' => $privateKey,
|
||||
]);
|
||||
}
|
||||
|
||||
it('opens cloudflare domain connect with the public server ip', function () {
|
||||
enableCloudflareDomainConnectForTest();
|
||||
$this->application->update(['fqdn' => 'https://app.example.com']);
|
||||
|
||||
$component = Livewire::test(Domains::class, ['application' => $this->application->fresh()])
|
||||
->call('openCloudflareAutoconfigureModal')
|
||||
->assertSet('showCloudflareAutoconfigureModal', true)
|
||||
->call('applyCloudflareAutoconfigure')
|
||||
->assertNotDispatched('error');
|
||||
|
||||
expect(collect($component->effects['xjs'] ?? [])->pluck('expression')->implode("\n"))
|
||||
->toContain('dash.cloudflare.com')
|
||||
->toContain('203.0.113.10');
|
||||
});
|
||||
|
||||
it('does not start cloudflare domain connect for a server without a public ip', function () {
|
||||
enableCloudflareDomainConnectForTest();
|
||||
$this->server->update(['ip' => '10.221.1.11']);
|
||||
$this->application->update(['fqdn' => 'https://app.example.com']);
|
||||
|
||||
$component = Livewire::test(Domains::class, ['application' => $this->application->fresh()])
|
||||
->assertSet('serverIp', '10.221.1.11')
|
||||
->call('openCloudflareAutoconfigureModal')
|
||||
->assertDispatched('error', DnsRecordHints::NO_PUBLIC_ADDRESS_MESSAGE)
|
||||
->assertSet('showCloudflareAutoconfigureModal', false)
|
||||
->call('applyCloudflareAutoconfigure')
|
||||
->assertDispatched('error', DnsRecordHints::NO_PUBLIC_ADDRESS_MESSAGE)
|
||||
->assertSet('showCloudflareAutoconfigureModal', false);
|
||||
|
||||
expect($component->effects['xjs'] ?? [])->toBeEmpty();
|
||||
});
|
||||
|
||||
it('uses only a public address for cloudflare domain connect on localhost', function () {
|
||||
enableCloudflareDomainConnectForTest();
|
||||
InstanceSettings::get()->update(['public_ipv4' => '198.51.100.20']);
|
||||
$localhost = Server::factory()->create([
|
||||
'id' => 0,
|
||||
'team_id' => $this->team->id,
|
||||
'private_key_id' => $this->server->private_key_id,
|
||||
'ip' => 'localhost',
|
||||
]);
|
||||
$destination = StandaloneDocker::withoutEvents(fn () => StandaloneDocker::forceCreate([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'localhost-docker',
|
||||
'network' => 'coolify-localhost',
|
||||
'server_id' => $localhost->id,
|
||||
]));
|
||||
$this->application->update([
|
||||
'destination_id' => $destination->id,
|
||||
'fqdn' => 'https://app.example.com',
|
||||
]);
|
||||
|
||||
$component = Livewire::test(Domains::class, ['application' => $this->application->fresh()])
|
||||
->call('applyCloudflareAutoconfigure')
|
||||
->assertNotDispatched('error');
|
||||
|
||||
expect(collect($component->effects['xjs'] ?? [])->pluck('expression')->implode("\n"))
|
||||
->toContain('198.51.100.20');
|
||||
});
|
||||
|
||||
it('adds a domain to the application', function () {
|
||||
Livewire::test(Domains::class, ['application' => $this->application->fresh()])
|
||||
->assertSee('Add domain')
|
||||
|
||||
Reference in New Issue
Block a user