diff --git a/pkg/proxy/util/utils.go b/pkg/proxy/util/utils.go index f1db309a941..2c1408da43b 100644 --- a/pkg/proxy/util/utils.go +++ b/pkg/proxy/util/utils.go @@ -17,6 +17,8 @@ limitations under the License. package util import ( + "context" + "errors" "fmt" "net" @@ -35,6 +37,11 @@ const ( IPv6ZeroCIDR = "::/0" ) +var ( + ErrAddressNotAllowed = errors.New("address not allowed") + ErrNoAddresses = errors.New("No addresses for hostname") +) + func IsZeroCIDR(cidr string) bool { if cidr == IPv4ZeroCIDR || cidr == IPv6ZeroCIDR { return true @@ -42,6 +49,46 @@ func IsZeroCIDR(cidr string) bool { return false } +// IsProxyableIP checks if a given IP address is permitted to be proxied +func IsProxyableIP(ip string) error { + netIP := net.ParseIP(ip) + if netIP == nil { + return ErrAddressNotAllowed + } + return isProxyableIP(netIP) +} + +func isProxyableIP(ip net.IP) error { + if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() || ip.IsInterfaceLocalMulticast() { + return ErrAddressNotAllowed + } + return nil +} + +// Resolver is an interface for net.Resolver +type Resolver interface { + LookupIPAddr(ctx context.Context, host string) ([]net.IPAddr, error) +} + +// IsProxyableHostname checks if the IP addresses for a given hostname are permitted to be proxied +func IsProxyableHostname(ctx context.Context, resolv Resolver, hostname string) error { + resp, err := resolv.LookupIPAddr(ctx, hostname) + if err != nil { + return err + } + + if len(resp) == 0 { + return ErrNoAddresses + } + + for _, host := range resp { + if err := isProxyableIP(host.IP); err != nil { + return err + } + } + return nil +} + func IsLocalIP(ip string) (bool, error) { addrs, err := net.InterfaceAddrs() if err != nil { diff --git a/pkg/proxy/util/utils_test.go b/pkg/proxy/util/utils_test.go index 0f4c19f2e8b..891a3520f1c 100644 --- a/pkg/proxy/util/utils_test.go +++ b/pkg/proxy/util/utils_test.go @@ -17,6 +17,7 @@ limitations under the License. package util import ( + "context" "net" "testing" @@ -27,6 +28,74 @@ import ( fake "k8s.io/kubernetes/pkg/proxy/util/testing" ) +func TestIsProxyableIP(t *testing.T) { + testCases := []struct { + ip string + want error + }{ + {"127.0.0.1", ErrAddressNotAllowed}, + {"127.0.0.2", ErrAddressNotAllowed}, + {"169.254.169.254", ErrAddressNotAllowed}, + {"169.254.1.1", ErrAddressNotAllowed}, + {"224.0.0.0", ErrAddressNotAllowed}, + {"10.0.0.1", nil}, + {"192.168.0.1", nil}, + {"172.16.0.1", nil}, + {"8.8.8.8", nil}, + {"::1", ErrAddressNotAllowed}, + {"fe80::", ErrAddressNotAllowed}, + {"ff02::", ErrAddressNotAllowed}, + {"ff01::", ErrAddressNotAllowed}, + {"2600::", nil}, + {"1", ErrAddressNotAllowed}, + {"", ErrAddressNotAllowed}, + } + + for i := range testCases { + got := IsProxyableIP(testCases[i].ip) + if testCases[i].want != got { + t.Errorf("case %d: expected %v, got %v", i, testCases[i].want, got) + } + } +} + +type dummyResolver struct { + ips []string + err error +} + +func (r *dummyResolver) LookupIPAddr(ctx context.Context, host string) ([]net.IPAddr, error) { + if r.err != nil { + return nil, r.err + } + resp := []net.IPAddr{} + for _, ipString := range r.ips { + resp = append(resp, net.IPAddr{IP: net.ParseIP(ipString)}) + } + return resp, nil +} + +func TestIsProxyableHostname(t *testing.T) { + testCases := []struct { + hostname string + ips []string + want error + }{ + {"k8s.io", []string{}, ErrNoAddresses}, + {"k8s.io", []string{"8.8.8.8"}, nil}, + {"k8s.io", []string{"169.254.169.254"}, ErrAddressNotAllowed}, + {"k8s.io", []string{"127.0.0.1", "8.8.8.8"}, ErrAddressNotAllowed}, + } + + for i := range testCases { + resolv := dummyResolver{ips: testCases[i].ips} + got := IsProxyableHostname(context.Background(), &resolv, testCases[i].hostname) + if testCases[i].want != got { + t.Errorf("case %d: expected %v, got %v", i, testCases[i].want, got) + } + } +} + func TestShouldSkipService(t *testing.T) { testCases := []struct { service *v1.Service diff --git a/pkg/registry/core/node/BUILD b/pkg/registry/core/node/BUILD index e026fe00b6d..0819655b314 100644 --- a/pkg/registry/core/node/BUILD +++ b/pkg/registry/core/node/BUILD @@ -19,6 +19,7 @@ go_library( "//pkg/apis/core/validation:go_default_library", "//pkg/features:go_default_library", "//pkg/kubelet/client:go_default_library", + "//pkg/proxy/util:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library", diff --git a/pkg/registry/core/node/strategy.go b/pkg/registry/core/node/strategy.go index 72828b48998..1287f93dee9 100644 --- a/pkg/registry/core/node/strategy.go +++ b/pkg/registry/core/node/strategy.go @@ -40,6 +40,7 @@ import ( "k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/client" + proxyutil "k8s.io/kubernetes/pkg/proxy/util" ) // nodeStrategy implements behavior for nodes @@ -217,6 +218,10 @@ func ResourceLocation(getter ResourceGetter, connection client.ConnectionInfoGet nil } + if err := proxyutil.IsProxyableHostname(ctx, &net.Resolver{}, info.Hostname); err != nil { + return nil, nil, errors.NewBadRequest(err.Error()) + } + // Otherwise, return the requested scheme and port, and the proxy transport return &url.URL{Scheme: schemeReq, Host: net.JoinHostPort(info.Hostname, portReq)}, proxyTransport, nil } diff --git a/pkg/registry/core/pod/BUILD b/pkg/registry/core/pod/BUILD index e7386dfa1ac..ddf8fcb5d15 100644 --- a/pkg/registry/core/pod/BUILD +++ b/pkg/registry/core/pod/BUILD @@ -20,6 +20,7 @@ go_library( "//pkg/apis/core/helper/qos:go_default_library", "//pkg/apis/core/validation:go_default_library", "//pkg/kubelet/client:go_default_library", + "//pkg/proxy/util:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/pkg/registry/core/pod/strategy.go b/pkg/registry/core/pod/strategy.go index 69e77c5a612..4c2ab6becb2 100644 --- a/pkg/registry/core/pod/strategy.go +++ b/pkg/registry/core/pod/strategy.go @@ -47,6 +47,7 @@ import ( "k8s.io/kubernetes/pkg/apis/core/helper/qos" "k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/kubelet/client" + proxyutil "k8s.io/kubernetes/pkg/proxy/util" ) // podStrategy implements behavior for Pods @@ -290,6 +291,10 @@ func ResourceLocation(getter ResourceGetter, rt http.RoundTripper, ctx context.C } } + if err := proxyutil.IsProxyableIP(pod.Status.PodIP); err != nil { + return nil, nil, errors.NewBadRequest(err.Error()) + } + loc := &url.URL{ Scheme: scheme, }