Commit dcb9b4b5 authored by Brendan Burns's avatar Brendan Burns

By default block service proxy to external IP addresses.

Service proxy uses redirects to Pods instead of direct access.
parent ddea2dd5
...@@ -168,6 +168,13 @@ const ( ...@@ -168,6 +168,13 @@ const (
// Enable nodes to exclude themselves from service load balancers // Enable nodes to exclude themselves from service load balancers
ServiceNodeExclusion utilfeature.Feature = "ServiceNodeExclusion" 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 // owner: @jsafrane
// alpha: v1.9 // alpha: v1.9
// //
...@@ -268,4 +275,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS ...@@ -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 // inherited features from apiextensions-apiserver, relisted here to get a conflict if it is changed
// unintentionally on either side: // unintentionally on either side:
apiextensionsfeatures.CustomResourceValidation: {Default: true, PreRelease: utilfeature.Beta}, 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 ...@@ -172,7 +172,7 @@ func (c LegacyRESTStorageProvider) NewLegacyRESTStorage(restOptionsGetter generi
controllerStorage := controllerstore.NewStorage(restOptionsGetter) 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{ restStorageMap := map[string]rest.Storage{
"pods": podStorage.Pod, "pods": podStorage.Pod,
......
...@@ -23,7 +23,9 @@ go_library( ...@@ -23,7 +23,9 @@ go_library(
"//pkg/apis/core/helper:go_default_library", "//pkg/apis/core/helper:go_default_library",
"//pkg/apis/core/validation:go_default_library", "//pkg/apis/core/validation:go_default_library",
"//pkg/capabilities:go_default_library", "//pkg/capabilities:go_default_library",
"//pkg/features:go_default_library",
"//pkg/registry/core/endpoint: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/ipallocator:go_default_library",
"//pkg/registry/core/service/portallocator:go_default_library", "//pkg/registry/core/service/portallocator:go_default_library",
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
...@@ -39,6 +41,7 @@ go_library( ...@@ -39,6 +41,7 @@ go_library(
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_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/registry/rest:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/names: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( ...@@ -54,6 +57,7 @@ go_test(
"//pkg/api/service:go_default_library", "//pkg/api/service:go_default_library",
"//pkg/apis/core:go_default_library", "//pkg/apis/core:go_default_library",
"//pkg/apis/core/helper: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/ipallocator:go_default_library",
"//pkg/registry/core/service/portallocator:go_default_library", "//pkg/registry/core/service/portallocator:go_default_library",
"//pkg/registry/registrytest:go_default_library", "//pkg/registry/registrytest:go_default_library",
...@@ -65,7 +69,9 @@ go_test( ...@@ -65,7 +69,9 @@ go_test(
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/rand: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/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/registry/rest:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library",
], ],
) )
......
...@@ -35,11 +35,14 @@ import ( ...@@ -35,11 +35,14 @@ import (
"k8s.io/apimachinery/pkg/watch" "k8s.io/apimachinery/pkg/watch"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request" genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/registry/rest"
utilfeature "k8s.io/apiserver/pkg/util/feature"
apiservice "k8s.io/kubernetes/pkg/api/service" apiservice "k8s.io/kubernetes/pkg/api/service"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/apis/core/helper"
"k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/registry/core/endpoint" "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/ipallocator"
"k8s.io/kubernetes/pkg/registry/core/service/portallocator" "k8s.io/kubernetes/pkg/registry/core/service/portallocator"
) )
...@@ -57,6 +60,7 @@ type REST struct { ...@@ -57,6 +60,7 @@ type REST struct {
serviceIPs ipallocator.Interface serviceIPs ipallocator.Interface
serviceNodePorts portallocator.Interface serviceNodePorts portallocator.Interface
proxyTransport http.RoundTripper proxyTransport http.RoundTripper
pods *podstore.REST
} }
// ServiceNodePort includes protocol and port number of a service NodePort. // ServiceNodePort includes protocol and port number of a service NodePort.
...@@ -70,7 +74,7 @@ type ServiceNodePort struct { ...@@ -70,7 +74,7 @@ type ServiceNodePort struct {
} }
// NewStorage returns a new REST. // 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 { serviceNodePorts portallocator.Interface, proxyTransport http.RoundTripper) *ServiceRest {
rest := &REST{ rest := &REST{
registry: registry, registry: registry,
...@@ -78,6 +82,7 @@ func NewStorage(registry Registry, endpoints endpoint.Registry, serviceIPs ipall ...@@ -78,6 +82,7 @@ func NewStorage(registry Registry, endpoints endpoint.Registry, serviceIPs ipall
serviceIPs: serviceIPs, serviceIPs: serviceIPs,
serviceNodePorts: serviceNodePorts, serviceNodePorts: serviceNodePorts,
proxyTransport: proxyTransport, proxyTransport: proxyTransport,
pods: pods,
} }
return &ServiceRest{ return &ServiceRest{
Service: rest, Service: rest,
...@@ -425,19 +430,57 @@ func (rs *REST) ResourceLocation(ctx genericapirequest.Context, id string) (*url ...@@ -425,19 +430,57 @@ func (rs *REST) ResourceLocation(ctx genericapirequest.Context, id string) (*url
} }
for i := range ss.Ports { for i := range ss.Ports {
if ss.Ports[i].Name == portStr { if ss.Ports[i].Name == portStr {
// Pick a random address. addrSeed := rand.Intn(len(ss.Addresses))
ip := ss.Addresses[rand.Intn(len(ss.Addresses))].IP // This is a little wonky, but it's expensive to test for the presence of a Pod
port := int(ss.Ports[i].Port) // So we repeatedly try at random and validate it, this means that for an invalid
return &url.URL{ // service with a lot of endpoints we're going to potentially make a lot of calls,
Scheme: svcScheme, // but in the expected case we'll only make one.
Host: net.JoinHostPort(ip, strconv.Itoa(port)), for try := 0; try < len(ss.Addresses); try++ {
}, rs.proxyTransport, nil 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)) 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; // This is O(N), but we expect haystack to be small;
// so small that we expect a linear search to be faster // so small that we expect a linear search to be faster
func containsNumber(haystack []int, needle int) bool { 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