|
| 1 | +## Admission Control for Pod resources in Spring Boot |
| 2 | + |
| 3 | +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. |
| 4 | + |
| 5 | +To be precise, this example will install two admission webhooks that watch and manage Pod resources: |
| 6 | +(1) a Mutating admission webhook that will add the label "app.kubernetes.io/name" with value "mutation-test" to the Pod resource |
| 7 | +(2) a Validation admission webhook that will verify all the new Pod resources have the label "app.kubernetes.io/name" |
| 8 | + |
| 9 | +**Note:** Kubernetes will first invoke mutating webhooks and then will invoke validation webhooks at this order. |
| 10 | + |
| 11 | +### Introduction |
| 12 | + |
| 13 | +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: |
| 14 | + |
| 15 | +```yaml |
| 16 | +apiVersion: admissionregistration.k8s.io/v1 |
| 17 | +kind: ValidatingWebhookConfiguration |
| 18 | +metadata: |
| 19 | + name: "my-validation-webhook" |
| 20 | +webhooks: |
| 21 | + - name: "my-validation-webhook" |
| 22 | + rules: ## 1 |
| 23 | + - apiGroups: [""] |
| 24 | + apiVersions: ["v1"] |
| 25 | + operations: ["CREATE"] |
| 26 | + resources: ["pods"] |
| 27 | + scope: "Namespaced" |
| 28 | + clientConfig: |
| 29 | + service: |
| 30 | + namespace: "test" |
| 31 | + name: "spring-boot-sample" ## 2 |
| 32 | + path: "/validate" |
| 33 | + caBundle: "<CA_BUNDLE>" ## 3 |
| 34 | + admissionReviewVersions: ["v1"] |
| 35 | +``` |
| 36 | +
|
| 37 | +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. |
| 38 | +
|
| 39 | +### Requirements |
| 40 | +
|
| 41 | +Before getting started, you need to: |
| 42 | +
|
| 43 | +1. have connected to your Kubernetes cluster using `kubectl/oc`. |
| 44 | +2. have installed [the Cert Manager operator](https://cert-manager.io/) into your Kubernetes cluster. |
| 45 | +3. have logged into a container registry such as `quay.io` at your choice. |
| 46 | + |
| 47 | +### Getting Started |
| 48 | + |
| 49 | +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: |
| 50 | + |
| 51 | +```xml |
| 52 | +<dependency> |
| 53 | + <groupId>io.dekorate</groupId> |
| 54 | + <artifactId>kubernetes-spring-starter</artifactId> |
| 55 | +</dependency> |
| 56 | +``` |
| 57 | + |
| 58 | +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: |
| 59 | + |
| 60 | +```xml |
| 61 | +<dependency> |
| 62 | + <groupId>io.dekorate</groupId> |
| 63 | + <artifactId>certmanager-annotations</artifactId> |
| 64 | +</dependency> |
| 65 | +``` |
| 66 | + |
| 67 | +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). |
| 68 | + |
| 69 | +### Deployment in Kubernetes |
| 70 | + |
| 71 | +Let's start by creating the Kubernetes namespace `test` where we'll install all the resources: |
| 72 | + |
| 73 | +``` |
| 74 | +kubectl create namespace test |
| 75 | +kubectl config set-context --current --namespace=test |
| 76 | +``` |
| 77 | + |
| 78 | +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: |
| 79 | + |
| 80 | +``` |
| 81 | +mvn clean install \ |
| 82 | + -Ddekorate.docker.registry=<CONTAINER REGISTRY URL> \ |
| 83 | + -Ddekorate.docker.group=<CONTAINER REGISTRY USER> \ |
| 84 | + -Ddekorate.docker.version=<VERSION> \ |
| 85 | + -Ddekorate.docker.autoPushEnabled=true |
| 86 | +``` |
| 87 | + |
| 88 | +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`. |
| 89 | + |
| 90 | +Moreover, the above command will also generate the following resources in the `target/classes/META-INF/kubernetes.yaml` file: |
| 91 | +- `Deployment` and `Service` resources |
| 92 | +- `Certificate` and `Issuer` resources to configure the Cert-Manager operator |
| 93 | + |
| 94 | +So, let's install all the generated resources into our Kubernetes cluster: |
| 95 | + |
| 96 | +``` |
| 97 | +kubectl apply -f target/classes/META-INF/dekorate/kubernetes.yml |
| 98 | +``` |
| 99 | + |
| 100 | +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: |
| 101 | + |
| 102 | +``` |
| 103 | +kubectl -n test get secret tls-secret -o yaml |
| 104 | +``` |
| 105 | + |
| 106 | +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)). |
| 107 | + |
| 108 | +Let's install the admission webhook with the annotation `cert-manager.io/inject-ca-from=test/spring-boot-sample`: |
| 109 | + |
| 110 | +``` |
| 111 | +kubectl apply -f k8s/validating-webhook-configuration.yml |
| 112 | +``` |
| 113 | + |
| 114 | +And let's verify that the `Cert-Manager` operator has populated the `caBundle` field thanks to the annotation `cert-manager.io/inject-ca-from`: |
| 115 | + |
| 116 | +``` |
| 117 | +kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io pod-policy.spring-boot.example.com -o yaml | grep caBundle |
| 118 | +``` |
| 119 | + |
| 120 | +**Note:** The content of the caBundle should be the same as in `kubectl -n test get secret tls-secret -o yaml | grep ca.crt`. |
| 121 | + |
| 122 | +So far, we have installed the validating webhook! Let's see it in action by creating a new POD resource: |
| 123 | + |
| 124 | +```yaml |
| 125 | +apiVersion: v1 |
| 126 | +kind: Pod |
| 127 | +metadata: |
| 128 | + name: pod-with-missing-label |
| 129 | +spec: |
| 130 | + containers: |
| 131 | + - image: any |
| 132 | + imagePullPolicy: IfNotPresent |
| 133 | + name: spring-boot-sample |
| 134 | +``` |
| 135 | + |
| 136 | +This POD resource should not pass the validation as the label "app.kubernetes.io/name" does not exist. |
| 137 | + |
| 138 | +When we install it, we should get the following error: |
| 139 | + |
| 140 | +``` |
| 141 | +> kubectl apply -f k8s/create-pod-with-missing-label-example.yaml |
| 142 | +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 |
| 143 | +``` |
| 144 | + |
| 145 | +So good so far! |
| 146 | + |
| 147 | +Let's now install the mutating webhook: |
| 148 | + |
| 149 | +``` |
| 150 | +kubectl apply -f k8s/mutating-webhook-configuration.yml |
| 151 | +``` |
| 152 | + |
| 153 | +And again, let's install the same Pod without annotations: |
| 154 | + |
| 155 | +``` |
| 156 | +> kubectl apply -f k8s/create-pod-with-missing-label-example.yaml |
| 157 | +pod/pod-with-missing-label created |
| 158 | +``` |
| 159 | + |
| 160 | +Now, the pod resource passed the validation because the mutate webhook added the missing label. We can see the installed Pod resource: |
| 161 | + |
| 162 | +``` |
| 163 | +> kubectl get pod pod-with-missing-label -o yaml | grep app.kubernetes.io/name |
| 164 | +app.kubernetes.io/name: mutation-test |
| 165 | +``` |
| 166 | + |
| 167 | +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)). |
0 commit comments