Unverified Commit a5c46303 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #57265 from brendandburns/svc-proxy

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. By default block service proxy to external IP addresses. **What this PR does / why we need it**: Currently, the Service Proxy on the APIServer allows unrestricted access to any IP address that the APIServer machine can reach. This is likely undesirable in many cases. Update the service proxy so that it filters Endpoints to only those that have a TargetRef that matches a known Pod. Fixes https://github.com/kubernetes/kubernetes/issues/58761 **Release note**: ```release-note By default disable access to external IP addresses from the apiserver service proxy. ```
parents 13b12e89 dcb9b4b5
......@@ -168,6 +168,13 @@ const (
// Enable nodes to exclude themselves from service load balancers
ServiceNodeExclusion utilfeature.Feature = "ServiceNodeExclusion"
// owner @brendandburns
// stable: v1.10
//
// Enable the service proxy to contact external IP addresses. Note this feature is present
// only for backward compatability, it will be removed in the 1.11 release.
ServiceProxyAllowExternalIPs utilfeature.Feature = "ServiceProxyAllowExternalIPs"
// owner: @jsafrane
// alpha: v1.9
//
......@@ -268,4 +275,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
// inherited features from apiextensions-apiserver, relisted here to get a conflict if it is changed
// unintentionally on either side:
apiextensionsfeatures.CustomResourceValidation: {Default: true, PreRelease: utilfeature.Beta},
// backward compatability features that enable backwards compatability but should be removed soon.
ServiceProxyAllowExternalIPs: {Default: false, PreRelease: utilfeature.Beta},
}
......@@ -172,7 +172,7 @@ func (c LegacyRESTStorageProvider) NewLegacyRESTStorage(restOptionsGetter generi
controllerStorage := controllerstore.NewStorage(restOptionsGetter)
serviceRest := service.NewStorage(serviceRegistry, endpointRegistry, ServiceClusterIPAllocator, ServiceNodePortAllocator, c.ProxyTransport)
serviceRest := service.NewStorage(serviceRegistry, endpointRegistry, podStorage.Pod, ServiceClusterIPAllocator, ServiceNodePortAllocator, c.ProxyTransport)
restStorageMap := map[string]rest.Storage{
"pods": podStorage.Pod,
......
......@@ -23,7 +23,9 @@ go_library(
"//pkg/apis/core/helper:go_default_library",
"//pkg/apis/core/validation:go_default_library",
"//pkg/capabilities:go_default_library",
"//pkg/features:go_default_library",
"//pkg/registry/core/endpoint:go_default_library",
"//pkg/registry/core/pod/storage:go_default_library",
"//pkg/registry/core/service/ipallocator:go_default_library",
"//pkg/registry/core/service/portallocator:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
......@@ -39,6 +41,7 @@ go_library(
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
],
)
......@@ -54,6 +57,7 @@ go_test(
"//pkg/api/service:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/helper:go_default_library",
"//pkg/registry/core/pod/storage:go_default_library",
"//pkg/registry/core/service/ipallocator:go_default_library",
"//pkg/registry/core/service/portallocator:go_default_library",
"//pkg/registry/registrytest:go_default_library",
......@@ -65,7 +69,9 @@ go_test(
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/rand:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library",
],
)
......
......@@ -35,11 +35,14 @@ import (
"k8s.io/apimachinery/pkg/watch"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
utilfeature "k8s.io/apiserver/pkg/util/feature"
apiservice "k8s.io/kubernetes/pkg/api/service"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/helper"
"k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/registry/core/endpoint"
podstore "k8s.io/kubernetes/pkg/registry/core/pod/storage"
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
"k8s.io/kubernetes/pkg/registry/core/service/portallocator"
)
......@@ -57,6 +60,7 @@ type REST struct {
serviceIPs ipallocator.Interface
serviceNodePorts portallocator.Interface
proxyTransport http.RoundTripper
pods *podstore.REST
}
// ServiceNodePort includes protocol and port number of a service NodePort.
......@@ -70,7 +74,7 @@ type ServiceNodePort struct {
}
// NewStorage returns a new REST.
func NewStorage(registry Registry, endpoints endpoint.Registry, serviceIPs ipallocator.Interface,
func NewStorage(registry Registry, endpoints endpoint.Registry, pods *podstore.REST, serviceIPs ipallocator.Interface,
serviceNodePorts portallocator.Interface, proxyTransport http.RoundTripper) *ServiceRest {
rest := &REST{
registry: registry,
......@@ -78,6 +82,7 @@ func NewStorage(registry Registry, endpoints endpoint.Registry, serviceIPs ipall
serviceIPs: serviceIPs,
serviceNodePorts: serviceNodePorts,
proxyTransport: proxyTransport,
pods: pods,
}
return &ServiceRest{
Service: rest,
......@@ -425,19 +430,57 @@ func (rs *REST) ResourceLocation(ctx genericapirequest.Context, id string) (*url
}
for i := range ss.Ports {
if ss.Ports[i].Name == portStr {
// Pick a random address.
ip := ss.Addresses[rand.Intn(len(ss.Addresses))].IP
port := int(ss.Ports[i].Port)
return &url.URL{
Scheme: svcScheme,
Host: net.JoinHostPort(ip, strconv.Itoa(port)),
}, rs.proxyTransport, nil
addrSeed := rand.Intn(len(ss.Addresses))
// This is a little wonky, but it's expensive to test for the presence of a Pod
// So we repeatedly try at random and validate it, this means that for an invalid
// service with a lot of endpoints we're going to potentially make a lot of calls,
// but in the expected case we'll only make one.
for try := 0; try < len(ss.Addresses); try++ {
addr := ss.Addresses[(addrSeed+try)%len(ss.Addresses)]
if !utilfeature.DefaultFeatureGate.Enabled(features.ServiceProxyAllowExternalIPs) {
if err := isValidAddress(ctx, &addr, rs.pods); err != nil {
glog.Errorf("Address %v isn't valid (%v)", addr, err)
continue
}
}
ip := addr.IP
port := int(ss.Ports[i].Port)
return &url.URL{
Scheme: svcScheme,
Host: net.JoinHostPort(ip, strconv.Itoa(port)),
}, rs.proxyTransport, nil
}
glog.Errorf("Failed to find a valid address, skipping subset: %v", ss)
}
}
}
return nil, nil, errors.NewServiceUnavailable(fmt.Sprintf("no endpoints available for service %q", id))
}
func isValidAddress(ctx genericapirequest.Context, addr *api.EndpointAddress, pods *podstore.REST) error {
if addr.TargetRef == nil {
return fmt.Errorf("Address has no target ref, skipping: %v", addr)
}
if genericapirequest.NamespaceValue(ctx) != addr.TargetRef.Namespace {
return fmt.Errorf("Address namespace doesn't match context namespace")
}
obj, err := pods.Get(ctx, addr.TargetRef.Name, &metav1.GetOptions{})
if err != nil {
return err
}
pod, ok := obj.(*api.Pod)
if !ok {
return fmt.Errorf("failed to cast to pod: %v", obj)
}
if pod == nil {
return fmt.Errorf("pod is missing, skipping (%s/%s)", addr.TargetRef.Namespace, addr.TargetRef.Name)
}
if pod.Status.PodIP != addr.IP {
return fmt.Errorf("pod ip doesn't match endpoint ip, skipping: %s vs %s (%s/%s)", pod.Status.PodIP, addr.IP, addr.TargetRef.Namespace, addr.TargetRef.Name)
}
return nil
}
// This is O(N), but we expect haystack to be small;
// so small that we expect a linear search to be faster
func containsNumber(haystack []int, needle int) bool {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment