func TestAdmissionIgnoresSubresources(t *testing.T) { indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc}) handler := createResourceQuota(&testclient.Fake{}, indexer) quota := &api.ResourceQuota{} quota.Name = "quota" quota.Namespace = "test" quota.Status = api.ResourceQuotaStatus{ Hard: api.ResourceList{}, Used: api.ResourceList{}, } quota.Status.Hard[api.ResourceMemory] = resource.MustParse("2Gi") quota.Status.Used[api.ResourceMemory] = resource.MustParse("1Gi") indexer.Add(quota) newPod := validPod("123", 1, getResourceRequirements(getResourceList("100m", "2Gi"), getResourceList("", ""))) err := handler.Admit(admission.NewAttributesRecord(newPod, "Pod", newPod.Namespace, newPod.Name, "pods", "", admission.Create, nil)) if err == nil { t.Errorf("Expected an error because the pod exceeded allowed quota") } err = handler.Admit(admission.NewAttributesRecord(newPod, "Pod", newPod.Namespace, newPod.Name, "pods", "subresource", admission.Create, nil)) if err != nil { t.Errorf("Did not expect an error because the action went to a subresource: %v", err) } }
func TestLimitRangerIgnoresSubresource(t *testing.T) { client := testclient.NewSimpleFake() indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc}) handler := &limitRanger{ Handler: admission.NewHandler(admission.Create, admission.Update), client: client, limitFunc: Limit, indexer: indexer, } limitRange := validLimitRangeNoDefaults() testPod := validPod("testPod", 1, api.ResourceRequirements{}) indexer.Add(&limitRange) err := handler.Admit(admission.NewAttributesRecord(&testPod, "Pod", limitRange.Namespace, "testPod", "pods", "", admission.Update, nil)) if err == nil { t.Errorf("Expected an error since the pod did not specify resource limits in its update call") } err = handler.Admit(admission.NewAttributesRecord(&testPod, "Pod", limitRange.Namespace, "testPod", "pods", "status", admission.Update, nil)) if err != nil { t.Errorf("Should have ignored calls to any subresource of pod %v", err) } }
func TestAssignsDefaultServiceAccountAndToleratesMissingAPIToken(t *testing.T) { ns := "myns" admit := NewServiceAccount(nil) admit.MountServiceAccountToken = true admit.RequireAPIToken = false // Add the default service account for the ns into the cache admit.serviceAccounts.Add(&api.ServiceAccount{ ObjectMeta: api.ObjectMeta{ Name: DefaultServiceAccountName, Namespace: ns, }, }) pod := &api.Pod{} attrs := admission.NewAttributesRecord(pod, "Pod", ns, "myname", string(api.ResourcePods), "", admission.Create, nil) err := admit.Admit(attrs) if err != nil { t.Errorf("Unexpected error: %v", err) } if pod.Spec.ServiceAccountName != DefaultServiceAccountName { t.Errorf("Expected service account %s assigned, got %s", DefaultServiceAccountName, pod.Spec.ServiceAccountName) } }
func TestAllowsReferencedSecretVolumes(t *testing.T) { ns := "myns" admit := NewServiceAccount(nil) admit.LimitSecretReferences = true admit.RequireAPIToken = false // Add the default service account for the ns with a secret reference into the cache admit.serviceAccounts.Add(&api.ServiceAccount{ ObjectMeta: api.ObjectMeta{ Name: DefaultServiceAccountName, Namespace: ns, }, Secrets: []api.ObjectReference{ {Name: "foo"}, }, }) pod := &api.Pod{ Spec: api.PodSpec{ Volumes: []api.Volume{ {VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "foo"}}}, }, }, } attrs := admission.NewAttributesRecord(pod, "Pod", ns, "myname", string(api.ResourcePods), "", admission.Create, nil) err := admit.Admit(attrs) if err != nil { t.Errorf("Unexpected error: %v", err) } }
// TestAdmissionNamespaceExists verifies that no client call is made when a namespace already exists func TestAdmissionNamespaceExists(t *testing.T) { namespace := "test" mockClient := &testclient.Fake{} store := cache.NewStore(cache.MetaNamespaceKeyFunc) store.Add(&api.Namespace{ ObjectMeta: api.ObjectMeta{Name: namespace}, }) handler := &provision{ client: mockClient, store: store, } pod := api.Pod{ ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, Spec: api.PodSpec{ Volumes: []api.Volume{{Name: "vol"}}, Containers: []api.Container{{Name: "ctr", Image: "image"}}, }, } err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", pod.Namespace, pod.Name, "pods", "", admission.Create, nil)) if err != nil { t.Errorf("Unexpected error returned from admission handler") } if len(mockClient.Actions()) != 0 { t.Errorf("No client request should have been made") } }
// TestAdmission verifies a namespace is created on create requests for namespace managed resources func TestAdmission(t *testing.T) { namespace := "test" mockClient := &testclient.Fake{} handler := &provision{ client: mockClient, store: cache.NewStore(cache.MetaNamespaceKeyFunc), } pod := api.Pod{ ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, Spec: api.PodSpec{ Volumes: []api.Volume{{Name: "vol"}}, Containers: []api.Container{{Name: "ctr", Image: "image"}}, }, } err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", pod.Namespace, pod.Name, "pods", "", admission.Create, nil)) if err != nil { t.Errorf("Unexpected error returned from admission handler") } actions := mockClient.Actions() if len(actions) != 1 { t.Errorf("Expected a create-namespace request") } if !actions[0].Matches("create", "namespaces") { t.Errorf("Expected a create-namespace request to be made via the client") } }
func TestRejectsUnreferencedImagePullSecrets(t *testing.T) { ns := "myns" admit := NewServiceAccount(nil) admit.LimitSecretReferences = true admit.RequireAPIToken = false // Add the default service account for the ns into the cache admit.serviceAccounts.Add(&api.ServiceAccount{ ObjectMeta: api.ObjectMeta{ Name: DefaultServiceAccountName, Namespace: ns, }, }) pod := &api.Pod{ Spec: api.PodSpec{ ImagePullSecrets: []api.LocalObjectReference{{Name: "foo"}}, }, } attrs := admission.NewAttributesRecord(pod, "Pod", ns, "myname", 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") } }
func TestDoNotAddImagePullSecrets(t *testing.T) { ns := "myns" admit := NewServiceAccount(nil) admit.LimitSecretReferences = true admit.RequireAPIToken = false // Add the default service account for the ns with a secret reference into the cache admit.serviceAccounts.Add(&api.ServiceAccount{ ObjectMeta: api.ObjectMeta{ Name: DefaultServiceAccountName, Namespace: ns, }, ImagePullSecrets: []api.LocalObjectReference{ {Name: "foo"}, {Name: "bar"}, }, }) pod := &api.Pod{ Spec: api.PodSpec{ ImagePullSecrets: []api.LocalObjectReference{{Name: "foo"}}, }, } attrs := admission.NewAttributesRecord(pod, "Pod", ns, "myname", string(api.ResourcePods), "", admission.Create, nil) err := admit.Admit(attrs) if err != nil { t.Errorf("Unexpected error: %v", err) } if len(pod.Spec.ImagePullSecrets) != 1 || pod.Spec.ImagePullSecrets[0].Name != "foo" { t.Errorf("unexpected image pull secrets: %v", pod.Spec.ImagePullSecrets) } }
func TestIgnoresNilObject(t *testing.T) { attrs := admission.NewAttributesRecord(nil, "Pod", "myns", "myname", 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) } }
func TestAdmission(t *testing.T) { handler := NewAlwaysDeny() err := handler.Admit(admission.NewAttributesRecord(nil, "kind", "namespace", "name", "resource", "subresource", admission.Create, nil)) if err == nil { t.Errorf("Expected error returned from admission handler") } }
func TestIncrementUsageReplicationControllers(t *testing.T) { namespace := "default" client := testclient.NewSimpleFake(&api.ReplicationControllerList{ Items: []api.ReplicationController{ { ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, }, }, }) status := &api.ResourceQuotaStatus{ Hard: api.ResourceList{}, Used: api.ResourceList{}, } r := api.ResourceReplicationControllers status.Hard[r] = resource.MustParse("2") status.Used[r] = resource.MustParse("1") dirty, err := IncrementUsage(admission.NewAttributesRecord(&api.ReplicationController{}, "ReplicationController", namespace, "name", "replicationcontrollers", "", admission.Create, nil), status, client) if err != nil { t.Errorf("Unexpected error: %v", err) } if !dirty { t.Errorf("Expected the status to get incremented, therefore should have been dirty") } quantity := status.Used[r] if quantity.Value() != int64(2) { t.Errorf("Expected new item count to be 2, but was %s", quantity.String()) } }
func admit(t *testing.T, ir admission.Interface, pods []*api.Pod) { for i := range pods { p := pods[i] if err := ir.Admit(admission.NewAttributesRecord(p, "Pod", "test", p.ObjectMeta.Name, "pods", "", admission.Create, nil)); err != nil { t.Error(err) } } }
func TestAdmissionIgnoresDelete(t *testing.T) { namespace := "default" handler := createResourceQuota(&testclient.Fake{}, nil) err := handler.Admit(admission.NewAttributesRecord(nil, "Pod", namespace, "name", "pods", "", admission.Delete, nil)) if err != nil { t.Errorf("ResourceQuota should admit all deletes: %v", err) } }
func TestIgnoresNonPodResource(t *testing.T) { pod := &api.Pod{} attrs := admission.NewAttributesRecord(pod, "Pod", "myns", "myname", "CustomResource", "", admission.Create, nil) err := NewServiceAccount(nil).Admit(attrs) if err != nil { t.Errorf("Expected non-pod resource allowed, got err: %v", err) } }
func TestIgnoresNonCreate(t *testing.T) { pod := &api.Pod{} for _, op := range []admission.Operation{admission.Update, admission.Delete, admission.Connect} { attrs := admission.NewAttributesRecord(pod, "Pod", "myns", "myname", string(api.ResourcePods), "", op, nil) handler := admission.NewChainHandler(NewServiceAccount(nil)) err := handler.Admit(attrs) if err != nil { t.Errorf("Expected %s operation allowed, got err: %v", op, err) } } }
// ensures the SecurityContext is denied if it defines anything more than Caps or Privileged func TestAdmission(t *testing.T) { handler := NewSecurityContextDeny(nil) var runAsUser int64 = 1 priv := true successCases := map[string]*api.SecurityContext{ "no sc": nil, "empty sc": {}, "valid sc": {Privileged: &priv, Capabilities: &api.Capabilities{}}, } pod := api.Pod{ Spec: api.PodSpec{ Containers: []api.Container{ {}, }, }, } for k, v := range successCases { pod.Spec.Containers[0].SecurityContext = v err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", "foo", "name", string(api.ResourcePods), "", "ignored", nil)) if err != nil { t.Errorf("Unexpected error returned from admission handler for case %s", k) } } errorCases := map[string]*api.SecurityContext{ "run as user": {RunAsUser: &runAsUser}, "se linux optons": {SELinuxOptions: &api.SELinuxOptions{}}, "mixed settings": {Privileged: &priv, RunAsUser: &runAsUser, SELinuxOptions: &api.SELinuxOptions{}}, } for k, v := range errorCases { pod.Spec.Containers[0].SecurityContext = v err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", "foo", "name", string(api.ResourcePods), "", "ignored", nil)) if err == nil { t.Errorf("Expected error returned from admission handler for case %s", k) } } }
func testAdmission(t *testing.T, pod *api.Pod, handler *denyExec, shouldAccept bool) { mockClient := &testclient.Fake{} mockClient.AddReactor("get", "pods", func(action testclient.Action) (bool, runtime.Object, error) { if action.(testclient.GetAction).GetName() == pod.Name { return true, pod, nil } t.Errorf("Unexpected API call: %#v", action) return true, nil, nil }) handler.client = mockClient // pods/exec { req := &rest.ConnectRequest{Name: pod.Name, ResourcePath: "pods/exec"} err := handler.Admit(admission.NewAttributesRecord(req, "Pod", "test", "name", "pods", "exec", 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") } } // pods/attach { req := &rest.ConnectRequest{Name: pod.Name, ResourcePath: "pods/attach"} err := handler.Admit(admission.NewAttributesRecord(req, "Pod", "test", "name", "pods", "attach", 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 TestDeniesInvalidServiceAccount(t *testing.T) { ns := "myns" // Build a test client that the admission plugin can use to look up the service account missing from its cache client := testclient.NewSimpleFake() admit := NewServiceAccount(client) pod := &api.Pod{} attrs := admission.NewAttributesRecord(pod, "Pod", ns, "myname", string(api.ResourcePods), "", admission.Create, nil) err := admit.Admit(attrs) if err == nil { t.Errorf("Expected error for missing service account, got none") } }
func TestExceedUsagePods(t *testing.T) { pod := validPod("123", 1, getResourceRequirements(getResourceList("100m", "1Gi"), getResourceList("", ""))) podList := &api.PodList{Items: []api.Pod{*pod}} client := testclient.NewSimpleFake(podList) status := &api.ResourceQuotaStatus{ Hard: api.ResourceList{}, Used: api.ResourceList{}, } r := api.ResourcePods status.Hard[r] = resource.MustParse("1") status.Used[r] = resource.MustParse("1") _, err := IncrementUsage(admission.NewAttributesRecord(&api.Pod{}, "Pod", pod.Namespace, "name", "pods", "", admission.Create, nil), status, client) if err == nil { t.Errorf("Expected error because this would exceed your quota") } }
func TestRejectsMirrorPodWithServiceAccount(t *testing.T) { pod := &api.Pod{ ObjectMeta: api.ObjectMeta{ Annotations: map[string]string{ kubelet.ConfigMirrorAnnotationKey: "true", }, }, Spec: api.PodSpec{ ServiceAccountName: "default", }, } attrs := admission.NewAttributesRecord(pod, "Pod", "myns", "myname", 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") } }
// TestIgnoreAdmission validates that a request is ignored if its not a create func TestIgnoreAdmission(t *testing.T) { namespace := "test" mockClient := &testclient.Fake{} handler := admission.NewChainHandler(createProvision(mockClient, nil)) pod := api.Pod{ ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, Spec: api.PodSpec{ Volumes: []api.Volume{{Name: "vol"}}, Containers: []api.Container{{Name: "ctr", Image: "image"}}, }, } err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", pod.Namespace, pod.Name, "pods", "", admission.Update, nil)) if err != nil { t.Errorf("Unexpected error returned from admission handler") } if len(mockClient.Actions()) != 0 { t.Errorf("No client request should have been made") } }
func TestIgnoresMirrorPod(t *testing.T) { pod := &api.Pod{ ObjectMeta: api.ObjectMeta{ Annotations: map[string]string{ kubelet.ConfigMirrorAnnotationKey: "true", }, }, Spec: api.PodSpec{ Volumes: []api.Volume{ {VolumeSource: api.VolumeSource{}}, }, }, } attrs := admission.NewAttributesRecord(pod, "Pod", "myns", "myname", 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) } }
// ConnectResource returns a function that handles a connect request on a rest.Storage object. func ConnectResource(connecter rest.Connecter, scope RequestScope, admit admission.Interface, connectOptionsKind, restPath string, subpath bool, subpathKey string) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter namespace, name, err := scope.Namer.Name(req) if err != nil { errorJSON(err, scope.Codec, w) return } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) opts, err := getRequestOptions(req, scope, connectOptionsKind, subpath, subpathKey) if err != nil { errorJSON(err, scope.Codec, w) return } if admit.Handles(admission.Connect) { connectRequest := &rest.ConnectRequest{ Name: name, Options: opts, ResourcePath: restPath, } userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(connectRequest, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, userInfo)) if err != nil { errorJSON(err, scope.Codec, w) return } } handler, err := connecter.Connect(ctx, name, opts) if err != nil { errorJSON(err, scope.Codec, w) return } handler.ServeHTTP(w, req.Request) err = handler.RequestError() if err != nil { errorJSON(err, scope.Codec, w) return } } }
func TestExceedUsageServices(t *testing.T) { namespace := "default" client := testclient.NewSimpleFake(&api.ServiceList{ Items: []api.Service{ { ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, }, }, }) status := &api.ResourceQuotaStatus{ Hard: api.ResourceList{}, Used: api.ResourceList{}, } r := api.ResourceServices status.Hard[r] = resource.MustParse("1") status.Used[r] = resource.MustParse("1") _, err := IncrementUsage(admission.NewAttributesRecord(&api.Service{}, "Service", namespace, "name", "services", "", admission.Create, nil), status, client) if err == nil { t.Errorf("Expected error because this would exceed usage") } }
func TestExceedUsagePersistentVolumeClaims(t *testing.T) { namespace := "default" client := testclient.NewSimpleFake(&api.PersistentVolumeClaimList{ Items: []api.PersistentVolumeClaim{ { ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, }, }, }) status := &api.ResourceQuotaStatus{ Hard: api.ResourceList{}, Used: api.ResourceList{}, } r := api.ResourcePersistentVolumeClaims status.Hard[r] = resource.MustParse("1") status.Used[r] = resource.MustParse("1") _, err := IncrementUsage(admission.NewAttributesRecord(&api.PersistentVolumeClaim{}, "PersistentVolumeClaim", namespace, "name", "persistentvolumeclaims", "", admission.Create, nil), status, client) if err == nil { t.Errorf("Expected error for exceeding hard limits") } }
func TestAssignsDefaultServiceAccountAndRejectsMissingAPIToken(t *testing.T) { ns := "myns" admit := NewServiceAccount(nil) admit.MountServiceAccountToken = true admit.RequireAPIToken = true // Add the default service account for the ns into the cache admit.serviceAccounts.Add(&api.ServiceAccount{ ObjectMeta: api.ObjectMeta{ Name: DefaultServiceAccountName, Namespace: ns, }, }) pod := &api.Pod{} attrs := admission.NewAttributesRecord(pod, "Pod", ns, "myname", string(api.ResourcePods), "", admission.Create, nil) err := admit.Admit(attrs) if err == nil { t.Errorf("Expected admission error for missing API token") } }
func TestIncrementUsagePods(t *testing.T) { pod := validPod("123", 1, getResourceRequirements(getResourceList("100m", "1Gi"), getResourceList("", ""))) podList := &api.PodList{Items: []api.Pod{*pod}} client := testclient.NewSimpleFake(podList) status := &api.ResourceQuotaStatus{ Hard: api.ResourceList{}, Used: api.ResourceList{}, } r := api.ResourcePods status.Hard[r] = resource.MustParse("2") status.Used[r] = resource.MustParse("1") dirty, err := IncrementUsage(admission.NewAttributesRecord(&api.Pod{}, "Pod", pod.Namespace, "new-pod", "pods", "", admission.Create, nil), status, client) if err != nil { t.Errorf("Unexpected error: %v", err) } if !dirty { t.Errorf("Expected the status to get incremented, therefore should have been dirty") } quantity := status.Used[r] if quantity.Value() != int64(2) { t.Errorf("Expected new item count to be 2, but was %s", quantity.String()) } }
func TestAddImagePullSecrets(t *testing.T) { ns := "myns" admit := NewServiceAccount(nil) admit.LimitSecretReferences = true admit.RequireAPIToken = false sa := &api.ServiceAccount{ ObjectMeta: api.ObjectMeta{ Name: DefaultServiceAccountName, Namespace: ns, }, ImagePullSecrets: []api.LocalObjectReference{ {Name: "foo"}, {Name: "bar"}, }, } // Add the default service account for the ns with a secret reference into the cache admit.serviceAccounts.Add(sa) pod := &api.Pod{} attrs := admission.NewAttributesRecord(pod, "Pod", ns, "myname", string(api.ResourcePods), "", admission.Create, nil) err := admit.Admit(attrs) if err != nil { t.Errorf("Unexpected error: %v", err) } if len(pod.Spec.ImagePullSecrets) != 2 || !reflect.DeepEqual(sa.ImagePullSecrets, pod.Spec.ImagePullSecrets) { t.Errorf("expected %v, got %v", sa.ImagePullSecrets, pod.Spec.ImagePullSecrets) } pod.Spec.ImagePullSecrets[1] = api.LocalObjectReference{Name: "baz"} if reflect.DeepEqual(sa.ImagePullSecrets, pod.Spec.ImagePullSecrets) { t.Errorf("accidentally mutated the ServiceAccount.ImagePullSecrets: %v", sa.ImagePullSecrets) } }
// TestAdmissionNamespaceExistsUnknownToHandler func TestAdmissionNamespaceExistsUnknownToHandler(t *testing.T) { namespace := "test" mockClient := &testclient.Fake{} mockClient.AddReactor("create", "namespaces", func(action testclient.Action) (bool, runtime.Object, error) { return true, nil, errors.NewAlreadyExists("namespaces", namespace) }) store := cache.NewStore(cache.MetaNamespaceKeyFunc) handler := &provision{ client: mockClient, store: store, } pod := api.Pod{ ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, Spec: api.PodSpec{ Volumes: []api.Volume{{Name: "vol"}}, Containers: []api.Container{{Name: "ctr", Image: "image"}}, }, } err := handler.Admit(admission.NewAttributesRecord(&pod, "Pod", pod.Namespace, pod.Name, "pods", "", admission.Create, nil)) if err != nil { t.Errorf("Unexpected error returned from admission handler") } }
func TestFetchesUncachedServiceAccount(t *testing.T) { ns := "myns" // Build a test client that the admission plugin can use to look up the service account missing from its cache client := testclient.NewSimpleFake(&api.ServiceAccount{ ObjectMeta: api.ObjectMeta{ Name: DefaultServiceAccountName, Namespace: ns, }, }) admit := NewServiceAccount(client) admit.RequireAPIToken = false pod := &api.Pod{} attrs := admission.NewAttributesRecord(pod, "Pod", ns, "myname", string(api.ResourcePods), "", admission.Create, nil) err := admit.Admit(attrs) if err != nil { t.Errorf("Unexpected error: %v", err) } if pod.Spec.ServiceAccountName != DefaultServiceAccountName { t.Errorf("Expected service account %s assigned, got %s", DefaultServiceAccountName, pod.Spec.ServiceAccountName) } }