From 5b9e2a55b5a1ddc535f4148fdfc4e99b42822097 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Tue, 16 Sep 2014 07:04:12 -0700 Subject: [PATCH] Add a flag to reject privileged containers in the apiserver. --- cmd/apiserver/apiserver.go | 6 +++ cmd/kubelet/kubelet.go | 10 +++-- pkg/api/validation/validation.go | 4 ++ pkg/api/validation/validation_test.go | 11 ++++++ pkg/capabilities/capabilities.go | 53 +++++++++++++++++++++++++++ pkg/capabilities/doc.go | 18 +++++++++ pkg/kubelet/kubelet.go | 27 ++++++-------- 7 files changed, 111 insertions(+), 18 deletions(-) create mode 100644 pkg/capabilities/capabilities.go create mode 100644 pkg/capabilities/doc.go diff --git a/cmd/apiserver/apiserver.go b/cmd/apiserver/apiserver.go index 0a2208d15c..2a4d82a017 100644 --- a/cmd/apiserver/apiserver.go +++ b/cmd/apiserver/apiserver.go @@ -28,6 +28,7 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" + "github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/master" @@ -49,6 +50,7 @@ var ( etcdServerList util.StringList machineList util.StringList corsAllowedOriginList util.StringList + allowPrivileged = flag.Bool("allow_privileged", false, "If true, allow privileged containers.") ) func init() { @@ -112,6 +114,10 @@ func main() { glog.Fatalf("-etcd_servers flag is required.") } + capabilities.InitializeCapabilities(capabilities.Capabilities{ + AllowPrivileged: *allowPrivileged, + }) + cloud := initCloudProvider(*cloudProvider, *cloudConfigFile) podInfoGetter := &client.HTTPPodInfoGetter{ diff --git a/cmd/kubelet/kubelet.go b/cmd/kubelet/kubelet.go index 1e407c2e95..6652234800 100644 --- a/cmd/kubelet/kubelet.go +++ b/cmd/kubelet/kubelet.go @@ -30,6 +30,7 @@ import ( "strings" "time" + "github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities" "github.com/GoogleCloudPlatform/kubernetes/pkg/health" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet" @@ -59,7 +60,7 @@ var ( dockerEndpoint = flag.String("docker_endpoint", "", "If non-empty, use this for the docker endpoint to communicate with") etcdServerList util.StringList rootDirectory = flag.String("root_dir", defaultRootDir, "Directory path for managing kubelet files (volume mounts,etc).") - allowPrivileged = flag.Bool("allow_privileged", false, "If true, allow containers to request privileged mode.") + allowPrivileged = flag.Bool("allow_privileged", false, "If true, allow containers to request privileged mode. [default=false]") ) func init() { @@ -104,6 +105,10 @@ func main() { etcd.SetLogger(util.NewLogger("etcd ")) + capabilities.InitializeCapabilities(capabilities.Capabilities{ + AllowPrivileged: *allowPrivileged, + }) + dockerClient, err := docker.NewClient(getDockerEndpoint()) if err != nil { glog.Fatal("Couldn't connect to docker.") @@ -152,8 +157,7 @@ func main() { cadvisorClient, etcdClient, *rootDirectory, - *syncFrequency, - *allowPrivileged) + *syncFrequency) health.AddHealthChecker("exec", health.NewExecHealthChecker(k)) health.AddHealthChecker("http", health.NewHTTPHealthChecker(&http.Client{})) diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 518178c117..2e0eb1f5c2 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -21,6 +21,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" errs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" + "github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) @@ -226,12 +227,15 @@ func validateContainers(containers []api.Container, volumes util.StringSet) errs for i := range containers { cErrs := errs.ErrorList{} ctr := &containers[i] // so we can set default values + capabilities := capabilities.GetCapabilities() if len(ctr.Name) == 0 { cErrs = append(cErrs, errs.NewFieldRequired("name", ctr.Name)) } else if !util.IsDNSLabel(ctr.Name) { cErrs = append(cErrs, errs.NewFieldInvalid("name", ctr.Name)) } else if allNames.Has(ctr.Name) { cErrs = append(cErrs, errs.NewFieldDuplicate("name", ctr.Name)) + } else if ctr.Privileged && !capabilities.AllowPrivileged { + cErrs = append(cErrs, errs.NewFieldInvalid("privileged", ctr.Privileged)) } else { allNames.Insert(ctr.Name) } diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index af69853c87..affb91aaf4 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -22,6 +22,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" + "github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) @@ -179,6 +180,9 @@ func TestValidateVolumeMounts(t *testing.T) { func TestValidateContainers(t *testing.T) { volumes := util.StringSet{} + capabilities.SetCapabilitiesForTests(capabilities.Capabilities{ + AllowPrivileged: true, + }) successCase := []api.Container{ {Name: "abc", Image: "image"}, @@ -193,11 +197,15 @@ func TestValidateContainers(t *testing.T) { }, }, }, + {Name: "abc-1234", Image: "image", Privileged: true}, } if errs := validateContainers(successCase, volumes); len(errs) != 0 { t.Errorf("expected success: %v", errs) } + capabilities.SetCapabilitiesForTests(capabilities.Capabilities{ + AllowPrivileged: false, + }) errorCases := map[string][]api.Container{ "zero-length name": {{Name: "", Image: "image"}}, "name > 63 characters": {{Name: strings.Repeat("a", 64), Image: "image"}}, @@ -248,6 +256,9 @@ func TestValidateContainers(t *testing.T) { }, }, }, + "privilege disabled": { + {Name: "abc", Image: "image", Privileged: true}, + }, } for k, v := range errorCases { if errs := validateContainers(v, volumes); len(errs) == 0 { diff --git a/pkg/capabilities/capabilities.go b/pkg/capabilities/capabilities.go new file mode 100644 index 0000000000..cd5ef4ba00 --- /dev/null +++ b/pkg/capabilities/capabilities.go @@ -0,0 +1,53 @@ +/* +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 capabilities + +import ( + "sync" +) + +// Capabilities defines the set of capabilities available within the system. +// For now these are global. Eventually they may be per-user +type Capabilities struct { + AllowPrivileged bool +} + +var once sync.Once +var capabilities *Capabilities + +// Initialize the capability set. This can only be done once per binary, subsequent calls are ignored. +func InitializeCapabilities(c Capabilities) { + // Only do this once + once.Do(func() { + capabilities = &c + }) +} + +// SetCapabilitiesForTests. Convenience method for testing. This should only be called from tests. +func SetCapabilitiesForTests(c Capabilities) { + capabilities = &c +} + +// Returns a read-only copy of the system capabilities. +func GetCapabilities() Capabilities { + if capabilities == nil { + InitializeCapabilities(Capabilities{ + AllowPrivileged: false, + }) + } + return *capabilities +} diff --git a/pkg/capabilities/doc.go b/pkg/capabilities/doc.go new file mode 100644 index 0000000000..7ab1949869 --- /dev/null +++ b/pkg/capabilities/doc.go @@ -0,0 +1,18 @@ +/* +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 capbabilities manages system level capabilities +package capabilities diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index ee0edd6c2e..5d9808f102 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -29,6 +29,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation" + "github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities" "github.com/GoogleCloudPlatform/kubernetes/pkg/health" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools" "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" @@ -67,19 +68,17 @@ func NewMainKubelet( cc CadvisorInterface, ec tools.EtcdClient, rd string, - ri time.Duration, - privileged bool) *Kubelet { + ri time.Duration) *Kubelet { return &Kubelet{ - hostname: hn, - dockerClient: dc, - cadvisorClient: cc, - etcdClient: ec, - rootDirectory: rd, - resyncInterval: ri, - podWorkers: newPodWorkers(), - runner: dockertools.NewDockerContainerCommandRunner(), - httpClient: &http.Client{}, - allowPrivileged: privileged, + hostname: hn, + dockerClient: dc, + cadvisorClient: cc, + etcdClient: ec, + rootDirectory: rd, + resyncInterval: ri, + podWorkers: newPodWorkers(), + runner: dockertools.NewDockerContainerCommandRunner(), + httpClient: &http.Client{}, } } @@ -121,8 +120,6 @@ type Kubelet struct { runner dockertools.ContainerCommandRunner // Optional, client for http requests, defaults to empty client httpClient httpGetInterface - // Optional, allow privileged containers, defaults to false - allowPrivileged bool } // Run starts the kubelet reacting to config updates @@ -340,7 +337,7 @@ func (kl *Kubelet) runContainer(pod *Pod, container *api.Container, podVolumes v return "", err } privileged := false - if kl.allowPrivileged { + if capabilities.GetCapabilities().AllowPrivileged { privileged = container.Privileged } else if container.Privileged { return "", fmt.Errorf("Container requested privileged mode, but it is disallowed globally.")