1# Kubernetes Admission Control
2
3In Kubernetes, [Admission Controllers](https://kubernetes.io/docs/admin/admission-controllers/) enforce semantic validation of objects during create, update, and delete operations. With OPA you can enforce custom policies on Kubernetes objects without recompiling or reconfiguring the Kubernetes API server.
4
5## Goals
6
7This tutorial shows how to enforce custom policies on Kubernetes objects using OPA. In this tutorial, you will define admission control rules that prevent users from creating Kubernetes Ingress objects that violate the following organization policy:
8
9- Ingress hostnames must be whitelisted on the Namespace containing the Ingress.
10- Two ingresses in different namespaces must not have the same hostname.
11
12## Prerequisites
13
14This tutorial requires Kubernetes 1.9 or later. To run the tutorial locally, we recommend using [minikube](https://kubernetes.io/docs/getting-started-guides/minikube).
15
16## Steps
17
18### 1. Start Kubernetes recommended Admisson Controllers enabled
19
20To implement admission control rules that validate Kubernetes resources during create, update, and delete operations, you must enable the `ValidatingAdmissionWebhook` when the Kubernetes API server is started. The [recommended set of admission controllers to enable](https://kubernetes.io/docs/admin/admission-controllers/#is-there-a-recommended-set-of-admission-controllers-to-use) is defined below.
21
22```bash
23ADMISSION_CONTROLLERS=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota
24```
25
26Start minikube with these admission controllers enabled:
27
28```bash
29minikube start --kubernetes-version v1.9.0 \
30  --extra-config=apiserver.Admission.PluginNames=$ADMISSION_CONTROLLERS
31```
32
33Make sure that the minikube ingress addon is enabled:
34
35```bash
36minikube addons enable ingress
37```
38
39### 2. Create a new Namespace to deploy OPA into
40
41When OPA is deployed on top of Kubernetes, policies are automatically loaded out of ConfigMaps in the `opa` namespace.
42
43```bash
44kubectl create namespace opa
45```
46
47Configure `kubectl` to use this namespace:
48
49```bash
50kubectl config set-context opa-tutorial --user minikube --cluster minikube --namespace opa
51kubectl config use-context opa-tutorial
52```
53
54### 3. Deploy OPA on top of Kubernetes
55
56Communication between Kubernetes and OPA must be secured using TLS. To configure TLS, use `openssl` to create a certificate authority (CA) and certificate/key pair for OPA:
57
58```bash
59openssl genrsa -out ca.key 2048
60openssl req -x509 -new -nodes -key ca.key -days 100000 -out ca.crt -subj "/CN=admission_ca"
61```
62
63Generate the TLS key and certificate for OPA:
64
65```bash
66cat >server.conf <<EOF
67[req]
68req_extensions = v3_req
69distinguished_name = req_distinguished_name
70[req_distinguished_name]
71[ v3_req ]
72basicConstraints = CA:FALSE
73keyUsage = nonRepudiation, digitalSignature, keyEncipherment
74extendedKeyUsage = clientAuth, serverAuth
75EOF
76```
77
78```bash
79openssl genrsa -out server.key 2048
80openssl req -new -key server.key -out server.csr -subj "/CN=opa.opa.svc" -config server.conf
81openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 100000 -extensions v3_req -extfile server.conf
82```
83
84> Note: the Common Name value you give to openssl MUST match the name of the OPA service created below.
85
86Create a Secret to store the TLS credentials for OPA:
87
88```bash
89kubectl create secret tls opa-server --cert=server.crt --key=server.key
90```
91
92Next, use the file below to deploy OPA as an admission controller.
93
94**[admission-controller.yaml](https://github.com/open-policy-agent/opa/docs/book/tutorials/kubernetes-admission-control-validation/admission-controller.yaml)**:
95<pre><code class="lang-yaml">{% include "./tutorials/kubernetes-admission-control-validation/admission-controller.yaml" %}</code></pre>
96
97```bash
98kubectl apply -f admission-controller.yaml
99```
100
101When OPA starts, the `kube-mgmt` container will load Kubernetes Namespace and Ingress objects into OPA. You can configure the sidecar to load any kind of Kubernetes object into OPA. The sidecar establishes watches on the Kubernetes API server so that OPA has access to an eventually consistent cache of Kubernetes objects.
102
103Next, generate the manifest that will be used to register OPA as an admission controller:
104
105```bash
106cat > webhook-configuration.yaml <<EOF
107kind: ValidatingWebhookConfiguration
108apiVersion: admissionregistration.k8s.io/v1beta1
109metadata:
110  name: opa-validating-webhook
111webhooks:
112  - name: validating-webhook.openpolicyagent.org
113    rules:
114      - operations: ["CREATE", "UPDATE"]
115        apiGroups: ["*"]
116        apiVersions: ["*"]
117        resources: ["*"]
118    clientConfig:
119      caBundle: $(cat ca.crt | base64 | tr -d '\n')
120      service:
121        namespace: opa
122        name: opa
123EOF
124```
125
126The generated configuration file inclues a base64 encoded representation of the CA certificate so that TLS connections can be established between the Kubernetes API server and OPA.
127
128Finally, register OPA as and admission controller:
129
130```bash
131kubectl apply -f webhook-configuration.yaml
132```
133
134You can follow the OPA logs to see the webhook requests being issued by the Kubernetes API server:
135
136```
137kubectl logs -l app=opa -c opa
138```
139
140### 4. Define a policy and load it into OPA via Kubernetes
141
142To test admission control, create a policy that restricts the hostnames that an ingress can use ([ingress-whitelist.rego](https://github.com/open-policy-agent/opa/docs/book/tutorials/kubernetes-admission-control-validation/ingress-whitelist.rego)):
143
144<pre><code class="lang-ruby">{% include "./tutorials/kubernetes-admission-control-validation/ingress-whitelist.rego" %}</code></pre>
145
146Store the policy in Kubernetes as a ConfigMap.
147
148```bash
149kubectl create configmap ingress-whitelist --from-file=ingress-whitelist.rego
150```
151
152The OPA sidecar will notice the ConfigMap and automatically load the policy into OPA.
153
154### 5. Exercise the policy
155
156Create two new namespaces to test the Ingress policy.
157
158**qa-namespace.yaml**:
159
160```yaml
161apiVersion: v1
162kind: Namespace
163metadata:
164  annotations:
165    ingress-whitelist: "*.qa.acmecorp.com,*.internal.acmecorp.com"
166  name: qa
167```
168
169**production-namespace.yaml**:
170
171```yaml
172apiVersion: v1
173kind: Namespace
174metadata:
175  annotations:
176    ingress-whitelist: "*.acmecorp.com"
177  name: production
178```
179
180```bash
181kubectl create -f qa-namespace.yaml
182kubectl create -f production-namespace.yaml
183```
184
185Next, define two Ingress objects. One of the Ingress objects will be permitted
186and the other will be rejected.
187
188**ingress-ok.yaml**:
189
190```yaml
191apiVersion: extensions/v1beta1
192kind: Ingress
193metadata:
194  name: ingress-ok
195spec:
196  rules:
197  - host: signin.acmecorp.com
198    http:
199      paths:
200      - backend:
201          serviceName: nginx
202          servicePort: 80
203```
204
205**ingress-bad.yaml**:
206
207```yaml
208apiVersion: extensions/v1beta1
209kind: Ingress
210metadata:
211  name: ingress-bad
212spec:
213  rules:
214  - host: acmecorp.com
215    http:
216      paths:
217      - backend:
218          serviceName: nginx
219          servicePort: 80
220```
221
222Finally, try to create both Ingress objects:
223
224```bash
225kubectl create -f ingress-ok.yaml -n production
226kubectl create -f ingress-bad.yaml -n qa
227```
228
229The second Ingress is rejected because it's hostname does not match the whitelist in the `qa` namespace.
230
231### 6. Modify the policy and exercise the changes
232
233OPA allows you to modify policies on-the-fly without recompiling any of the services that offload policy decisions to it.
234
235To enforce the second half of the policy from the start of this tutorial you can load another policy into OPA that rejects Ingress objects in different namespaces from sharing the same hostname.
236
237[ingress-conflicts.rego](https://github.com/open-policy-agent/opa/docs/book/tutorials/kubernetes-admission-control-validation/ingress-conflicts.rego):
238
239<pre><code class="lang-ruby">{% include "./tutorials/kubernetes-admission-control-validation/ingress-conflicts.rego" %}</code></pre>
240
241```bash
242kubectl create configmap ingress-conflicts --from-file=ingress-conflicts.rego
243```
244
245The OPA sidecar annotates ConfigMaps containing policies to indicate if they
246were installed successfully. Verify the ConfigMap was installed successfully.
247
248```
249kubectl get configmap ingress-conflicts -o yaml
250```
251
252Test that you cannot create an Ingress in another namespace with the same hostname as the one created earlier.
253
254**staging-namespace.yaml**:
255
256```
257apiVersion: v1
258kind: Namespace
259metadata:
260  annotations:
261    ingress-whitelist: "*.acmecorp.com"
262  name: staging
263 ```
264
265```bash
266kubectl create -f staging-namespace.yaml
267```
268
269```bash
270kubectl create -f ingress-ok.yaml -n staging
271```
272
273## Wrap Up
274
275Congratulations for finishing the tutorial!
276
277This tutorial showed how you can leverage OPA to enforce admission control
278decisions in Kubernetes clusters without modifying or recompiling any
279Kubernetes components. Furthermore, once Kubernetes is configured to use OPA as
280an External Admission Controller, policies can be modified on-the-fly to
281satisfy changing operational requirements.
282
283For more information about deploying OPA on top of Kubernetes, see
284[Deployments - Kubernetes](/deployments.md#kubernetes).
285