diff --git a/changelog/25080.txt b/changelog/25080.txt new file mode 100644 index 000000000000..ad43618d8281 --- /dev/null +++ b/changelog/25080.txt @@ -0,0 +1,3 @@ +```release-note:bug +storage/raft: Fix auto_join not working with mDNS provider. +``` diff --git a/vault/raft.go b/vault/raft.go index 3aaab7a84745..2d5969896b5d 100644 --- a/vault/raft.go +++ b/vault/raft.go @@ -8,6 +8,7 @@ import ( "encoding/base64" "errors" "fmt" + "net" "net/http" "net/url" "strings" @@ -1227,17 +1228,15 @@ func (c *Core) raftLeaderInfo(leaderInfo *raft.LeaderJoinInfo, disco *discover.D // default to 8200 when no port is provided port = 8200 } - // Addrs returns either IPv4 or IPv6 address, without scheme or port + // Addrs returns either IPv4 or IPv6 address, without scheme and most of them without port + // IPv6 can be explicit such as "[::1]" or implicit "::1" clusterIPs, err := disco.Addrs(leaderInfo.AutoJoin, c.logger.StandardLogger(nil)) if err != nil { return nil, fmt.Errorf("failed to parse addresses from auto-join metadata: %w", err) } for _, ip := range clusterIPs { - if strings.Count(ip, ":") >= 2 && !strings.HasPrefix(ip, "[") { - // An IPv6 address in implicit form, however we need it in explicit form to use in a URL. - ip = fmt.Sprintf("[%s]", ip) - } - u := fmt.Sprintf("%s://%s:%d", scheme, ip, port) + addr := formatDiscoveredAddr(ip, port) + u := fmt.Sprintf("%s://%s", scheme, addr) info := *leaderInfo info.LeaderAPIAddr = u ret = append(ret, &info) @@ -1459,3 +1458,19 @@ func newDiscover() (*discover.Discover, error) { discover.WithProviders(providers), ) } + +// formatDiscoveredAddr joins ip and port if addr does not already contain a port +func formatDiscoveredAddr(addr string, defaultPort uint) string { + // addr is an implicit IPv6 address + if !strings.HasPrefix(addr, "[") && strings.Count(addr, ":") > 1 { + return fmt.Sprintf("[%s]:%d", addr, defaultPort) + } + ip, port, err := net.SplitHostPort(addr) + if err != nil { + return fmt.Sprintf("%s:%d", addr, defaultPort) + } + if strings.ContainsRune(ip, ':') { + ip = fmt.Sprintf("[%s]", ip) + } + return fmt.Sprintf("%s:%s", ip, port) +} diff --git a/vault/raft_test.go b/vault/raft_test.go new file mode 100644 index 000000000000..b5f54b590519 --- /dev/null +++ b/vault/raft_test.go @@ -0,0 +1,33 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package vault + +import ( + "testing" +) + +// TestFormatDiscoveredAddr validates that the string returned by formatDiscoveredAddr always respect the format `host:port`. +func TestFormatDiscoveredAddr(t *testing.T) { + type TestCase struct { + addr string + port uint + res string + } + cases := []TestCase{ + {addr: "127.0.0.1", port: uint(8200), res: "127.0.0.1:8200"}, + {addr: "192.168.137.1:8201", port: uint(8200), res: "192.168.137.1:8201"}, + {addr: "fe80::aa5e:45ff:fe54:c6ce", port: uint(8200), res: "[fe80::aa5e:45ff:fe54:c6ce]:8200"}, + {addr: "::1", port: uint(8200), res: "[::1]:8200"}, + {addr: "[::1]", port: uint(8200), res: "[::1]:8200"}, + {addr: "[::1]:8201", port: uint(8200), res: "[::1]:8201"}, + {addr: "[fe80::aa5e:45ff:fe54:c6ce]", port: uint(8200), res: "[fe80::aa5e:45ff:fe54:c6ce]:8200"}, + {addr: "[fe80::aa5e:45ff:fe54:c6ce]:8201", port: uint(8200), res: "[fe80::aa5e:45ff:fe54:c6ce]:8201"}, + } + for i, c := range cases { + res := formatDiscoveredAddr(c.addr, c.port) + if res != c.res { + t.Errorf("case %d result shoud be \"%s\" but is \"%s\"", i, c.res, res) + } + } +}