...

Source file src/k8s.io/kubernetes/plugin/pkg/admission/security/podsecurity/admission_test.go

Documentation: k8s.io/kubernetes/plugin/pkg/admission/security/podsecurity

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package podsecurity
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"strings"
    24  	"testing"
    25  
    26  	corev1 "k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/runtime/schema"
    30  	"k8s.io/apimachinery/pkg/types"
    31  	"k8s.io/apiserver/pkg/admission"
    32  	"k8s.io/apiserver/pkg/authentication/user"
    33  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    34  	"k8s.io/apiserver/pkg/warning"
    35  	"k8s.io/client-go/informers"
    36  	"k8s.io/client-go/kubernetes/fake"
    37  	"k8s.io/kubernetes/pkg/apis/apps"
    38  	"k8s.io/kubernetes/pkg/apis/batch"
    39  	"k8s.io/kubernetes/pkg/apis/core"
    40  	v1 "k8s.io/kubernetes/pkg/apis/core/v1"
    41  	podsecurityadmission "k8s.io/pod-security-admission/admission"
    42  	"k8s.io/utils/pointer"
    43  	"sigs.k8s.io/yaml"
    44  )
    45  
    46  func TestConvert(t *testing.T) {
    47  	extractor := podsecurityadmission.DefaultPodSpecExtractor{}
    48  	internalTypes := map[schema.GroupResource]runtime.Object{
    49  		core.Resource("pods"):                   &core.Pod{},
    50  		core.Resource("replicationcontrollers"): &core.ReplicationController{},
    51  		core.Resource("podtemplates"):           &core.PodTemplate{},
    52  		apps.Resource("replicasets"):            &apps.ReplicaSet{},
    53  		apps.Resource("deployments"):            &apps.Deployment{},
    54  		apps.Resource("statefulsets"):           &apps.StatefulSet{},
    55  		apps.Resource("daemonsets"):             &apps.DaemonSet{},
    56  		batch.Resource("jobs"):                  &batch.Job{},
    57  		batch.Resource("cronjobs"):              &batch.CronJob{},
    58  	}
    59  	for _, r := range extractor.PodSpecResources() {
    60  		internalType, ok := internalTypes[r]
    61  		if !ok {
    62  			t.Errorf("no internal type registered for %s", r.String())
    63  			continue
    64  		}
    65  		externalType, err := convert(internalType)
    66  		if err != nil {
    67  			t.Errorf("error converting %T: %v", internalType, err)
    68  			continue
    69  		}
    70  		_, _, err = extractor.ExtractPodSpec(externalType)
    71  		if err != nil {
    72  			t.Errorf("error extracting from %T: %v", externalType, err)
    73  			continue
    74  		}
    75  	}
    76  }
    77  
    78  func BenchmarkVerifyPod(b *testing.B) {
    79  	p, err := newPlugin(nil)
    80  	if err != nil {
    81  		b.Fatal(err)
    82  	}
    83  
    84  	p.InspectFeatureGates(utilfeature.DefaultFeatureGate)
    85  
    86  	enforceImplicitPrivilegedNamespace := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "enforce-implicit", Labels: map[string]string{}}}
    87  	enforcePrivilegedNamespace := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "enforce-privileged", Labels: map[string]string{"pod-security.kubernetes.io/enforce": "privileged"}}}
    88  	enforceBaselineNamespace := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "enforce-baseline", Labels: map[string]string{"pod-security.kubernetes.io/enforce": "baseline"}}}
    89  	enforceRestrictedNamespace := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "enforce-restricted", Labels: map[string]string{"pod-security.kubernetes.io/enforce": "restricted"}}}
    90  	warnBaselineNamespace := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "warn-baseline", Labels: map[string]string{"pod-security.kubernetes.io/warn": "baseline"}}}
    91  	warnRestrictedNamespace := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "warn-restricted", Labels: map[string]string{"pod-security.kubernetes.io/warn": "restricted"}}}
    92  	enforceWarnAuditBaseline := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "enforce-warn-audit-baseline", Labels: map[string]string{"pod-security.kubernetes.io/enforce": "baseline", "pod-security.kubernetes.io/warn": "baseline", "pod-security.kubernetes.io/audit": "baseline"}}}
    93  	warnBaselineAuditRestrictedNamespace := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "warn-baseline-audit-restricted", Labels: map[string]string{"pod-security.kubernetes.io/warn": "baseline", "pod-security.kubernetes.io/audit": "restricted"}}}
    94  	c := fake.NewSimpleClientset(
    95  		enforceImplicitPrivilegedNamespace,
    96  		enforcePrivilegedNamespace,
    97  		enforceBaselineNamespace,
    98  		enforceRestrictedNamespace,
    99  		warnBaselineNamespace,
   100  		warnRestrictedNamespace,
   101  		enforceWarnAuditBaseline,
   102  		warnBaselineAuditRestrictedNamespace,
   103  	)
   104  	p.SetExternalKubeClientSet(c)
   105  
   106  	informerFactory := informers.NewSharedInformerFactory(c, 0)
   107  	p.SetExternalKubeInformerFactory(informerFactory)
   108  	stopCh := make(chan struct{})
   109  	defer close(stopCh)
   110  	informerFactory.Start(stopCh)
   111  	informerFactory.WaitForCacheSync(stopCh)
   112  
   113  	if err := p.ValidateInitialization(); err != nil {
   114  		b.Fatal(err)
   115  	}
   116  
   117  	corePod := &core.Pod{}
   118  	v1Pod := &corev1.Pod{}
   119  	data, err := ioutil.ReadFile("testdata/pod_restricted.yaml")
   120  	if err != nil {
   121  		b.Fatal(err)
   122  	}
   123  	if err := yaml.Unmarshal(data, v1Pod); err != nil {
   124  		b.Fatal(err)
   125  	}
   126  	if err := v1.Convert_v1_Pod_To_core_Pod(v1Pod, corePod, nil); err != nil {
   127  		b.Fatal(err)
   128  	}
   129  
   130  	appsDeployment := &apps.Deployment{
   131  		ObjectMeta: metav1.ObjectMeta{Name: "mydeployment"},
   132  		Spec: apps.DeploymentSpec{
   133  			Template: core.PodTemplateSpec{
   134  				ObjectMeta: corePod.ObjectMeta,
   135  				Spec:       corePod.Spec,
   136  			},
   137  		},
   138  	}
   139  
   140  	namespaces := []string{
   141  		"enforce-implicit", "enforce-privileged", "enforce-baseline", "enforce-restricted",
   142  		"warn-baseline", "warn-restricted",
   143  		"enforce-warn-audit-baseline", "warn-baseline-audit-restricted",
   144  	}
   145  	for _, namespace := range namespaces {
   146  		b.Run(namespace+"_pod", func(b *testing.B) {
   147  			ctx := context.Background()
   148  			attrs := admission.NewAttributesRecord(
   149  				corePod.DeepCopy(), nil,
   150  				schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"},
   151  				namespace, "mypod",
   152  				schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"},
   153  				"",
   154  				admission.Create, &metav1.CreateOptions{}, false,
   155  				&user.DefaultInfo{Name: "myuser"},
   156  			)
   157  			b.ResetTimer()
   158  			for i := 0; i < b.N; i++ {
   159  				if err := p.Validate(ctx, attrs, nil); err != nil {
   160  					b.Fatal(err)
   161  				}
   162  			}
   163  		})
   164  
   165  		b.Run(namespace+"_deployment", func(b *testing.B) {
   166  			ctx := context.Background()
   167  			attrs := admission.NewAttributesRecord(
   168  				appsDeployment.DeepCopy(), nil,
   169  				schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
   170  				namespace, "mydeployment",
   171  				schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
   172  				"",
   173  				admission.Create, &metav1.CreateOptions{}, false,
   174  				&user.DefaultInfo{Name: "myuser"},
   175  			)
   176  			b.ResetTimer()
   177  			for i := 0; i < b.N; i++ {
   178  				if err := p.Validate(ctx, attrs, nil); err != nil {
   179  					b.Fatal(err)
   180  				}
   181  			}
   182  		})
   183  	}
   184  }
   185  
   186  func BenchmarkVerifyNamespace(b *testing.B) {
   187  	p, err := newPlugin(nil)
   188  	if err != nil {
   189  		b.Fatal(err)
   190  	}
   191  
   192  	p.InspectFeatureGates(utilfeature.DefaultFeatureGate)
   193  
   194  	namespace := "enforce"
   195  	enforceNamespaceBaselineV1 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace, Labels: map[string]string{"pod-security.kubernetes.io/enforce": "baseline"}}}
   196  	enforceNamespaceRestrictedV1 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace, Labels: map[string]string{"pod-security.kubernetes.io/enforce": "restricted"}}}
   197  
   198  	enforceNamespaceBaselineCore := &core.Namespace{}
   199  	if err := v1.Convert_v1_Namespace_To_core_Namespace(enforceNamespaceBaselineV1, enforceNamespaceBaselineCore, nil); err != nil {
   200  		b.Fatal(err)
   201  	}
   202  	enforceNamespaceRestrictedCore := &core.Namespace{}
   203  	if err := v1.Convert_v1_Namespace_To_core_Namespace(enforceNamespaceRestrictedV1, enforceNamespaceRestrictedCore, nil); err != nil {
   204  		b.Fatal(err)
   205  	}
   206  
   207  	v1Pod := &corev1.Pod{}
   208  	data, err := ioutil.ReadFile("testdata/pod_baseline.yaml")
   209  	if err != nil {
   210  		b.Fatal(err)
   211  	}
   212  	if err := yaml.Unmarshal(data, v1Pod); err != nil {
   213  		b.Fatal(err)
   214  	}
   215  
   216  	// https://github.com/kubernetes/community/blob/master/sig-scalability/configs-and-limits/thresholds.md#kubernetes-thresholds
   217  	ownerA := metav1.OwnerReference{
   218  		APIVersion: "apps/v1",
   219  		Kind:       "ReplicaSet",
   220  		Name:       "myapp-123123",
   221  		UID:        types.UID("7610a7f4-8f80-4f88-95b5-6cefdd8e9dbd"),
   222  		Controller: pointer.Bool(true),
   223  	}
   224  	ownerB := metav1.OwnerReference{
   225  		APIVersion: "apps/v1",
   226  		Kind:       "ReplicaSet",
   227  		Name:       "myapp-234234",
   228  		UID:        types.UID("7610a7f4-8f80-4f88-95b5-as765as76f55"),
   229  		Controller: pointer.Bool(true),
   230  	}
   231  
   232  	// number of warnings printed for the entire namespace
   233  	namespaceWarningCount := 1
   234  
   235  	podCount := 3000
   236  	objects := make([]runtime.Object, 0, podCount+1)
   237  	objects = append(objects, enforceNamespaceBaselineV1)
   238  	for i := 0; i < podCount; i++ {
   239  		v1PodCopy := v1Pod.DeepCopy()
   240  		v1PodCopy.Name = fmt.Sprintf("pod%d", i)
   241  		v1PodCopy.UID = types.UID(fmt.Sprintf("pod%d", i))
   242  		v1PodCopy.Namespace = namespace
   243  		switch i % 3 {
   244  		case 0:
   245  			v1PodCopy.OwnerReferences = []metav1.OwnerReference{ownerA}
   246  		case 1:
   247  			v1PodCopy.OwnerReferences = []metav1.OwnerReference{ownerB}
   248  		default:
   249  			// no owner references
   250  		}
   251  		objects = append(objects, v1PodCopy)
   252  	}
   253  
   254  	c := fake.NewSimpleClientset(
   255  		objects...,
   256  	)
   257  	p.SetExternalKubeClientSet(c)
   258  
   259  	informerFactory := informers.NewSharedInformerFactory(c, 0)
   260  	p.SetExternalKubeInformerFactory(informerFactory)
   261  	stopCh := make(chan struct{})
   262  	defer close(stopCh)
   263  	informerFactory.Start(stopCh)
   264  	informerFactory.WaitForCacheSync(stopCh)
   265  
   266  	if err := p.ValidateInitialization(); err != nil {
   267  		b.Fatal(err)
   268  	}
   269  
   270  	ctx := context.Background()
   271  	attrs := admission.NewAttributesRecord(
   272  		enforceNamespaceRestrictedCore.DeepCopy(), enforceNamespaceBaselineCore.DeepCopy(),
   273  		schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"},
   274  		namespace, namespace,
   275  		schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespaces"},
   276  		"",
   277  		admission.Update, &metav1.UpdateOptions{}, false,
   278  		&user.DefaultInfo{Name: "myuser"},
   279  	)
   280  	b.ResetTimer()
   281  	for i := 0; i < b.N; i++ {
   282  		dc := dummyRecorder{agent: "", text: ""}
   283  		ctxWithRecorder := warning.WithWarningRecorder(ctx, &dc)
   284  		if err := p.Validate(ctxWithRecorder, attrs, nil); err != nil {
   285  			b.Fatal(err)
   286  		}
   287  		// should either be a single aggregated warning, or a unique warning per pod
   288  		if dc.count != (1+namespaceWarningCount) && dc.count != (podCount+namespaceWarningCount) {
   289  			b.Fatalf("expected either %d or %d warnings, got %d", 1+namespaceWarningCount, podCount+namespaceWarningCount, dc.count)
   290  		}
   291  		// warning should contain the runAsNonRoot issue
   292  		if e, a := "runAsNonRoot", dc.text; !strings.Contains(a, e) {
   293  			b.Fatalf("expected warning containing %q, got %q", e, a)
   294  		}
   295  	}
   296  }
   297  
   298  type dummyRecorder struct {
   299  	count int
   300  	agent string
   301  	text  string
   302  }
   303  
   304  func (r *dummyRecorder) AddWarning(agent, text string) {
   305  	r.count++
   306  	r.agent = agent
   307  	r.text = text
   308  	return
   309  }
   310  
   311  var _ warning.Recorder = &dummyRecorder{}
   312  

View as plain text