Back to Articles

Kubernetes RBAC: How to Create a User or Service Account and Generate a new Kubeconfig file


Quick Points

  • Kubernetes doesn’t have real users; it uses certificates
  • Service accounts authenticate to a cluster with tokens
  • The process of setting up each is similar with only minor differences
  • Both “Users” and service accounts must be tied to a Role or ClusterRole via a RoleBinding or ClusterRoleBinding

Just the Code

If you would just like the code and the commands to run, please use the quick links below to get there:

Outline

Confusion and RBAC in Kubernetes

When I started learning Kubernetes, this was by far the hardest piece of the puzzle for me to understand. Kubernetes doesn’t really have users. It uses either certificates for “users” or tokens for service accounts. I got frustrated trying to learn this concept because it seemed like many developers who wrote articles on this always misconfigured something and didn’t check the code they presented. Or worse, they casually used a flag such as --insecure-skip-tls-verify=true, which should immediately make you nervous. So in an attempt to correct this, I present the process below. We will go through both certificates and service account tokens.

Big Differences Between a User and a Kubernetes Service Account

What are the big differences between users and service accounts? In my mind, there are three: who/what they are intended for, where their identity comes from, and how you revoke access. Users and certificates are meant for actual people to authenticate to your cluster. Their identity comes from the certificate subject (CN) set during certificate creation, and access is revoked by rotating the cluster CA or waiting for the certificate to expire. Service accounts and their tokens are meant for automation outside of the cluster or identities within the cluster. Their identity comes from the service account name and the namespace it is created in, and access can be revoked either by the token expiring or by deleting the service account itself or its token.

Explanation: Users and Certificates

Both approaches will use either a Role or a ClusterRole The biggest difference is that a Role will restrict the account to a single namespace while a ClusterRole is used for cluster wide permissions. An example of a Role definition and a ClusterRole definition can be found here Role and ClusterRole Definition

You can then bind the user we will create to this role by using the below RoleBinding

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: example-rolebinding
  namespace: default
subjects:
- kind: User
  name: example-user
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: example-role
  apiGroup: rbac.authorization.k8s.io

After we have set up a role for our user example-user, you can check that this user can do the things you want by running kubectl auth can-i get pods --as=example-user. Try some things you don’t want the user to be able to do and make sure your restrictions are proper. Now let’s create our example-user. This process involves a lot of certificate work and it is worth doing more research to understand them all. This process will also be used to create a new kubeconfig file. You can then send this file to the user you are setting up and they’ll be off to the races.

#generate the private key
openssl genrsa -out example-user.pem

#just a user
openssl req -new -key example-user.pem -out example-user.csr -subj "/CN=example-user"

#user and group for reference
#openssl req -new -key example-user.pem -out example-user.csr -subj "/CN=example-user/O=ops-team"

#get a base 64 encoded version of the csr to submit to kubernetes
cat example-user.csr | base64 | tr -d "\n"

#create a csr object in kubernetes
kubectl apply -f - <<EOF
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: example-user
spec:
  request: <BASE64_ENCODED_CSR>
  signerName: kubernetes.io/kube-apiserver-client
  expirationSeconds: 86400  # one day
  usages:
  - digital signature
  - key encipherment
  - client auth
EOF

#check the status of the csr
kubectl get csr

#approve the CSR
kubectl certificate approve example-user

#check the new status
kubectl describe csr/example-user

#get the user's certificate and save it to a file
kubectl get csr/example-user -o jsonpath="{.status.certificate}" | base64 -d > example-user.crt

#get server of your current kubectl config and context to use for CLUSTER_ENDPOINT below
kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'

#get the certificate authority data from your current kubectl config and context
#PROTECT THIS FILE! delete it when done.
kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d > cluster_ca.crt

#configure your new file
kubectl config set-cluster new-cluster \
  --server=<CLUSTER_ENDPOINT> \
  --certificate-authority=<PATH_TO_CLUSTER_CA.CRT_FILE_ABOVE> \
  --embed-certs=true \
  --kubeconfig=new-config.yaml

