Provide users access to Kubernetes cluster

Kubernetes allows external credentials for authentication, and these are:

  1. Static Token Files.
  2. Certificates.
  3. 3rd Party Identity Services.

All these are managed externally to the cluster by us because Kubernetes does not provide by itself any support for users. In this case we will configure the access to the Kubernetes  cluster for an individual user using the certificates method.
The process is simple,
First, the cluster administrators or the user generate the client key for the user (user.key). This key will be used to authenticate to the cluster therefore in order to be recognized as legit by the cluster, this key has to be signed by the cluster certificate authority.
Second, it is necessary to generate, from the key, a certificate signing request (user.csr) which is a file that contain the key.
Third, The key is signed by the cluster certification authority.
Fourth, the signed key (user.crt) handed out to the user.

The following step is get the certificated signed which produce a .crt file (user.crt) and finally the signed corticate has to be provided to the user so she can install on her local machine and start communicating with the cluster as shown above.

First, we verify if the cluster is configured to accept client certificates, so let’s run the following commands.
Run a command to find the pod name that runs the cluster api and check it. 

root@km1:~# k get pods -n kube-system | grep kube-apiserver
kube-apiserver-km1 1/1         Running 32 (18m ago) 91d 

now that we have the pod name we can verify if it accepts client certificates:   

root@km1:~# k describe pod kube-apiserver-km1 -n kube-system
Name:                          kube-apiserver-km1
Namespace:                     kube-system
. . .  <edited>
    Command:
         kube-apiserver
         --advertise-address=192.168.100.250
         --allow-privileged=true
         --authorization-mode=Node,RBAC
         --client-ca-file=/etc/kubernetes/pki/ca.crt
         --enable-admission-plugins=NodeRestriction
         --enable-bootstrap-token-auth=true
. . .  <edited>

We see in the highlighted line that we can use client certificate.
We have to generate the user certificate and sign it with the K8s cluster CA certificate. Notice that we are now work from the cluster master node virtual machine so to making this process easier to follow.
Since we are in the cluster master node, we have access to the cluster certificates that we need to use for signing the user private certificate. 

root@km1:~# ll /etc/kubernetes/pki
total 68
. . .  (edited)
-rw-r--r--  1 root root 1164  Jan 4 17:30 apiserver-kubelet-client.crt
-rw-------  1 root root 1679 J an 4 17:30 apiserver-kubelet-client.key
-rw-r--r--  1 root root 1099  Jan 4 17:30 ca.crt
-rw-------  1 root root 1675  Jan 4 17:30 ca.key

drwxr-xr-x  2 root root 4096  Jan 4 17:30 etcd/
-rw-r--r--  1 root root 1115    Jan 4 17:30 front-proxy-ca.crt
. . .  (edited)

The ca.crt and ca.key are the cluster certificate we have to use.

Create .key file

So now we can start creating the .key file in the user local computer:

tmp-ssl ✌️ $ openssl genrsa -out willy.key 2048 
tmp-ssl ✌️ $ openssl genrsa -out willy.key 2048
Generating RSA private key, 2048 bit long modulus
.................+++
...........................................+++
e is 65537 (0x10001)
tmp-ssl ✌️ $ ls -l
total 8
-rw-r--r-- 1 willy staff 1.6K Apr 6 09:58 willy.key
tmp-ssl ✌️ $  

Create .csr file

For creating the .csr file we need to keep in mind that if we want to create this certificate for a particular user we will need a user name (in certs lingo it is called “common name”) and also is convenient have a user group (in this context certs lingo “organization”), so we will use “willy” for the user name and “developers” for the group name.
We run the command as follow: 

tmp-ssl ✌️ $ openssl req -new -key willy.key -out willy.csr -subj "/CN=willy/O=developers"
tmp-ssl ✌️ $ ls -l
total 16
-rw-r--r-- 1 willy    staff    915    Apr 6 10:14    willy.csr
-rw-r--r-- 1 willy    staff    1679   Apr 6 09:58    willy.key
tmp-ssl ✌️ $ 

