/* Copyright 2022 The Kubernetes Authors. 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 pspmigrator import ( "context" "encoding/json" "fmt" "log" "os" "path/filepath" "testing" "time" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" "k8s.io/api/policy/v1beta1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" // Uncomment to load all auth plugins _ "k8s.io/client-go/plugin/pkg/client/auth" // // Or uncomment to load specific auth plugins _ "k8s.io/client-go/plugin/pkg/client/auth/azure" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" _ "k8s.io/client-go/plugin/pkg/client/auth/openstack" ) var ( pspName string = "pspmigrator-test" clientset *kubernetes.Clientset ) type PSPOptions struct { Annotations map[string]string DefaultAddCapabilities []string RunAsGroup map[string]string } func GeneratePSPObject(options PSPOptions) v1beta1.PodSecurityPolicy { annotations := []byte("{}") if len(options.Annotations) > 0 { annotations, _ = json.Marshal(options.Annotations) } defaultAddCapabilities, err := json.Marshal(options.DefaultAddCapabilities) if err != nil { log.Panic(err) } pspObjJson := fmt.Sprintf(`{ "metadata":{ "name":"%s", "annotations": %s }, "spec":{ "defaultAddCapabilities":%s, "volumes":["*"], "seLinux":{"rule":"RunAsAny"}, "runAsUser":{"rule":"RunAsAny"}, "supplementalGroups":{"rule":"RunAsAny"}, "fsGroup":{"rule":"RunAsAny"}, "allowPrivilegeEscalation":true} }`, pspName, annotations, defaultAddCapabilities) fmt.Println(pspObjJson) var pspObj v1beta1.PodSecurityPolicy if err := json.Unmarshal([]byte(pspObjJson), &pspObj); err != nil { log.Panic(err) } return pspObj } func skipCI(t *testing.T) { if os.Getenv("CI") != "" { t.Skip("Skipping testing in CI environment") } } func TestIsPSPNotMutating(t *testing.T) { pspObj := GeneratePSPObject(PSPOptions{}) yes, fields, annotations := IsPSPMutating(&pspObj) fmt.Println("Mutating, fields, annotations:", yes, fields, annotations) if yes == true { t.Error("Mutating should be false but was true") } if len(fields) > 0 { t.Errorf("Expected fields to be empty, but got: %s", fields) } if len(annotations) > 0 { t.Errorf("Expected annoations to be empty, but got: %s", annotations) } } func TestIsPSPMutatingDefaultAddCapabilitiesOnly(t *testing.T) { pspObj := GeneratePSPObject(PSPOptions{DefaultAddCapabilities: []string{"CHOWN"}}) yes, fields, annotations := IsPSPMutating(&pspObj) fmt.Println("Mutating, fields, annotations:", yes, fields, annotations) if yes == false { t.Error("Mutating should be true but was false") } if len(fields) != 1 { t.Errorf("Only DefaultAddCapabilities should have been reported as mutating, but got %s", fields) } if fields[0] != "DefaultAddCapabilities" { t.Errorf("Expected DefaultAddCapabilities to be mutating but got %s", fields[0]) } } func TestIsPSPMutatingAnnotation(t *testing.T) { pspObj := GeneratePSPObject(PSPOptions{Annotations: map[string]string{"seccomp.security.alpha.kubernetes.io/defaultProfileName": "a"}}) yes, fields, annotations := IsPSPMutating(&pspObj) fmt.Println("Mutating, fields, annotations:", yes, fields, annotations) if yes == false { t.Error("Mutating should be true but was false") } } func CreateClientSet() *kubernetes.Clientset { home := homedir.HomeDir() kubecfgPath := filepath.Join(home, ".kube", "config") config, err := clientcmd.BuildConfigFromFlags("", kubecfgPath) clientset, err := kubernetes.NewForConfig(config) if err != nil { panic(err) } return clientset } func SetupIntegrationTests(namespace string) { nsSpec := &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} clientset.CoreV1().Namespaces().Create(context.Background(), nsSpec, metav1.CreateOptions{}) // create Role and Rolebindings cr := rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{Name: "psptest-" + namespace}, Rules: []rbacv1.PolicyRule{ rbacv1.PolicyRule{ APIGroups: []string{"policy"}, Resources: []string{"podsecuritypolicies"}, Verbs: []string{"use"}, ResourceNames: []string{pspName}, }, }, } crb := rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{Name: "psptest-" + namespace}, Subjects: []rbacv1.Subject{ rbacv1.Subject{ Kind: "Group", Name: "system:serviceaccounts:" + namespace, APIGroup: "rbac.authorization.k8s.io", }, }, RoleRef: rbacv1.RoleRef{ Kind: "ClusterRole", APIGroup: "rbac.authorization.k8s.io", Name: "psptest-" + namespace, }, } clientset.RbacV1().ClusterRoles().Create(context.TODO(), &cr, metav1.CreateOptions{}) clientset.RbacV1().ClusterRoleBindings().Create(context.TODO(), &crb, metav1.CreateOptions{}) } func TeardownIntegrationTests(namespace string) { clientset.CoreV1().Namespaces().Delete(context.Background(), namespace, metav1.DeleteOptions{}) clientset.PolicyV1beta1().PodSecurityPolicies().Delete(context.TODO(), pspName, metav1.DeleteOptions{}) clientset.RbacV1().ClusterRoleBindings().Delete(context.TODO(), "psptest-"+namespace, metav1.DeleteOptions{}) clientset.RbacV1().ClusterRoles().Delete(context.TODO(), "psptest-"+namespace, metav1.DeleteOptions{}) } func int32Ptr(i int32) *int32 { return &i } func CreateDeployment(name string) *appsv1.Deployment { deployment := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, Spec: appsv1.DeploymentSpec{ Replicas: int32Ptr(1), Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{ "app": "demo", }, }, Template: v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{ "app": "demo", }, }, Spec: v1.PodSpec{ Containers: []v1.Container{ { Name: "test", Image: "k8s.gcr.io/echoserver:1.4", }, }, }, }, }, } return deployment } func TestIngreation(t *testing.T) { // TODO have CI that deploys K8s cluster for testing skipCI(t) clientset = CreateClientSet() cases := []struct { Name string DefaultAddCapabilities []string Annotations map[string]string Expected bool }{ {"mutating-podspec", []string{"CHOWN"}, map[string]string{}, true}, {"non-mutated-pod", []string{}, map[string]string{}, false}, {"mutating-annotations", []string{}, map[string]string{ "seccomp.security.alpha.kubernetes.io/defaultProfileName": "runtime/default", "seccomp.security.alpha.kubernetes.io/allowedProfileNames": "*", }, true}, } for _, tc := range cases { t.Run(tc.Name, func(t *testing.T) { // Create PSP policy pspObj := GeneratePSPObject(PSPOptions{ DefaultAddCapabilities: tc.DefaultAddCapabilities, Annotations: tc.Annotations, }) clientset.PolicyV1beta1().PodSecurityPolicies().Create(context.TODO(), &pspObj, metav1.CreateOptions{}) defer clientset.PolicyV1beta1().PodSecurityPolicies().Delete(context.TODO(), pspObj.Name, metav1.DeleteOptions{}) namespace := "pspmigrator-" + tc.Name SetupIntegrationTests(namespace) defer TeardownIntegrationTests(namespace) deployment := CreateDeployment(namespace) result, err := clientset.AppsV1().Deployments(namespace).Create(context.TODO(), deployment, metav1.CreateOptions{}) defer clientset.AppsV1().Deployments(namespace).Delete(context.TODO(), deployment.Name, metav1.DeleteOptions{}) if err != nil { panic(err) } fmt.Printf("Created deployment %q.\n", result.GetObjectMeta().GetName()) set := labels.Set{"app": "demo"} listOptions := metav1.ListOptions{LabelSelector: set.AsSelector().String()} var pods *v1.PodList for i := 0; i < 60; i++ { pods, err = clientset.CoreV1().Pods(namespace).List(context.TODO(), listOptions) if len(pods.Items) == 1 { break } time.Sleep(1 * time.Second) } if err != nil { t.Error(err.Error()) } if len(pods.Items) != 1 { fmt.Println(pods, err) t.Errorf("Expected only a single pod, but got %v pods", len(pods.Items)) } pod := pods.Items[0] mutated, diff, err := IsPodBeingMutatedByPSP(&pod, clientset) fmt.Println(diff) if mutated != tc.Expected { t.Errorf("Expected mutated to be %v but got %v", tc.Expected, mutated) } }) } }