Merge pull request #8233 from csrwng/connect_adm_ctrl

Admission control to prevent exec on privileged pods
This commit is contained in:
Derek Carr
2015-05-21 14:35:28 -04:00
25 changed files with 587 additions and 108 deletions

View File

@@ -37,6 +37,11 @@ func (alwaysAdmit) Admit(a admission.Attributes) (err error) {
return nil
}
func (alwaysAdmit) Handles(operation admission.Operation) bool {
return true
}
// NewAlwaysAdmit creates a new always admit admission handler
func NewAlwaysAdmit() admission.Interface {
return new(alwaysAdmit)
}

View File

@@ -38,6 +38,11 @@ func (alwaysDeny) Admit(a admission.Attributes) (err error) {
return admission.NewForbidden(a, errors.New("Admission control is denying all modifications"))
}
func (alwaysDeny) Handles(operation admission.Operation) bool {
return true
}
// NewAlwaysDeny creates an always deny admission handler
func NewAlwaysDeny() admission.Interface {
return new(alwaysDeny)
}

View File

@@ -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,
}
}

View File

@@ -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,
},
},
},
},
}
}

View File

@@ -40,6 +40,7 @@ func init() {
// limitRanger enforces usage limits on a per resource basis in the namespace
type limitRanger struct {
*admission.Handler
client client.Interface
limitFunc LimitFunc
indexer cache.Indexer
@@ -47,11 +48,6 @@ type limitRanger struct {
// Admit admits resources into cluster that do not violate any defined LimitRange in the namespace
func (l *limitRanger) Admit(a admission.Attributes) (err error) {
// ignore deletes
if a.GetOperation() == "DELETE" {
return nil
}
obj := a.GetObject()
resource := a.GetResource()
name := "Unknown"
@@ -99,9 +95,15 @@ func NewLimitRanger(client client.Interface, limitFunc LimitFunc) admission.Inte
}
indexer, reflector := cache.NewNamespaceKeyedIndexerAndReflector(lw, &api.LimitRange{}, 0)
reflector.Run()
return &limitRanger{client: client, limitFunc: limitFunc, indexer: indexer}
return &limitRanger{
Handler: admission.NewHandler(admission.Create, admission.Update),
client: client,
limitFunc: limitFunc,
indexer: indexer,
}
}
// Min returns the lesser of its 2 arguments
func Min(a int64, b int64) int64 {
if a < b {
return a
@@ -109,6 +111,7 @@ func Min(a int64, b int64) int64 {
return b
}
// Max returns the greater of its 2 arguments
func Max(a int64, b int64) int64 {
if a > b {
return a

View File

@@ -42,15 +42,12 @@ func init() {
// It looks at all incoming requests in a namespace context, and if the namespace does not exist, it creates one.
// It is useful in deployments that do not want to restrict creation of a namespace prior to its usage.
type provision struct {
*admission.Handler
client client.Interface
store cache.Store
}
func (p *provision) Admit(a admission.Attributes) (err error) {
// only handle create requests
if a.GetOperation() != "CREATE" {
return nil
}
defaultVersion, kind, err := latest.RESTMapper.VersionAndKindForResource(a.GetResource())
if err != nil {
return admission.NewForbidden(a, err)
@@ -83,6 +80,7 @@ func (p *provision) Admit(a admission.Attributes) (err error) {
return nil
}
// NewProvision creates a new namespace provision admission control handler
func NewProvision(c client.Interface) admission.Interface {
store := cache.NewStore(cache.MetaNamespaceKeyFunc)
reflector := cache.NewReflector(
@@ -99,8 +97,13 @@ func NewProvision(c client.Interface) admission.Interface {
0,
)
reflector.Run()
return createProvision(c, store)
}
func createProvision(c client.Interface, store cache.Store) admission.Interface {
return &provision{
client: c,
store: store,
Handler: admission.NewHandler(admission.Create),
client: c,
store: store,
}
}

View File

@@ -41,7 +41,7 @@ func TestAdmission(t *testing.T) {
Containers: []api.Container{{Name: "ctr", Image: "image"}},
},
}
err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespace, "pods", "CREATE", nil))
err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespace, "pods", admission.Create, nil))
if err != nil {
t.Errorf("Unexpected error returned from admission handler")
}
@@ -72,7 +72,7 @@ func TestAdmissionNamespaceExists(t *testing.T) {
Containers: []api.Container{{Name: "ctr", Image: "image"}},
},
}
err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespace, "pods", "CREATE", nil))
err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespace, "pods", admission.Create, nil))
if err != nil {
t.Errorf("Unexpected error returned from admission handler")
}
@@ -85,10 +85,7 @@ func TestAdmissionNamespaceExists(t *testing.T) {
func TestIgnoreAdmission(t *testing.T) {
namespace := "test"
mockClient := &testclient.Fake{}
handler := &provision{
client: mockClient,
store: cache.NewStore(cache.MetaNamespaceKeyFunc),
}
handler := admission.NewChainHandler(createProvision(mockClient, nil))
pod := api.Pod{
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
Spec: api.PodSpec{
@@ -96,7 +93,7 @@ func TestIgnoreAdmission(t *testing.T) {
Containers: []api.Container{{Name: "ctr", Image: "image"}},
},
}
err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespace, "pods", "UPDATE", nil))
err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespace, "pods", admission.Update, nil))
if err != nil {
t.Errorf("Unexpected error returned from admission handler")
}
@@ -123,7 +120,7 @@ func TestAdmissionNamespaceExistsUnknownToHandler(t *testing.T) {
Containers: []api.Container{{Name: "ctr", Image: "image"}},
},
}
err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespace, "pods", "CREATE", nil))
err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespace, "pods", admission.Create, nil))
if err != nil {
t.Errorf("Unexpected error returned from admission handler")
}