#add the user credentials to the kubeconfig
kubectl config set-credentials my-user \
  --client-certificate=<PATH_TO_EXAMPLE-USER.CRT_ABOVE> \
  --client-key=<PATH_TO_EXAMPLE-USER.PEM_ABOVE> \
  --embed-certs=true \
  --kubeconfig=new-config.yaml

#tie cluster and user together
kubectl config set-context my-context \
  --cluster=new-cluster \
  --user=my-user \
  --namespace=default \
  --kubeconfig=new-config.yaml

#give the file a default context
kubectl config use-context my-context --kubeconfig=new-config.yaml

You can now test your new file by running kubectl get pods --kubeconfig=new-config.yaml

Explanation: Service Accounts and Tokens

Same as the user approach, we will use either a Role or a ClusterRole. The biggest difference is that a Role will restrict the account to a single namespace while a ClusterRole is used for cluster wide permissions. An example of a Role definition and a ClusterRole definition can be found here Role and ClusterRole Definition

You can then bind the serviceaccount we will create to this role by using the below RoleBinding

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: example-rolebinding
  namespace: default
subjects:
- kind: ServiceAccount
  name: example-service-account
  namespace: default
roleRef:
  kind: Role
  name: example-role
  apiGroup: rbac.authorization.k8s.io

Now Lets create our example-service-account. This process involves a lot of certificate work and it is worth doing more research to understand them all. This process will also be used to create a new kubeconfig file. This config file then can be used in your automation tool of choice to give it access to your cluster.


kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: example-service-account
  namespace: default
---
apiVersion: v1
kind: Secret
metadata:
  name: example-service-account-token
  namespace: default
  annotations:
    kubernetes.io/service-account.name: example-service-account
type: kubernetes.io/service-account-token
EOF

#get the service account's token. Use for SECRET_TOKEN
kubectl get secret example-service-account-token -o jsonpath='{.data.token}' | base64 -d

#get server of your current kubectlconfig and context use for CLUSTER_ENDPOINT below
kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'

#get the certificate authority data from your current kubectl config and context
#PROTECT THIS FILE!  delete it when done.
kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d > cluster_ca.crt


#configure your new file
kubectl config set-cluster new-cluster \
  --server=<CLUSTER_ENDPOINT> \
  --certificate-authority=<PATH_TO_CLUSTER_CA.CRT_FILE_ABOVE> \
  --embed-certs=true \
  --kubeconfig=new-config.yaml

#Main difference between service account and user setup in the file is here
#service accounts use tokens while users use certificates
kubectl config set-credentials my-user \
  --token="<SECRET_TOKEN>" \
  --kubeconfig=new-config.yaml

#tie cluster and user together
kubectl config set-context my-context \
  --cluster=new-cluster \
  --user=my-user \
  --namespace=default \
  --kubeconfig=new-config.yaml

#give the file a default context
kubectl config use-context my-context --kubeconfig=new-config.yaml

kubectl get pods —kubeconfig=new-config.yaml After we have setup a role for our service account example-service-account. You can check that this service account can do the things you want by running kubectl auth can-i get pods --as=system:serviceaccount:default:example-service-account. Try and do some things you don’t want the service account to be able to do as well and make sure your restrictions are proper.

Kubernetes Role and ClusterRole Definition

Both types of auth for this article will use the Role and RoleBinding. But a clusterRole and ClusterRoleBinding are provided for reference. Be sure to update the metadata.name metadata.namespace, rules and the roleRef for your case. You can see which verbs are allowed for a resouce by running kubectl get --raw /apis/<group>/<version> | jq. For example kubectl get --raw /apis/autoscaling/v2 | jq will give you the verbs for HPAs.

Role

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: example-role
  namespace: default
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]

ClusterRole and ClusterRoleBinding for reference. not used in this article

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-viewer
rules:
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cluster-viewer-binding
subjects:
- kind: Group
  name: ops-team
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-viewer
  apiGroup: rbac.authorization.k8s.io

Just the Code: Users and Certificates

Reference Role and ClusterRole Definition for the Role created for this user.

