Feb 19, 2025

Kubernetes RBAC: Service Account, Token Auth & Role Binding

In this tutorial, you will learn how to secure a Kubernetes microservice—called the “Grade Submission API”—using Role-Based Access Control (RBAC). You will:

  1. Deploy the grade-submission API with endpoints:

    • POST /grades to add a grade record.

    • GET /grades to retrieve all grade records.

  2. Add a kube-rbac-proxy sidecar to ensure only valid tokens (from authorized users) can pass through.

  3. Configure ServiceAccounts, ClusterRoles, and ClusterRoleBindings so each component—client and proxy—gets only the minimal privileges it needs.

By following these steps, you will see the API in two modes:

  1. Open/Unauthenticated: Anyone can read/write grades.

  2. Secured/Authenticated: Only valid bearer tokens can perform API calls.

Prerequisites & Course Resources

Note: Windows users should run commands (particularly curl) inside Git Bash for better compatibility with the syntax shown.

Step 1: Clone the Starter Repository

Clone and open the repository:

git clone https://github.com/rslim087a/k8s-rbac-starter
cd

Inside, you will find all relevant YAML files (e.g., namespace.yaml, deployment.yaml, service.yaml, and RBAC resources).

Step 2: Deploy the Grade Submission API (Unsecured)

Apply all the manifests to your cluster:

kubectl apply -f

This will:

  1. Create a Namespace: grade-demo.

  2. Create a Deployment for grade-submission-api (listening on port 3000).

  3. Create a NodePort Service on port 31000.

Test the Unauthenticated API

According to the README, you can POST grades with:

curl -X POST <http://localhost:31000/grades> \\
  -H "Content-Type: application/json" \\
  -d '{"name": "Harry", "subject": "Defense Against Dark Arts", "score": 95}'

curl -X POST <http://localhost:31000/grades> \\
  -H "Content-Type: application/json" \\
  -d '{"name": "Ron", "subject": "Charms", "score": 82}'

curl -X POST <http://localhost:31000/grades> \\
  -H "Content-Type: application/json" \\
  -d '{"name": "Hermione", "subject": "Potions", "score": 98}'

Retrieve all grades:

curl

These calls succeed without any authentication, which highlights the need for role-based access control.

Step 3: Introduce the kube-rbac-proxy Sidecar

To lock down these endpoints, you will add a kube-rbac-proxy container to the same Pod. That container will:

  1. Listen on port 8443 (HTTPS).

  2. Validate incoming requests (by checking the bearer token against the Kubernetes API).

  3. Forward valid requests to the main container at port 3000.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: grade-submission-api
  namespace: grade-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: grade-submission
  template:
    metadata:
      labels:
        app: grade-submission
    spec:
      # Using a new service account that we'll define later
      serviceAccountName: grade-service-proxy
      containers:
      - name: grade-submission-api
        image: rslim087/kubernetes-course-grade-submission-api:stateless
        ports:
          - containerPort: 3000

      - name: kube-rbac-proxy
        image: gcr.io/kubebuilder/kube-rbac-proxy:v0.14.0
        args:
          - "--secure-listen-address=0.0.0.0:8443"
          - "--upstream=http://127.0.0.1:3000/"
          - "--logtostderr=true"
          - "--v=10"
        ports:
          - containerPort: 8443
            name

The Service now targets 8443 (named https) instead of 3000. Any request coming to NodePort 31000 gets routed to the sidecar first.

Test with HTTPS and -k (to skip certificate verification):

curl -k

You should get Unauthorized—kube-rbac-proxy now requires valid tokens.

Step 4: Set Up a ServiceAccount & Token for the Client

To authenticate, the client (curl) must present a valid Kubernetes token.

  1. ServiceAccount for the client:

    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: grade-service-account
      namespace
    
    
  2. Secret of type kubernetes.io/service-account-token:

    apiVersion: v1
    kind: Secret
    metadata:
      name: grade-sa-token
      namespace: grade-demo
      annotations:
        kubernetes.io/service-account.name: grade-service-account
    type
    
    

Apply these, then extract the token:

kubectl apply -f 03-rbac.yaml

# Extract the Base64-decoded token
export TOKEN=$(kubectl get secret grade-sa-token -n grade-demo \\
  -o jsonpath='{.data.token}' | base64 --decode)

# Make a request with the token
curl -k https://localhost:31000/grades \\
  -H "Authorization: Bearer $TOKEN"

Still likely Unauthorized or Forbidden, because you must define RBAC permissions first.

