diff --git a/cmd/kube-apiserver/app/plugins.go b/cmd/kube-apiserver/app/plugins.go index 0e49415ed11..40381f8d043 100644 --- a/cmd/kube-apiserver/app/plugins.go +++ b/cmd/kube-apiserver/app/plugins.go @@ -32,6 +32,7 @@ import ( // Admission policies _ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit" _ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/deny" + _ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/exec/denyprivileged" _ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/limitranger" _ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/namespace/autoprovision" _ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/namespace/exists" diff --git a/plugin/pkg/admission/exec/denyprivileged/admission.go b/plugin/pkg/admission/exec/denyprivileged/admission.go new file mode 100644 index 00000000000..a019198dfbb --- /dev/null +++ b/plugin/pkg/admission/exec/denyprivileged/admission.go @@ -0,0 +1,81 @@ +/* +Copyright 2015 The Kubernetes Authors 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 denyprivileged + +import ( + "fmt" + "io" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/admission" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" +) + +func init() { + admission.RegisterPlugin("DenyExecOnPrivileged", func(client client.Interface, config io.Reader) (admission.Interface, error) { + return NewDenyExecOnPrivileged(client), nil + }) +} + +// denyExecOnPrivileged is an implementation of admission.Interface which says no to a pod/exec on +// a privileged pod +type denyExecOnPrivileged struct { + *admission.Handler + client client.Interface +} + +func (d *denyExecOnPrivileged) Admit(a admission.Attributes) (err error) { + connectRequest, ok := a.GetObject().(*rest.ConnectRequest) + if !ok { + return errors.NewBadRequest("a connect request was received, but could not convert the request object.") + } + // Only handle exec requests on pods + if connectRequest.ResourcePath != "pods/exec" { + return nil + } + pod, err := d.client.Pods(a.GetNamespace()).Get(connectRequest.Name) + if err != nil { + return admission.NewForbidden(a, err) + } + if isPrivileged(pod) { + return admission.NewForbidden(a, fmt.Errorf("Cannot exec into a privileged container")) + } + return nil +} + +// isPrivileged will return true a pod has any privileged containers +func isPrivileged(pod *api.Pod) bool { + for _, c := range pod.Spec.Containers { + if c.SecurityContext == nil { + continue + } + if *c.SecurityContext.Privileged { + return true + } + } + return false +} + +// NewDenyExecOnPrivileged creates a new admission controller that denies an exec operation on a privileged pod +func NewDenyExecOnPrivileged(client client.Interface) admission.Interface { + return &denyExecOnPrivileged{ + Handler: admission.NewHandler(admission.Connect), + client: client, + } +} diff --git a/plugin/pkg/admission/exec/denyprivileged/admission_test.go b/plugin/pkg/admission/exec/denyprivileged/admission_test.go new file mode 100644 index 00000000000..e3e90aa71cd --- /dev/null +++ b/plugin/pkg/admission/exec/denyprivileged/admission_test.go @@ -0,0 +1,91 @@ +/* +Copyright 2015 The Kubernetes Authors 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 denyprivileged + +import ( + "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/admission" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client/testclient" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" +) + +// TestAdmission verifies a namespace is created on create requests for namespace managed resources +func TestAdmissionAccept(t *testing.T) { + testAdmission(t, acceptPod("podname"), true) +} + +func TestAdmissionDeny(t *testing.T) { + testAdmission(t, denyPod("podname"), false) +} + +func testAdmission(t *testing.T, pod *api.Pod, shouldAccept bool) { + mockClient := &testclient.Fake{ + ReactFn: func(action testclient.FakeAction) (runtime.Object, error) { + if action.Action == "get-pod" && action.Value.(string) == pod.Name { + return pod, nil + } + t.Errorf("Unexpected API call: %#v", action) + return nil, nil + }, + } + handler := &denyExecOnPrivileged{ + client: mockClient, + } + req := &rest.ConnectRequest{Name: pod.Name, ResourcePath: "pods/exec"} + err := handler.Admit(admission.NewAttributesRecord(req, "Pod", "test", "pods", admission.Connect, nil)) + if shouldAccept && err != nil { + t.Errorf("Unexpected error returned from admission handler: %v", err) + } + if !shouldAccept && err == nil { + t.Errorf("An error was expected from the admission handler. Received nil") + } + +} + +func acceptPod(name string) *api.Pod { + return &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: name, Namespace: "test"}, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "ctr1", Image: "image"}, + {Name: "ctr2", Image: "image2"}, + }, + }, + } +} + +func denyPod(name string) *api.Pod { + privileged := true + return &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: name, Namespace: "test"}, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "ctr1", Image: "image"}, + { + Name: "ctr2", + Image: "image2", + SecurityContext: &api.SecurityContext{ + Privileged: &privileged, + }, + }, + }, + }, + } +}