From 505f9207b8836ee3df62fbb353d297d0781b7cb9 Mon Sep 17 00:00:00 2001 From: Jose Date: Tue, 12 Jul 2022 13:23:35 +0200 Subject: [PATCH 1/5] Ignore target and .cache files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c17e4658..7d5b5cb9 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ hs_err_pid* admission-controller-framework.iml .idea +target +.cache \ No newline at end of file From 8600e5cd9781fdb3a28c1fc73fd4a07521bc439d Mon Sep 17 00:00:00 2001 From: Jose Date: Tue, 12 Jul 2022 13:23:47 +0200 Subject: [PATCH 2/5] Use Dekorate in the Spring Boot example and add documentation --- .../quarkus/AdmissionControllerConfig.java | 8 +- samples/spring-boot/Dockerfile | 5 + samples/spring-boot/README.md | 167 ++++++++++++++++++ ...create-pod-with-missing-label-example.yaml | 9 + .../k8s/mutating-webhook-configuration.yml | 22 +++ .../k8s/validating-webhook-configuration.yml | 22 +++ samples/spring-boot/pom.xml | 18 ++ .../webhook/sample/springboot/Config.java | 8 +- .../src/main/resources/application.properties | 26 +++ .../src/main/resources/kubernetes/common.yml | 8 + 10 files changed, 289 insertions(+), 4 deletions(-) create mode 100644 samples/spring-boot/Dockerfile create mode 100644 samples/spring-boot/README.md create mode 100644 samples/spring-boot/k8s/create-pod-with-missing-label-example.yaml create mode 100644 samples/spring-boot/k8s/mutating-webhook-configuration.yml create mode 100644 samples/spring-boot/k8s/validating-webhook-configuration.yml create mode 100644 samples/spring-boot/src/main/resources/kubernetes/common.yml diff --git a/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/AdmissionControllerConfig.java b/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/AdmissionControllerConfig.java index 7bf370b2..9e834bf8 100644 --- a/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/AdmissionControllerConfig.java +++ b/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/AdmissionControllerConfig.java @@ -42,7 +42,9 @@ public AdmissionController mutatingController() { @Named(VALIDATING_CONTROLLER) public AdmissionController validatingController() { return new AdmissionController<>((resource, operation) -> { - if (resource.getMetadata().getLabels().get(APP_NAME_LABEL_KEY) == null) { + if (resource.getMetadata() == null + || resource.getMetadata().getLabels() == null + || resource.getMetadata().getLabels().get(APP_NAME_LABEL_KEY) == null) { throw new NotAllowedException("Missing label: " + APP_NAME_LABEL_KEY); } }); @@ -62,7 +64,9 @@ public AsyncAdmissionController asyncMutatingController() { @Named(ASYNC_VALIDATING_CONTROLLER) public AsyncAdmissionController asyncValidatingController() { return new AsyncAdmissionController<>((resource, operation) -> { - if (resource.getMetadata().getLabels().get(APP_NAME_LABEL_KEY) == null) { + if (resource.getMetadata() == null + || resource.getMetadata().getLabels() == null + || resource.getMetadata().getLabels().get(APP_NAME_LABEL_KEY) == null) { throw new NotAllowedException("Missing label: " + APP_NAME_LABEL_KEY); } }); diff --git a/samples/spring-boot/Dockerfile b/samples/spring-boot/Dockerfile new file mode 100644 index 00000000..b825bc2c --- /dev/null +++ b/samples/spring-boot/Dockerfile @@ -0,0 +1,5 @@ +FROM openjdk:11 +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} spring-boot.jar +EXPOSE 8080 +ENTRYPOINT ["java","-jar","/spring-boot.jar"] diff --git a/samples/spring-boot/README.md b/samples/spring-boot/README.md new file mode 100644 index 00000000..4dce117a --- /dev/null +++ b/samples/spring-boot/README.md @@ -0,0 +1,167 @@ +## Admission Control for Pod resources in Spring Boot + +The idea of this example is to demonstrate how we can alter resources and, then, validate them using [Admission Control webhooks](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers) webhooks in Spring Boot. + +To be precise, this example will install two admission webhooks that watch and manage Pod resources: +(1) a Mutating admission webhook that will add the label "app.kubernetes.io/name" with value "mutation-test" to the Pod resource +(2) a Validation admission webhook that will verify all the new Pod resources have the label "app.kubernetes.io/name" + +**Note:** Kubernetes will first invoke mutating webhooks and then will invoke validation webhooks at this order. + +### Introduction + +The admission controllers are installed via either [the ValidatingWebhookConfiguration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#validatingwebhookconfiguration-v1-admissionregistration-k8s-io) resource for validation admission webhooks or [the MutatingWebhooksConfiguration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#mutatingwebhookconfiguration-v1-admissionregistration-k8s-io) resource for mutating admission webhooks. For example, the ValidatingWebhookConfiguration resource looks like: + +```yaml +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: "my-validation-webhook" +webhooks: + - name: "my-validation-webhook" + rules: ## 1 + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + scope: "Namespaced" + clientConfig: + service: + namespace: "test" + name: "spring-boot-sample" ## 2 + path: "/validate" + caBundle: "" ## 3 + admissionReviewVersions: ["v1"] +``` + +The above admission webhook configuration is going to watch "CREATE" events of POD resources (see ## 1). When new POD resources are installed into our Kubernetes cluster, then Kubernetes will invoke the POST resource with path "/validate" from the service name "spring-boot-sample" and Kubernetes namespace "test" (see ## 2). The complete URL that Kubernetes will do is "https://spring-boot-sample.test.svc:443/validate", so our application needs to be configured with SSL enabled and the webhook needs to be provided with the right CA bundle (see ## 3) that is accepted by the application. + +### Requirements + +Before getting started, you need to: + +1. have connected to your Kubernetes cluster using `kubectl/oc`. +2. have installed [the Cert Manager operator](https://cert-manager.io/) into your Kubernetes cluster. +3. have logged into a container registry such as `quay.io` at your choice. + +### Getting Started + +The first thing we need is to add the [Dekorate Spring Boot](https://dekorate.io/docs/spring-boot-integration) extension to generate the `Deployment` and `Service` Kubernetes resources into our Maven pom file: + +```xml + + io.dekorate + kubernetes-spring-starter + +``` + +The second thing we need is to enable SSL in Spring Boot. In this example, we're going to use the [Dekorate Cert-Manager](https://dekorate.io/docs/cert-manager) extension to generate the Cert-Manager resources and map the volumes where the certifications will be installed. See more information in [the Spring Boot with Cert-Manager example](https://github.com/dekorateio/dekorate/tree/main/examples/spring-boot-with-certmanager-example#enable-https-transport) from Dekorate. So, let's add this extension to our Maven pom file as well: + +```xml + + io.dekorate + certmanager-annotations + +``` + +Finally, update the application properties as suggested in [the Spring Boot with Cert-Manager example](https://github.com/dekorateio/dekorate/tree/main/examples/spring-boot-with-certmanager-example#enable-https-transport). + +### Deployment in Kubernetes + +Let's start by creating the Kubernetes namespace `test` where we'll install all the resources: + +``` +kubectl create namespace test +kubectl config set-context --current --namespace=test +``` + +Next, let's generate all the manifests and push the container image into your container registry. This example uses [Dekorate](https://dekorate.io/) to generate all the Kubernetes resources, so we can generate the manifests and build/push the image at once simply by running the following Maven command: + +``` +mvn clean install \ + -Ddekorate.docker.registry= \ + -Ddekorate.docker.group= \ + -Ddekorate.docker.version= \ + -Ddekorate.docker.autoPushEnabled=true +``` + +For example, if our container registry url is `quay.io`, the group is `jcarvaja` and the version is `latest`; the previous Maven command will build and push the image `quay.io/jcarvaja/spring-boot-sample:latest`. + +Moreover, the above command will also generate the following resources in the `target/classes/META-INF/kubernetes.yaml` file: +- `Deployment` and `Service` resources +- `Certificate` and `Issuer` resources to configure the Cert-Manager operator + +So, let's install all the generated resources into our Kubernetes cluster: + +``` +kubectl apply -f target/classes/META-INF/dekorate/kubernetes.yml +``` + +When installed, the Cert-Manager operator will watch the `Certificate` and `Issuer` resources and will generate a secret named `tls-secret`. We can see the content by running the following command: + +``` +kubectl -n test get secret tls-secret -o yaml +``` + +Next, we need to configure and install the admission webhook with the proper CA certificate (key `ca.crt` from the secret). Luckily, we can leverage this configuration to the Cert-Manager operator by using the annotation `cert-manager.io/inject-ca-from` (more information in [here](https://cert-manager.io/docs/concepts/ca-injector/#injecting-ca-data-from-a-certificate-resource)). + +Let's install the admission webhook with the annotation `cert-manager.io/inject-ca-from=test/spring-boot-sample`: + +``` +kubectl apply -f k8s/validating-webhook-configuration.yml +``` + +And let's verify that the `Cert-Manager` operator has populated the `caBundle` field thanks to the annotation `cert-manager.io/inject-ca-from`: + +``` +kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io pod-policy.spring-boot.example.com -o yaml | grep caBundle +``` + +**Note:** The content of the caBundle should be the same as in `kubectl -n test get secret tls-secret -o yaml | grep ca.crt`. + +So far, we have installed the validating webhook! Let's see it in action by creating a new POD resource: + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: pod-with-missing-label +spec: + containers: + - image: any + imagePullPolicy: IfNotPresent + name: spring-boot-sample +``` + +This POD resource should not pass the validation as the label "app.kubernetes.io/name" does not exist. + +When we install it, we should get the following error: + +``` +> kubectl apply -f k8s/create-pod-with-missing-label-example.yaml +Error from server: error when creating "k8s/create-pod-with-missing-label-example.yaml": admission webhook "pod-policy.spring-boot.example.com" denied the request: Missing label: app.kubernetes.io/name +``` + +So good so far! + +Let's now install the mutating webhook: + +``` +kubectl apply -f k8s/mutating-webhook-configuration.yml +``` + +And again, let's install the same Pod without annotations: + +``` +> kubectl apply -f k8s/create-pod-with-missing-label-example.yaml +pod/pod-with-missing-label created +``` + +Now, the pod resource passed the validation because the mutate webhook added the missing label. We can see the installed Pod resource: + +``` +> kubectl get pod pod-with-missing-label -o yaml | grep app.kubernetes.io/name +app.kubernetes.io/name: mutation-test +``` + +This label was added by our mutate webhook (see logic in [here](https://github.com/java-operator-sdk/admission-controller-framework/blob/ce64f6e2a1a11a538d73acf6c49af96c04ed484d/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/Config.java#L57)). \ No newline at end of file diff --git a/samples/spring-boot/k8s/create-pod-with-missing-label-example.yaml b/samples/spring-boot/k8s/create-pod-with-missing-label-example.yaml new file mode 100644 index 00000000..0c391514 --- /dev/null +++ b/samples/spring-boot/k8s/create-pod-with-missing-label-example.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod-with-missing-label +spec: + containers: + - image: any + imagePullPolicy: IfNotPresent + name: spring-boot-sample \ No newline at end of file diff --git a/samples/spring-boot/k8s/mutating-webhook-configuration.yml b/samples/spring-boot/k8s/mutating-webhook-configuration.yml new file mode 100644 index 00000000..fc424d06 --- /dev/null +++ b/samples/spring-boot/k8s/mutating-webhook-configuration.yml @@ -0,0 +1,22 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: "pod-mutating.spring-boot.example.com" + annotations: + cert-manager.io/inject-ca-from: test/spring-boot-sample +webhooks: + - name: "pod-mutating.spring-boot.example.com" + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + scope: "Namespaced" + clientConfig: + service: + namespace: "test" + name: "spring-boot-sample" + path: "/mutate" + admissionReviewVersions: ["v1"] + sideEffects: None + timeoutSeconds: 5 \ No newline at end of file diff --git a/samples/spring-boot/k8s/validating-webhook-configuration.yml b/samples/spring-boot/k8s/validating-webhook-configuration.yml new file mode 100644 index 00000000..dac1c42a --- /dev/null +++ b/samples/spring-boot/k8s/validating-webhook-configuration.yml @@ -0,0 +1,22 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: "pod-policy.spring-boot.example.com" + annotations: + cert-manager.io/inject-ca-from: test/spring-boot-sample +webhooks: + - name: "pod-policy.spring-boot.example.com" + rules: + - apiGroups: [""] + apiVersions: ["v1"] + operations: ["CREATE"] + resources: ["pods"] + scope: "Namespaced" + clientConfig: + service: + namespace: "test" + name: "spring-boot-sample" + path: "/validate" + admissionReviewVersions: ["v1"] + sideEffects: None + timeoutSeconds: 5 \ No newline at end of file diff --git a/samples/spring-boot/pom.xml b/samples/spring-boot/pom.xml index 79c01b82..add72414 100644 --- a/samples/spring-boot/pom.xml +++ b/samples/spring-boot/pom.xml @@ -16,6 +16,7 @@ 11 2.6.6 + 2.11.1 @@ -39,6 +40,16 @@ org.springframework.boot spring-boot-starter-webflux + + io.dekorate + kubernetes-spring-starter + ${dekorate.version} + + + io.dekorate + certmanager-annotations + ${dekorate.version} + org.springframework.boot spring-boot-starter-test @@ -62,6 +73,13 @@ org.springframework.boot spring-boot-maven-plugin ${spring-boot-dependencies.version} + + + + repackage + + + diff --git a/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/Config.java b/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/Config.java index ccc754b1..6be91bb5 100644 --- a/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/Config.java +++ b/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/Config.java @@ -30,7 +30,9 @@ public AdmissionController mutatingController() { @Bean public AdmissionController validatingController() { return new AdmissionController<>((resource, operation) -> { - if (resource.getMetadata().getLabels().get(APP_NAME_LABEL_KEY) == null) { + if (resource.getMetadata() == null + || resource.getMetadata().getLabels() == null + || resource.getMetadata().getLabels().get(APP_NAME_LABEL_KEY) == null) { throw new NotAllowedException("Missing label: " + APP_NAME_LABEL_KEY); } }); @@ -62,7 +64,9 @@ public AsyncAdmissionController asyncMutatingController() { @Bean public AsyncAdmissionController asyncValidatingController() { return new AsyncAdmissionController<>((resource, operation) -> { - if (resource.getMetadata().getLabels().get(APP_NAME_LABEL_KEY) == null) { + if (resource.getMetadata() == null + || resource.getMetadata().getLabels() == null + || resource.getMetadata().getLabels().get(APP_NAME_LABEL_KEY) == null) { throw new NotAllowedException("Missing label: " + APP_NAME_LABEL_KEY); } }); diff --git a/samples/spring-boot/src/main/resources/application.properties b/samples/spring-boot/src/main/resources/application.properties index 8b137891..6dbc7b91 100644 --- a/samples/spring-boot/src/main/resources/application.properties +++ b/samples/spring-boot/src/main/resources/application.properties @@ -1 +1,27 @@ +server.port=443 +server.ssl.enabled=true +server.ssl.key-store-type=PKCS12 +## To include the keystore secret +dekorate.options.input-path=kubernetes +## To generate the Certificate and the Issuer resources +dekorate.certificate.secret-name=tls-secret +dekorate.certificate.dnsNames=spring-boot-sample.test.svc,localhost +dekorate.certificate.self-signed.enabled=true +dekorate.certificate.subject.organizations=Dekorate,Community +dekorate.certificate.duration=2160h0m0s +dekorate.certificate.renewBefore=360h0m0s +dekorate.certificate.privateKey.algorithm=RSA +dekorate.certificate.privateKey.encoding=PKCS8 +dekorate.certificate.privateKey.size=2048 +dekorate.certificate.keystores.pkcs12.create=true +dekorate.certificate.keystores.pkcs12.passwordSecretRef.name=pkcs12-pass +dekorate.certificate.keystores.pkcs12.passwordSecretRef.key=password +dekorate.certificate.usages=server auth,client auth + +## To configure the application for using the generated Certificate and Issuer resources +dekorate.kubernetes.env-vars[0].name=SERVER_SSL_KEY_STORE +dekorate.kubernetes.env-vars[0].value=/etc/certs/keystore.p12 +dekorate.kubernetes.env-vars[1].name=SERVER_SSL_KEY_STORE_PASSWORD +dekorate.kubernetes.env-vars[1].secret=pkcs12-pass +dekorate.kubernetes.env-vars[1].value=password \ No newline at end of file diff --git a/samples/spring-boot/src/main/resources/kubernetes/common.yml b/samples/spring-boot/src/main/resources/kubernetes/common.yml new file mode 100644 index 00000000..3c76a673 --- /dev/null +++ b/samples/spring-boot/src/main/resources/kubernetes/common.yml @@ -0,0 +1,8 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: pkcs12-pass +data: + password: c3VwZXJzZWNyZXQ= +type: Opaque From f1e1e15162d87eec03865b5b78ace7918182179b Mon Sep 17 00:00:00 2001 From: Jose Date: Tue, 12 Jul 2022 16:06:50 +0200 Subject: [PATCH 3/5] Don't check by metadata null --- .../admission/sample/quarkus/AdmissionControllerConfig.java | 6 ++---- .../javaoperatorsdk/webhook/sample/springboot/Config.java | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/AdmissionControllerConfig.java b/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/AdmissionControllerConfig.java index 9e834bf8..4fead571 100644 --- a/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/AdmissionControllerConfig.java +++ b/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/AdmissionControllerConfig.java @@ -42,8 +42,7 @@ public AdmissionController mutatingController() { @Named(VALIDATING_CONTROLLER) public AdmissionController validatingController() { return new AdmissionController<>((resource, operation) -> { - if (resource.getMetadata() == null - || resource.getMetadata().getLabels() == null + if (resource.getMetadata().getLabels() == null || resource.getMetadata().getLabels().get(APP_NAME_LABEL_KEY) == null) { throw new NotAllowedException("Missing label: " + APP_NAME_LABEL_KEY); } @@ -64,8 +63,7 @@ public AsyncAdmissionController asyncMutatingController() { @Named(ASYNC_VALIDATING_CONTROLLER) public AsyncAdmissionController asyncValidatingController() { return new AsyncAdmissionController<>((resource, operation) -> { - if (resource.getMetadata() == null - || resource.getMetadata().getLabels() == null + if (resource.getMetadata().getLabels() == null || resource.getMetadata().getLabels().get(APP_NAME_LABEL_KEY) == null) { throw new NotAllowedException("Missing label: " + APP_NAME_LABEL_KEY); } diff --git a/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/Config.java b/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/Config.java index 6be91bb5..851a3625 100644 --- a/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/Config.java +++ b/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/Config.java @@ -30,8 +30,7 @@ public AdmissionController mutatingController() { @Bean public AdmissionController validatingController() { return new AdmissionController<>((resource, operation) -> { - if (resource.getMetadata() == null - || resource.getMetadata().getLabels() == null + if (resource.getMetadata().getLabels() == null || resource.getMetadata().getLabels().get(APP_NAME_LABEL_KEY) == null) { throw new NotAllowedException("Missing label: " + APP_NAME_LABEL_KEY); } @@ -64,8 +63,7 @@ public AsyncAdmissionController asyncMutatingController() { @Bean public AsyncAdmissionController asyncValidatingController() { return new AsyncAdmissionController<>((resource, operation) -> { - if (resource.getMetadata() == null - || resource.getMetadata().getLabels() == null + if (resource.getMetadata().getLabels() == null || resource.getMetadata().getLabels().get(APP_NAME_LABEL_KEY) == null) { throw new NotAllowedException("Missing label: " + APP_NAME_LABEL_KEY); } From cf6ba70451fc4ced0adb385036d98741764c6aaa Mon Sep 17 00:00:00 2001 From: Jose Date: Wed, 13 Jul 2022 06:59:49 +0200 Subject: [PATCH 4/5] Replace the Dekorate docker extension by the Dekorate JIB extension --- samples/spring-boot/Dockerfile | 5 ----- samples/spring-boot/README.md | 16 ++++++++-------- samples/spring-boot/pom.xml | 5 +++++ .../src/main/resources/application.properties | 2 ++ 4 files changed, 15 insertions(+), 13 deletions(-) delete mode 100644 samples/spring-boot/Dockerfile diff --git a/samples/spring-boot/Dockerfile b/samples/spring-boot/Dockerfile deleted file mode 100644 index b825bc2c..00000000 --- a/samples/spring-boot/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM openjdk:11 -ARG JAR_FILE=target/*.jar -COPY ${JAR_FILE} spring-boot.jar -EXPOSE 8080 -ENTRYPOINT ["java","-jar","/spring-boot.jar"] diff --git a/samples/spring-boot/README.md b/samples/spring-boot/README.md index 4dce117a..18f3b723 100644 --- a/samples/spring-boot/README.md +++ b/samples/spring-boot/README.md @@ -75,14 +75,14 @@ kubectl create namespace test kubectl config set-context --current --namespace=test ``` -Next, let's generate all the manifests and push the container image into your container registry. This example uses [Dekorate](https://dekorate.io/) to generate all the Kubernetes resources, so we can generate the manifests and build/push the image at once simply by running the following Maven command: +Next, let's generate all the manifests and push the container image into your container registry. This example uses [Dekorate](https://dekorate.io/) to generate all the Kubernetes resources and the [Dekorate JIB](https://dekorate.io/docs/jib-build-hook) extension to build/push the image. Therefore, we can generate the manifests and build/push the image at once simply by running the following Maven command: ``` mvn clean install \ - -Ddekorate.docker.registry= \ - -Ddekorate.docker.group= \ - -Ddekorate.docker.version= \ - -Ddekorate.docker.autoPushEnabled=true + -Ddekorate.jib.registry= \ + -Ddekorate.jib.group= \ + -Ddekorate.jib.version= \ + -Ddekorate.jib.autoPushEnabled=true ``` For example, if our container registry url is `quay.io`, the group is `jcarvaja` and the version is `latest`; the previous Maven command will build and push the image `quay.io/jcarvaja/spring-boot-sample:latest`. @@ -138,7 +138,7 @@ This POD resource should not pass the validation as the label "app.kubernetes.io When we install it, we should get the following error: ``` -> kubectl apply -f k8s/create-pod-with-missing-label-example.yaml +> kubectl apply -f k8s/create-pod-with-missing-label-example.yml Error from server: error when creating "k8s/create-pod-with-missing-label-example.yaml": admission webhook "pod-policy.spring-boot.example.com" denied the request: Missing label: app.kubernetes.io/name ``` @@ -153,11 +153,11 @@ kubectl apply -f k8s/mutating-webhook-configuration.yml And again, let's install the same Pod without annotations: ``` -> kubectl apply -f k8s/create-pod-with-missing-label-example.yaml +> kubectl apply -f k8s/create-pod-with-missing-label-example.yml pod/pod-with-missing-label created ``` -Now, the pod resource passed the validation because the mutate webhook added the missing label. We can see the installed Pod resource: +Now, the pod resource passed the validation because the mutating webhook added the missing label. We can see the installed Pod resource: ``` > kubectl get pod pod-with-missing-label -o yaml | grep app.kubernetes.io/name diff --git a/samples/spring-boot/pom.xml b/samples/spring-boot/pom.xml index add72414..ea65aefe 100644 --- a/samples/spring-boot/pom.xml +++ b/samples/spring-boot/pom.xml @@ -50,6 +50,11 @@ certmanager-annotations ${dekorate.version} + + io.dekorate + jib-annotations + ${dekorate.version} + org.springframework.boot spring-boot-starter-test diff --git a/samples/spring-boot/src/main/resources/application.properties b/samples/spring-boot/src/main/resources/application.properties index 6dbc7b91..9bc6156a 100644 --- a/samples/spring-boot/src/main/resources/application.properties +++ b/samples/spring-boot/src/main/resources/application.properties @@ -1,6 +1,8 @@ server.port=443 server.ssl.enabled=true server.ssl.key-store-type=PKCS12 +## Container +dekorate.jib.from=openjdk:11 ## To include the keystore secret dekorate.options.input-path=kubernetes From 1646157993d2bee1666c211fd8c865a138f3eb4e Mon Sep 17 00:00:00 2001 From: Jose Date: Wed, 13 Jul 2022 07:00:21 +0200 Subject: [PATCH 5/5] Fix the mutating webhook to create the labels map if it does not exist --- .../sample/quarkus/AdmissionControllerConfig.java | 9 +++++++++ ...le.yaml => create-pod-with-missing-label-example.yml} | 0 .../webhook/sample/springboot/Config.java | 9 +++++++++ 3 files changed, 18 insertions(+) rename samples/spring-boot/k8s/{create-pod-with-missing-label-example.yaml => create-pod-with-missing-label-example.yml} (100%) diff --git a/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/AdmissionControllerConfig.java b/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/AdmissionControllerConfig.java index 4fead571..344149c3 100644 --- a/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/AdmissionControllerConfig.java +++ b/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/AdmissionControllerConfig.java @@ -1,5 +1,6 @@ package io.javaoperatorsdk.webhook.admission.sample.quarkus; +import java.util.HashMap; import java.util.concurrent.CompletableFuture; import javax.enterprise.context.Dependent; @@ -33,6 +34,10 @@ public class AdmissionControllerConfig { @Named(MUTATING_CONTROLLER) public AdmissionController mutatingController() { return new AdmissionController<>((resource, operation) -> { + if (resource.getMetadata().getLabels() == null) { + resource.getMetadata().setLabels(new HashMap<>()); + } + resource.getMetadata().getLabels().putIfAbsent(APP_NAME_LABEL_KEY, "mutation-test"); return resource; }); @@ -54,6 +59,10 @@ public AdmissionController validatingController() { public AsyncAdmissionController asyncMutatingController() { return new AsyncAdmissionController<>( (AsyncMutator) (resource, operation) -> CompletableFuture.supplyAsync(() -> { + if (resource.getMetadata().getLabels() == null) { + resource.getMetadata().setLabels(new HashMap<>()); + } + resource.getMetadata().getLabels().putIfAbsent(APP_NAME_LABEL_KEY, "mutation-test"); return resource; })); diff --git a/samples/spring-boot/k8s/create-pod-with-missing-label-example.yaml b/samples/spring-boot/k8s/create-pod-with-missing-label-example.yml similarity index 100% rename from samples/spring-boot/k8s/create-pod-with-missing-label-example.yaml rename to samples/spring-boot/k8s/create-pod-with-missing-label-example.yml diff --git a/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/Config.java b/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/Config.java index 851a3625..1e83946a 100644 --- a/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/Config.java +++ b/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/Config.java @@ -1,5 +1,6 @@ package io.javaoperatorsdk.webhook.sample.springboot; +import java.util.HashMap; import java.util.concurrent.CompletableFuture; import org.springframework.context.annotation.Bean; @@ -22,6 +23,10 @@ public class Config { @Bean public AdmissionController mutatingController() { return new AdmissionController<>((resource, operation) -> { + if (resource.getMetadata().getLabels() == null) { + resource.getMetadata().setLabels(new HashMap<>()); + } + resource.getMetadata().getLabels().putIfAbsent(APP_NAME_LABEL_KEY, "mutation-test"); return resource; }); @@ -55,6 +60,10 @@ public AdmissionController errorValidatingController() { public AsyncAdmissionController asyncMutatingController() { return new AsyncAdmissionController<>( (AsyncMutator) (resource, operation) -> CompletableFuture.supplyAsync(() -> { + if (resource.getMetadata().getLabels() == null) { + resource.getMetadata().setLabels(new HashMap<>()); + } + resource.getMetadata().getLabels().putIfAbsent(APP_NAME_LABEL_KEY, "mutation-test"); return resource; }));