Step 5: Give kube-rbac-proxy Permission to Verify Tokens

ServiceAccount for the Proxy

Our Deployment references serviceAccountName: grade-service-proxy. Create that:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: grade-service-proxy
  namespace

ClusterRole for the Proxy

The proxy must create tokenreviews (in authentication.k8s.io) and subjectaccessreviews (in authorization.k8s.io):

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: grade-service-proxy-role
rules:
- apiGroups: ["authentication.k8s.io"]
  resources: ["tokenreviews"]
  verbs: ["create"]
- apiGroups: ["authorization.k8s.io"]
  resources: ["subjectaccessreviews"]
  verbs: ["create"

ClusterRoleBinding for the Proxy

Bind this role to grade-service-proxy:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: grade-submission-proxy-binding
subjects:
- kind: ServiceAccount
  name: grade-service-proxy
  namespace: grade-demo
roleRef:
  kind: ClusterRole
  name: grade-service-proxy-role
  apiGroup

Now kube-rbac-proxy can validate tokens properly.

Step 6: Grant the Client Access to the Grade Submission API

We must define which non-resource URLs (e.g., /grades) the client can access. In Kubernetes RBAC, these are configured via nonResourceURLs.

ClusterRole for the Client

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: grade-service-role
rules:
- nonResourceURLs: ["/*"]
  verbs: ["get", "post"

For more fine-grained control, specify ["/grades", "/grades/"], etc.

ClusterRoleBinding for the Client

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: grade-submission-binding
subjects:
- kind: ServiceAccount
  name: grade-service-account
  namespace: grade-demo
roleRef:
  kind: ClusterRole
  name: grade-service-role
  apiGroup

Now your grade-service-account can call the microservice endpoints at /grades.

Step 7: Test the Authenticated API

Reapply all resources, re-extract the token, and make authenticated requests:

kubectl apply -f .
export TOKEN=$(kubectl get secret grade-sa-token -n grade-demo \\
  -o jsonpath='{.data.token}' | base64 --decode)

From the README, here is how you POST grades with authentication (using -k to ignore TLS cert warnings and Authorization: Bearer for the token):

curl -k -X POST https://localhost:31000/grades \\
  -H "Content-Type: application/json" \\
  -d '{"name": "Harry", "subject": "Defense Against Dark Arts", "score": 95}' \\
  -H "Authorization: Bearer $TOKEN"

curl -k -X POST https://localhost:31000/grades \\
  -H "Content-Type: application/json" \\
  -d '{"name": "Ron", "subject": "Charms", "score": 82}' \\
  -H "Authorization: Bearer $TOKEN"

curl -k -X POST https://localhost:31000/grades \\
  -H "Content-Type: application/json" \\
  -d '{"name": "Hermione", "subject": "Potions", "score": 98}' \\
  -H "Authorization: Bearer $TOKEN"

And verify with:

curl -k https://localhost:31000/grades \\
  -H "Authorization: Bearer $TOKEN"

This time, calls succeed only if you have the correct bearer token. Without the token, or with an invalid token, the request should fail (Unauthorized).

Conclusion

You have successfully:

  1. Deployed the Grade Submission API.

  2. Secured it via a kube-rbac-proxy sidecar that checks requests against Kubernetes RBAC.

  3. Created two ServiceAccounts:

    • grade-service-proxy: Has ClusterRole rules allowing it to create token/subjectaccess reviews.

    • grade-service-account: Used by the client (curl) to authenticate and authorized for GET/POST at /grades.

  4. Tested the API using both unauthenticated (HTTP on port 31000) and authenticated (HTTPS on port 31000 with bearer token).

Next Steps:

  • Narrow down nonResourceURLs: ["/*"] to just the endpoints you want accessible, such as "/grades".

  • Explore advanced scenarios where different roles can have read-only vs. write permissions for certain API paths.

  • Combine resource-based rules (e.g., Pods, Deployments) with these non-resource URL rules for a full production scenario.

With this pattern, you ensure that only appropriately credentialed users can interact with your microservice, keeping your cluster more secure and better aligned with the principle of least privilege.

Kubernetes Training

If you find these guides helpful, check out our Kubernetes Training course

Let’s keep in touch

Subscribe to the mailing list and receive the latest updates

Let’s keep in touch

Subscribe to the mailing list and receive the latest updates

Let’s keep in touch

Subscribe to the mailing list and receive the latest updates

Let’s keep in touch

Subscribe to the mailing list and receive the latest updates