So we have the .csr file, and now we have two options for getting the user cert signed:
    1. Create the user certificate manually by signing our .csr with ca.crt and cs.key from the              cluster using openssl command.
   2. Use the K8s to do it for us by creating a K8s csr resource with the .csr file and after that           we can approve it with a kubectl command.   

Generate .crt certificate manually

We copy our .csr file to the master node in the cluster server that has the name “km1”:

tmp-ssl ✌️ $ scp ./willy.csr root@km1:~/tmp
willy.csr                                                          100%   915
495.9KB/s  00:00 
mp-ssl ✌️ $ 

So in the cluster master node we have:

root@km1:~/tmp# ls -l
total 4
-rw-r--r-- 1 root    root    915    Apr 6 16:57    willy.csr
root@km1:~/tmp# 

now we can run the command to sign the cert:

root@km1:~/tmp# openssl x509 -req -in willy.csr \
> -CA /etc/kubernetes/pki/ca.crt \
> -CAkey /etc/kubernetes/pki/ca.key \
> -CAcreateserial \
> -out willy.crt \
> -days 365

Signature ok
subject=CN = willy, O = developers
Getting CA Private Key
root@km1:~/tmp# ls -l
total 8
-rw-r--r-- 1 root  root 1 017  Apr 7 00:51  willy.crt
-rw-r--r-- 1 root  root  915   Apr 6 16:57  willy.csr
root@km1:~/tmp# 

Now we have the certificate we need for the user to access the cluster. But we have this file in the master node of the cluster (km1) so we could have access to the cluster “ca.” files. We need to copy the file willy.crt into the user local machine where the user will use it.
For simplicity I just created a new empty local file named willy.crt and pasted in it the content of the file in the cluster master node. Once done we have in the local server all what we need: 

tmp-ssl ✌️ $ ls -l
total 24
-rw-r--r--    1  willy   staff   1017   Apr 6 21:03   willy.crt
-rw-r--r--    1  willy   staff    915   Apr 6 10:14   willy.csr
-rw-r--r--    1  willy   staff   1679  Apr 6 09:58    willy.key
tmp-ssl ✌️ $  

Now we create the kubeconfig file for the user in his local machine:

tmp-ssl ✌️ $ k config set-credentials willy \
> --client-certificate willy.crt \
> --client-key willy.key


User "willy" set.
tmp-ssl ✌️ $ 

if we check the kubeconfig file we will find that the user willy was injected there:

. . .
clusters:
- cluster:
     certificate-authority-data: LS0tLS1CRUdJT
     server: https://192.168.100.250:6443
  name: kc-austin
- cluster:
. . .
- name: kubernetes-admin2
   user:
       client-certificate-data: LS0tLS1CRU
       client-key-data: LS0tLS1CR
- name: willy
   user:
       client-certificate: /Users/willy/tmp-ssl/willy.crt
       client-key: /Users/willy/tmp-ssl/willy.key 

we also need to add a context for this user in the kubeconfig file and for that we need to have the cluster name which in this case is “kc-austin”.

tmp-ssl ✌️ $ k config set-context willy-kc-austin --user willy --cluster kc-austin
Context "willy-kc-austin" created.
tmp-ssl ✌️ $ 

if we check the kubeconfig file we see:

. . .  <edited>
contexts:
- context:
      cluster: kc-austin
      user: kc-austin-admin
   name: kc-austin-admin@kc-austin
- context:
     cluster: kubernetes2
     user: kubernetes-admin2
  name: kubernetes-admin2@kubernetes2
- context:
     cluster: kubernetes
     user: kubernetes-admin
  name: kubernetes-admin@kubernetes
- context:
     cluster: kc-austin
     user: willy
  name: willy@kc-austin

current-context: kc-austin-admin@kc-austin
. . .  <edited>

so now we can see the contexts we have:

tmp-ssl ✌️ $ k config get-contexts
CURRENT NAME                          CLUSTER      AUTHINFO        NAMESPACE
*       kc-austin-admin@kc-austin     kc-austin    kc-austin-admin
        kubernetes-admin2@kubernetes2 kubernetes2  kubernetes-admin2
        kubernetes-admin@kubernetes   kubernetes   kubernetes-admin
         willy@kc-austin              ck-austin    willy
