Kubernetes auth: X509 client certificates
Kubernetes supports multiple means of authentication, for example Static Token File, Static Password File as well as OIDC, which are all very well documented. But another common way of authentication is to make use of X509 client certificates. In this blog post I will explain a bit about X509 client certificates as well as demonstrate how to set them up for use in your Kubernetes cluster.
In contrast to the other authentication methods, using client certificates for authentication uses public key cryptography for authentication instead of passwords or tokens. The advantage of using client certificates for authentication is that there is no provider storing any of this information, it’s pure cryptography, which is why we can trust requests identifying itself with a certificate.
Kubernetes has no user storage itself, therefore the identity must come from the chosen authentication mean. For example if you were to choose Open ID Connect, then the OIDC providers stores this information, and on authentication requests returns the respective user, which Kubernetes then uses to perform authorization. X509 client certificates fit that use case perfectly, as the content is signed by the Kubernetes cluster certificate authority and the Kubernetes apiserver only has to verify that the signature is legitimate. This means the user and group specified in the certificate are used once the signature is verified - no storage required.
In the case of X509 client certificates, Kubernetes verifies that the provided client certificate is in fact signed by the cluster’s certificate authority. Once Kubernetes has verified the certificate, it will treat the “Common Name” as the username and the “Organization” as the group of the user. Using this information one can then give a group or a user specific permission, using RBAC. This is how an example
ClusterRole manifest, that has read-only permissions for all Pods and Namespaces would look like:
apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: name: read-only-user rules: - apiGroups: - "" resources: - pods - namespaces verbs: - get - list - watch
Now to give a user these permissions, a
ClusterRoleBinding has to be created.
apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: read-only-users roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: read-only-user subjects: - apiGroup: rbac.authorization.k8s.io kind: User name: brancz
ClusterRoleBinding gives the user named “brancz” the roles specified in the
ClusterRole called “read-only-user”.
That was easy enough. I have written a small script to automate the process. The script first generates the client certificates, signs them with the cluster’s certificate authority and finally generates a bundled
kubeconfig. As a result the
kubeconfig is ready to be used with the Kubernetes cluster. The file is placed in the
clients/$USER/ directory (and creates missing directories as needed).
#!/usr/bin/env bash if [[ "$#" -ne 2 ]]; then echo "Usage: $0 user group" exit 1 fi USER=$1 GROUP=$2 CLUSTERENDPOINT=https://<apiserver-ip>:<apiserver-port> CLUSTERNAME=your-kubernetes-cluster-name CACERT=cluster/tls/ca.crt CAKEY=cluster/tls/ca.key CLIENTCERTKEY=clients/$USER/$USER.key CLIENTCERTCSR=clients/$USER/$USER.csr CLIENTCERTCRT=clients/$USER/$USER.crt mkdir -p clients/$USER openssl genrsa -out $CLIENTCERTKEY 4096 openssl req -new -key $CLIENTCERTKEY -out $CLIENTCERTCSR \ -subj "/O=$GROUP/CN=$USER" openssl x509 -req -days 365 -sha256 -in $CLIENTCERTCSR -CA $CACERT -CAkey $CAKEY -set_serial 2 -out $CLIENTCERTCRT cat <<-EOF > clients/$USER/kubeconfig apiVersion: v1 kind: Config preferences: colors: true current-context: $CLUSTERNAME clusters: - name: $CLUSTERNAME cluster: server: $CLUSTERENDPOINT certificate-authority-data: $(cat $CACERT | base64 --wrap=0) contexts: - context: cluster: $CLUSTERNAME user: $USER name: $CLUSTERNAME users: - name: $USER user: client-certificate-data: $(cat $CLIENTCERTCRT | base64 --wrap=0) client-key-data: $(cat $CLIENTCERTKEY | base64 --wrap=0) EOF
Note: This script was written to be run on Fedora 26, there may be slight incompatibilities across distributions. Make sure to adapt the paths to your certificate authority if they are different. The content of this blog post works on Kubernetes 1.7.x+.
Thanks for reading, for any questions and feedback feel free to contact me on twitter.