From cf7e2756b56f3c34c419f704ecaa6b37881041a5 Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Tue, 24 Mar 2015 16:09:16 -0700 Subject: [PATCH] Add HostNetworkSources capability to limit use of HostNetwork. --- cmd/kube-apiserver/app/server.go | 2 ++ cmd/kubelet/app/server.go | 12 ++++++- pkg/capabilities/capabilities.go | 6 +++- pkg/kubelet/kubelet.go | 26 +++++++++++++- pkg/kubelet/kubelet_test.go | 59 ++++++++++++++++++++++++++++++++ pkg/kubelet/types.go | 25 +++++++++++++- pkg/kubelet/types_test.go | 44 ++++++++++++++++++++++++ pkg/kubelet/util.go | 5 +-- 8 files changed, 173 insertions(+), 6 deletions(-) create mode 100644 pkg/kubelet/types_test.go diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index c6899821953..916683192f8 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -184,6 +184,8 @@ func (s *APIServer) Run(_ []string) error { capabilities.Initialize(capabilities.Capabilities{ AllowPrivileged: s.AllowPrivileged, + // TODO(vmarmol): Implement support for HostNetworkSources. + HostNetworkSources: []string{}, }) cloud := cloudprovider.InitCloudProvider(s.CloudProvider, s.CloudConfigFile) diff --git a/cmd/kubelet/app/server.go b/cmd/kubelet/app/server.go index ff56532450d..19af21c1b02 100644 --- a/cmd/kubelet/app/server.go +++ b/cmd/kubelet/app/server.go @@ -21,6 +21,7 @@ import ( "fmt" "math/rand" "net" + "strings" "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" @@ -61,6 +62,7 @@ type KubeletServer struct { DockerEndpoint string RootDirectory string AllowPrivileged bool + HostNetworkSources string RegistryPullQPS float64 RegistryBurst int RunOnce bool @@ -106,6 +108,7 @@ func NewKubeletServer() *KubeletServer { ImageGCHighThresholdPercent: 90, ImageGCLowThresholdPercent: 80, NetworkPluginName: "", + HostNetworkSources: kubelet.FileSource, } } @@ -124,6 +127,7 @@ func (s *KubeletServer) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&s.DockerEndpoint, "docker_endpoint", s.DockerEndpoint, "If non-empty, use this for the docker endpoint to communicate with") fs.StringVar(&s.RootDirectory, "root_dir", s.RootDirectory, "Directory path for managing kubelet files (volume mounts,etc).") fs.BoolVar(&s.AllowPrivileged, "allow_privileged", s.AllowPrivileged, "If true, allow containers to request privileged mode. [default=false]") + fs.StringVar(&s.HostNetworkSources, "host_network_sources", s.HostNetworkSources, "Comma-separated list of sources from which the Kubelet allows pods to use of host network. For all sources use \"*\" [default=\"file\"]") fs.Float64Var(&s.RegistryPullQPS, "registry_qps", s.RegistryPullQPS, "If > 0, limit registry pull QPS to this value. If 0, unlimited. [default=0.0]") fs.IntVar(&s.RegistryBurst, "registry_burst", s.RegistryBurst, "Maximum size of a bursty pulls, temporarily allows pulls to burst to this number, while still not exceeding registry_qps. Only used if --registry_qps > 0") fs.BoolVar(&s.RunOnce, "runonce", s.RunOnce, "If true, exit after spawning pods from local manifests or remote urls. Exclusive with --api_servers, and --enable-server") @@ -178,9 +182,14 @@ func (s *KubeletServer) Run(_ []string) error { cloud := cloudprovider.InitCloudProvider(s.CloudProvider, s.CloudConfigFile) glog.Infof("Successfully initialized cloud provider: %q from the config file: %q\n", s.CloudProvider, s.CloudConfigFile) + hostNetworkSources, err := kubelet.GetValidatedSources(strings.Split(s.HostNetworkSources, ",")) + if err != nil { + return err + } kcfg := KubeletConfig{ Address: s.Address, AllowPrivileged: s.AllowPrivileged, + HostNetworkSources: hostNetworkSources, HostnameOverride: s.HostnameOverride, RootDirectory: s.RootDirectory, ConfigFile: s.Config, @@ -321,7 +330,7 @@ func RunKubelet(kcfg *KubeletConfig) { glog.Infof("No api server defined - no events will be sent.") } kubelet.SetupLogging() - kubelet.SetupCapabilities(kcfg.AllowPrivileged) + kubelet.SetupCapabilities(kcfg.AllowPrivileged, kcfg.HostNetworkSources) credentialprovider.SetPreferredDockercfgPath(kcfg.RootDirectory) @@ -383,6 +392,7 @@ type KubeletConfig struct { CadvisorInterface cadvisor.Interface Address util.IP AllowPrivileged bool + HostNetworkSources []string HostnameOverride string RootDirectory string ConfigFile string diff --git a/pkg/capabilities/capabilities.go b/pkg/capabilities/capabilities.go index d907bfba232..8cdfccbfc91 100644 --- a/pkg/capabilities/capabilities.go +++ b/pkg/capabilities/capabilities.go @@ -24,6 +24,9 @@ import ( // For now these are global. Eventually they may be per-user type Capabilities struct { AllowPrivileged bool + + // List of pod sources for which using host network is allowed. + HostNetworkSources []string } var once sync.Once @@ -46,7 +49,8 @@ func SetForTests(c Capabilities) { func Get() Capabilities { if capabilities == nil { Initialize(Capabilities{ - AllowPrivileged: false, + AllowPrivileged: false, + HostNetworkSources: []string{}, }) } return *capabilities diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index 80cfa5779a2..5ffc45bf498 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -1007,6 +1007,20 @@ const ( PodInfraContainerImage = "kubernetes/pause:latest" ) +// Determined whether the specified pod is allowed to use host networking +func allowHostNetwork(pod *api.Pod) (bool, error) { + podSource, err := getPodSource(pod) + if err != nil { + return false, err + } + for _, source := range capabilities.Get().HostNetworkSources { + if source == podSource { + return true, nil + } + } + return false, nil +} + // createPodInfraContainer starts the pod infra container for a pod. Returns the docker container ID of the newly created container. func (kl *Kubelet) createPodInfraContainer(pod *api.Pod) (dockertools.DockerID, error) { var ports []api.ContainerPort @@ -1040,11 +1054,21 @@ func (kl *Kubelet) createPodInfraContainer(pod *api.Pod) (dockertools.DockerID, if ref != nil { kl.recorder.Eventf(ref, "pulled", "Successfully pulled image %q", container.Image) } - // TODO(vmarmol): Auth. + + // Use host networking if specified and allowed. netNamespace := "" if pod.Spec.HostNetwork { + allowed, err := allowHostNetwork(pod) + if err != nil { + return "", err + } + if !allowed { + return "", fmt.Errorf("pod with UID %q specified host networking, but is disallowed", pod.UID) + } + netNamespace = "host" } + id, err := kl.runContainer(pod, container, nil, netNamespace, "") if err != nil { return "", err diff --git a/pkg/kubelet/kubelet_test.go b/pkg/kubelet/kubelet_test.go index 4d635fb0640..dc3293a4d1a 100644 --- a/pkg/kubelet/kubelet_test.go +++ b/pkg/kubelet/kubelet_test.go @@ -35,6 +35,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" + "github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/record" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/cadvisor" @@ -3456,3 +3457,61 @@ func TestDoNotCacheStatusForStaticPods(t *testing.T) { t.Errorf("unexpected status %#v found for static pod %q", status, podFullName) } } + +func TestHostNetworkAllowed(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + + capabilities.SetForTests(capabilities.Capabilities{ + HostNetworkSources: []string{ApiserverSource, FileSource}, + }) + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + Annotations: map[string]string{ + ConfigSourceAnnotationKey: FileSource, + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "foo"}, + }, + HostNetwork: true, + }, + } + _, err := kubelet.createPodInfraContainer(pod) + if err != nil { + t.Errorf("expected pod infra creation to succeed: %v", err) + } +} + +func TestHostNetworkDisallowed(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + + capabilities.SetForTests(capabilities.Capabilities{ + HostNetworkSources: []string{}, + }) + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + Annotations: map[string]string{ + ConfigSourceAnnotationKey: FileSource, + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "foo"}, + }, + HostNetwork: true, + }, + } + _, err := kubelet.createPodInfraContainer(pod) + if err == nil { + t.Errorf("expected pod infra creation to fail") + } +} diff --git a/pkg/kubelet/types.go b/pkg/kubelet/types.go index 431df5c4fe4..5d3aa9cc25f 100644 --- a/pkg/kubelet/types.go +++ b/pkg/kubelet/types.go @@ -16,7 +16,11 @@ limitations under the License. package kubelet -import "github.com/GoogleCloudPlatform/kubernetes/pkg/api" +import ( + "fmt" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" +) const ConfigSourceAnnotationKey = "kubernetes.io/config.source" const ConfigMirrorAnnotationKey = "kubernetes.io/config.mirror" @@ -64,3 +68,22 @@ type PodUpdate struct { Op PodOperation Source string } + +// Gets all validated sources from the specified sources. +func GetValidatedSources(sources []string) ([]string, error) { + validated := make([]string, 0, len(sources)) + for _, source := range sources { + switch source { + case AllSource: + return []string{FileSource, HTTPSource, ApiserverSource}, nil + case FileSource, HTTPSource, ApiserverSource: + validated = append(validated, source) + break + case "": + break + default: + return []string{}, fmt.Errorf("unknown pod source %q", source) + } + } + return validated, nil +} diff --git a/pkg/kubelet/types_test.go b/pkg/kubelet/types_test.go new file mode 100644 index 00000000000..ff88063c5ce --- /dev/null +++ b/pkg/kubelet/types_test.go @@ -0,0 +1,44 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kubelet + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetValidatedSources(t *testing.T) { + // Empty. + sources, err := GetValidatedSources([]string{""}) + require.NoError(t, err) + require.Len(t, sources, 0) + + // Success. + sources, err = GetValidatedSources([]string{FileSource, ApiserverSource}) + require.NoError(t, err) + require.Len(t, sources, 2) + + // All. + sources, err = GetValidatedSources([]string{AllSource}) + require.NoError(t, err) + require.Len(t, sources, 3) + + // Unknown source. + sources, err = GetValidatedSources([]string{"taco"}) + require.Error(t, err) +} diff --git a/pkg/kubelet/util.go b/pkg/kubelet/util.go index 8a36464e14c..2c23c2d46a8 100644 --- a/pkg/kubelet/util.go +++ b/pkg/kubelet/util.go @@ -27,9 +27,10 @@ import ( ) // TODO: move this into pkg/capabilities -func SetupCapabilities(allowPrivileged bool) { +func SetupCapabilities(allowPrivileged bool, hostNetworkSources []string) { capabilities.Initialize(capabilities.Capabilities{ - AllowPrivileged: allowPrivileged, + AllowPrivileged: allowPrivileged, + HostNetworkSources: hostNetworkSources, }) }