Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
SUSE:SLE-15-SP3:Update
kubernetes1.23.29114
kube-apiserver-admission-plugin-policy.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File kube-apiserver-admission-plugin-policy.patch of Package kubernetes1.23.29114
From 64f3b999c3e488ebc73c2d9a628b73ec092a0caf Mon Sep 17 00:00:00 2001 From: Rita Zhang <rita.z.zhang@gmail.com> Date: Sun, 21 May 2023 16:21:08 -0700 Subject: [PATCH] Add ephemeralcontainer to imagepolicy securityaccount admission plugin Signed-off-by: Rita Zhang <rita.z.zhang@gmail.com> --- plugin/pkg/admission/imagepolicy/admission.go | 26 ++-- .../admission/imagepolicy/admission_test.go | 135 +++++++++++++++++- .../pkg/admission/serviceaccount/admission.go | 55 ++++++- .../serviceaccount/admission_test.go | 93 +++++++++++- 4 files changed, 290 insertions(+), 19 deletions(-) Index: kubernetes-1.23.17/plugin/pkg/admission/imagepolicy/admission.go =================================================================== --- kubernetes-1.23.17.orig/plugin/pkg/admission/imagepolicy/admission.go +++ kubernetes-1.23.17/plugin/pkg/admission/imagepolicy/admission.go @@ -132,8 +132,8 @@ func (a *Plugin) webhookError(pod *api.P // Validate makes an admission decision based on the request attributes func (a *Plugin) Validate(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) (err error) { - // Ignore all calls to subresources or resources other than pods. - if attributes.GetSubresource() != "" || attributes.GetResource().GroupResource() != api.Resource("pods") { + // Ignore all calls to subresources other than ephemeralcontainers or calls to resources other than pods. + if (attributes.GetSubresource() != "" && attributes.GetSubresource() != "ephemeralcontainers") || attributes.GetResource().GroupResource() != api.Resource("pods") { return nil } @@ -144,13 +144,21 @@ func (a *Plugin) Validate(ctx context.Co // Build list of ImageReviewContainerSpec var imageReviewContainerSpecs []v1alpha1.ImageReviewContainerSpec - containers := make([]api.Container, 0, len(pod.Spec.Containers)+len(pod.Spec.InitContainers)) - containers = append(containers, pod.Spec.Containers...) - containers = append(containers, pod.Spec.InitContainers...) - for _, c := range containers { - imageReviewContainerSpecs = append(imageReviewContainerSpecs, v1alpha1.ImageReviewContainerSpec{ - Image: c.Image, - }) + if attributes.GetSubresource() == "" { + containers := make([]api.Container, 0, len(pod.Spec.Containers)+len(pod.Spec.InitContainers)) + containers = append(containers, pod.Spec.Containers...) + containers = append(containers, pod.Spec.InitContainers...) + for _, c := range containers { + imageReviewContainerSpecs = append(imageReviewContainerSpecs, v1alpha1.ImageReviewContainerSpec{ + Image: c.Image, + }) + } + } else if attributes.GetSubresource() == "ephemeralcontainers" { + for _, c := range pod.Spec.EphemeralContainers { + imageReviewContainerSpecs = append(imageReviewContainerSpecs, v1alpha1.ImageReviewContainerSpec{ + Image: c.Image, + }) + } } imageReview := v1alpha1.ImageReview{ Spec: v1alpha1.ImageReviewSpec{ Index: kubernetes-1.23.17/plugin/pkg/admission/imagepolicy/admission_test.go =================================================================== --- kubernetes-1.23.17.orig/plugin/pkg/admission/imagepolicy/admission_test.go +++ kubernetes-1.23.17/plugin/pkg/admission/imagepolicy/admission_test.go @@ -595,17 +595,23 @@ func TestContainerCombinations(t *testin test string pod *api.Pod wantAllowed, wantErr bool + subresource string + operation admission.Operation }{ { test: "Single container allowed", pod: goodPod("good"), wantAllowed: true, + subresource: "", + operation: admission.Create, }, { test: "Single container denied", pod: goodPod("bad"), wantAllowed: false, wantErr: true, + subresource: "", + operation: admission.Create, }, { test: "One good container, one bad", @@ -627,6 +633,8 @@ func TestContainerCombinations(t *testin }, wantAllowed: false, wantErr: true, + subresource: "", + operation: admission.Create, }, { test: "Multiple good containers", @@ -648,6 +656,8 @@ func TestContainerCombinations(t *testin }, wantAllowed: true, wantErr: false, + subresource: "", + operation: admission.Create, }, { test: "Multiple bad containers", @@ -669,6 +679,8 @@ func TestContainerCombinations(t *testin }, wantAllowed: false, wantErr: true, + subresource: "", + operation: admission.Create, }, { test: "Good container, bad init container", @@ -692,6 +704,8 @@ func TestContainerCombinations(t *testin }, wantAllowed: false, wantErr: true, + subresource: "", + operation: admission.Create, }, { test: "Bad container, good init container", @@ -715,6 +729,8 @@ func TestContainerCombinations(t *testin }, wantAllowed: false, wantErr: true, + subresource: "", + operation: admission.Create, }, { test: "Good container, good init container", @@ -738,6 +754,123 @@ func TestContainerCombinations(t *testin }, wantAllowed: true, wantErr: false, + subresource: "", + operation: admission.Create, + }, + { + test: "Good container, good init container, bad ephemeral container when updating ephemeralcontainers subresource", + pod: &api.Pod{ + Spec: api.PodSpec{ + ServiceAccountName: "default", + SecurityContext: &api.PodSecurityContext{}, + Containers: []api.Container{ + { + Image: "good", + SecurityContext: &api.SecurityContext{}, + }, + }, + InitContainers: []api.Container{ + { + Image: "good", + SecurityContext: &api.SecurityContext{}, + }, + }, + EphemeralContainers: []api.EphemeralContainer{ + { + EphemeralContainerCommon: api.EphemeralContainerCommon{ + Image: "bad", + SecurityContext: &api.SecurityContext{}, + }, + }, + }, + }, + }, + wantAllowed: false, + wantErr: true, + subresource: "ephemeralcontainers", + operation: admission.Update, + }, + { + test: "Good container, good init container, bad ephemeral container when updating subresource=='' which sets initContainer and container only", + pod: &api.Pod{ + Spec: api.PodSpec{ + ServiceAccountName: "default", + SecurityContext: &api.PodSecurityContext{}, + Containers: []api.Container{ + { + Image: "good", + SecurityContext: &api.SecurityContext{}, + }, + }, + InitContainers: []api.Container{ + { + Image: "good", + SecurityContext: &api.SecurityContext{}, + }, + }, + EphemeralContainers: []api.EphemeralContainer{ + { + EphemeralContainerCommon: api.EphemeralContainerCommon{ + Image: "bad", + SecurityContext: &api.SecurityContext{}, + }, + }, + }, + }, + }, + wantAllowed: true, + wantErr: false, + subresource: "", + operation: admission.Update, + }, + + { + test: "Bad container, good ephemeral container when updating subresource=='ephemeralcontainers' which sets ephemeralcontainers only", + pod: &api.Pod{ + Spec: api.PodSpec{ + ServiceAccountName: "default", + SecurityContext: &api.PodSecurityContext{}, + Containers: []api.Container{ + { + Image: "bad", + SecurityContext: &api.SecurityContext{}, + }, + }, + EphemeralContainers: []api.EphemeralContainer{ + { + EphemeralContainerCommon: api.EphemeralContainerCommon{ + Image: "good", + SecurityContext: &api.SecurityContext{}, + }, + }, + }, + }, + }, + wantAllowed: true, + wantErr: false, + subresource: "ephemeralcontainers", + operation: admission.Update, + }, + { + test: "Good ephemeral container", + pod: &api.Pod{ + Spec: api.PodSpec{ + ServiceAccountName: "default", + SecurityContext: &api.PodSecurityContext{}, + EphemeralContainers: []api.EphemeralContainer{ + { + EphemeralContainerCommon: api.EphemeralContainerCommon{ + Image: "good", + SecurityContext: &api.SecurityContext{}, + }, + }, + }, + }, + }, + wantAllowed: true, + wantErr: false, + subresource: "ephemeralcontainers", + operation: admission.Update, }, } for _, tt := range tests { @@ -759,7 +892,7 @@ func TestContainerCombinations(t *testin return } - attr := admission.NewAttributesRecord(tt.pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, &user.DefaultInfo{}) + attr := admission.NewAttributesRecord(tt.pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), tt.subresource, tt.operation, &metav1.CreateOptions{}, false, &user.DefaultInfo{}) err = wh.Validate(context.TODO(), attr, nil) if tt.wantAllowed { Index: kubernetes-1.23.17/plugin/pkg/admission/serviceaccount/admission.go =================================================================== --- kubernetes-1.23.17.orig/plugin/pkg/admission/serviceaccount/admission.go +++ kubernetes-1.23.17/plugin/pkg/admission/serviceaccount/admission.go @@ -100,7 +100,7 @@ var _ = genericadmissioninitializer.Want // 5. If MountServiceAccountToken is true, it adds a VolumeMount with the pod's ServiceAccount's api token secret to containers func NewServiceAccount() *Plugin { return &Plugin{ - Handler: admission.NewHandler(admission.Create), + Handler: admission.NewHandler(admission.Create, admission.Update), // TODO: enable this once we've swept secret usage to account for adding secret references to service accounts LimitSecretReferences: false, // Auto mount service account API token secrets @@ -140,7 +140,10 @@ func (s *Plugin) Admit(ctx context.Conte if shouldIgnore(a) { return nil } - + if a.GetOperation() != admission.Create { + // we only mutate pods during create requests + return nil + } pod := a.GetObject().(*api.Pod) // Don't modify the spec of mirror pods. @@ -180,6 +183,15 @@ func (s *Plugin) Validate(ctx context.Co pod := a.GetObject().(*api.Pod) + if a.GetOperation() == admission.Update && a.GetSubresource() == "ephemeralcontainers" { + return s.limitEphemeralContainerSecretReferences(pod, a) + } + + if a.GetOperation() != admission.Create { + // we only validate pod specs during create requests + return nil + } + // Mirror pods have restrictions on what they can reference if _, isMirrorPod := pod.Annotations[api.MirrorPodAnnotationKey]; isMirrorPod { if len(pod.Spec.ServiceAccountName) != 0 { @@ -205,6 +217,10 @@ func (s *Plugin) Validate(ctx context.Co return nil } + // Require container pods to have service accounts + if len(pod.Spec.ServiceAccountName) == 0 { + return admission.NewForbidden(a, fmt.Errorf("no service account specified for pod %s/%s", a.GetNamespace(), pod.Name)) + } // Ensure the referenced service account exists serviceAccount, err := s.getServiceAccount(a.GetNamespace(), pod.Spec.ServiceAccountName) if err != nil { @@ -221,10 +237,7 @@ func (s *Plugin) Validate(ctx context.Co } func shouldIgnore(a admission.Attributes) bool { - if a.GetResource().GroupResource() != api.Resource("pods") { - return true - } - if a.GetSubresource() != "" { + if a.GetResource().GroupResource() != api.Resource("pods") || (a.GetSubresource() != "" && a.GetSubresource() != "ephemeralcontainers") { return true } obj := a.GetObject() @@ -348,6 +361,36 @@ func (s *Plugin) limitSecretReferences(s } } return nil +} + +func (s *Plugin) limitEphemeralContainerSecretReferences(pod *api.Pod, a admission.Attributes) error { + // Require ephemeral container pods to have service accounts + if len(pod.Spec.ServiceAccountName) == 0 { + return admission.NewForbidden(a, fmt.Errorf("no service account specified for pod %s/%s", a.GetNamespace(), pod.Name)) + } + // Ensure the referenced service account exists + serviceAccount, err := s.getServiceAccount(a.GetNamespace(), pod.Spec.ServiceAccountName) + if err != nil { + return admission.NewForbidden(a, fmt.Errorf("error looking up service account %s/%s: %v", a.GetNamespace(), pod.Spec.ServiceAccountName, err)) + } + if !s.enforceMountableSecrets(serviceAccount) { + return nil + } + // Ensure all secrets the ephemeral containers reference are allowed by the service account + mountableSecrets := sets.NewString() + for _, s := range serviceAccount.Secrets { + mountableSecrets.Insert(s.Name) + } + for _, container := range pod.Spec.EphemeralContainers { + for _, env := range container.Env { + if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil { + if !mountableSecrets.Has(env.ValueFrom.SecretKeyRef.Name) { + return fmt.Errorf("ephemeral container %s with envVar %s referencing secret.secretName=\"%s\" is not allowed because service account %s does not reference that secret", container.Name, env.Name, env.ValueFrom.SecretKeyRef.Name, serviceAccount.Name) + } + } + } + } + return nil } func (s *Plugin) mountServiceAccountToken(serviceAccount *corev1.ServiceAccount, pod *api.Pod) { Index: kubernetes-1.23.17/plugin/pkg/admission/serviceaccount/admission_test.go =================================================================== --- kubernetes-1.23.17.orig/plugin/pkg/admission/serviceaccount/admission_test.go +++ kubernetes-1.23.17/plugin/pkg/admission/serviceaccount/admission_test.go @@ -28,7 +28,6 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apiserver/pkg/admission" admissiontesting "k8s.io/apiserver/pkg/admission/testing" "k8s.io/client-go/informers" @@ -225,10 +224,10 @@ func TestAssignsDefaultServiceAccountAnd } if !reflect.DeepEqual(expectedVolumes, pod.Spec.Volumes) { - t.Errorf("unexpected volumes: %s", diff.ObjectReflectDiff(expectedVolumes, pod.Spec.Volumes)) + t.Errorf("unexpected volumes: %s", cmp.Diff(expectedVolumes, pod.Spec.Volumes)) } if !reflect.DeepEqual(expectedVolumeMounts, pod.Spec.Containers[0].VolumeMounts) { - t.Errorf("unexpected volumes: %s", diff.ObjectReflectDiff(expectedVolumeMounts, pod.Spec.Containers[0].VolumeMounts)) + t.Errorf("unexpected volumes: %s", cmp.Diff(expectedVolumeMounts, pod.Spec.Containers[0].VolumeMounts)) } // ensure result converted to v1 matches defaulted object @@ -545,6 +544,34 @@ func TestAllowsReferencedSecret(t *testi if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err != nil { t.Errorf("Unexpected error: %v", err) } + + pod2 = &api.Pod{ + Spec: api.PodSpec{ + ServiceAccountName: DefaultServiceAccountName, + EphemeralContainers: []api.EphemeralContainer{ + { + EphemeralContainerCommon: api.EphemeralContainerCommon{ + Name: "container-2", + Env: []api.EnvVar{ + { + Name: "env-1", + ValueFrom: &api.EnvVarSource{ + SecretKeyRef: &api.SecretKeySelector{ + LocalObjectReference: api.LocalObjectReference{Name: "foo"}, + }, + }, + }, + }, + }, + }, + }, + }, + } + // validate enforces restrictions on secret mounts when operation==create and subresource=='' or operation==update and subresource==ephemeralcontainers" + attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "ephemeralcontainers", admission.Update, &metav1.UpdateOptions{}, false, nil) + if err := admit.Validate(context.TODO(), attrs, nil); err != nil { + t.Errorf("Unexpected error: %v", err) + } } func TestRejectsUnreferencedSecretVolumes(t *testing.T) { @@ -622,6 +649,66 @@ func TestRejectsUnreferencedSecretVolume if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err == nil || !strings.Contains(err.Error(), "with envVar") { t.Errorf("Unexpected error: %v", err) } + + pod2 = &api.Pod{ + Spec: api.PodSpec{ + ServiceAccountName: DefaultServiceAccountName, + InitContainers: []api.Container{ + { + Name: "container-1", + Env: []api.EnvVar{ + { + Name: "env-1", + ValueFrom: &api.EnvVarSource{ + SecretKeyRef: &api.SecretKeySelector{ + LocalObjectReference: api.LocalObjectReference{Name: "foo"}, + }, + }, + }, + }, + }, + }, + }, + } + attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil) + if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err != nil { + t.Errorf("admit only enforces restrictions on secret mounts when operation==create. Unexpected error: %v", err) + } + attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) + if err := admit.Validate(context.TODO(), attrs, nil); err == nil || !strings.Contains(err.Error(), "with envVar") { + t.Errorf("validate only enforces restrictions on secret mounts when operation==create and subresource==''. Unexpected error: %v", err) + } + + pod2 = &api.Pod{ + Spec: api.PodSpec{ + ServiceAccountName: DefaultServiceAccountName, + EphemeralContainers: []api.EphemeralContainer{ + { + EphemeralContainerCommon: api.EphemeralContainerCommon{ + Name: "container-2", + Env: []api.EnvVar{ + { + Name: "env-1", + ValueFrom: &api.EnvVarSource{ + SecretKeyRef: &api.SecretKeySelector{ + LocalObjectReference: api.LocalObjectReference{Name: "foo"}, + }, + }, + }, + }, + }, + }, + }, + }, + } + attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil) + if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err != nil { + t.Errorf("admit only enforces restrictions on secret mounts when operation==create and subresource==''. Unexpected error: %v", err) + } + attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "ephemeralcontainers", admission.Update, &metav1.UpdateOptions{}, false, nil) + if err := admit.Validate(context.TODO(), attrs, nil); err == nil || !strings.Contains(err.Error(), "with envVar") { + t.Errorf("validate enforces restrictions on secret mounts when operation==update and subresource==ephemeralcontainers. Unexpected error: %v", err) + } } func TestAllowUnreferencedSecretVolumesForPermissiveSAs(t *testing.T) {
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor