mirror of
https://github.com/coollabsio/coolify.git
synced 2026-03-11 08:55:47 +00:00
djsisson comment about addressing overlapping of CIDR network overlaps.
This commit is contained in:
parent
91ea235b5f
commit
aaf0b94bce
2 changed files with 150 additions and 0 deletions
|
|
@ -79,6 +79,10 @@ class EdgeProxyRemoteRouteService
|
|||
|
||||
$routes = [];
|
||||
$warnings = [];
|
||||
$networkOverlapWarning = $this->detectDockerNetworkOverlapWarning($service, $edgeProxyServer, $tunnelHost);
|
||||
if (! is_null($networkOverlapWarning)) {
|
||||
$warnings[] = $networkOverlapWarning;
|
||||
}
|
||||
|
||||
foreach ($applications as $application) {
|
||||
$domains = collect(explode(',', (string) $application->fqdn))
|
||||
|
|
@ -430,6 +434,88 @@ class EdgeProxyRemoteRouteService
|
|||
return $protocol;
|
||||
}
|
||||
|
||||
private function detectDockerNetworkOverlapWarning(Service $service, Server $edgeProxyServer, string $tunnelHost): ?string
|
||||
{
|
||||
// Only run this for persisted server models (normal runtime) to avoid noisy checks in synthetic test stubs.
|
||||
if (! $edgeProxyServer->exists) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$normalizedTunnelHost = trim($tunnelHost, '[]');
|
||||
if (! filter_var($normalizedTunnelHost, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($this->resolveEdgeDockerSubnets($edgeProxyServer) as $subnet) {
|
||||
if ($this->ipv4InCidr($normalizedTunnelHost, $subnet)) {
|
||||
return sprintf(
|
||||
'Edge proxy route warning for service %s: remote host %s overlaps edge Docker network subnet %s. This can break VPN/WireGuard routing. Configure Docker default-address-pools to a non-overlapping range (for example base 172.20.0.0/16 with size 24) and recreate overlapping networks.',
|
||||
$service->uuid,
|
||||
$normalizedTunnelHost,
|
||||
$subnet
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function resolveEdgeDockerSubnets(Server $edgeProxyServer): array
|
||||
{
|
||||
try {
|
||||
$subnetsOutput = $this->runRemoteCommands($edgeProxyServer, [
|
||||
"docker network inspect \$(docker network ls -q) --format '{{range .IPAM.Config}}{{println .Subnet}}{{end}}' 2>/dev/null | sort -u || true",
|
||||
], false);
|
||||
} catch (\Throwable) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (! is_string($subnetsOutput) || trim($subnetsOutput) === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return collect(preg_split('/\R+/', $subnetsOutput) ?: [])
|
||||
->map(fn (string $line) => trim($line))
|
||||
->filter(fn (string $line) => preg_match('/^\d{1,3}(?:\.\d{1,3}){3}\/\d{1,2}$/', $line) === 1)
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
private function ipv4InCidr(string $ip, string $cidr): bool
|
||||
{
|
||||
[$networkIp, $prefixLength] = array_pad(explode('/', $cidr, 2), 2, null);
|
||||
if (! is_string($networkIp) || ! is_string($prefixLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! filter_var($networkIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! preg_match('/^\d+$/', $prefixLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$prefixLength = (int) $prefixLength;
|
||||
if ($prefixLength < 0 || $prefixLength > 32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ipLong = ip2long($ip);
|
||||
$networkLong = ip2long($networkIp);
|
||||
if ($ipLong === false || $networkLong === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($prefixLength === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$mask = -1 << (32 - $prefixLength);
|
||||
|
||||
return ($ipLong & $mask) === ($networkLong & $mask);
|
||||
}
|
||||
|
||||
private function parseDomainUrl(string $domain): ?Url
|
||||
{
|
||||
$normalizedDomain = trim($domain);
|
||||
|
|
|
|||
|
|
@ -612,3 +612,67 @@ YAML;
|
|||
->and($payload)->toContain('http://10.8.0.24:9050')
|
||||
->and($payload)->not->toContain('minecraft.example.com');
|
||||
});
|
||||
|
||||
it('returns warning when remote tunnel ip overlaps with an edge docker subnet', function () {
|
||||
$manager = new class extends EdgeProxyRemoteRouteService
|
||||
{
|
||||
public array $calls = [];
|
||||
|
||||
protected function runRemoteCommands(Server $server, array $commands, bool $throwError = true): ?string
|
||||
{
|
||||
$this->calls[] = [
|
||||
'commands' => $commands,
|
||||
'throw_error' => $throwError,
|
||||
];
|
||||
|
||||
if (str_contains(implode("\n", $commands), 'docker network inspect')) {
|
||||
return "10.8.0.0/24\n172.18.0.0/16\n";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
$edgeProxyServer = Mockery::mock(Server::class)->makePartial();
|
||||
$edgeProxyServer->id = 0;
|
||||
$edgeProxyServer->exists = true;
|
||||
$edgeProxyServer->shouldReceive('proxyType')->andReturn('TRAEFIK');
|
||||
$edgeProxyServer->shouldReceive('proxyPath')->andReturn('/tmp/proxy');
|
||||
|
||||
$deploymentServer = Mockery::mock(Server::class)->makePartial();
|
||||
$deploymentServer->id = 20;
|
||||
$deploymentServer->ip = '10.8.0.40';
|
||||
$deploymentServer->proxy = ['type' => 'NONE'];
|
||||
|
||||
$service = new Service;
|
||||
$service->uuid = 'service-overlap-warning';
|
||||
$service->docker_compose_raw = <<<'YAML'
|
||||
services:
|
||||
app:
|
||||
ports:
|
||||
- "9060:3000"
|
||||
YAML;
|
||||
|
||||
$application = new ServiceApplication;
|
||||
$application->name = 'app';
|
||||
$application->fqdn = 'https://overlap.example.com:3000';
|
||||
|
||||
$service->setRelation('applications', collect([$application]));
|
||||
$application->setRelation('service', $service);
|
||||
|
||||
$warnings = $manager->syncServiceWithServers($service, $edgeProxyServer, $deploymentServer);
|
||||
|
||||
expect($warnings)->not->toBeEmpty()
|
||||
->and(collect($warnings)->contains(fn (string $warning) => str_contains($warning, 'overlaps edge Docker network subnet 10.8.0.0/24')));
|
||||
|
||||
$writeCall = collect($manager->calls)->first(
|
||||
fn (array $call) => str_contains(implode("\n", $call['commands']), 'base64 -d | tee')
|
||||
);
|
||||
expect($writeCall)->not->toBeNull();
|
||||
|
||||
preg_match("/echo '([^']+)' \\| base64 -d/", $writeCall['commands'][1], $payloadMatches);
|
||||
$payload = base64_decode($payloadMatches[1]);
|
||||
|
||||
expect($payload)->toContain('Host(`overlap.example.com`)')
|
||||
->and($payload)->toContain('http://10.8.0.40:9060');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue