Secure Kubernetes Access with RBAC

KubernetesBeginner
Practice Now

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.