# Users, Groups, Roles and API Access in Kubernetes

This blog post describes the nuances of how `users` and `groups` are configured in Kubernetes and how the `role-based access control` (RBAC) mechanism applies for them.

We will also dive into the usage of the `kubectl` command line tool to check API access in Kubernetes. It especially focuses on the difference between the `--user` and `--as` options in `kubectl`*.*

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">This is a hands-on article. You may choose to follow along with the article or view the video on Youtube</div>
</div>

%[https://youtu.be/I-iVrIWfMl8?si=PsM2iqvMbxCe98c2] 

# Prerequisites

You need to have a Kubernetes cluster, and the [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one using [minikube](https://minikube.sigs.k8s.io/docs/start/).

[OpenSSL](https://www.openssl.org/source/) command line utility will be used to view the x509 certificates.

# Role-based access control

Role-based access control (RBAC) is a method of regulating access to computer or network resources based on the roles of individual users within your organisation.

You may choose to go through the [Kubernetes RBAC documentation](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) before reading ahead.

# Checking API Access

The `kubectl auth can-i` command can be used to determine whether a user has permissions to execute a certain action.

## **Scenario for the Default Admin User**

In the first example, we will work with the default admin user.

Check the contexts available in the minikube cluster:

```plaintext
kubectl config get-contexts minikube
```

You should see an output similar to:

```plaintext
CURRENT   NAME       CLUSTER    AUTHINFO   NAMESPACE
*         minikube   minikube   minikube   default
```

Check the name of the admin user that is created with our basic minikube installation:

```plaintext
kubectl config view -o jsonpath='{.contexts[?(@.name=="minikube")].context.user}'
```

You should see an output similar to:

```plaintext
minikube
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><em>There is no entity or resource named </em><code>User</code><em> in Kubernetes. A user in Kubernetes is nothing but a key and certificate pair issued by the Kubernetes cluster and presented to the Kubernetes API.</em></div>
</div>

Check the certificate of the minikube user:

```plaintext
kubectl config view -o jsonpath='{.users[?(@.name=="minikube")].user.client-certificate}'
```

You should see an output similar to:

```plaintext
/Users/adityasamant/.minikube/profiles/minikube/client.crt
```

View the Subject of this certificate (use the path generated in the previous command):

```plaintext
openssl x509 -in /Users/adityasamant/.minikube/profiles/minikube/client.crt -text -noout | grep Subject | grep -v "Public Key Info"
```

You should see an output similar to:

```plaintext
Subject: O=system:masters, CN=minikube-user
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><em>CN is the name of the user and O is the group that this user will belong to. As can be seen above, the minikube admin user (marked by CN) is part of the system:masters group (marked by O).</em></div>
</div>

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><em>system:masters is a group which is hardcoded into the Kubernetes API server source code as having unrestricted rights to the Kubernetes API server. Any user who is a member of this group has full cluster-admin rights to the cluster. Even if every cluster role and role is deleted from the cluster, users who are members of this group retain full access to the cluster.</em></div>
</div>

Use the `kubectl auth can-i` command to verify a few scenarios.

Check permissions to create pods:

```plaintext
kubectl auth can-i create pods
```

```plaintext
yes
```

Check permissions to create deployments:

```plaintext
kubectl auth can-i create deployments
```

```plaintext
yes
```

Check permissions to delete secrets:

```plaintext
kubectl auth can-i delete secrets
```

```plaintext
yes
```

## **Scenario for a Normal User**

Configure a normal user and verify how the `kubectl auth can-i` commands can be used to check the access. To do this we need to [issue a certificate](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/#normal-user) [for the user.](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/#normal-user)

Create a private key and a csr file:

```plaintext
openssl genrsa -out jane.key 2048
openssl req -new -key jane.key -out jane.csr -subj "/CN=jane"
```

This will generate a private key named `jane.key` and a certificate signing request named `jane.csr`*.*

Get the base64 encoded value of the CSR file content:

```plaintext
cat jane.csr | base64 | tr -d "\n"
```

Create a CertificateSigningRequest:

```yaml
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: jane
spec:
  request: <base64 encoded csr>
  signerName: kubernetes.io/kube-apiserver-client
  expirationSeconds: 86400  # one day
  usages:
  - client auth
EOF
```

Get the CSR:

```plaintext
kubectl get csr jane
```

```plaintext
NAME   AGE   SIGNERNAME                            REQUESTOR       REQUESTEDDURATION   CONDITION
jane   55s   kubernetes.io/kube-apiserver-client   minikube-user   24h                 Pending
```

Approve the CSR:

```plaintext
kubectl certificate approve jane
```

```plaintext
kubectl get csr jane
```

```plaintext
NAME   AGE     SIGNERNAME                            REQUESTOR       REQUESTEDDURATION   CONDITION
jane   2m17s   kubernetes.io/kube-apiserver-client   minikube-user   24h                 Approved,Issued
```

## **Granting permissions via RBAC**

Create a `clusterrole` granting permissions to only create pods:

```plaintext
kubectl create clusterrole createpods --verb=create --resource=pods
```

```plaintext
clusterrole.rbac.authorization.k8s.io/createpods created
```

Create a `clusterrolebinding` to bind the `clusterrole` with user `jane`:

```plaintext
kubectl create clusterrolebinding createpods --clusterrole=createpods --user=jane
```

```plaintext
clusterrolebinding.rbac.authorization.k8s.io/createpods created
```

### **Difference between ‘--as’ and ‘--user’ options of kubectl**

`kubectl` has a number of global `options` that can be passed as an argument to any `kubectl` command. The list can be found with the following command:

```plaintext
kubectl options
```

Two of the options are `--user` and `--as`*.* It is important to understand the difference between them.  

```plaintext
--user='':
The name of the kubeconfig user to use

--as='':
Username to impersonate for the operation. 
User could be a regular user or a service account in a namespace.
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><em>The --user option is used when you want to trigger the kubectl command under the context of a user which is configured in the kubeconfig file. This option throws an error if the user is not present in the kubeconfig file.</em></div>
</div>

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><em>The --as option is used to impersonate any user or serviceaccount. --as can be used for a user irrespective of whether that user is present in the kubeconfig file or not.</em></div>
</div>

Let’s put the theory into action with the help of the user we created.

### Using ‘--as’ to Check Access

Check permissions to create pods:

```plaintext
kubectl auth can-i create pods --as=jane
```

```plaintext
yes
```

Check permissions to create deployments:

```plaintext
kubectl auth can-i create deployments --as=jane
```

```plaintext
no
```

Check permissions to delete secrets:

```plaintext
kubectl auth can-i delete secrets --as=jane
```

```plaintext
no
```

Due to the fact that we explicitly assigned the `clusterrole createpods` to user `jane`, we see that `jane` has access to create pods, but no access to create deployments or delete secrets. Great, this is as expected.

### Using ‘--user’ to Check Access

Try the same commands, but this time using the `--user` option:

```plaintext
kubectl auth can-i create pods --user=jane
```

```plaintext
error: auth info "jane" does not exist
```

The command leads to an error. This is because we have not configured the user `jane` in the `kubeconfig` file.

We will fix this in the next step.

### Adding the user to kubeconfig

Get the user’s certificate:

```plaintext
kubectl get csr jane -o jsonpath='{.status.certificate}'| base64 -d > jane.crt
```

Add the new credentials to kubeconfig:

```plaintext
kubectl config set-credentials jane --client-key=jane.key --client-certificate=jane.crt --embed-certs=true
```

```plaintext
User "jane" set.
```

Add the context to kubeconfig:

```plaintext
kubectl config set-context jane --cluster=minikube --user=jane
```

```plaintext
Context "jane" created.
```

Let’s try the `kubectl auth can-i` command once again to verify the permissions on user jane:

Check permissions to create pods:

```plaintext
kubectl auth can-i create pods --user=jane
```

```plaintext
yes
```

Check permissions to create deployments:

```plaintext
kubectl auth can-i create deployments --user=jane
```

```plaintext
no
```

Check permissions to delete secrets:

```plaintext
kubectl auth can-i delete secrets --user=jane
```

```plaintext
no
```

Now everything works as expected.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><em>The --as option does not check the actual presence of the user in the kubeconfig. It only checks the explicitly configured roles and bindings that are bound to a user and returns a response based on that.</em></div>
</div>

### Example for a non-existent user

If the `--as` option is used for a non-existent user, there is no error thrown as shown below.

```plaintext
kubectl auth can-i create pods --as=nobody
```

```plaintext
no
```

## **Scenario for a Custom Admin User**

Configure a new admin user and verify the behaviour of the `kubectl auth can-i` commands.

This time we will check the permissions that a `user` inherits via the `group` it is attached to.

[Issue a certificate](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/#normal-user) for the new admin user.

Create a private key and a csr file:

```plaintext
openssl genrsa -out poweruser.key 2048
openssl req -new -key poweruser.key -out poweruser.csr -subj "/CN=poweruser/O=system:masters"
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><em>CN is the name of the user and O is the group that this user will belong to.</em></div>
</div>

Create a CertificateSigningRequest:

```yaml
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: poweruser
spec:
  request: <base64 encoded csr>
  signerName: kubernetes.io/kube-apiserver-client
  expirationSeconds: 86400  # one day
  usages:
  - client auth
EOF
```

The above command throws an error as the `kube-apiserver` blocks any `CertificateSigningRequest` that attempts to add a user as part of the `system:masters` group.

> *Error from server (Forbidden): error when creating “STDIN”:* [*certificatesigningrequests.certificates.k8s.io*](http://certificatesigningrequests.certificates.k8s.io) [*“poweruser” is forbidden: use of*](http://certificatesigningrequests.certificates.k8s.io/) [*kubernetes.io/kube-apiserver-client signer with system:masters group i*](http://kubernetes.io/kube-apiserver-client)*s not allowed*

## **Granting permissions via RBAC through groups**

In order to create a new admin user we will create a custom admin group that replicates the behaviour of the `system:masters` group. Let’s call it `example:masters`

To do this, create a new clusterrolebinding as below:

```yaml
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: example-cluster-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: example:masters
EOF
```

The next step is to add the new admin user to the `example:masters` group.

Delete the previous files created for `poweruser`:

```plaintext
rm poweruser.key poweruser.csr
```

Create a new private key and a csr file:

```plaintext
openssl genrsa -out poweruser.key 2048
openssl req -new -key poweruser.key -out poweruser.csr -subj "/CN=poweruser/O=example:masters"
```

Create a CertificateSigningRequest:

```yaml
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: poweruser
spec:
  request: <base64 encoded csr>
  signerName: kubernetes.io/kube-apiserver-client
  expirationSeconds: 86400  # one day
  usages:
  - client auth
EOF
```

Approve the CSR:

```plaintext
kubectl certificate approve poweruser
```

```plaintext
certificatesigningrequest.certificates.k8s.io/poweruser approved
```

Get the certificate:

```plaintext
kubectl get csr poweruser -o jsonpath='{.status.certificate}'| base64 -d > poweruser.crt
```

View the Subject of this certificate:

```plaintext
openssl x509 -in poweruser.crt -text -noout | grep Subject | grep -v "Public Key Info"
```

```plaintext
Subject: O=example:masters, CN=poweruser
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><em>The above output shows that the user </em><code>poweruser</code><em> belongs to the </em><code>example:masters</code><em> group.</em></div>
</div>

Add the new admin user to `kubeconfig`.

Add the new credentials:

```plaintext
kubectl config set-credentials poweruser --client-key=poweruser.key --client-certificate=poweruser.crt --embed-certs=true
```

```plaintext
User "poweruser" set.
```

Add the context:

```plaintext
kubectl config set-context poweruser --cluster=minikube --user=poweruser
```

```plaintext
Context "poweruser" created.
```

Try the `kubectl auth can-i` command to verify the permissions on the new admin user:

Check permissions to create pods:

```plaintext
kubectl auth can-i create pods --user=poweruser
```

```plaintext
yes
```

Check permissions to create deployments:

```plaintext
kubectl auth can-i create deployments --user=poweruser
```

```plaintext
yes
```

Check permissions to delete secrets:

```plaintext
kubectl auth can-i delete secrets --user=poweruser
```

```plaintext
yes
```

Try the same commands but with using the `--as` option.

Check permissions to create pods:

```plaintext
kubectl auth can-i create pods --as=poweruser
```

```plaintext
no
```

Check permissions to create deployments:

```plaintext
kubectl auth can-i create deployments --as=poweruser
```

```plaintext
no
```

Check permissions to delete secrets:

```plaintext
kubectl auth can-i delete secrets --as=poweruser
```

```plaintext
no
```

**Strange!!!** The expected output was yes for all 3 commands, as *poweruser* is an admin user with full access to the cluster.

We can prove this as follows:

Switch the context to work with the `poweruser`:

```plaintext
kubectl config use-context poweruser
```

Create a pod:

```plaintext
kubectl run nginx --image=nginx
```

```plaintext
pod/nginx created
```

Create the deployment:

```plaintext
kubectl create deployment nginx-deploy --image=nginx
```

```plaintext
deployment.apps/nginx-deploy created
```

Create and delete a secret:

```plaintext
kubectl create secret generic test-secret --from-literal=secret=1234
```

```plaintext
secret/test-secret created
```

```plaintext
kubectl delete secrets test-secret
```

```plaintext
secret "test-secret" deleted
```

My first thought was that this is a defect in Kubernetes.

I raised issue [#122579](https://github.com/kubernetes/kubernetes/issues/122579) to Kubernetes for confirmation.

The reality is that the behaviour is as-expected. The API server has no knowledge of group membership apart from what is encoded directly in the credential or provided by a token webhook.

To overcome this you need to pass the group you want to impersonate with the `--as-group` flag.  

### The ‘--as-group’ option of kubectl

Try the same commands but this time append the *\--as-group* option as well.

Check permissions to create pods:

```plaintext
kubectl auth can-i create pods --as=poweruser --as-group=example:masters
```

```plaintext
yes
```

Check permissions to create deployments:

```plaintext
kubectl auth can-i create deployments --as=poweruser --as-group=example:masters
```

```plaintext
yes
```

Check permissions to delete secrets:

```plaintext
kubectl auth can-i delete secrets --as=poweruser --as-group=example:masters
```

```plaintext
yes
```

Now the results are as expected.

# Summary

The `kubectl auth can-i` command behaves differently for the `--user` and `--as` options.

The `--user` option checks for the actual presence of the user in the `kubeconfig` file and has the ability to check permissions derived from the group of the user.

The `--as` option can be used to check permissions for any user irrespective of its presence in the kubeconfig file. It checks permissions which are ***directly bound*** to the user through RBAC, and does not check permissions that are derived from the user’s group. The API server has no knowledge of group membership apart from whatever is encoded directly in the credential or provided by a token webhook.

The `--as-group` option should be used to check for permissions that are derived from the user’s group.

# Cleaning up

Delete the resources created during this lab:

```plaintext
rm jane*
rm poweruser*
kubectl delete pod nginx
kubectl delete deployment nginx-deploy
```

Optionally, you can delete the entire minikube cluster:

```plaintext
minikube delete --all
```
