Introduction
Kubernetes authentication identifies a caller; authorization decides what that identity may do. RBAC expresses permissions as verbs on API resources and binds them to users, groups, or ServiceAccounts.
You will create a namespace-scoped reader, connect its identity to permissions, then prove both allowed and denied boundaries through impersonation.
RBAC becomes easier when read as three questions: Who? (ServiceAccount), What may they do? (Role), and Who receives those permissions? (RoleBinding). Creating only one or two of these objects does not complete the authorization path.
Create a ServiceAccount Identity
In this step, create a namespaced ServiceAccount for an application component.
A ServiceAccount is a Kubernetes API identity intended for workloads and automation. It is not a Linux user inside the container. Giving this example a dedicated identity avoids inheriting the namespace's default ServiceAccount.
cd /home/labex/project/rbac-access
kubectl create serviceaccount inventory-reader -n practice-rbac
kubectl get serviceaccount inventory-reader -n practice-rbac -o yaml
Its full identity is system:serviceaccount:practice-rbac:inventory-reader. Creating an identity alone grants no application permissions.
In the YAML output, metadata confirms the namespace and name. Do not expect a long-lived token Secret to appear automatically; modern Kubernetes projects short-lived tokens into Pods when needed.
The returned object contains the dedicated name and namespace but no automatically generated token Secret:
kind: ServiceAccount
metadata:
name: inventory-reader
namespace: practice-rbac
Define Namespace-Scoped Permissions
In this step, create a Role that permits reading Pods and ConfigMaps only in practice-rbac.
Each rule combines API groups, resources, and verbs. For core resources such as Pods and ConfigMaps, apiGroups: [""] uses the empty string. The verbs get, list, and watch describe individual reads, collection reads, and change streams.
cd /home/labex/project/rbac-access
cat <<'EOF' > inventory-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: {name: inventory-reader, namespace: practice-rbac}
rules:
- apiGroups: [""]
resources: [pods, configmaps]
verbs: [get, list, watch]
EOF
Apply and describe the Role. describe presents its policy rules as a table that is easier to review than raw YAML:
kubectl apply -f inventory-role.yaml
kubectl describe role inventory-reader -n practice-rbac
The empty API group selects core resources. A Role never grants access outside its namespace.
Also notice what is absent: there is no create, update, patch, or delete. Least privilege is defined as much by omitted permissions as by granted ones.
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
configmaps [] [] [get list watch]
pods [] [] [get list watch]
The table expands the combined YAML rule into one row per resource. Both rows have the same three read-only verbs.
Bind the Role to the Identity
In this step, connect the Role to the ServiceAccount with a RoleBinding.
The imperative command is compact because both objects already exist. --role names the permission source, while --serviceaccount uses the namespace:name form to identify the subject.
cd /home/labex/project/rbac-access
kubectl create rolebinding inventory-reader -n practice-rbac --role=inventory-reader --serviceaccount=practice-rbac:inventory-reader
kubectl get rolebinding inventory-reader -n practice-rbac -o yaml
roleRef names the permissions; subjects names the identities receiving them. Both remain namespace-scoped here.
A Role and ServiceAccount with the same name are still unrelated until this binding exists. Names are for humans; the RoleBinding is the actual authorization connection.
In the YAML, trace the connection from roleRef.name: inventory-reader to the subject whose kind is ServiceAccount, name is inventory-reader, and namespace is practice-rbac.
Test Allowed and Denied Actions
In this step, use kubectl auth can-i with --as to impersonate the ServiceAccount.
Authorization testing needs both positive and negative cases. A yes proves required work is possible, while a no proves destructive and cross-namespace access was not accidentally granted.
cd /home/labex/project/rbac-access
IDENTITY=system:serviceaccount:practice-rbac:inventory-reader
kubectl auth can-i list pods -n practice-rbac --as="$IDENTITY"
kubectl auth can-i get configmaps -n practice-rbac --as="$IDENTITY"
kubectl auth can-i delete pods -n practice-rbac --as="$IDENTITY" || true
kubectl auth can-i list pods -n default --as="$IDENTITY" || true
The first two answers should be yes; deletion and cross-namespace listing should be no. Impersonation tests authorization without exposing or creating a token.
kubectl auth can-i returns a nonzero shell status together with a no answer. The two || true suffixes keep this deliberate denial test moving; they do not change the authorization decision.
yes
yes
no
no
Read these as two required abilities followed by two protected boundaries. A test containing only yes answers would not demonstrate least privilege.
The current lab user is allowed to impersonate this ServiceAccount for testing. --as changes the identity evaluated by the API server for that command; it does not change your shell user or permanently switch kubectl credentials.
Summary
You created a ServiceAccount, expressed read-only namespace permissions with a Role, assigned them through a RoleBinding, and proved both allowed and denied actions with impersonation.


