Merge pull request #71981 from micahhausler/automated-cherry-pick-of-#71980-upstream-release-1.13

Automated cherry pick of #71980: Disable proxy to loopback and linklocal
pull/58/head
Kubernetes Prow Robot 2018-12-12 03:53:55 -08:00 committed by GitHub
commit 1c51137454
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 128 additions and 0 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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",

View File

@ -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
}

View File

@ -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",

View File

@ -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,
}