292 lines
8.8 KiB
Go
292 lines
8.8 KiB
Go
/*
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|