Kubernetes User Access: Short-Lived Certificates and RBAC

Posted on July 9, 2020

Updated

Kubernetes authenticates requests and then authorizes actions. Normal human users are external identities rather than Kubernetes User objects. Prefer your organization’s federated identity for production. This disposable lab demonstrates a short-lived client certificate and a namespace-scoped read-only Role without exporting the cluster CA private key.

Prepare an isolated lab

Use a disposable kubeadm-style cluster, such as the kind lab, and an empty working directory. Confirm your admin kubeconfig points to that lab. Managed services may require their own identity integration, such as EKS access entries, instead of this signer.

kubectl config current-context
kubectl create namespace access-lab
umask 077
openssl req -new -newkey rsa:2048 -nodes \
  -keyout reader.key -out reader.csr -subj '/CN=cloudhero-reader'
REQUEST=$(openssl base64 -A -in reader.csr)
cat > reader-request.yaml <<EOF
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: cloudhero-reader
spec:
  request: ${REQUEST}
  signerName: kubernetes.io/kube-apiserver-client
  expirationSeconds: 3600
  usages: [client auth]
EOF
kubectl apply -f reader-request.yaml

Review and approve as the administrator

Inspect the identity and requested usages before approving. Approval permission is sensitive. The requester should not be allowed to approve arbitrary identities. The signer controls the final certificate lifetime.

openssl req -in reader.csr -noout -subject
kubectl certificate approve cloudhero-reader
kubectl wait --for=jsonpath='{.status.certificate}' \
  csr/cloudhero-reader --timeout=60s
kubectl get csr cloudhero-reader -o jsonpath='{.status.certificate}' \
  | openssl base64 -d -A > reader.crt
openssl x509 -in reader.crt -noout -subject -dates
kubectl -n access-lab create role pod-reader \
  --verb=get,list,watch --resource=pods
kubectl -n access-lab create rolebinding pod-reader \
  --role=pod-reader --user=cloudhero-reader

Create a separate kubeconfig and test authorization

This example requires an admin kubeconfig with embedded CA data, as kind generates. It writes only the new reader.kubeconfig.

SERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
kubectl config view --raw --minify \
  -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' \
  | openssl base64 -d -A > reader-ca.crt
kubectl --kubeconfig=reader.kubeconfig config set-cluster lab \
  --server="$SERVER" --certificate-authority=reader-ca.crt --embed-certs=true
kubectl --kubeconfig=reader.kubeconfig config set-credentials reader \
  --client-certificate=reader.crt --client-key=reader.key --embed-certs=true
kubectl --kubeconfig=reader.kubeconfig config set-context reader \
  --cluster=lab --user=reader --namespace=access-lab
kubectl --kubeconfig=reader.kubeconfig config use-context reader
kubectl --kubeconfig=reader.kubeconfig get pods
kubectl --kubeconfig=reader.kubeconfig auth can-i get secrets -n access-lab
# Expect 'no' and a nonzero exit status for the last command.

Remove access and understand the limit

Delete the RoleBinding using the admin context, then confirm that the reader cannot list Pods. Removing a binding removes that authorization; it does not revoke the certificate or remove permissions granted by another binding. Kubernetes client certificates do not provide a general per-certificate revocation mechanism. Use short lifetimes and a centrally managed identity lifecycle.

kubectl -n access-lab delete rolebinding pod-reader
kubectl --kubeconfig=reader.kubeconfig auth can-i list pods -n access-lab
# Expect 'no'. Continue cleanup using the admin kubeconfig:
kubectl delete csr cloudhero-reader
kubectl delete namespace access-lab
rm -f reader.key reader.csr reader.crt reader-ca.crt \
  reader-request.yaml reader.kubeconfig

Sources and further reading