= Admission Control for Kubernetes on AWS :toc: :icons: :linkcss: :imagesdir: ../../resources/images Doc Writer v1.0, 2018-01-17 Kubernetes https://kubernetes.io/docs/admin/admission-controllers/[Admission Controllers] perform *semantic validation* of resources during create, update, and delete operations. In Kubernetes 1.8+, you can use the http://www.openpolicyagent.org/[Open Policy Agent] and https://kubernetes.io/docs/admin/extensible-admission-controllers/#external-admission-webhooks[Kubernetes External Admission Webhooks] to enforce custom admission control policies without recompiling or reconfiguring the Kubernetes API server. The Open Policy Agent is a general-purpose policy engine that aims to policy-enable other projects or services. It provides a rich, declarative policy language where policy decisions may be booleans (e.g. allow/deny), strings (e.g. hostnames), numbers (e.g. ratelimits), arrays (e.g. destination hosts for a pod), or dictionaries (e.g. JSON patch changes to a pod definition). For example, using OPA with Kubernetes you could enforce any of the following policies: * All images come from an AWS repository (other than a whitelist) * Images are pulled from the same AWS repository in the same region as the cluster * Each namespace is controlled by the users in different AWS IAM groups == Goals This tutorial shows how to use OPA to enforce custom policies on resources in Kubernetes running on AWS. For the purpose of this tutorial, you will define a policy that requires all pod images to come from an AWS image repository. == Prerequisites This tutorial requires a Kubernetes 1.8 cluster. Keep in mind that External Admission Webhook support in Kubernetes is currently in **alpha**. == Overview . Create the Kubernetes cluster with `kops` and enable the External Admission Webhooks . Install the OpenPolicyAgent as an External Admission Webhook . Write admission control policies with the OpenPolicyAgent == Step 1: Create Cluster with External Admission Controllers via `kops` Create a Kubernetes 1.8 cluster with `kops` as shown in the link:../cluster-install/readme.adoc[Cluster Install] instructions. Update the Cluster spec to enable webhooks. (Remember that when you set up the cluster you chose the name `example.cluster.k8s.local`. ) ```bash kops edit example.cluster.k8s.local ``` ```yaml kubeAPIServer: admissionControl: - NamespaceLifecycle - LimitRanger - ServiceAccount - PersistentVolumeLabel - DefaultStorageClass - DefaultTolerationSeconds - GenericAdmissionWebhook - ResourceQuota runtimeConfig: admissionregistration.k8s.io/v1alpha1: "true" ``` Then update `kops`. ```bash kops update example.cluster.k8s.local --yes ``` == Step 2: Deploy OPA on Kubernetes === 2.1 Generate service-certificate for OPA First, create the required OpenSSL configuration files. Put these in your current directory; we will only use them once to generate certificates. You may discard them after running the `openssl` commands below. *server.conf* ```bash [req] req_extensions = v3_req distinguished_name = req_distinguished_name [req_distinguished_name] [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment extendedKeyUsage = clientAuth, serverAuth ``` Now create local files that contain the CA and server key pair. Create a certificate authority: ```bash openssl genrsa -out ca.key 2048 ``` ```bash openssl req -x509 -new -nodes -key ca.key -days 100000 -out ca.crt -subj "/CN=admission_ca" ``` Create a server certiticate. IMPORTANT: the CN must match the name of the service used to expose the external admission controller. ```bash openssl genrsa -out server.key 2048 ``` ```bash openssl req -new -key server.key -out server.csr -subj "/CN=opa.opa.svc" -config server.conf ``` ```bash openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 100000 -extensions v3_req -extfile server.conf ``` === 2.2: Deploy OPA on Kubernetes First, create a namespace to deploy OPA into. ```bash kubectl create namespace opa ``` Create a Service to expose the OPA API. The Kubernetes API server will lookup the Service and execute webhook requests against it. **opa-admission-controller-service.yaml**: ```yaml kind: Service apiVersion: v1 metadata: name: opa spec: selector: app: opa ports: - name: https protocol: TCP port: 443 targetPort: 443 ``` ```bash kubectl create -f opa-admission-controller-service.yaml -n opa ``` Next, create Secrets containing the TLS credentials for OPA: ```bash kubectl create secret generic opa-ca --from-file=ca.crt -n opa kubectl create secret tls opa-server --cert=server.crt --key=server.key -n opa ``` Finally, create the Deployment to run OPA as an Admission Controller. The deployment contains two containers: `opa` and `kube-mgmt`. `opa` by itself is a general-purpose policy engine and knows nothing about Kubernetes. `kube-mgmt` is a collection of Kubernetes-specific code that helps OPA interact with kubernetes. **opa-admission-controller-deployment.yaml**: ```yaml apiVersion: apps/v1 kind: Deployment metadata: labels: app: opa name: opa spec: replicas: 1 selector: matchLabels: app: opa template: metadata: labels: app: opa name: opa spec: containers: - name: opa image: openpolicyagent/opa:0.5.13 args: - "run" - "--server" - "--tls-cert-file=/certs/tls.crt" - "--tls-private-key-file=/certs/tls.key" - "--addr=0.0.0.0:443" - "--insecure-addr=127.0.0.1:8181" volumeMounts: - readOnly: true mountPath: /certs name: opa-server - name: kube-mgmt image: openpolicyagent/kube-mgmt:0.5 args: - "--replicate=v1/pods" - "--pod-name=$(MY_POD_NAME)" - "--pod-namespace=$(MY_POD_NAMESPACE)" - "--register-admission-controller" - "--admission-controller-ca-cert-file=/certs/ca.crt" - "--admission-controller-service-name=opa" - "--admission-controller-service-namespace=$(MY_POD_NAMESPACE)" volumeMounts: - readOnly: true mountPath: /certs name: opa-ca env: - name: MY_POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: MY_POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace volumes: - name: opa-server secret: secretName: opa-server - name: opa-ca secret: secretName: opa-ca ``` ```bash kubectl create -f opa-admission-controller-deployment.yaml -n opa ``` When OPA starts, the sidecar (`kube-mgmt`) will register it as an External Admission Controller. To verify that registration succeeded, query the Kubernetes API for the list of External Admission Controllers. ```bash kubectl describe externaladmissionhookconfigurations admission.openpolicyagent.org ``` Finally, you can follow the OPA logs to see the webhook requests being issued by the Kubernetes API server: ``` kubectl logs -l app=opa -c opa -n opa ``` == Step 3: Enforce Kubernetes Admission Control with OPA === 3.1 Load a policy into OPA To test admission control, create a policy that requires all images to come from an AWS repository. For details on the policy language, see the http://www.openpolicyagent.org/docs/[Open Policy Agent] documentation. NOTE: Below replace the Amazon account ID 123456789 with your own account if your want the pod to actually come up. If you just want to see the admission controller in action, you can leave it with the fake ID. That account ID also appears in the image names when you create pods below; just make sure the account IDs are the same. **image_source.rego**: ```ruby package system # Deny requests that include container images not from ECR. deny[explanation] { image_name = input.spec.object.Spec.Containers[_].Image image_name_parts = split(image_name, "/") repo_name = image_name_parts[0] not startswith(repo_name, "12345678.dkr.ecr.us-west-2.amazonaws.com") explanation = sprintf("image '%v' not from AWS ECR", [image_name]) } # main is entry point to policy. # Boilerplate required by admission webhook. # Actual policy decision is `status`, which takes the form # {"allowed": BOOLEAN, "status": {"reason": STRING}} main = { "apiVersion": "admission.k8s.io/v1alpha1", "kind": "AdmissionReview", "status": {"allowed": allowed, "status": {"reason": reason}} } # Boilerplate: construct 'reason' and 'allowed' variables. # Real policy is the collection of 'deny' statements above. # If not denied, allow. reason = msg { msg = concat(", ", deny) } default allowed = true allowed = false { n = count(deny); n > 0 } ``` Store the policy in Kubernetes as a ConfigMap. ```bash kubectl create configmap image-source --from-file=image_source.rego -n opa ``` The OPA sidecar will notice the ConfigMap and automatically load the contained policy into OPA. === 3.2 Check that the policy is working To verify that your policy is working, create separate test pods. **nginx-pod.yaml**: ```yaml kind: Pod apiVersion: v1 metadata: name: nginx labels: app: nginx spec: containers: - image: nginx name: nginx ``` NOTE: Below replace the Amazon account ID 123456789 with your own account if your want the pod to actually come up. If you just want to see the admission controller in action, you can leave it with the fake ID. **amazon-linux-pod.yaml**: ```yaml kind: Pod apiVersion: v1 metadata: name: amazon-linux-pod labels: app: amazon-linux spec: containers: - image: 123456789.dkr.ecr.us-west-2.amazonaws.com/amazon-linux name: amazon-linux ``` Verify that you can create an amazon-linux pod. ```bash kubectl create -f amazon-linux-pod.yaml ``` Verify that you CANNOT create an nginx pod and receive the appropriate error message. ```bash kubectl create -f nginx-pod.yaml ``` ```bash Error from server (image 'nginx' not from AWS ECR): error when creating "nginx-pod.yaml": ``` This example shows how to ensure ALL images come from an AWS repository. But in reality you might have a collection of images like `nginx` that can come from outside of AWS. Or maybe you only want to apply the policy to certain Kubernetes namespaces. OPA's policy language is flexible enough to add image whitelists and control the applicable namespaces. Just modify your policy locally, update the ConfigMap, and `kube-mgmt` will update OPA with your changes. == Wrap Up Congratulations for finishing the tutorial! This tutorial showed how you can leverage OPA to enforce admission control decisions in Kubernetes clusters without modifying or recompiling any Kubernetes components. Furthermore, once Kubernetes is configured to use OPA as an External Admission Controller, policies can be modified on-the-fly to satisfy changing operational requirements. You are now ready to continue on with the workshop! :frame: none :grid: none :valign: top [align="center", cols="2", grid="none", frame="none"] |===== |image:button-continue-developer.png[link=../../04-path-security-and-networking/404-network-policies/] |image:button-continue-operations.png[link=../../04-path-security-and-networking/404-network-policies/] |link:../../developer-path.adoc[Go to Developer Index] |link:../../operations-path.adoc[Go to Operations Index] |=====