k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/plugin/pkg/admission/security/podsecurity/admission_test.go (about) 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{}