diff --git a/pkg/certinjectionwebhook/admission_controller.go b/pkg/certinjectionwebhook/admission_controller.go index 0d185d4..a79dcab 100644 --- a/pkg/certinjectionwebhook/admission_controller.go +++ b/pkg/certinjectionwebhook/admission_controller.go @@ -8,6 +8,7 @@ import ( "context" "encoding/json" "fmt" + "strings" "github.com/pkg/errors" admissionv1 "k8s.io/api/admission/v1" @@ -59,7 +60,6 @@ func NewAdmissionController( caCertsData string, imagePullSecrets corev1.LocalObjectReference, ) (*admissionController, error) { - if len(labels) == 0 && len(annotations) == 0 { return nil, errors.New("at least one label or annotation required") } @@ -116,7 +116,7 @@ func (ac *admissionController) Admit(ctx context.Context, request *admissionv1.A return &admissionv1.AdmissionResponse{Allowed: true} } - if !(intersect(ac.labels, pod.Labels) || intersect(ac.annotations, pod.Annotations)) { + if !ac.matches(pod) { logger.Info("does not contain matching labels or annotations, letting it through") return &admissionv1.AdmissionResponse{Allowed: true} } @@ -250,10 +250,24 @@ func (ac *admissionController) setBuildServicePodDefaults(ctx context.Context, p var universalDeserializer = serializer.NewCodecFactory(runtime.NewScheme()).UniversalDeserializer() -func intersect(a []string, b map[string]string) bool { - for _, k := range a { - if _, ok := b[k]; ok { - return true +func (ac *admissionController) matches(pod corev1.Pod) bool { + return matchesMetadata(ac.labels, pod.Labels) || matchesMetadata(ac.annotations, pod.Annotations) +} + +func matchesMetadata(matchers []string, metadata map[string]string) bool { + for _, matcher := range matchers { + key, value, hasValue := strings.Cut(matcher, "=") + key = strings.TrimSpace(key) + value = strings.TrimSpace(value) + + if hasValue { + if metadata[key] == value { + return true + } + } else { + if _, ok := metadata[key]; ok { + return true + } } } return false diff --git a/pkg/certinjectionwebhook/admission_controller_test.go b/pkg/certinjectionwebhook/admission_controller_test.go index 2f4b691..fab50ba 100644 --- a/pkg/certinjectionwebhook/admission_controller_test.go +++ b/pkg/certinjectionwebhook/admission_controller_test.go @@ -77,8 +77,10 @@ func testPodAdmissionController(t *testing.T, when spec.G, it spec.S) { when("#Admit", func() { const ( - label = "some/label" - annotation = "some.annotation" + label = "some/label" + annotation = "some.annotation" + labelValue = "label-value" + annotationValue = "annotation-value" setupCACertsImage = "some-ca-certs-image" caCertsData = "some-ca-certs-data" @@ -587,7 +589,6 @@ func testPodAdmissionController(t *testing.T, when spec.G, it spec.S) { require.NoError(t, err) require.Equal(t, true, response.Allowed) - }) it("sets the ca certs on all containers on the pods that are annotated", func() { @@ -1176,6 +1177,76 @@ func testPodAdmissionController(t *testing.T, when spec.G, it spec.S) { assert.ElementsMatch(t, expectedPatch, actualPatch) }) + it("patches pods that match by label value", func() { + ac, err := certinjectionwebhook.NewAdmissionController( + name, + path, + func(ctx context.Context) context.Context { return ctx }, + []string{label + "= " + labelValue}, + []string{}, + envVars, + "", + "", + corev1.LocalObjectReference{}, + ) + require.NoError(t, err) + + testPod.Labels = map[string]string{ + label: labelValue, + } + + bytes, err := json.Marshal(testPod) + require.NoError(t, err) + + admissionRequest := &admissionv1.AdmissionRequest{ + Name: "testAdmissionRequest", + Object: runtime.RawExtension{ + Raw: bytes, + }, + Operation: admissionv1.Create, + Resource: metav1.GroupVersionResource{Version: "v1", Resource: "pods"}, + } + + response := ac.Admit(ctx, admissionRequest) + wtesting.ExpectAllowed(t, response) + require.NotEmpty(t, response.Patch) + }) + + it("patches pods that match by annotation value", func() { + ac, err := certinjectionwebhook.NewAdmissionController( + name, + path, + func(ctx context.Context) context.Context { return ctx }, + []string{}, + []string{annotation + " =" + annotationValue}, + envVars, + "", + "", + corev1.LocalObjectReference{}, + ) + require.NoError(t, err) + + testPod.Annotations = map[string]string{ + annotation: annotationValue, + } + + bytes, err := json.Marshal(testPod) + require.NoError(t, err) + + admissionRequest := &admissionv1.AdmissionRequest{ + Name: "testAdmissionRequest", + Object: runtime.RawExtension{ + Raw: bytes, + }, + Operation: admissionv1.Create, + Resource: metav1.GroupVersionResource{Version: "v1", Resource: "pods"}, + } + + response := ac.Admit(ctx, admissionRequest) + wtesting.ExpectAllowed(t, response) + require.NotEmpty(t, response.Patch) + }) + it("only patches pods", func() { testPod.Annotations = map[string]string{ annotation: "some value", @@ -1341,7 +1412,6 @@ func testPodAdmissionController(t *testing.T, when spec.G, it spec.S) { assert.ElementsMatch(t, expectedPatch, actualPatch) }) - }) it("#Path returns path", func() { @@ -1350,5 +1420,4 @@ func testPodAdmissionController(t *testing.T, when spec.G, it spec.S) { require.Equal(t, ac.Path(), path) }) - }