tmp-ssl ✌️ $ 

the list shows several clusters that are available in the network, but we care just the one highlighted, so we now change the context to test:

tmp-ssl ✌️ $ k config use-context willy@kc-austin
Switched to context "willy@kc-austin".

tmp-ssl ✌️ $ k config get-contexts
CURRENT NAME                          CLUSTER     AUTHINFO        NAMESPACE
        kc-austin-admin@kc-austin     kc-austin   kc-austin-admin
        kubernetes-admin2@kubernetes2 kubernetes2 kubernetes-admin2
        kubernetes-admin@kubernetes   kubernetes  kubernetes-admin
*       willy@kc-austin               ck-austin   willy                

but if we try to use this context we will get rejected because even when we configure a authentication for the user willy, we didn’t define any authorization for him.
For solve this we need to create a role and a role binding for the user but for that we have to switch context back to the administrator as the administrator has authority to do so. 

tmp-ssl ✌️ $ k config use-context kc-austin-admin@kc-austin
Switched to context "kc-austin-admin@kc-austin". 

So now we can create a role for listing pods for example and its corresponding role binding to the user willy:

tmp-ssl ✌️ $ k create role allow_pod_list --resource pods --verb list
role.rbac.authorization.k8s.io/allow_pod_list created
tmp-ssl ✌️ $ k create rolebinding allow_pod_list --role allow_pod_list --user willy
rolebinding.rbac.authorization.k8s.io/allow_pod_list created 

so now we switch context back to willy and test:

tmp-ssl ✌️ $ k config use-context willy@kc-austin
Switched to context "willy@kc-austin".
tmp-ssl ✌️ $ k config current-context
willy@kc-austin
tmp-ssl ✌️ $ k get pods
NAME                     READY      STATUS           RESTARTS             AGE
coffee-7c86d7d67c-429hb  1/1        Running          19 (8m38s ago)       69d
coffee-7c86d7d67c-s9v5r  1/1        Running          19 (8m38s ago)       69d
tmp-ssl ✌️ $ k get nodes

Error from server (Forbidden): nodes is forbidden: User "willy" cannot list resource "nodes" in API group "" at the cluster scope 

as we see the user can list pods but cannot list nodes which is the expected behavior.

Create user .crt using Kubernetes Api

Let’s generate a new .key for a new user named "eddy"

tmp-ssl ✌️ $ openssl genrsa -out eddy.key 2048
Generating RSA private key, 2048 bit long modulus
...................+++
.....................+++
e is 65537 (0x10001)
tmp-ssl ✌️ $ ls -l
total 32
-rw-r--r-- 1  willy    staff 1.  675    Apr 8 09:19    eddy.key 

We create a .csr from the .key already created (answers highlighted):

tmp-ssl ✌️ $ openssl req -new -key eddy.key -out eddy.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:US
State or Province Name (full name) []:FL
Locality Name (eg, city) []:O
Organization Name (eg, company) []:NC
Organizational Unit Name (eg, section) []:CS
Common Name (eg, fully qualified host name) []:eddy
Email Address []:eddy@gmail.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:

tmp-ssl ✌️ $ ls -l e*
-rw-r--r-- 1   willy    staff  1013   Apr 8 09:37    eddy.csr
-rw-r--r-- 1   willy    staff  1675   Apr 8 09:19    eddy.key 

Now we create a .yaml manifest so we can ask the cluster to create the .crt for eddy.

tmp-ssl ✌️ $ cat eddy.csr
-----BEGIN CERTIFICATE REQUEST-----
MIICszCCAZsCAQAwbjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkZMMQowCAYDVQQH
. . . . 
Gekyx96SnB6LOX33EoCTm5iagr2gNQk=
-----END CERTIFICATE REQUEST----- 

We have to change this cert format to base64 in order to include it in our yaml manifest and for that we run the following command:

tmp-ssl ✌️ $ cat eddy.csr | base64 | tr -d "\n"

this command grab the certificate, encode it in base64 and strip out the new lines and returns us the result that we copy and paste in the .yaml file.
Our eddy-csr.yaml files looks like this:

apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
    name: eddy
spec:
    groups:
    - developers
request: LS0tLS1CRUdJTiBDRVJUSUZJ. . . <edited>. . .
signerName: kubernetes.io/kube-apiserver-client
expirationSeconds: 86400 # one day
usages:
    - client auth 

We now create the .csr request in the cluster as follow:

tmp-ssl ✌️ $ k create -f eddy-csr.yaml
certificatesigningrequest.certificates.k8s.io/eddy created
tmp-ssl ✌️ $  

and we check if it was processed:

tmp-ssl ✌️ $ k get csr
NAME  AGE   SIGNERNAME              REQUESTOR         REQDUR.     CONDITION
eddy   38s  kubernetes.io/kube-. . .kubernetes-admin  24h         Pending
tmp-ssl ✌️ $  

the certificate is shown “Pending” which means is pending for approval by the cluster administrator, se we go ahead and approve it:

tmp-ssl ✌️ $ k certificate approve eddy
certificatesigningrequest.certificates.k8s.io/eddy approved
tmp-ssl ✌️ $ k get csr
NAME   AGE   SIGNERNAME          REQUESTOR          REQDUR.    CONDITION 
eddy   79m  kubernetes.io/kube-  kubernetes-admin   24h        Approved,Issued
tmp-ssl ✌️ $  

as we see the certificate was approved and issued. Now we need to retrieve it so we can send it to the user 

tmp-ssl ✌️ $ k get csr eddy -o yaml
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
      creationTimestamp: "2022-04-08T14:00:57Z"
      name: eddy
      resourceVersion: "2124723"
      uid: 79c926d6-28c4-43c9-b0ea-68bde8808af2
spec:
     expirationSeconds: 86400
     groups:
     - system:masters
     - system:authenticated
     request: LS0tLS1CRU . . .. . . lDQVRFIFJFUVVFU1QtLS0tLQo=
     signerName: kubernetes.io/kube-apiserver-client
     usages:
     - client auth
     username: kubernetes-admin
status:
     certificate: LS0tLS1CRUdJTi . . .. . . iM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRQ3BXLytmT3NuTXlnbFlrVkRrcStUM2JEOFFzOWlJRWI1TQpud0FtaGxyeWt2MXc5UkFuRi9NR0JyTXQyNWNNenFPeDROcHQwRXdMamZZTXRTUVAreWgybVBTcUVEUtLS0tLQo=
    conditions:
    - lastTransitionTime: "2022-04-08T15:19:33Z"
      lastUpdateTime: "2022-04-08T15:19:33Z"
      message: This CSR was approved by kubectl certificate approve.
      reason: KubectlApprove
      status: "True"
      type: Approved
tmp-ssl ✌️ $  

now we extract the cert that is in the status field and translate from encoded base64

tmp-ssl ✌️ echo ‘LS0tLS1CRUdJTi . .. . lDQVRFIFJFUVVFU1QtLS0tLQo=' | base64 --decode > eddy.crt
tmp-ssl ✌️ $ ls -l e*
-rw-r--r-- 1 willy   staff   1599  Apr 8 09:56    eddy-csr.yaml
-rw-r--r-- 1 willy   staff   1172   Apr 8 11:47   eddy.crt
-rw-r--r-- 1 willy   staff   1013   Apr 8 09:37   eddy.csr
-rw-r--r-- 1 willy   staff   1675   Apr 8 09:19   eddy.key
tmp-ssl ✌️ $  

We pass the certificates to user eddy.
Here we can create a kubeconfig file for user eddy, let’s say we called eddy.config using as a base the file we have in ~/.kube/config and changing the certificate files as shown below. 

. . .
- name: willy
   user:
      client-certificate: /Users/willy/tmp-ssl/eddy.crt
      client-key: /Users/willy/tmp-ssl/eddy.key 

note that alternatively we could insert the base64 encoded version instead of providing the path to the files. If we do that, we need to change the name of the parameters as shown below:

. . .
client-certificate-data: LS0tLS1CRUdJ
client-key-data: LS0tLS1CR 

we concluded our task.