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
- Big Differences Between a User and a Kubernetes Service Account
- Explanation: Users and Certificates
- Explanation: Service Accounts and Tokens
- Kubernetes Role and ClusterRole Definition
- Just the Code: Users and Certificates
- Just the Code: Service Accounts and Tokens
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