RoleBinding

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: example-rolebinding
  namespace: default
subjects:
- kind: User
  name: example-user
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: example-role
  apiGroup: rbac.authorization.k8s.io
#generate the private key
openssl genrsa -out example-user.pem

#just a user
openssl req -new -key example-user.pem -out example-user.csr -subj "/CN=example-user"

#user and group
openssl req -new -key example-user.pem -out example-user.csr -subj "/CN=example-user/O=ops-team"

#get a base 64 encoded version of the csr to submit to kubernetes
cat example-user.csr | base64 | tr -d "\n"

#create a csr object in kubernetes
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: example-user
spec:
  request: <BASE64_ENCODED_CSR>
  signerName: kubernetes.io/kube-apiserver-client
  expirationSeconds: 86400  # one day
  usages:
  - digital signature
  - key encipherment
  - client auth
EOF

#check the status of the csr
kubectl get csr

#approve the CSR
kubectl certificate approve example-user

#check the new status
kubectl describe csr/example-user

#get the user's certificate and save it to a file
kubectl get csr/example-user -o jsonpath="{.status.certificate}" | base64 -d > example-user.crt

#get server of your current kubectlconfig and context use for CLUSTER_ENDPOINT below
kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'

#get the certificate authority data from your current kubectl config and context
#PROTECT THIS FILE!  delete it when done.
kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d > cluster_ca.crt

#configure your new file
kubectl config set-cluster new-cluster \
  --server=<CLUSTER_ENDPOINT> \
  --certificate-authority=<PATH_TO_CLUSTER_CA.CRT_FILE_ABOVE> \
  --embed-certs=true \
  --kubeconfig=new-config.yaml

#add the user credentials to the kubeconfig
kubectl config set-credentials my-user \
  --client-certificate=<PATH_TO_EXAMPLE-USER.CRT_ABOVE> \
  --client-key=<PATH_TO_EXAMPLE-USER.PEM_ABOVE> \
  --embed-certs=true \
  --kubeconfig=new-config.yaml

#tie cluster and user together
kubectl config set-context my-context \
  --cluster=new-cluster \
  --user=my-user \
  --namespace=default \
  --kubeconfig=new-config.yaml

#give the file a default context
kubectl config use-context my-context --kubeconfig=new-config.yaml

Just the Code: Service Accounts and Tokens

Reference Role and ClusterRole Definition for the Role used for this service account.

RoleBinding

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: example-rolebinding
  namespace: default
subjects:
- kind: ServiceAccount
  name: example-service-account
  namespace: default
roleRef:
  kind: Role
  name: example-role
  apiGroup: rbac.authorization.k8s.io

kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: example-service-account
  namespace: default
---
apiVersion: v1
kind: Secret
metadata:
  name: example-service-account-token
  namespace: default
  annotations:
    kubernetes.io/service-account.name: example-service-account
type: kubernetes.io/service-account-token
EOF

#get the service account's token. Use for SECRET_TOKEN
kubectl get secret example-service-account-token -o jsonpath='{.data.token}' | base64 -d

#get server of your current kubectlconfig and context use for CLUSTER_ENDPOINT below
kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'

#get the certificate authority data from your current kubectl config and context
#PROTECT THIS FILE!  delete it when done.
kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d > cluster_ca.crt

#configure your new file
kubectl config set-cluster new-cluster \
  --server=<CLUSTER_ENDPOINT> \
  --certificate-authority=<PATH_TO_CLUSTER_CA.CRT_FILE_ABOVE> \
  --embed-certs=true \
  --kubeconfig=new-config.yaml

#Main difference between service account and user setup in the file is here
#service accounts use tokens while users use certificates
kubectl config set-credentials my-user \
  --token="<SECRET_TOKEN>" \
  --kubeconfig=new-config.yaml

#tie cluster and user together
kubectl config set-context my-context \
  --cluster=new-cluster \
  --user=my-user \
  --namespace=default \
  --kubeconfig=new-config.yaml

#give the file a default context
kubectl config use-context my-context --kubeconfig=new-config.yaml