mirror of
https://github.com/coollabsio/coolify.git
synced 2026-09-27 17:55:59 -04:00
Merge remote-tracking branch 'origin/next' into audit-policies
This commit is contained in:
@@ -1006,6 +1006,7 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null)
|
||||
'--ulimit' => 'ulimits',
|
||||
'--privileged' => 'privileged',
|
||||
'--ip' => 'ip',
|
||||
'--ip6' => 'ip6',
|
||||
'--shm-size' => 'shm_size',
|
||||
'--gpus' => 'gpus',
|
||||
'--hostname' => 'hostname',
|
||||
|
||||
@@ -127,10 +127,44 @@ function connectProxyToNetworks(Server $server)
|
||||
return $commands->flatten();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate shell commands to fix a Docker network that has an IPv6 gateway with CIDR notation.
|
||||
*
|
||||
* Docker 25+ may store IPv6 gateways with CIDR (e.g. fd7d:f7d2:7e77::1/64), which causes
|
||||
* ParseAddr errors in Docker Compose. This detects the issue and recreates the network.
|
||||
*
|
||||
* @see https://github.com/coollabsio/coolify/issues/8649
|
||||
*
|
||||
* @param string $network Network name to check and fix
|
||||
* @return array Shell commands to execute on the remote server
|
||||
*/
|
||||
function fixNetworkIpv6CidrGateway(string $network): array
|
||||
{
|
||||
return [
|
||||
"if docker network inspect {$network} >/dev/null 2>&1; then",
|
||||
" IPV6_GW=\$(docker network inspect {$network} --format '{{range .IPAM.Config}}{{.Gateway}} {{end}}' 2>/dev/null | tr ' ' '\n' | grep '/' || true)",
|
||||
' if [ -n "$IPV6_GW" ]; then',
|
||||
" echo \"Fixing network {$network}: IPv6 gateway has CIDR notation (\$IPV6_GW)\"",
|
||||
" CONTAINERS=\$(docker network inspect {$network} --format '{{range .Containers}}{{.Name}} {{end}}' 2>/dev/null)",
|
||||
' for c in $CONTAINERS; do',
|
||||
" [ -n \"\$c\" ] && docker network disconnect {$network} \"\$c\" 2>/dev/null || true",
|
||||
' done',
|
||||
" docker network rm {$network} 2>/dev/null || true",
|
||||
" docker network create --attachable {$network} 2>/dev/null || true",
|
||||
' for c in $CONTAINERS; do',
|
||||
" [ -n \"\$c\" ] && [ \"\$c\" != \"coolify-proxy\" ] && docker network connect {$network} \"\$c\" 2>/dev/null || true",
|
||||
' done',
|
||||
' fi',
|
||||
'fi',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures all required networks exist before docker compose up.
|
||||
* This must be called BEFORE docker compose up since the compose file declares networks as external.
|
||||
*
|
||||
* Also detects and fixes networks with IPv6 CIDR gateway notation that causes ParseAddr errors.
|
||||
*
|
||||
* @param Server $server The server to ensure networks on
|
||||
* @return \Illuminate\Support\Collection Commands to create networks if they don't exist
|
||||
*/
|
||||
@@ -140,17 +174,23 @@ function ensureProxyNetworksExist(Server $server)
|
||||
|
||||
if ($server->isSwarm()) {
|
||||
$commands = $networks->map(function ($network) {
|
||||
return [
|
||||
"echo 'Ensuring network $network exists...'",
|
||||
"docker network ls --format '{{.Name}}' | grep -q '^{$network}$' || docker network create --driver overlay --attachable $network",
|
||||
];
|
||||
return array_merge(
|
||||
fixNetworkIpv6CidrGateway($network),
|
||||
[
|
||||
"echo 'Ensuring network $network exists...'",
|
||||
"docker network ls --format '{{.Name}}' | grep -q '^{$network}$' || docker network create --driver overlay --attachable $network",
|
||||
]
|
||||
);
|
||||
});
|
||||
} else {
|
||||
$commands = $networks->map(function ($network) {
|
||||
return [
|
||||
"echo 'Ensuring network $network exists...'",
|
||||
"docker network ls --format '{{.Name}}' | grep -q '^{$network}$' || docker network create --attachable $network",
|
||||
];
|
||||
return array_merge(
|
||||
fixNetworkIpv6CidrGateway($network),
|
||||
[
|
||||
"echo 'Ensuring network $network exists...'",
|
||||
"docker network ls --format '{{.Name}}' | grep -q '^{$network}$' || docker network create --attachable $network",
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1424,24 +1424,48 @@ function checkIPAgainstAllowlist($ip, $allowlist)
|
||||
}
|
||||
|
||||
$mask = (int) $mask;
|
||||
$isIpv6Subnet = filter_var($subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
|
||||
$maxMask = $isIpv6Subnet ? 128 : 32;
|
||||
|
||||
// Validate mask
|
||||
if ($mask < 0 || $mask > 32) {
|
||||
// Validate mask for address family
|
||||
if ($mask < 0 || $mask > $maxMask) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate network addresses
|
||||
$ip_long = ip2long($ip);
|
||||
$subnet_long = ip2long($subnet);
|
||||
if ($isIpv6Subnet) {
|
||||
// IPv6 CIDR matching using binary string comparison
|
||||
$ipBin = inet_pton($ip);
|
||||
$subnetBin = inet_pton($subnet);
|
||||
|
||||
if ($ip_long === false || $subnet_long === false) {
|
||||
continue;
|
||||
}
|
||||
if ($ipBin === false || $subnetBin === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mask_long = ~((1 << (32 - $mask)) - 1);
|
||||
// Build a 128-bit mask from $mask prefix bits
|
||||
$maskBin = str_repeat("\xff", (int) ($mask / 8));
|
||||
$remainder = $mask % 8;
|
||||
if ($remainder > 0) {
|
||||
$maskBin .= chr(0xFF & (0xFF << (8 - $remainder)));
|
||||
}
|
||||
$maskBin = str_pad($maskBin, 16, "\x00");
|
||||
|
||||
if (($ip_long & $mask_long) == ($subnet_long & $mask_long)) {
|
||||
return true;
|
||||
if (($ipBin & $maskBin) === ($subnetBin & $maskBin)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// IPv4 CIDR matching
|
||||
$ip_long = ip2long($ip);
|
||||
$subnet_long = ip2long($subnet);
|
||||
|
||||
if ($ip_long === false || $subnet_long === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mask_long = ~((1 << (32 - $mask)) - 1);
|
||||
|
||||
if (($ip_long & $mask_long) == ($subnet_long & $mask_long)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Special case: 0.0.0.0 means allow all
|
||||
@@ -1459,6 +1483,67 @@ function checkIPAgainstAllowlist($ip, $allowlist)
|
||||
return false;
|
||||
}
|
||||
|
||||
function deduplicateAllowlist(array $entries): array
|
||||
{
|
||||
if (count($entries) <= 1) {
|
||||
return array_values($entries);
|
||||
}
|
||||
|
||||
// Normalize each entry into [original, ip, mask]
|
||||
$parsed = [];
|
||||
foreach ($entries as $entry) {
|
||||
$entry = trim($entry);
|
||||
if (empty($entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($entry === '0.0.0.0') {
|
||||
// Special case: bare 0.0.0.0 means "allow all" — treat as /0
|
||||
$parsed[] = ['original' => $entry, 'ip' => '0.0.0.0', 'mask' => 0];
|
||||
} elseif (str_contains($entry, '/')) {
|
||||
[$ip, $mask] = explode('/', $entry);
|
||||
$parsed[] = ['original' => $entry, 'ip' => $ip, 'mask' => (int) $mask];
|
||||
} else {
|
||||
$ip = $entry;
|
||||
$isIpv6 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
|
||||
$parsed[] = ['original' => $entry, 'ip' => $ip, 'mask' => $isIpv6 ? 128 : 32];
|
||||
}
|
||||
}
|
||||
|
||||
$count = count($parsed);
|
||||
$redundant = array_fill(0, $count, false);
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
if ($redundant[$i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for ($j = 0; $j < $count; $j++) {
|
||||
if ($i === $j || $redundant[$j]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Entry $j is redundant if its mask is narrower/equal (>=) than $i's mask
|
||||
// AND $j's network IP falls within $i's CIDR range
|
||||
if ($parsed[$j]['mask'] >= $parsed[$i]['mask']) {
|
||||
$cidr = $parsed[$i]['ip'].'/'.$parsed[$i]['mask'];
|
||||
if (checkIPAgainstAllowlist($parsed[$j]['ip'], [$cidr])) {
|
||||
$redundant[$j] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$result = [];
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
if (! $redundant[$i]) {
|
||||
$result[] = $parsed[$i]['original'];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function get_public_ips()
|
||||
{
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user