mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 17:52:32 +00:00
VAULT-33758: IPv6 address conformance for proxy and agent (#29517)
This is a follow-up to our initial work[0] to address RFC-5952 §4 conformance for IPv6 addresses in Vault. The initial pass focused on the vault server configuration and start-up routines. This follow-up focuses on Agent and Proxy, with a few minor improvements for server. The approach generally mirrors the server implementation but also adds support for normalization with CLI configuration overrides. One aspect we do not normalize currently is Agent/Proxy client creation to the Vault server with credentials taken from environment variables, as it would require larger changes to the `api` module. In practice this ought to be fine for the majority of cases. [0]: https://github.com/hashicorp/vault/pull/29228
This commit is contained in:
@@ -4,88 +4,106 @@
|
||||
package configutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NormalizeAddr takes an address as a string and returns a normalized copy.
|
||||
// If the addr is a URL, IP Address, or host:port address that includes an IPv6
|
||||
// address, the normalized copy will be conformant with RFC-5942 §4
|
||||
// See: https://rfc-editor.org/rfc/rfc5952.html
|
||||
func NormalizeAddr(address string) string {
|
||||
if address == "" {
|
||||
// NormalizeAddr takes a string of a Host, Host:Port, URL, or Destination
|
||||
// Address and returns a copy where any IP addresses have been normalized to be
|
||||
// conformant with RFC 5942 §4. If the input string does not match any of the
|
||||
// supported syntaxes, or the "host" section is not an IP address, the input
|
||||
// will be returned unchanged. Supported syntaxes are:
|
||||
//
|
||||
// Host host or [host]
|
||||
// Host:Port host:port or [host]:port
|
||||
// URL scheme://user@host/path?query#frag or scheme://user@[host]/path?query#frag
|
||||
// Destination Address user@host:port or user@[host]:port
|
||||
//
|
||||
// See:
|
||||
//
|
||||
// https://rfc-editor.org/rfc/rfc3986.html
|
||||
// https://rfc-editor.org/rfc/rfc5942.html
|
||||
// https://rfc-editor.org/rfc/rfc5952.html
|
||||
func NormalizeAddr(addr string) string {
|
||||
if addr == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
var ip net.IP
|
||||
var port string
|
||||
bracketedIPv6 := false
|
||||
// Host
|
||||
ip := net.ParseIP(addr)
|
||||
if ip != nil {
|
||||
// net.IP.String() is RFC 5942 §4 compliant
|
||||
return ip.String()
|
||||
}
|
||||
|
||||
// Try parsing it as a URL
|
||||
pu, err := url.Parse(address)
|
||||
// [Host]
|
||||
if strings.HasPrefix(addr, "[") && strings.HasSuffix(addr, "]") {
|
||||
if len(addr) < 3 {
|
||||
return addr
|
||||
}
|
||||
|
||||
// If we've been given a bracketed IP address, return the address
|
||||
// normalized without brackets.
|
||||
ip := net.ParseIP(addr[1 : len(addr)-1])
|
||||
if ip != nil {
|
||||
return ip.String()
|
||||
}
|
||||
|
||||
// Our input is not a valid schema.
|
||||
return addr
|
||||
}
|
||||
|
||||
// Host:Port
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err == nil {
|
||||
// We've been given something that appears to be a URL. See if the hostname
|
||||
// is an IP address
|
||||
ip = net.ParseIP(pu.Hostname())
|
||||
} else {
|
||||
// We haven't been given a URL. Try and parse it as an IP address
|
||||
ip = net.ParseIP(address)
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil {
|
||||
// We haven't been given a URL or IP address, try parsing an IP:Port
|
||||
// combination.
|
||||
idx := strings.LastIndex(address, ":")
|
||||
if idx > 0 {
|
||||
// We've perhaps received an IP:Port address
|
||||
addr := address[:idx]
|
||||
port = address[idx+1:]
|
||||
if strings.HasPrefix(addr, "[") && strings.HasSuffix(addr, "]") {
|
||||
addr = strings.TrimPrefix(strings.TrimSuffix(addr, "]"), "[")
|
||||
bracketedIPv6 = true
|
||||
}
|
||||
ip = net.ParseIP(addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If our IP is nil whatever was passed in does not contain an IP address.
|
||||
if ip == nil {
|
||||
return address
|
||||
}
|
||||
|
||||
if v4 := ip.To4(); v4 != nil {
|
||||
return address
|
||||
}
|
||||
|
||||
if v6 := ip.To16(); v6 != nil {
|
||||
// net.IP String() will return IPv6 RFC-5952 conformant addresses.
|
||||
|
||||
if pu != nil {
|
||||
// Return the URL in conformant fashion
|
||||
if port := pu.Port(); port != "" {
|
||||
pu.Host = fmt.Sprintf("[%s]:%s", v6.String(), port)
|
||||
} else {
|
||||
pu.Host = fmt.Sprintf("[%s]", v6.String())
|
||||
}
|
||||
return pu.String()
|
||||
// Our host isn't an IP address so we can return it unchanged
|
||||
return addr
|
||||
}
|
||||
|
||||
// Handle IP:Port addresses
|
||||
if port != "" {
|
||||
// Return the address:port or [address]:port
|
||||
if bracketedIPv6 {
|
||||
return fmt.Sprintf("[%s]:%s", v6.String(), port)
|
||||
} else {
|
||||
return fmt.Sprintf("%s:%s", v6.String(), port)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle just an IP address
|
||||
return v6.String()
|
||||
// net.JoinHostPort handles bracketing for RFC 5952 §6
|
||||
return net.JoinHostPort(ip.String(), port)
|
||||
}
|
||||
|
||||
// It shouldn't be possible to get to this point. If we somehow we manage
|
||||
// to, return the string unchanged.
|
||||
return address
|
||||
// URL
|
||||
u, err := url.Parse(addr)
|
||||
if err == nil {
|
||||
uhost := u.Hostname()
|
||||
ip := net.ParseIP(uhost)
|
||||
if ip == nil {
|
||||
// Our URL doesn't contain an IP address so we can return our input unchanged.
|
||||
return addr
|
||||
} else {
|
||||
uhost = ip.String()
|
||||
}
|
||||
|
||||
if uport := u.Port(); uport != "" {
|
||||
uhost = net.JoinHostPort(uhost, uport)
|
||||
} else {
|
||||
if !strings.HasPrefix(uhost, "[") && !strings.HasSuffix(uhost, "]") {
|
||||
// Ensure the IPv6 URL host is bracketed post-normalization.
|
||||
// When*url.URL.String() reassembles the URL it will not consider
|
||||
// whether or not the *url.URL.Host is RFC 5952 §6 and RFC 3986 §3.2.2
|
||||
// conformant.
|
||||
uhost = "[" + uhost + "]"
|
||||
}
|
||||
}
|
||||
u.Host = uhost
|
||||
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// Destination Address
|
||||
if idx := strings.LastIndex(addr, "@"); idx > 0 {
|
||||
if idx+1 > len(addr) {
|
||||
return addr
|
||||
}
|
||||
|
||||
return addr[:idx+1] + NormalizeAddr(addr[idx+1:])
|
||||
}
|
||||
|
||||
// Our input did not match our supported schemas. Return it unchanged.
|
||||
return addr
|
||||
}
|
||||
|
||||
@@ -21,26 +21,86 @@ func TestNormalizeAddr(t *testing.T) {
|
||||
isErrorExpected bool
|
||||
}{
|
||||
"hostname": {
|
||||
addr: "vaultproject.io",
|
||||
expected: "vaultproject.io",
|
||||
},
|
||||
"hostname port": {
|
||||
addr: "vaultproject.io:8200",
|
||||
expected: "vaultproject.io:8200",
|
||||
},
|
||||
"hostname URL": {
|
||||
addr: "https://vaultproject.io",
|
||||
expected: "https://vaultproject.io",
|
||||
},
|
||||
"hostname port URL": {
|
||||
addr: "https://vaultproject.io:8200",
|
||||
expected: "https://vaultproject.io:8200",
|
||||
},
|
||||
"hostname destination address": {
|
||||
addr: "user@vaultproject.io",
|
||||
expected: "user@vaultproject.io",
|
||||
},
|
||||
"hostname destination address URL": {
|
||||
addr: "http://user@vaultproject.io",
|
||||
expected: "http://user@vaultproject.io",
|
||||
},
|
||||
"hostname destination address URL port": {
|
||||
addr: "http://user@vaultproject.io:8200",
|
||||
expected: "http://user@vaultproject.io:8200",
|
||||
},
|
||||
"ipv4": {
|
||||
addr: "10.10.1.10",
|
||||
expected: "10.10.1.10",
|
||||
},
|
||||
"ipv4 invalid bracketed": {
|
||||
addr: "[10.10.1.10]",
|
||||
expected: "10.10.1.10",
|
||||
},
|
||||
"ipv4 IP:Port addr": {
|
||||
addr: "10.10.1.10:8500",
|
||||
expected: "10.10.1.10:8500",
|
||||
},
|
||||
"ipv4 invalid IP:Port addr": {
|
||||
addr: "[10.10.1.10]:8500",
|
||||
expected: "10.10.1.10:8500",
|
||||
},
|
||||
"ipv4 URL": {
|
||||
addr: "https://10.10.1.10:8200",
|
||||
expected: "https://10.10.1.10:8200",
|
||||
},
|
||||
"ipv6 IP:Port addr no brackets": {
|
||||
addr: "2001:0db8::0001:8500",
|
||||
expected: "2001:db8::1:8500",
|
||||
"ipv4 invalid URL": {
|
||||
addr: "https://[10.10.1.10]:8200",
|
||||
expected: "https://10.10.1.10:8200",
|
||||
},
|
||||
"ipv6 IP:Port addr with brackets": {
|
||||
"ipv4 destination address": {
|
||||
addr: "username@10.10.1.10",
|
||||
expected: "username@10.10.1.10",
|
||||
},
|
||||
"ipv4 invalid destination address": {
|
||||
addr: "username@10.10.1.10",
|
||||
expected: "username@10.10.1.10",
|
||||
},
|
||||
"ipv4 destination address port": {
|
||||
addr: "username@10.10.1.10:8200",
|
||||
expected: "username@10.10.1.10:8200",
|
||||
},
|
||||
"ipv4 invalid destination address port": {
|
||||
addr: "username@[10.10.1.10]:8200",
|
||||
expected: "username@10.10.1.10:8200",
|
||||
},
|
||||
"ipv4 destination address URL": {
|
||||
addr: "https://username@10.10.1.10",
|
||||
expected: "https://username@10.10.1.10",
|
||||
},
|
||||
"ipv4 destination address URL port": {
|
||||
addr: "https://username@10.10.1.10:8200",
|
||||
expected: "https://username@10.10.1.10:8200",
|
||||
},
|
||||
"ipv6 invalid address": {
|
||||
addr: "[2001:0db8::0001]",
|
||||
expected: "2001:db8::1",
|
||||
},
|
||||
"ipv6 IP:Port RFC-5952 4.1 conformance leading zeroes": {
|
||||
addr: "[2001:0db8::0001]:8500",
|
||||
expected: "[2001:db8::1]:8500",
|
||||
},
|
||||
@@ -52,6 +112,26 @@ func TestNormalizeAddr(t *testing.T) {
|
||||
addr: "https://[2001:0db8::0001]:8200",
|
||||
expected: "https://[2001:db8::1]:8200",
|
||||
},
|
||||
"ipv6 bracketed destination address with port RFC-5952 4.1 conformance leading zeroes": {
|
||||
addr: "username@[2001:0db8::0001]:8200",
|
||||
expected: "username@[2001:db8::1]:8200",
|
||||
},
|
||||
"ipv6 invalid ambiguous destination address with port": {
|
||||
addr: "username@2001:0db8::0001:8200",
|
||||
// Since the address and port are ambiguous the value appears to be
|
||||
// only an address and as such is normalized as an address only
|
||||
expected: "username@2001:db8::1:8200",
|
||||
},
|
||||
"ipv6 invalid leading zeroes ambiguous destination address with port": {
|
||||
addr: "username@2001:db8:0:1:1:1:1:1:8200",
|
||||
// Since the address and port are ambiguous the value is treated as
|
||||
// a string because it has too many colons to be a valid IPv6 address.
|
||||
expected: "username@2001:db8:0:1:1:1:1:1:8200",
|
||||
},
|
||||
"ipv6 destination address no port RFC-5952 4.1 conformance leading zeroes": {
|
||||
addr: "username@2001:0db8::0001",
|
||||
expected: "username@2001:db8::1",
|
||||
},
|
||||
"ipv6 RFC-5952 4.2.2 conformance one 16-bit 0 field": {
|
||||
addr: "2001:db8:0:1:1:1:1:1",
|
||||
expected: "2001:db8:0:1:1:1:1:1",
|
||||
@@ -60,6 +140,14 @@ func TestNormalizeAddr(t *testing.T) {
|
||||
addr: "https://[2001:db8:0:1:1:1:1:1]:8200",
|
||||
expected: "https://[2001:db8:0:1:1:1:1:1]:8200",
|
||||
},
|
||||
"ipv6 destination address with port RFC-5952 4.2.2 conformance one 16-bit 0 field": {
|
||||
addr: "username@[2001:db8:0:1:1:1:1:1]:8200",
|
||||
expected: "username@[2001:db8:0:1:1:1:1:1]:8200",
|
||||
},
|
||||
"ipv6 destination address no port RFC-5952 4.2.2 conformance one 16-bit 0 field": {
|
||||
addr: "username@2001:db8:0:1:1:1:1:1",
|
||||
expected: "username@2001:db8:0:1:1:1:1:1",
|
||||
},
|
||||
"ipv6 RFC-5952 4.2.3 conformance longest run of 0 bits shortened": {
|
||||
addr: "2001:0:0:1:0:0:0:1",
|
||||
expected: "2001:0:0:1::1",
|
||||
@@ -68,14 +156,35 @@ func TestNormalizeAddr(t *testing.T) {
|
||||
addr: "https://[2001:0:0:1:0:0:0:1]:8200",
|
||||
expected: "https://[2001:0:0:1::1]:8200",
|
||||
},
|
||||
"ipv6 destination address with port RFC-5952 4.2.3 conformance longest run of 0 bits shortened": {
|
||||
addr: "username@[2001:0:0:1:0:0:0:1]:8200",
|
||||
expected: "username@[2001:0:0:1::1]:8200",
|
||||
},
|
||||
"ipv6 destination address no port RFC-5952 4.2.3 conformance longest run of 0 bits shortened": {
|
||||
addr: "username@2001:0:0:1:0:0:0:1",
|
||||
expected: "username@2001:0:0:1::1",
|
||||
},
|
||||
"ipv6 RFC-5952 4.2.3 conformance equal runs of 0 bits shortened": {
|
||||
addr: "2001:db8:0:0:1:0:0:1",
|
||||
expected: "2001:db8::1:0:0:1",
|
||||
},
|
||||
"ipv6 URL RFC-5952 4.2.3 conformance equal runs of 0 bits shortened": {
|
||||
"ipv6 URL no port RFC-5952 4.2.3 conformance equal runs of 0 bits shortened": {
|
||||
addr: "https://[2001:db8:0:0:1:0:0:1]",
|
||||
expected: "https://[2001:db8::1:0:0:1]",
|
||||
},
|
||||
"ipv6 URL with port RFC-5952 4.2.3 conformance equal runs of 0 bits shortened": {
|
||||
addr: "https://[2001:db8:0:0:1:0:0:1]:8200",
|
||||
expected: "https://[2001:db8::1:0:0:1]:8200",
|
||||
},
|
||||
|
||||
"ipv6 destination address with port RFC-5952 4.2.3 conformance equal runs of 0 bits shortened": {
|
||||
addr: "username@[2001:db8:0:0:1:0:0:1]:8200",
|
||||
expected: "username@[2001:db8::1:0:0:1]:8200",
|
||||
},
|
||||
"ipv6 destination address no port RFC-5952 4.2.3 conformance equal runs of 0 bits shortened": {
|
||||
addr: "username@2001:db8:0:0:1:0:0:1",
|
||||
expected: "username@2001:db8::1:0:0:1",
|
||||
},
|
||||
"ipv6 RFC-5952 4.3 conformance downcase hex letters": {
|
||||
addr: "2001:DB8:AC3:FE4::1",
|
||||
expected: "2001:db8:ac3:fe4::1",
|
||||
@@ -84,6 +193,14 @@ func TestNormalizeAddr(t *testing.T) {
|
||||
addr: "https://[2001:DB8:AC3:FE4::1]:8200",
|
||||
expected: "https://[2001:db8:ac3:fe4::1]:8200",
|
||||
},
|
||||
"ipv6 destination address with port RFC-5952 4.3 conformance downcase hex letters": {
|
||||
addr: "username@[2001:DB8:AC3:FE4::1]:8200",
|
||||
expected: "username@[2001:db8:ac3:fe4::1]:8200",
|
||||
},
|
||||
"ipv6 destination address no port RFC-5952 4.3 conformance downcase hex letters": {
|
||||
addr: "username@2001:DB8:AC3:FE4::1",
|
||||
expected: "username@2001:db8:ac3:fe4::1",
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
name := name
|
||||
|
||||
Reference in New Issue
Block a user