1
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
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
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
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
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
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