service account / x509 certificate and kubeconfig. Deep dive into the common technics to authenticate someone / something on kubernetes

Kubernetes make a distinction between authentication and authorisation. This post focus on the authentication mechanism, authorisation which is done via Role, RoleBinding, ClusterRole and ClusterRoleBinding is a topic for another day.

Kubectl is a convenience layer that speak to the API server. You can see those query being made with a verbose flag in kubectl. For instance:

# kubectl get namespaces -v=6 -n default
I0624 17:26:18.599399   39157 loader.go:375] Config loaded from file:  /home/mickael/.kube/config
I0624 17:26:19.575659   39157 round_trippers.go:443] GET https://116.203.202.210:6443/api/v1/namespaces?limit=500 200 OK in 966 milliseconds
NAME                   STATUS   AGE
cert-manager           Active   18d
default                Active   18d
...

As you can see, kubectl use our kubeconfig file securely authenticate us to the API server. Without authentication, the api server refuse to do anything:

# curl -I --insecure https://116.203.202.210:6443/api/v1/namespaces?limit=500
HTTP/2 403
cache-control: no-cache, private
content-type: application/json
x-content-type-options: nosniff
content-length: 320
date: Wed, 24 Jun 2020 07:29:48 GMT

Our query comes back with a 403 HTTP status code which stands for “Forbidden”. The server effectively understood the query but refused to answer it because of some issues from the client.

The reason is we’re missing authentication. Authentication can be quite thick to understand if you simply dig into the official documentation. This post is my attempt at making authentication on a kubernetes cluster simple. Before digging deeper, let’s make our life easier by creating a few environment variable:

API_SERVER=$(kubectl -n default get endpoints kubernetes --no-headers | awk '{ print $2 }')

Authentication

Kubernetes handle authentication either via a bearer token or x509 certificates.

Authentication with a Bearer Token

How to create a token?

Bearer token either comes from:

  • the secret associated to a service account.
  • a static token file known by the API server from one of its flag. See the doc
  • a static password file known by API server from its flag. See the doc
  • other ways that are discussed in the doc

In the common use case, you’d get a token from a service account by following this steps:

  1. Create a service account:
    cat <<EOF | kubectl apply -f -
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: testing-account
    
  2. find the secret kubernetes have associated to the service account:
    SECRET_NAME=$(kubectl get serviceaccount testing-account -o=jsonpath='{.secrets[0].name}')
    
  3. extract the token and root certificate from the secret:
    TOKEN=$(kubectl get secret $SECRET_NAME -o=go-template='' | base64 -d)
    

How to use a token via curl?

The way you use a bearer token is sending it to the API server via the “Authorization” header like Authorization: Bearer 31ada4fd-adec-460c-809a-9e56ceb75269. For example:

curl -X GET --insecure https://$API_SERVER/apis/ -H "Authorization: Bearer $TOKEN"

# let's make things secure by using the proper ca certificate and removing the insecure flag:
kubectl get secret $SECRET_NAME -o=go-template='' | base64 -d > /tmp/ca.crt
curl --cacert /tmp/ca.crt https://$API_SERVER/apis/ -H "Authorization: Bearer $TOKEN"

Authentication with X509 Certificate

How to create the certificate?

  1. with openssl, create a key and certificate signing request:
    openssl genrsa -out testing-user.key 2048
    openssl req -new -key testing-user.key -out testing-user.csr -subj "/CN=testing-user/O=testing-group"
    
  2. send your certificate to kubernetes:
    cat <<EOF | kubectl apply -n kubernetes-dashboard -f -
    apiVersion: certificates.k8s.io/v1beta1
    kind: CertificateSigningRequest
    metadata:
      name: testing-user
    spec:
      request: $(cat testing-user.csr | base64 | tr -d '\n')
      usages:
      - digital signature
      - key encipherment
      - client auth
    EOF
    
  3. generate the cert by signing the csr:
    kubectl get csr
    kubectl certificate approve testing-user
    

How to use the certificate via curl ?

# get the certificate
kubectl get csr testing-user -o=go-template='' | base64 -d > /tmp/testing-user.crt
curl --insecure --key testing-user.key --cert /tmp/testing-user.crt  -X GET https://$API_SERVER/apis/

# let's remove the insecure flag by first pulling the cacert from the cluster:
kubectl config view --raw -o=jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d > /tmp/ca.crt
curl --cacert /tmp/ca.crt --key testing-user.key --cert /tmp/testing-user.crt  -X GET https://$API_SERVER/apis/

How to use the certificate via kubeconfig?

cat > /tmp/kube-config.yaml <<EOF
apiVersion: v1
kind: Config
clusters:
  - name: kubernetes
    cluster:
      certificate-authority-data: $(cat /tmp/ca.crt | base64 | tr -d '\n')
      server: https://$API_SERVER
users:
  - name: testing-user
    user:
      client-certificate-data: $(cat /tmp/testing-user.crt | base64 | tr -d '\n')
      client-key-data: $(cat ./testing-user.key | base64 | tr -d '\n')
contexts:
- context:
    cluster: kubernetes
    user: testing-user
  name: testing-user@kubernetes
current-context: testing-user@kubernetes
EOF

With the proper authorisation set in the Role, RoleBinding and ClusterRole / ClusterRoleBinding:

kubectl get pod --kubeconfig=/tmp/kube-config.yaml