pspmigrator/pspmutating_test.go

292 lines
8.8 KiB
Go
Raw Normal View History

/*
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)
}
})
}
}