chore: imported articles and examples from the private kb

This commit is contained in:
Michele Cereda
2023-04-22 14:17:14 +02:00
parent 745c9ada43
commit bff03ec54d
6 changed files with 598 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
FROM python:alpine
RUN pip3 --no-cache-dir install flask jsonpatch
ADD webhook.py /
USER nobody
EXPOSE 8080
ENTRYPOINT python3 /webhook.py

View File

@@ -0,0 +1,263 @@
# Create an admission webhook
The example below will create a webhook which acts as both a `ValidatingAdmissionWebhook` and a `MutatingAdmissionWebhook`, but a real world one can act as only one of them. Or more. Your choice.
The procedure is executed in a `minikube` cluster, and will use a self signed certificate for the webhook connection.
> Be aware of the pros and cons of an `AdmissionWebhook` before deploying one:
>
> - when deploying the resources it validates it will **need to be up and running**, or those resources will be rejected
> - it will need to manage exception to avoid downtime
## Table of content <!-- omit in toc -->
1. [Concepts reminder](#concepts-reminder)
1. [Check the webhook controllers are enabled](#check-the-webhook-controllers-are-enabled)
1. [Write the webhook](#write-the-webhook)
1. [Create a certificate](#create-a-certificate)
1. [Configure the webhook](#configure-the-webhook)
1. [Troubleshooting](#troubleshooting)
1. [I cannot deploy the resources because the webhook cannot be reached](#i-cannot-deploy-the-resources-because-the-webhook-cannot-be-reached)
1. [Further readings](#further-readings)
1. [Sources](#sources)
## Concepts reminder
There are 2 special admission controllers in the list included in the Kubernetes `apiserver`:
- `ValidatingAdmissionWebhook`s, which can reject a request but cannot modify the object they are receiving in the admission request, and
- `MutatingAdmissionWebhook`s, which can modify objects by creating a patch that will be sent back in the admission response
These send admission requests to external HTTP callbacks and receive admission responses. If these two controllers are enabled, a Kubernetes administrator can create and configure an admission webhook in the cluster.
To do this:
1. [check if the admission webhook controllers are enabled](#check-the-webhook-controllers-are-enabled) in the cluster, and configure them if needed
1. [write the HTTP callback](#write-the-webhook) that will handle an admission requests; this can be a simple HTTP server that's deployed to the cluster, or even a serverless function just like in [Kelsey's validating webhook demo]
1. [configure the webhook](#configure-the-webhook) through the `ValidatingWebhookConfiguration` and/or `MutatingWebhookConfiguration` resources
## Check the webhook controllers are enabled
Check:
1. if the `MutatingAdmissionWebhook` and `ValidatingAdmissionWebhook` admission controllers are listed in the correct order in the `admission-control` flag of `kube-apiserver`:
```sh
$ kubectl get pods --namespace 'kube-system' 'kube-apiserver-minikube' -o 'yaml' \
| yq -y '.spec.containers[]
| select(.name == "kube-apiserver")
| .command' \
- \
| grep -e '--enable-admission-plugins' \
| tr ',' '\n' \
| grep 'AdmissionWebhook'
MutatingAdmissionWebhook
ValidatingAdmissionWebhook
```
1. if the admission registration API is enabled in your cluster by running the following:
```sh
$ kubectl api-versions | grep 'admissionregistration.k8s.io'
admissionregistration.k8s.io/v1
admissionregistration.k8s.io/v1beta1
```
## Write the webhook
Every language is fine as long as the webhook:
- accepts an [admission request];
- spits out an [admission response];
- uses a certificate to secure the connection, as all admission webhooks need to be on SSL; a self-signed certificate will be more than fine for testing
Example: [webhook.py]
After the webhook's creation:
1. create a containerized image of the webhook and save it to the registry
Dockerfile: [Dockerfile]
```sh
$ docker build -t webhook .
# on minikube
# will also need imagePullPolicy=Never in the container's spec
$ minikube cache add webhook
```
1. (if needed, see [below](#create-a-certificate)) to generate the self signed CA, a `CertificateSigningRequest` and the certificate, then create a `Secret` based on this certificate;
1. create a `Deployment` that will use the image created above; the service **must** be secured via SSL, so mount the secret created from the previous step as volumes in it
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: webhook
namespace: test
labels:
app: webhook
spec:
selector:
matchLabels:
app: webhook
template:
metadata:
namespace: test
labels:
app: webhook
spec:
containers:
- name: webhook
image: webhook
imagePullPolicy: Never # needed by minikube
ports:
- containerPort: 8443
volumeMounts:
- mountPath: /cert
name: cert
volumes:
- name: cert
secret:
secretName: webhook
```
1. create a `Service` pointing to the correct ports in same namespace as the `Deployment`
```yaml
apiVersion: v1
kind: Service
metadata:
name: webhook
namespace: test
spec:
selector:
app: webhook
ports:
- protocol: TCP
port: 443
targetPort: 8443
```
### Create a certificate
For testing purposes, let's just reuse the [script][certificate script] originally written by the Istio team to generate a certificate signing request:
```sh
cd /tmp
curl --continue-at - --remote-name https://raw.githubusercontent.com/istio/istio/release-0.7/install/kubernetes/webhook-create-signed-cert.sh
bash /tmp/webhook-create-signed-cert.sh --service webhook --namespace test --secret webhook
cd -
```
## Configure the webhook
Create a `ValidatingWebhookConfiguration` to register the service for validation upon pod creation:
```yaml
apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
name: validating-webhook
namespace: test
webhooks:
- name: webhook.example.com
failurePolicy: Fail
clientConfig:
service:
name: webhook
namespace: test
path: /validate
rules:
- apiGroups: [""]
resources:
- "pods"
apiVersions:
- "*"
operations:
- CREATE
```
At the same way, create a `MutatingWebhookConfiguration` to register the service for mutation upon pod creation:
```yaml
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
name: mutating-webhook
namespace: test
labels:
component: mutating-controller
webhooks:
- name: webhook.example.com
failurePolicy: Fail
clientConfig:
service:
name: webhook
namespace: test
path: /mutate
rules:
- apiGroups: [""]
resources:
- "pods"
apiVersions:
- "*"
operations:
- CREATE
```
## Troubleshooting
### I cannot deploy the resources because the webhook cannot be reached
**Solution:** remove the AdmissionWebhook and the Service forwarding to it, then reapply its definition
## Further readings
- [Admission request]
- [Admission response]
- This example's [Dockerfile]
- This example's [webhook source][webhook.py]
- This example's [resources]
- This example's [cert script]
- [Extensible admission controllers]
## Sources
All the references in the [further readings] section, plus the following:
- [Creating your own admission controller]
- [Diving into Kubernetes mutatingAdmissionWebhook]
- [how to write validating and mutating admission controller webhooks in python for kubernetes]
- [building a kubernetes mutating admission webhook]
- [K8S admission webhooks]
- [How to Master Admission Webhooks In Kubernetes]
- [openshift's generic admission server]
- [kelsey's validating webhook demo]
- [morvencao's kube-mutating-webhook-tutorial]
- [writing a very basic kubernetes mutating admission webhook]
- Istio's [script][certificate script] to generate a certificate signing request
<!-- project's references -->
[admission request]: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#request
[admission response]: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#response
[cert script]: create-signed-cert.sh
[certificate script]: https://raw.githubusercontent.com/istio/istio/release-0.7/install/kubernetes/webhook-create-signed-cert.sh
[dockerfile]: Dockerfile
[extensible admission controllers]: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/
[resources]: resources.yaml
[webhook.py]: webhook.py
<!-- internal references -->
[further readings]: #further-readings
<!-- external references -->
[building a kubernetes mutating admission webhook]: https://didil.medium.com/building-a-kubernetes-mutating-admission-webhook-7e48729523ed
[creating your own admission controller]: https://docs.giantswarm.io/guides/creating-your-own-admission-controller/
[diving into kubernetes mutatingadmissionwebhook]: https://medium.com/ibm-cloud/diving-into-kubernetes-mutatingadmissionwebhook-6ef3c5695f74
[how to master admission webhooks in kubernetes]: https://digizoo.com.au/1376/mastering-admission-webhooks-in-kubernetes-gke-part-1/
[how to write validating and mutating admission controller webhooks in python for kubernetes]: https://medium.com/analytics-vidhya/how-to-write-validating-and-mutating-admission-controller-webhooks-in-python-for-kubernetes-1e27862cb798
[k8s admission webhooks]: https://banzaicloud.com/blog/k8s-admission-webhooks/
[kelsey's validating webhook demo]: https://github.com/kelseyhightower/denyenv-validating-admission-webhook
[morvencao's kube-mutating-webhook-tutorial]: https://github.com/morvencao/kube-mutating-webhook-tutorial
[openshift's generic admission server]: https://github.com/openshift/generic-admission-server
[writing a very basic kubernetes mutating admission webhook]: https://medium.com/ovni/writing-a-very-basic-kubernetes-mutating-admission-webhook-398dbbcb63ec

View File

@@ -0,0 +1,131 @@
#!/bin/bash
set -e
usage() {
cat <<EOF
Generate certificate suitable for use with an Istio webhook service.
This script uses k8s' CertificateSigningRequest API to a generate a
certificate signed by k8s CA suitable for use with Istio webhook
services. This requires permissions to create and approve CSR. See
https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster for
detailed explantion and additional instructions.
The server key/cert k8s CA cert are stored in a k8s secret.
usage: ${0} [OPTIONS]
The following flags are required.
--service Service name of webhook.
--namespace Namespace where webhook service and secret reside.
--secret Secret name for CA certificate and server certificate/key pair.
EOF
exit 1
}
while [[ $# -gt 0 ]]; do
case ${1} in
--service)
service="$2"
shift
;;
--secret)
secret="$2"
shift
;;
--namespace)
namespace="$2"
shift
;;
*)
usage
;;
esac
shift
done
[ -z "${service}" ] && service=istio-sidecar-injector
[ -z "${secret}" ] && secret=sidecar-injector-certs
[ -z "${namespace}" ] && namespace=istio-system
if [ ! -x "$(command -v openssl)" ]; then
echo "openssl not found"
exit 1
fi
csrName="${service}.${namespace}"
tmpdir="$(mktemp -d)"
echo "creating certs in tmpdir ${tmpdir} "
cat <<EOF >> "${tmpdir}/csr.conf"
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
[req_distinguished_name]
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = ${service}
DNS.2 = ${service}.${namespace}
DNS.3 = ${service}.${namespace}.svc
EOF
openssl genrsa -out "${tmpdir}/server-key.pem" 2048
openssl req -new -key "${tmpdir}/server-key.pem" -subj "/CN=${service}.${namespace}.svc" -out ${tmpdir}/server.csr -config ${tmpdir}/csr.conf
# clean-up any previously created CSR for our service. Ignore errors if not present.
kubectl delete csr "${csrName}" 2>/dev/null || true
# create server cert/key CSR and send to k8s API
cat <<EOF | kubectl create -f -
apiVersion: certificates.k8s.io/v1beta1
kind: CertificateSigningRequest
metadata:
name: ${csrName}
spec:
groups:
- system:authenticated
request: $(cat "${tmpdir}/server.csr" | base64 | tr -d '\n')
usages:
- digital signature
- key encipherment
- server auth
EOF
# verify CSR has been created
while true; do
kubectl get csr "${csrName}"
if [ "$?" -eq 0 ]; then
break
fi
done
# approve and fetch the signed certificate
kubectl certificate approve "${csrName}"
# verify certificate has been signed
for x in $(seq 10); do
serverCert="$(kubectl get csr "${csrName}" -o jsonpath='{.status.certificate}')"
if [[ "${serverCert}" != '' ]]; then
break
fi
sleep 1
done
if [[ ${serverCert} == '' ]]; then
echo "ERROR: After approving csr ${csrName}, the signed certificate did not appear on the resource. Giving up after 10 attempts." >&2
echo "See https://istio.io/docs/setup/kubernetes/sidecar-injection.html for more details on troubleshooting." >&2
exit 1
fi
echo "${serverCert}" | openssl base64 -d -A -out "${tmpdir}/server-cert.pem"
# create the secret with CA cert and server cert/key
kubectl create secret generic "${secret}" \
--from-file="key.pem=${tmpdir}/server-key.pem" \
--from-file="cert.pem=${tmpdir}/server-cert.pem" \
--dry-run -o 'yaml' |
kubectl -n "${namespace}" apply -f -

View File

@@ -0,0 +1,89 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: webhook
namespace: test
labels:
app: webhook
spec:
selector:
matchLabels:
app: webhook
template:
metadata:
namespace: test
labels:
app: webhook
spec:
containers:
- name: webhook
image: webhook
imagePullPolicy: Never # only needed locally
ports:
- containerPort: 8443
volumeMounts:
- mountPath: /cert
name: cert
volumes:
- name: cert
secret:
secretName: webhook
---
apiVersion: v1
kind: Service
metadata:
name: webhook
namespace: test
spec:
selector:
app: webhook
ports:
- protocol: TCP
port: 443
targetPort: 8443
---
apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
name: webhook-validate
namespace: test
webhooks:
- name: test.example.com
failurePolicy: Fail
clientConfig:
service:
name: webhook
namespace: test
path: /validate
rules:
- apiGroups: [""]
resources:
- "pods"
apiVersions:
- "*"
operations:
- CREATE
---
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
name: webhook-mutate
namespace: test
labels:
component: mutating-controller
webhooks:
- name: test.example.com
failurePolicy: Fail
clientConfig:
service:
name: webhook
namespace: test
path: /mutate
rules:
- apiGroups: [""]
resources:
- "pods"
apiVersions:
- "*"
operations:
- CREATE

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python3
from base64 import b64encode
from flask import Flask, request, jsonify
from jsonpatch import JsonPatch
admission_webhook = Flask(__name__)
labels = [
"app.kubernetes.io/component",
"app.kubernetes.io/name",
"app.kubernetes.io/part-of",
"app.kubernetes.io/version"
]
@admission_webhook.route('/validate', methods=['POST'])
def validate():
info = request.get_json()
for label in labels:
try:
info["request"]["object"]["metadata"]["labels"][label]
except:
return validation_response(info["request"]["uid"], False, f"missing required label '{label}'")
return validation_response(info["request"]["uid"], True, "request validated successfully")
def validation_response(uid, allowed, message):
return jsonify({
"response": {
"allowed": allowed,
"status": {
"message": message
},
"uid": uid
}
})
@admission_webhook.route('/mutate', methods=['POST'])
def mutate():
info = request.get_json()
return mutation_response(
info["request"]["uid"],
True,
"adding label 'revised'",
json_patch = JsonPatch([{
"op": "add",
"path": "/metadata/labels/revised",
"value": "yes"
}])
)
def mutation_response(uid, allowed, message, json_patch):
base64_patch = b64encode(json_patch.to_string().encode("utf-8")).decode("utf-8")
return jsonify({
"response": {
"allowed": allowed,
"status": {
"message": message
},
"patchType": "JSONPatch",
"patch": base64_patch,
"uid": uid
}
})
if __name__ == '__main__':
admission_webhook.run(host='0.0.0.0', port=8443, ssl_context=("/cert/cert.pem", "/cert/key.pem"))

43
knowledge base/golang.md Normal file
View File

@@ -0,0 +1,43 @@
# Golang
## Table of contents <!-- omit in toc -->
1. [Inspect a variable](#inspect-a-variable)
1. [JSON data](#json-data)
1. [Create a container image of a Go app](#create-a-container-image-of-a-go-app)
1. [Further readings](#further-readings)
1. [Sources](#sources)
## Inspect a variable
See [how to print struct variables in console]
## JSON data
See [deserializing json in go a tutorial]
## Create a container image of a Go app
See [building minimal docker containers for go applications] and [create the smallest and secured golang docker image based on scratch]
## Further readings
- [Building minimal Docker containers for Go applications]
- [Create the smallest and secured Golang Docker image based on Scratch]
- [Deserializing JSON in Go a tutorial]
- [How to print struct variables in console]
## Sources
All the references in the [further readings] section, plus the following:
<!-- project's references -->
<!-- internal references -->
[further readings]: #further-readings
<!-- external references -->
[building minimal docker containers for go applications]: https://www.cloudbees.com/blog/building-minimal-docker-containers-for-go-applications/
[create the smallest and secured golang docker image based on scratch]: https://medium.com/@chemidy/create-the-smallest-and-secured-golang-docker-image-based-on-scratch-4752223b7324
[deserializing json in go a tutorial]: https://medium.com/@fsufitch/deserializing-json-in-go-a-tutorial-d042412958ea
[how to print struct variables in console]: https://stackoverflow.com/questions/24512112/how-to-print-struct-variables-in-console#24512194