fix(backups): pin a single bracket-free IP for mc --resolve

S3 backup uploads fail when the endpoint has an AAAA record: mc parses
--resolve values with netip.ParseAddr and rejects the bracketed IPv6
form. Its resolver map also keeps only one IP per host, so emit a single
entry, preferring IPv4, without brackets.
This commit is contained in:
Marvin
2026-07-24 07:24:44 +02:00
parent f06bacc8e0
commit 12033e8091
2 changed files with 23 additions and 3 deletions
+10 -3
View File
@@ -134,10 +134,17 @@ class SafeWebhookUrl implements ValidationRule
return [];
}
return array_map(
fn (string $ip): string => sprintf('%s:%d=%s', $target['host'], $target['port'], filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? '['.$ip.']' : $ip),
// The MinIO client keeps one --resolve IP per host and parses it with Go's
// netip.ParseAddr, which rejects bracketed IPv6. Emit a single entry, preferring
// IPv4 for reachability, without brackets.
$preferredIps = array_values(array_filter(
$target['ips'],
);
fn (string $ip): bool => filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false,
));
$ip = $preferredIps[0] ?? $target['ips'][0];
return [sprintf('%s:%d=%s', $target['host'], $target['port'], $ip)];
}
public static function redactedUrlForLog(string $url): string
+13
View File
@@ -239,6 +239,19 @@ it('builds MinIO client resolve options for S3 backup uploads', function () {
expect($options)->toContain('localhost:9000=127.0.0.1');
});
it('collapses dual-stack S3 hosts to a single bracket-free IPv4 resolve entry', function () {
InstanceSettings::unguarded(fn () => InstanceSettings::query()->updateOrCreate(['id' => 0], [
'webhook_allowed_internal_hosts' => ['localhost'],
'webhook_allow_localhost' => true,
]));
// localhost resolves to both 127.0.0.1 and ::1. The MinIO client keeps one IP per
// host and rejects bracketed IPv6, so exactly one bracket-free IPv4 entry is produced.
$options = SafeWebhookUrl::minioClientResolveOptions('http://localhost:9000');
expect($options)->toBe(['localhost:9000=127.0.0.1']);
});
it('rejects trailing-dot hostnames to avoid DNS pinning mismatch', function () {
$rule = new SafeWebhookUrl(fn (string $host): array => ['93.184.216.34']);