File kube-apiserver-admission-plugin-policy.patch of Package kubernetes1.18.29481
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.18.10/plugin/pkg/admission/imagepolicy/admission.go
===================================================================
--- kubernetes-1.18.10.orig/plugin/pkg/admission/imagepolicy/admission.go
+++ kubernetes-1.18.10/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.18.10/plugin/pkg/admission/imagepolicy/admission_test.go
===================================================================
--- kubernetes-1.18.10.orig/plugin/pkg/admission/imagepolicy/admission_test.go
+++ kubernetes-1.18.10/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.18.10/plugin/pkg/admission/serviceaccount/admission.go
===================================================================
--- kubernetes-1.18.10.orig/plugin/pkg/admission/serviceaccount/admission.go
+++ kubernetes-1.18.10/plugin/pkg/admission/serviceaccount/admission.go
@@ -108,7 +108,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
@@ -162,7 +162,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.
@@ -207,6 +210,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 {
@@ -232,6 +244,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 {
@@ -248,10 +264,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()
@@ -421,6 +434,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) error {
Index: kubernetes-1.18.10/plugin/pkg/admission/serviceaccount/admission_test.go
===================================================================
--- kubernetes-1.18.10.orig/plugin/pkg/admission/serviceaccount/admission_test.go
+++ kubernetes-1.18.10/plugin/pkg/admission/serviceaccount/admission_test.go
@@ -27,7 +27,6 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
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"
@@ -278,10 +277,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))
}
}
@@ -630,6 +629,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) {
@@ -708,6 +735,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) {