View File

@@ -42,6 +42,7 @@ func init() {
// It rejects all incoming requests in a namespace context if the namespace does not exist.
// It is useful in deployments that want to enforce pre-declaration of a Namespace resource.
type exists struct {
*admission.Handler
client client.Interface
store cache.Store
}
@@ -75,6 +76,7 @@ func (e *exists) Admit(a admission.Attributes) (err error) {
return admission.NewForbidden(a, fmt.Errorf("Namespace %s does not exist", a.GetNamespace()))
}
// NewExists creates a new namespace exists admission control handler
func NewExists(c client.Interface) admission.Interface {
store := cache.NewStore(cache.MetaNamespaceKeyFunc)
reflector := cache.NewReflector(
@@ -92,7 +94,8 @@ func NewExists(c client.Interface) admission.Interface {
)
reflector.Run()
return &exists{
client: c,
store: store,
client: c,
store: store,
Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete),
}
}

View File

@@ -41,14 +41,12 @@ func init() {
// lifecycle is an implementation of admission.Interface.
// It enforces life-cycle constraints around a Namespace depending on its Phase
type lifecycle struct {
*admission.Handler
client client.Interface
store cache.Store
}
func (l *lifecycle) Admit(a admission.Attributes) (err error) {
if a.GetOperation() != "CREATE" {
return nil
}
defaultVersion, kind, err := latest.RESTMapper.VersionAndKindForResource(a.GetResource())
if err != nil {
return admission.NewForbidden(a, err)
@@ -80,6 +78,7 @@ func (l *lifecycle) Admit(a admission.Attributes) (err error) {
return admission.NewForbidden(a, fmt.Errorf("Namespace %s is terminating", a.GetNamespace()))
}
// NewLifecycle creates a new namespace lifecycle admission control handler
func NewLifecycle(c client.Interface) admission.Interface {
store := cache.NewStore(cache.MetaNamespaceKeyFunc)
reflector := cache.NewReflector(
@@ -97,7 +96,8 @@ func NewLifecycle(c client.Interface) admission.Interface {
)
reflector.Run()
return &lifecycle{
client: c,
store: store,
Handler: admission.NewHandler(admission.Create),
client: c,
store: store,
}
}

View File

@@ -39,10 +39,9 @@ func TestAdmission(t *testing.T) {
store := cache.NewStore(cache.MetaNamespaceIndexFunc)
store.Add(namespaceObj)
mockClient := &testclient.Fake{}
handler := &lifecycle{
client: mockClient,
store: store,
}
lfhandler := NewLifecycle(mockClient).(*lifecycle)
lfhandler.store = store
handler := admission.NewChainHandler(lfhandler)
pod := api.Pod{
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespaceObj.Namespace},
Spec: api.PodSpec{
@@ -50,7 +49,7 @@ func TestAdmission(t *testing.T) {
Containers: []api.Container{{Name: "ctr", Image: "image"}},
},
}
err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespaceObj.Namespace, "pods", "CREATE", nil))
err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespaceObj.Namespace, "pods", admission.Create, nil))
if err != nil {
t.Errorf("Unexpected error returned from admission handler: %v", err)
}
@@ -60,21 +59,20 @@ func TestAdmission(t *testing.T) {
store.Add(namespaceObj)
// verify create operations in the namespace cause an error
err = handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespaceObj.Namespace, "pods", "CREATE", nil))
err = handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespaceObj.Namespace, "pods", admission.Create, nil))
if err == nil {
t.Errorf("Expected error rejecting creates in a namespace when it is terminating")
}
// verify update operations in the namespace can proceed
err = handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespaceObj.Namespace, "pods", "UPDATE", nil))
err = handler.Admit(admission.NewAttributesRecord(&pod, "Pod", namespaceObj.Namespace, "pods", admission.Update, nil))
if err != nil {
t.Errorf("Unexpected error returned from admission handler: %v", err)
}
// verify delete operations in the namespace can proceed
err = handler.Admit(admission.NewAttributesRecord(nil, "Pod", namespaceObj.Namespace, "pods", "DELETE", nil))
err = handler.Admit(admission.NewAttributesRecord(nil, "Pod", namespaceObj.Namespace, "pods", admission.Delete, nil))
if err != nil {
t.Errorf("Unexpected error returned from admission handler: %v", err)
}
}

View File

@@ -42,10 +42,12 @@ func init() {
}
type quota struct {
*admission.Handler
client client.Interface
indexer cache.Indexer
}
// NewResourceQuota creates a new resource quota admission control handler
func NewResourceQuota(client client.Interface) admission.Interface {
lw := &cache.ListWatch{
ListFunc: func() (runtime.Object, error) {
@@ -57,7 +59,15 @@ func NewResourceQuota(client client.Interface) admission.Interface {
}
indexer, reflector := cache.NewNamespaceKeyedIndexerAndReflector(lw, &api.ResourceQuota{}, 0)
reflector.Run()
return &quota{client: client, indexer: indexer}
return createResourceQuota(client, indexer)
}
func createResourceQuota(client client.Interface, indexer cache.Indexer) admission.Interface {
return &quota{
Handler: admission.NewHandler(admission.Create, admission.Update),
client: client,
indexer: indexer,
}
}
var resourceToResourceName = map[string]api.ResourceName{
@@ -161,7 +171,7 @@ func IncrementUsage(a admission.Attributes, status *api.ResourceQuotaStatus, cli
}
obj := a.GetObject()
// handle max counts for each kind of resource (pods, services, replicationControllers, etc.)
if a.GetOperation() == "CREATE" {
if a.GetOperation() == admission.Create {
// TODO v1beta1 had camel case, v1beta3 went to all lower, we can remove this line when we deprecate v1beta1
resourceNormalized := strings.ToLower(a.GetResource())
resourceName := resourceToResourceName[resourceNormalized]
@@ -185,7 +195,7 @@ func IncrementUsage(a admission.Attributes, status *api.ResourceQuotaStatus, cli
deltaCPU := resourcequota.PodCPU(pod)
deltaMemory := resourcequota.PodMemory(pod)
// if this is an update, we need to find the delta cpu/memory usage from previous state
if a.GetOperation() == "UPDATE" {
if a.GetOperation() == admission.Update {
oldPod, err := client.Pods(a.GetNamespace()).Get(pod.Name)
if err != nil {
return false, err

View File

@@ -40,10 +40,10 @@ func getResourceRequirements(cpu, memory string) api.ResourceRequirements {
func TestAdmissionIgnoresDelete(t *testing.T) {
namespace := "default"
handler := NewResourceQuota(&testclient.Fake{})
err := handler.Admit(admission.NewAttributesRecord(nil, "Pod", namespace, "pods", "DELETE", nil))
handler := createResourceQuota(&testclient.Fake{}, nil)
err := handler.Admit(admission.NewAttributesRecord(nil, "Pod", namespace, "pods", admission.Delete, nil))
if err != nil {
t.Errorf("ResourceQuota should admit all deletes", err)
t.Errorf("ResourceQuota should admit all deletes: %v", err)
}
}
@@ -67,9 +67,9 @@ func TestIncrementUsagePods(t *testing.T) {
r := api.ResourcePods
status.Hard[r] = resource.MustParse("2")
status.Used[r] = resource.MustParse("1")
dirty, err := IncrementUsage(admission.NewAttributesRecord(&api.Pod{}, "Pod", namespace, "pods", "CREATE", nil), status, client)
dirty, err := IncrementUsage(admission.NewAttributesRecord(&api.Pod{}, "Pod", namespace, "pods", admission.Create, nil), status, client)
if err != nil {
t.Errorf("Unexpected error", err)
t.Errorf("Unexpected error: %v", err)
}
if !dirty {
t.Errorf("Expected the status to get incremented, therefore should have been dirty")
@@ -107,9 +107,9 @@ func TestIncrementUsageMemory(t *testing.T) {
Volumes: []api.Volume{{Name: "vol"}},
Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "1Gi")}},
}}
dirty, err := IncrementUsage(admission.NewAttributesRecord(newPod, "Pod", namespace, "pods", "CREATE", nil), status, client)
dirty, err := IncrementUsage(admission.NewAttributesRecord(newPod, "Pod", namespace, "pods", admission.Create, nil), status, client)
if err != nil {
t.Errorf("Unexpected error", err)
t.Errorf("Unexpected error: %v", err)
}
if !dirty {
t.Errorf("Expected the status to get incremented, therefore should have been dirty")
@@ -148,7 +148,7 @@ func TestExceedUsageMemory(t *testing.T) {
Volumes: []api.Volume{{Name: "vol"}},
Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "3Gi")}},
}}
_, err := IncrementUsage(admission.NewAttributesRecord(newPod, "Pod", namespace, "pods", "CREATE", nil), status, client)
_, err := IncrementUsage(admission.NewAttributesRecord(newPod, "Pod", namespace, "pods", admission.Create, nil), status, client)
if err == nil {
t.Errorf("Expected memory usage exceeded error")
}
@@ -181,9 +181,9 @@ func TestIncrementUsageCPU(t *testing.T) {
Volumes: []api.Volume{{Name: "vol"}},
Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "1Gi")}},
}}
dirty, err := IncrementUsage(admission.NewAttributesRecord(newPod, "Pod", namespace, "pods", "CREATE", nil), status, client)
dirty, err := IncrementUsage(admission.NewAttributesRecord(newPod, "Pod", namespace, "pods", admission.Create, nil), status, client)
if err != nil {
t.Errorf("Unexpected error", err)
t.Errorf("Unexpected error: %v", err)
}
if !dirty {
t.Errorf("Expected the status to get incremented, therefore should have been dirty")
@@ -222,7 +222,7 @@ func TestUnboundedCPU(t *testing.T) {
Volumes: []api.Volume{{Name: "vol"}},
Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("0m", "1Gi")}},
}}
_, err := IncrementUsage(admission.NewAttributesRecord(newPod, "Pod", namespace, "pods", "CREATE", nil), status, client)
_, err := IncrementUsage(admission.NewAttributesRecord(newPod, "Pod", namespace, "pods", admission.Create, nil), status, client)
if err == nil {
t.Errorf("Expected CPU unbounded usage error")
}
@@ -255,7 +255,7 @@ func TestUnboundedMemory(t *testing.T) {
Volumes: []api.Volume{{Name: "vol"}},
Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("250m", "0")}},
}}
_, err := IncrementUsage(admission.NewAttributesRecord(newPod, "Pod", namespace, "pods", "CREATE", nil), status, client)
_, err := IncrementUsage(admission.NewAttributesRecord(newPod, "Pod", namespace, "pods", admission.Create, nil), status, client)
if err == nil {
t.Errorf("Expected memory unbounded usage error")
}
@@ -288,7 +288,7 @@ func TestExceedUsageCPU(t *testing.T) {
Volumes: []api.Volume{{Name: "vol"}},
Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("500m", "1Gi")}},
}}
_, err := IncrementUsage(admission.NewAttributesRecord(newPod, "Pod", namespace, "pods", "CREATE", nil), status, client)
_, err := IncrementUsage(admission.NewAttributesRecord(newPod, "Pod", namespace, "pods", admission.Create, nil), status, client)
if err == nil {
t.Errorf("Expected CPU usage exceeded error")
}
@@ -314,7 +314,7 @@ func TestExceedUsagePods(t *testing.T) {
r := api.ResourcePods
status.Hard[r] = resource.MustParse("1")
status.Used[r] = resource.MustParse("1")
_, err := IncrementUsage(admission.NewAttributesRecord(&api.Pod{}, "Pod", namespace, "pods", "CREATE", nil), status, client)
_, err := IncrementUsage(admission.NewAttributesRecord(&api.Pod{}, "Pod", namespace, "pods", admission.Create, nil), status, client)
if err == nil {
t.Errorf("Expected error because this would exceed your quota")
}
@@ -336,9 +336,9 @@ func TestIncrementUsageServices(t *testing.T) {
r := api.ResourceServices
status.Hard[r] = resource.MustParse("2")
status.Used[r] = resource.MustParse("1")
dirty, err := IncrementUsage(admission.NewAttributesRecord(&api.Service{}, "Service", namespace, "services", "CREATE", nil), status, client)
dirty, err := IncrementUsage(admission.NewAttributesRecord(&api.Service{}, "Service", namespace, "services", admission.Create, nil), status, client)
if err != nil {
t.Errorf("Unexpected error", err)
t.Errorf("Unexpected error: %v", err)
}
if !dirty {
t.Errorf("Expected the status to get incremented, therefore should have been dirty")
@@ -365,7 +365,7 @@ func TestExceedUsageServices(t *testing.T) {
r := api.ResourceServices
status.Hard[r] = resource.MustParse("1")
status.Used[r] = resource.MustParse("1")
_, err := IncrementUsage(admission.NewAttributesRecord(&api.Service{}, "Service", namespace, "services", "CREATE", nil), status, client)
_, err := IncrementUsage(admission.NewAttributesRecord(&api.Service{}, "Service", namespace, "services", admission.Create, nil), status, client)
if err == nil {
t.Errorf("Expected error because this would exceed usage")
}
@@ -387,9 +387,9 @@ func TestIncrementUsageReplicationControllers(t *testing.T) {
r := api.ResourceReplicationControllers
status.Hard[r] = resource.MustParse("2")
status.Used[r] = resource.MustParse("1")
dirty, err := IncrementUsage(admission.NewAttributesRecord(&api.ReplicationController{}, "ReplicationController", namespace, "replicationControllers", "CREATE", nil), status, client)
dirty, err := IncrementUsage(admission.NewAttributesRecord(&api.ReplicationController{}, "ReplicationController", namespace, "replicationControllers", admission.Create, nil), status, client)
if err != nil {
t.Errorf("Unexpected error", err)
t.Errorf("Unexpected error: %v", err)
}
if !dirty {
t.Errorf("Expected the status to get incremented, therefore should have been dirty")
@@ -416,7 +416,7 @@ func TestExceedUsageReplicationControllers(t *testing.T) {
r := api.ResourceReplicationControllers
status.Hard[r] = resource.MustParse("1")
status.Used[r] = resource.MustParse("1")
_, err := IncrementUsage(admission.NewAttributesRecord(&api.ReplicationController{}, "ReplicationController", namespace, "replicationControllers", "CREATE", nil), status, client)
_, err := IncrementUsage(admission.NewAttributesRecord(&api.ReplicationController{}, "ReplicationController", namespace, "replicationControllers", admission.Create, nil), status, client)
if err == nil {
t.Errorf("Expected error for exceeding hard limits")
}
@@ -438,7 +438,7 @@ func TestExceedUsageSecrets(t *testing.T) {
r := api.ResourceSecrets
status.Hard[r] = resource.MustParse("1")
status.Used[r] = resource.MustParse("1")
_, err := IncrementUsage(admission.NewAttributesRecord(&api.Secret{}, "Secret", namespace, "secrets", "CREATE", nil), status, client)
_, err := IncrementUsage(admission.NewAttributesRecord(&api.Secret{}, "Secret", namespace, "secrets", admission.Create, nil), status, client)
if err == nil {
t.Errorf("Expected error for exceeding hard limits")
}
@@ -460,7 +460,7 @@ func TestExceedUsagePersistentVolumeClaims(t *testing.T) {
r := api.ResourcePersistentVolumeClaims
status.Hard[r] = resource.MustParse("1")
status.Used[r] = resource.MustParse("1")
_, err := IncrementUsage(admission.NewAttributesRecord(&api.PersistentVolumeClaim{}, "PersistentVolumeClaim", namespace, "persistentVolumeClaims", "CREATE", nil), status, client)
_, err := IncrementUsage(admission.NewAttributesRecord(&api.PersistentVolumeClaim{}, "PersistentVolumeClaim", namespace, "persistentVolumeClaims", admission.Create, nil), status, client)
if err == nil {
t.Errorf("Expected error for exceeding hard limits")
}

View File

@@ -34,20 +34,21 @@ func init() {
// plugin contains the client used by the SecurityContextDeny admission controller
type plugin struct {
*admission.Handler
client client.Interface
}
// NewSecurityContextDeny creates a new instance of the SecurityContextDeny admission controller
func NewSecurityContextDeny(client client.Interface) admission.Interface {
return &plugin{client}
return &plugin{
Handler: admission.NewHandler(admission.Create, admission.Update),
client: client,
}
}
// Admit will deny any SecurityContext that defines options that were not previously available in the api.Container
// struct (Capabilities and Privileged)
func (p *plugin) Admit(a admission.Attributes) (err error) {
if a.GetOperation() == "DELETE" {
return nil
}
if a.GetResource() != string(api.ResourcePods) {
return nil
}

View File

@@ -63,3 +63,19 @@ func TestAdmission(t *testing.T) {
}
}
}
func TestHandles(t *testing.T) {
handler := NewSecurityContextDeny(nil)
tests := map[admission.Operation]bool{
admission.Update: true,
admission.Create: true,
admission.Delete: false,
admission.Connect: false,
}
for op, expected := range tests {
result := handler.Handles(op)
if result != expected {
t.Errorf("Unexpected result for operation %s: %v\n", op, result)
}
}
}

View File

@@ -53,6 +53,8 @@ func init() {
var _ = admission.Interface(&serviceAccount{})
type serviceAccount struct {
*admission.Handler
// LimitSecretReferences rejects pods that reference secrets their service accounts do not reference
LimitSecretReferences bool
// MountServiceAccountToken creates Volume and VolumeMounts for the first referenced ServiceAccountToken for the pod's service account
@@ -102,6 +104,7 @@ func NewServiceAccount(cl client.Interface) *serviceAccount {
)
return &serviceAccount{
Handler: admission.NewHandler(admission.Create),
// TODO: enable this once we've swept secret usage to account for adding secret references to service accounts
LimitSecretReferences: false,
// Auto mount service account API token secrets
@@ -130,10 +133,6 @@ func (s *serviceAccount) Stop() {
}
func (s *serviceAccount) Admit(a admission.Attributes) (err error) {
// We only care about Pod CREATE operations
if a.GetOperation() != "CREATE" {
return nil
}
if a.GetResource() != string(api.ResourcePods) {
return nil
}

View File

@@ -29,9 +29,10 @@ import (
func TestIgnoresNonCreate(t *testing.T) {
pod := &api.Pod{}
for _, op := range []string{"UPDATE", "DELETE", "CUSTOM"} {
for _, op := range []admission.Operation{admission.Update, admission.Delete, admission.Connect} {
attrs := admission.NewAttributesRecord(pod, "Pod", "myns", string(api.ResourcePods), op, nil)
err := NewServiceAccount(nil).Admit(attrs)
handler := admission.NewChainHandler(NewServiceAccount(nil))
err := handler.Admit(attrs)
if err != nil {
t.Errorf("Expected %s operation allowed, got err: %v", op, err)
}
@@ -40,7 +41,7 @@ func TestIgnoresNonCreate(t *testing.T) {
func TestIgnoresNonPodResource(t *testing.T) {
pod := &api.Pod{}
attrs := admission.NewAttributesRecord(pod, "Pod", "myns", "CustomResource", "CREATE", nil)
attrs := admission.NewAttributesRecord(pod, "Pod", "myns", "CustomResource", admission.Create, nil)
err := NewServiceAccount(nil).Admit(attrs)
if err != nil {
t.Errorf("Expected non-pod resource allowed, got err: %v", err)
@@ -48,7 +49,7 @@ func TestIgnoresNonPodResource(t *testing.T) {
}
func TestIgnoresNilObject(t *testing.T) {
attrs := admission.NewAttributesRecord(nil, "Pod", "myns", string(api.ResourcePods), "CREATE", nil)
attrs := admission.NewAttributesRecord(nil, "Pod", "myns", string(api.ResourcePods), admission.Create, nil)
err := NewServiceAccount(nil).Admit(attrs)
if err != nil {
t.Errorf("Expected nil object allowed allowed, got err: %v", err)
@@ -57,7 +58,7 @@ func TestIgnoresNilObject(t *testing.T) {
func TestIgnoresNonPodObject(t *testing.T) {
obj := &api.Namespace{}
attrs := admission.NewAttributesRecord(obj, "Pod", "myns", string(api.ResourcePods), "CREATE", nil)
attrs := admission.NewAttributesRecord(obj, "Pod", "myns", string(api.ResourcePods), admission.Create, nil)
err := NewServiceAccount(nil).Admit(attrs)
if err != nil {
t.Errorf("Expected non pod object allowed, got err: %v", err)
@@ -77,7 +78,7 @@ func TestIgnoresMirrorPod(t *testing.T) {
},
},
}
attrs := admission.NewAttributesRecord(pod, "Pod", "myns", string(api.ResourcePods), "CREATE", nil)
attrs := admission.NewAttributesRecord(pod, "Pod", "myns", string(api.ResourcePods), admission.Create, nil)
err := NewServiceAccount(nil).Admit(attrs)
if err != nil {
t.Errorf("Expected mirror pod without service account or secrets allowed, got err: %v", err)
@@ -95,7 +96,7 @@ func TestRejectsMirrorPodWithServiceAccount(t *testing.T) {
ServiceAccount: "default",
},
}
attrs := admission.NewAttributesRecord(pod, "Pod", "myns", string(api.ResourcePods), "CREATE", nil)
attrs := admission.NewAttributesRecord(pod, "Pod", "myns", string(api.ResourcePods), admission.Create, nil)
err := NewServiceAccount(nil).Admit(attrs)
if err == nil {
t.Errorf("Expected a mirror pod to be prevented from referencing a service account")
@@ -115,7 +116,7 @@ func TestRejectsMirrorPodWithSecretVolumes(t *testing.T) {
},
},
}
attrs := admission.NewAttributesRecord(pod, "Pod", "myns", string(api.ResourcePods), "CREATE", nil)
attrs := admission.NewAttributesRecord(pod, "Pod", "myns", string(api.ResourcePods), admission.Create, nil)
err := NewServiceAccount(nil).Admit(attrs)
if err == nil {
t.Errorf("Expected a mirror pod to be prevented from referencing a secret volume")
@@ -137,7 +138,7 @@ func TestAssignsDefaultServiceAccountAndToleratesMissingAPIToken(t *testing.T) {
})
pod := &api.Pod{}
attrs := admission.NewAttributesRecord(pod, "Pod", ns, string(api.ResourcePods), "CREATE", nil)
attrs := admission.NewAttributesRecord(pod, "Pod", ns, string(api.ResourcePods), admission.Create, nil)
err := admit.Admit(attrs)
if err != nil {
t.Errorf("Unexpected error: %v", err)
@@ -161,7 +162,7 @@ func TestFetchesUncachedServiceAccount(t *testing.T) {
admit := NewServiceAccount(client)
pod := &api.Pod{}
attrs := admission.NewAttributesRecord(pod, "Pod", ns, string(api.ResourcePods), "CREATE", nil)
attrs := admission.NewAttributesRecord(pod, "Pod", ns, string(api.ResourcePods), admission.Create, nil)
err := admit.Admit(attrs)
if err != nil {
t.Errorf("Unexpected error: %v", err)
@@ -180,7 +181,7 @@ func TestDeniesInvalidServiceAccount(t *testing.T) {
admit := NewServiceAccount(client)
pod := &api.Pod{}
attrs := admission.NewAttributesRecord(pod, "Pod", ns, string(api.ResourcePods), "CREATE", nil)
attrs := admission.NewAttributesRecord(pod, "Pod", ns, string(api.ResourcePods), admission.Create, nil)
err := admit.Admit(attrs)
if err == nil {
t.Errorf("Expected error for missing service account, got none")
@@ -242,7 +243,7 @@ func TestAutomountsAPIToken(t *testing.T) {
},
},
}
attrs := admission.NewAttributesRecord(pod, "Pod", ns, string(api.ResourcePods), "CREATE", nil)
attrs := admission.NewAttributesRecord(pod, "Pod", ns, string(api.ResourcePods), admission.Create, nil)
err := admit.Admit(attrs)
if err != nil {
t.Errorf("Unexpected error: %v", err)
@@ -391,7 +392,7 @@ func TestRejectsUnreferencedSecretVolumes(t *testing.T) {
},
},
}
attrs := admission.NewAttributesRecord(pod, "Pod", ns, string(api.ResourcePods), "CREATE", nil)
attrs := admission.NewAttributesRecord(pod, "Pod", ns, string(api.ResourcePods), admission.Create, nil)
err := admit.Admit(attrs)
if err == nil {
t.Errorf("Expected rejection for using a secret the service account does not reference")