Secure Your Kubernetes App with RBAC
Harden a Kubernetes app with ServiceAccounts and least-privilege RBAC.
Introduction
30 Second Summary
A working app can look healthy while carrying permissions it never uses. Those hidden permissions give mistakes more room to spread.
In this project, you will secure an existing Kubernetes app by separating its workload identity from its operations identity. You'll use dedicated ServiceAccounts and RBAC to keep each identity's access narrow while the app stays available.
What You'll Build
You'll refresh the live app in your browser while Windows PowerShell checks reveal which Pod actions the operations identity can perform.
By the end of this project, you'll have:
- A hardened app identity you can inspect on a running Pod. You'll see hello-app-sa in place of default while the browser app keeps responding.
- A repeatable least-privilege authorization matrix that returns yes for Pod observation. It returns no for Pod deletion or access to Kubernetes Secrets.
- A tested namespace boundary showing that ops-auditor cannot list Pods in kube-system.
- Secret Mission: Grant ops-auditor the same narrow Pod access in a new staging namespace without widening its permissions cluster-wide.
Do I need any experience before starting?
This project continues from the linked "Deploy a Containerized App to Kubernetes" project. You need its running dev-cluster and existing hello-k8s folder.
You'll reuse Docker Desktop, kind, kubectl, Windows PowerShell, and Visual Studio Code on the confirmed Windows setup.
Before We Start
This opening checkpoint helps you commit to the security goal before the hands-on work begins.
You are extending a live Kubernetes app so every identity and permission has a deliberate purpose.
The finished app will use a dedicated workload identity. A separate operations identity will receive only namespace-scoped observation access.
Confirm the App and Default Identity
Security changes only mean something when you can compare them with a healthy baseline. You will prove the inherited cluster is healthy before changing any controls.
A ServiceAccount gives a Kubernetes workload an identity inside the cluster. This check reveals the identity inherited by the existing app.
In this step, get ready to:
- Confirm that all three inherited cluster nodes are Ready.
- Inspect a running Pod to reveal its default identity.
- Port-forward the existing Service to confirm the app still responds.
Confirm the inherited cluster
The kind cluster from your previous project carries the app you are securing. PowerShell gives you a terminal for checking that inherited environment.
- Press the Windows key to open Windows search.
- Type PowerShell into the search field.
- Press Enter to open PowerShell.
- Move to the existing hello-k8s folder by running this command:
cd ~/Desktop/hello-k8s
How Does the Folder Command Help?
This switches PowerShell to the existing project folder on your Desktop. Later steps add security manifests inside this folder.
- Check the inherited cluster nodes by running this command:
kubectl get nodes
What Does the Node Check Prove?
This asks the current Kubernetes cluster for its nodes. A healthy result proves the local cluster can accept the security changes you make later.
You should see three node rows. Each node should show Ready.
Your inherited cluster is responding. That gives you a reliable baseline for every authorization test ahead.
Not Seeing Three Ready Nodes?
- Return to Docker Desktop from earlier to confirm its engine is still running.
- Confirm that the dev-cluster containers are running before repeating the node check.
Ask for help with the state shown in your terminal: Help me diagnose why my inherited kind cluster does not show three Ready nodes..
Reveal the Pod identity
Each Pod runs as a ServiceAccount inside its namespace. Inspecting the live Pod shows the identity the app currently inherits.
- List the current Pods by running this command:
kubectl get pods
What Does the Pod List Show?
This lists the Pods available through your current kubectl context. The generated Pod name identifies one running copy of the hello-app workload.
You should see at least one Pod whose name begins with hello-app. Its current state should be Running.
- Record one current Pod name here: your current hello-app Pod name.
- Inspect your current hello-app Pod name by substituting it for <podname> in this command:
kubectl get pods/<podname> -o yaml
Why Inspect YAML?
- The -o yaml option displays the Pod's full configuration.
- The spec.serviceAccountName field records the identity assigned to that Pod.
In the YAML output, you will find spec.serviceAccountName: default.
Kubernetes assigned default because the existing Pod template does not specify a ServiceAccount. Every Pod created from that template inherits the same identity.
- Reconfirm your ServiceAccount impersonation permission by running this command:
kubectl auth can-i impersonate serviceaccounts --all-namespaces
What Does Impersonation Mean?
Impersonation lets your authorization checks act as a ServiceAccount without placing that identity inside a running Pod. The --all-namespaces option checks that ability across the cluster.
You should see yes. This confirms you can run the identity-specific permission checks used later.
Missing the Pod or Permission?
- Confirm that you copied a current Pod name from the latest Pod list.
- Repeat the impersonation check from the same PowerShell window connected to dev-cluster.
Ask for help with the output you received: Help me troubleshoot my Pod identity or ServiceAccount impersonation check..
Prove the app still responds
Port forwarding creates a temporary route from port 8080 on your computer to port 80 on the existing Service. This final baseline confirms that the inherited workload still serves traffic.
Before you run the final check, predict whether the app will still respond through the existing Service.
- Start the port-forward by running this command:
kubectl port-forward service/hello-service 8080:80
How Does Port Forwarding Work?
- The service/hello-service target sends traffic through the existing Service.
- The 8080:80 mapping connects your computer's local port to the Service port.
- The command keeps running while the temporary route remains active.
PowerShell remains occupied while the port-forward is active. Keep this window running during the browser check.
- Press the Windows key to open Windows search.
- Type the name of your web browser into the search field.
- Press Enter to open the browser.
- Enter http://localhost:8080 in the address bar.
- Press Enter to load the app.
You should see the existing app page from your previous project respond successfully. The inherited workload is still available through hello-service.
App Not Loading in the Browser?
- Keep the PowerShell window running while the browser loads the app.
- Confirm that the browser address is exactly http://localhost:8080.
- Confirm that the earlier Pod list still shows the hello-app Pod running.
Ask for help with the terminal output or browser result: Help me troubleshoot why my port-forwarded Kubernetes app is not loading..
- Switch back to the PowerShell window from earlier.
- Press Ctrl+C to stop the port-forward.
The PowerShell prompt should return. Your app baseline and default identity are now confirmed.
You have proved the cluster is healthy without changing any manifests. Next, you will replace the shared default identity with a dedicated tokenless ServiceAccount.
Give the App a Tokenless Identity
Your browser check proved the inherited app is healthy. Its Pods still use the shared default ServiceAccount in Kubernetes.
This step gives the hello-app Deployment a dedicated workload identity. Automatic token mounting stays disabled because the HTTP app does not call the Kubernetes API.
In this step, get ready to:
- Define the dedicated hello-app-sa ServiceAccount in a manifest.
- Apply the ServiceAccount to the hello-app Deployment.
- Prove the new identity has no Pod-list permission while the app still responds.
Create the tokenless ServiceAccount manifest
A manifest records the identity as project code. The automountServiceAccountToken: false setting prevents Pods using this ServiceAccount from receiving an automatic API credential.
Why use a dedicated identity?
An application-specific ServiceAccount gives the workload a clear identity boundary. Disabling token automount removes credentials that this HTTP app never uses.
Visual Studio Code may display a Workspace Trust prompt after you open the project folder. Trust this folder because it contains the local project you created.
- Press the Windows key to open the search bar.
- Type Visual Studio Code into the search bar.
- Press Enter to open Visual Studio Code.
- Click File in the top menu bar.
- Click Open Folder....
- Select the hello-k8s folder on your Desktop.
- Click Select Folder.
- Click Yes, I trust the authors if the Workspace Trust prompt appears.
You should see hello-k8s at the top of the Explorer sidebar.
- Select the hello-k8s folder in the Explorer sidebar.
- Click the New Folder... button in the Explorer toolbar.
- Type k8s as the folder name.
- Press Enter to create the folder.
The k8s folder now gives your Kubernetes manifests a predictable home inside hello-k8s.
- Select the new k8s folder in the Explorer sidebar.
- Click the New File... button in the Explorer toolbar.
- Type hello-app-serviceaccount.yaml as the file name.
- Press Enter to create the file.
- Define the dedicated ServiceAccount by pasting this manifest into k8s/hello-app-serviceaccount.yaml:
apiVersion: v1
kind: ServiceAccount
metadata:
name: hello-app-sa
namespace: default
automountServiceAccountToken: false
What does this manifest do?
- The ServiceAccount kind creates a workload identity named hello-app-sa.
- The namespace: default field places the identity beside the existing application.
- The automountServiceAccountToken: false field opts Pods out of automatic API credential mounting.
- Save k8s/hello-app-serviceaccount.yaml by pressing Ctrl+S.
- Confirm the Explorer sidebar lists hello-app-serviceaccount.yaml inside the k8s folder.
Manifest file in the wrong place?
Check that the Explorer shows hello-k8s/k8s/hello-app-serviceaccount.yaml. Drag the file into the k8s folder if it appears beside that folder.
Ask for help with the file location if the path still looks different. Help me place my ServiceAccount manifest in the correct project folder.
✔️ Awesome, I've got everything!
Your saved k8s/hello-app-serviceaccount.yaml manifest is ready to apply.
ⓧ I'd like to double check the full code
Compare your complete k8s/hello-app-serviceaccount.yaml file with this reference.
apiVersion: v1
kind: ServiceAccount
metadata:
name: hello-app-sa
namespace: default
automountServiceAccountToken: false
What should match?
The name must be hello-app-sa. The namespace must be default.
The final line must set automountServiceAccountToken to false.
Apply the identity to the Deployment
Creating a manifest only records the desired ServiceAccount. Applying it creates the identity in the cluster.
- Return to the PowerShell window connected to dev-cluster.
- Create the ServiceAccount from the saved manifest by running:
kubectl apply -f k8s/hello-app-serviceaccount.yaml
What does this command do?
The kubectl apply command reads the manifest. It creates hello-app-sa in the connected cluster.
You should see output confirming that the ServiceAccount was created.
Manifest not found?
Confirm PowerShell is still inside the hello-k8s folder. Confirm the Explorer path is k8s/hello-app-serviceaccount.yaml.
Use the command's error text when asking for help. Help me diagnose why kubectl cannot apply my ServiceAccount manifest.
The ServiceAccount now exists. The Deployment still needs its Pod template updated so replacement Pods use that identity.
- Assign hello-app-sa to the Deployment Pod template by running the commands below:
kubectl set serviceaccount deployment hello-app hello-app-sa
kubectl rollout status deployment/hello-app
What happens during the rollout?
- The first command updates the hello-app Pod template to use hello-app-sa.
- The template change causes Kubernetes to replace the existing Pods.
- The second command watches the Deployment until its latest rollout finishes.
You should see PowerShell confirm that the rollout completed.
Rollout not completing?
Keep Docker Desktop running while the Deployment replaces its Pods. Check the PowerShell error for a missing Deployment name or a cluster connection problem.
Ask for targeted help if the rollout remains incomplete. Help me troubleshoot the hello-app rollout after changing its ServiceAccount.
Verify the identity and application
A completed rollout proves the Deployment replaced its Pods. Inspecting one replacement Pod proves the new identity reached the running workload.
- List the newly created Pods by running:
kubectl get pods
What are you looking for?
Find a running Pod whose name begins with hello-app. A recent age indicates that it came from the completed rollout.
- Choose a running hello-app Pod with a recent age.
- Record its name here: your new hello-app Pod name.
- Inspect that replacement Pod by running:
kubectl get pods/[[NEW_POD_NAME="your new hello-app Pod name"]] -o yaml
Why inspect the live Pod?
The Deployment template describes the intended identity. The live Pod YAML confirms which ServiceAccount the running workload actually received.
Find serviceAccountName: hello-app-sa in the Pod YAML. This confirms the default identity has been replaced.
Before you run the authorization check, do you expect this dedicated identity to list Pods without a permission grant?
- Test the ServiceAccount's Pod-list permission by running:
kubectl auth can-i list pods --as=system:serviceaccount:default:hello-app-sa -n default
What does this authorization check prove?
The --as=system:serviceaccount:default:hello-app-sa option evaluates the request as the new identity. No permission grant allows that identity to list Pods.
Token automounting controls credential exposure inside a Pod. This check separately proves the identity lacks Pod-list authorization.
You should see no. Your application identity is dedicated and unprivileged.
Before you restart the port-forward, do you expect removing the automatic API credential to affect the app's HTTP response?
- Forward the existing Service to your local browser by running:
kubectl port-forward service/hello-service 8080:80
What does the port-forward prove?
The command exposes hello-service at http://localhost:8080. PowerShell keeps this connection active until you stop it.
- Return to the browser tab from earlier.
- Refresh http://localhost:8080.
You should see the same application response as before. The app still serves traffic under hello-app-sa.
- Return to PowerShell after confirming the response.
- Press Ctrl+C to stop the port-forward.
App not loading?
Keep the port-forward command running while you refresh the browser. Confirm the browser address is http://localhost:8080.
Ask for help with the PowerShell output if the connection closes. Help me troubleshoot the hello-service port-forward after changing the Deployment ServiceAccount.
Well done. Your application now runs with its own tokenless identity while continuing to serve traffic.
Next, you will create a separate operations identity and test what it can access before any permissions are granted.
Create and Test the Operations Identity
Your Kubernetes app now runs under the dedicated hello-app-sa workload identity. Its successful browser check proves the app still serves traffic.
Operations checks need their own ServiceAccount so app duties stay separate from operator duties. A clean identity gives you a clear authorization baseline.
Your first permission tests expose that baseline before any access policy exists. This makes every later permission change measurable.
In this step, get ready to:
- Define a separate operations ServiceAccount.
- Apply the identity without adding an authorization policy.
- Test its access to Pods plus Secrets.
Create the operations identity manifest
The operations identity lives in its own manifest. It also disables automatic token mounting because this identity is being created for controlled authorization tests.
- Press the Windows key to open Windows Search.
- Type Visual Studio Code into Windows Search.
- Press Enter to open Visual Studio Code.
- Click File in the top menu bar.
- Click Open Folder.
- Select the hello-k8s folder on your Desktop.
- Click Select Folder.
The Explorer sidebar now shows the existing hello-k8s project. Its k8s folder already contains hello-app-serviceaccount.yaml.
- Expand the k8s folder in the Explorer sidebar.
- Click the New File icon beside the k8s folder.
- Enter ops-auditor-serviceaccount.yaml as the file name.
- Add the operations identity by pasting this manifest into k8s/ops-auditor-serviceaccount.yaml:
apiVersion: v1
kind: ServiceAccount
metadata:
name: ops-auditor
namespace: default
automountServiceAccountToken: false
What Does This Manifest Do?
- The ServiceAccount kind creates an identity that workloads or authorization checks can use.
- The name: ops-auditor field keeps this identity separate from hello-app-sa.
- The namespace: default field places the identity beside the existing application.
- The automountServiceAccountToken: false setting opts out of automatic API credential mounting.
- Press Ctrl+S to save k8s/ops-auditor-serviceaccount.yaml.
- Confirm the Explorer sidebar lists ops-auditor-serviceaccount.yaml inside the k8s folder.
Manifest in the Wrong Place?
If the file appears beside k8s in the sidebar, drag it into the k8s folder. The path must read k8s/ops-auditor-serviceaccount.yaml.
If Visual Studio Code highlights the YAML, compare its indentation with the reference below. Help me check the operations ServiceAccount manifest.
✔️ Awesome, I've got everything!
Your k8s folder now contains separate manifests for the application identity and the operations identity.
ⓧ I'd like to double check the full code
The existing k8s/hello-app-serviceaccount.yaml file remains unchanged:
apiVersion: v1
kind: ServiceAccount
metadata:
name: hello-app-sa
namespace: default
automountServiceAccountToken: false
The new k8s/ops-auditor-serviceaccount.yaml file contains:
apiVersion: v1
kind: ServiceAccount
metadata:
name: ops-auditor
namespace: default
automountServiceAccountToken: false
Apply the operations identity
The manifest describes the desired object on disk. Applying it sends that definition to the cluster while your PowerShell session remains connected to dev-cluster.
- Create the operations identity by running this command in PowerShell:
kubectl apply -f k8s/ops-auditor-serviceaccount.yaml
What Does This Command Do?
- The apply operation submits the saved resource definition to Kubernetes.
- The -f flag tells the command to read a manifest file.
- The k8s/ops-auditor-serviceaccount.yaml path selects the operations identity manifest.
- Confirm the PowerShell output names ops-auditor without reporting an error.
The cluster now has a distinct operations identity. No access policy has been assigned to it.
Apply Command Not Working?
If the command cannot find the manifest, confirm PowerShell is still inside the hello-k8s folder. Also confirm the file appears inside its k8s subfolder.
If the command reports a YAML problem, compare the spacing under metadata with the full-code tab above. Help me troubleshoot the ServiceAccount apply command.
Test the empty permission baseline
An authorization check asks Kubernetes whether an identity may perform one specific action. Impersonation lets you perform that check as ops-auditor without creating or exposing a token.
Before you run the first check, choose your prediction: allowed or denied.
- Test whether ops-auditor can list Pods in default by running:
kubectl auth can-i list pods --as=system:serviceaccount:default:ops-auditor -n default
How Is This Permission Tested?
- The list pods portion asks about viewing the namespace's Pod collection.
- The --as=system:serviceaccount:default:ops-auditor value performs the check as the new ServiceAccount.
- The -n default value limits the question to the default namespace.
You'll see no. That denial is intentional because it proves the new identity received no inherited Pod permissions.
Before you run the Secret check, predict whether its result matches the Pod check.
- Test whether ops-auditor can read Secrets in default by running:
kubectl auth can-i get secrets --as=system:serviceaccount:default:ops-auditor -n default
What Does the Second Check Prove?
- The get secrets portion asks whether the identity can read sensitive namespace data.
- The same --as=system:serviceaccount:default:ops-auditor value keeps the tested identity unchanged.
- The same -n default value keeps the namespace boundary unchanged.
You'll see no again. You now have a repeatable baseline showing that the operations identity cannot list Pods or read Secrets.
You now have a separate operations identity with a proven zero-permission baseline. Next, you'll add a namespace-scoped RBAC policy that permits Pod observation without opening destructive or sensitive access.
Grant Pod Observation Access
The denial you just saw proves that a new Kubernetes ServiceAccount starts without application permissions.
Your operations identity now needs an RBAC Role that grants Pod observation inside the default namespace. A RoleBinding connects that narrow policy to ops-auditor without opening destructive or sensitive actions.
In this step, get ready to:
- Create a namespace-scoped Role for Pod observation.
- Bind the Role to the operations ServiceAccount.
- Run an authorization matrix that proves the allowed and denied actions.
Define the Pod observer Role
A Role turns your security intent into explicit resource rules. This Role limits observation to Pods in the default namespace.
Why use a custom Role?
A broader prebuilt grant can include unrelated read permissions. The custom pod-observer Role exposes every allowed resource.
Its verb list keeps the policy easy to audit. This makes least privilege visible in the manifest.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Visual Studio Code and press Enter to open it.
- Click File in the top menu bar.
- Select Open Folder.
- Double-click the hello-k8s folder on your Desktop.
You should see the existing k8s folder in the Explorer view.
- Select the Explorer view in the Activity Bar.
- Select the k8s folder.
- Select the New File... button.
- Enter ops-auditor-rbac.yaml and press Enter.
- Define the Pod observer Role by pasting this YAML into k8s/ops-auditor-rbac.yaml:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-observer
namespace: default
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
---
What does this Role allow?
- The empty apiGroups value selects resources from the core API group.
- The pods rule permits the get, list, and watch observation verbs.
- The separate pods/log rule permits only the get verb for Pod logs.
- The default namespace keeps the grant within one namespace.
- Save k8s/ops-auditor-rbac.yaml by pressing Cmd+S (macOS) or Ctrl+S (Windows).
The Explorer view should now list ops-auditor-rbac.yaml inside the k8s folder.
File in the wrong location?
Check that ops-auditor-rbac.yaml appears beneath the k8s folder. Move the file into that folder if it appears beside it.
Keep the YAML indentation exactly as shown. Tabs can make the rules invalid.
Help me check the location and YAML indentation of my RBAC manifest.
Bind the operations identity
The Role describes permissions without choosing who receives them. The RoleBinding assigns those permissions to the existing ops-auditor ServiceAccount.
- Place your cursor on the line after --- in k8s/ops-auditor-rbac.yaml.
- Add the RoleBinding by pasting this YAML:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ops-auditor-pod-observer
namespace: default
subjects:
- kind: ServiceAccount
name: ops-auditor
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: pod-observer
How does the binding work?
- The subjects section identifies the ops-auditor ServiceAccount in default.
- The roleRef section points to the pod-observer Role.
- The RoleBinding lives in default. Its grant applies only within that namespace.
- Save k8s/ops-auditor-rbac.yaml.
The file should now contain two YAML documents separated by ---.
Binding section looks nested?
Make sure the second apiVersion starts at the far left. It must align with the first apiVersion.
Confirm that roleRef aligns with subjects.
Help me fix the RoleBinding structure in my YAML manifest.
✔️ Awesome, I've got everything!
Great. Double check that k8s/ops-auditor-rbac.yaml is saved before applying it.
ⓧ I'd like to double check the full code
Your unchanged application ServiceAccount manifest should still look like this:
apiVersion: v1
kind: ServiceAccount
metadata:
name: hello-app-sa
namespace: default
automountServiceAccountToken: false
Your new RBAC manifest should match this file exactly:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-observer
namespace: default
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ops-auditor-pod-observer
namespace: default
subjects:
- kind: ServiceAccount
name: ops-auditor
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: pod-observer
Your operations ServiceAccount manifest should remain unchanged:
apiVersion: v1
kind: ServiceAccount
metadata:
name: ops-auditor
namespace: default
automountServiceAccountToken: false
The manifest changes cluster state once Kubernetes accepts it. Your existing ServiceAccounts remain unchanged.
- Switch back to PowerShell from earlier.
- Create the Role and RoleBinding by running this command:
kubectl apply -f k8s/ops-auditor-rbac.yaml
What does this command do?
The kubectl command sends both YAML documents to the current cluster. Kubernetes creates the namespaced Role and RoleBinding from the manifest.
You should see output confirming that the pod-observer Role and ops-auditor-pod-observer RoleBinding were created.
Manifest rejected?
Return to k8s/ops-auditor-rbac.yaml if the command reports a YAML parsing problem. Check the indentation near rules, subjects, and roleRef.
Confirm that PowerShell is still inside the hello-k8s folder if the manifest cannot be found.
Help me diagnose the error from applying my RBAC manifest.
Run the authorization matrix
Authorization checks turn the policy into evidence. Positive checks prove the observer capabilities while negative checks expose the boundary.
Before you run the matrix, which two actions do you expect the observer policy to allow?
- Test all four permissions by running these commands:
kubectl auth can-i list pods --as=system:serviceaccount:default:ops-auditor -n default
kubectl auth can-i get pods --subresource=log --as=system:serviceaccount:default:ops-auditor -n default
kubectl auth can-i delete pods --as=system:serviceaccount:default:ops-auditor -n default
kubectl auth can-i get secrets --as=system:serviceaccount:default:ops-auditor -n default
How to read the matrix
- Listing Pods returns yes.
- Getting Pod logs returns yes.
- Deleting Pods returns no.
- Getting Secrets returns no.
That is least privilege in action: observation succeeds within a narrow boundary.
Pod deletion and Secret reads remain blocked.
Results do not match?
If both observation checks return no, confirm that the RoleBinding subject uses ops-auditor in the default namespace.
If a destructive or sensitive check returns yes, review the Role verbs and resources for an extra permission.
Help me compare my authorization results with my Role and RoleBinding.
Your operations identity can now observe Pods without deleting them or reading Secrets. Next, you will audit the full grant and prove that it stops at the namespace boundary.
Audit Access Boundaries
Your Kubernetes operations ServiceAccount now has the narrow Pod permissions you granted in default.
A positive permission test is incomplete on its own. This audit proves the RBAC grant stops at the namespace boundary.
You will also confirm that the secured app remains available.
In this step, get ready to:
- Review the effective Pod permissions in default.
- Run a negative authorization matrix across the remaining boundaries.
- Confirm the app still responds under hello-app-sa.
Review the effective grant
An effective permission list shows what the API authorizer calculates for an identity. It gives you one place to compare the live grant with your pod-observer Role.
- Switch back to the PowerShell window from earlier.
- List the effective permissions for ops-auditor in default by running this command:
kubectl auth can-i --list --as=system:serviceaccount:default:ops-auditor --namespace=default
How to read the grant
This command asks the cluster to calculate the effective rules for the impersonated identity in default.
- The pods entry confirms the get, list, and watch observation verbs.
- The pods/log entry confirms the get verb for Pod logs.
- The --namespace=default flag limits this permission calculation to default.
Among the returned rows, you will find the Pod permissions from the applied pod-observer Role.
Cannot find the Pod permissions?
- Check that PowerShell remains connected to the inherited dev-cluster.
- Check that the applied Role is named pod-observer.
- Check that the RoleBinding is named ops-auditor-pod-observer.
Ask for help with the live permission list.
Test the denied boundaries
Least privilege includes a clear boundary around each grant. Negative authorization tests make that boundary visible.
Before you run the matrix, consider what you expect each result to be.
- Test the remaining authorization boundaries by running these commands:
kubectl auth can-i list pods --as=system:serviceaccount:default:ops-auditor -n kube-system
kubectl auth can-i delete pods --as=system:serviceaccount:default:ops-auditor -n default
kubectl auth can-i get secrets --as=system:serviceaccount:default:ops-auditor -n default
What should you see?
The three commands return no in order.
- The kube-system result proves the RoleBinding does not cross its namespace boundary.
- The Pod deletion result proves the Role excludes destructive verbs.
- The Secret result proves the Role excludes sensitive resources.
Together, these results prove the observer identity cannot expand beyond its intended job.
Did any check return yes?
- Check that the RoleBinding exists only in default.
- Check that its roleRef names pod-observer.
- Compare the verbs entries in k8s/ops-auditor-rbac.yaml with the target action.
Ask for help tracing the unexpected permission.
Recheck the secured app
Authorization is only useful when the workload still performs its real job. The final check reconnects your browser to hello-service.
The port-forward occupies this PowerShell session until you stop it. Your browser uses the temporary connection to reach the Service.
Before you reconnect the app, predict whether its dedicated identity changed the page's availability.
- Start the port-forward by running this command:
kubectl port-forward service/hello-service 8080:80
What does this command do?
This forwards local port 8080 to port 80 on hello-service.
The Service selects a running app Pod. The browser request therefore tests the secured workload through its existing network path.
- Open a new tab in your browser.
- Enter http://localhost:8080 in the address bar.
- Refresh the application page.
You will see the application page load successfully at http://localhost:8080.
The page loads through hello-service while the Deployment remains configured with hello-app-sa.
- Return to the PowerShell window running the port-forward.
- Stop the port-forward by pressing Ctrl+C.
Strong work. Your audit now proves the intended access boundary.
The app still serves traffic under its dedicated hello-app-sa identity.
Secret mission
Grant Controlled Staging Access
Extend ops-auditor into staging without giving it cluster-wide permissions. You will prove Pods are visible while Secrets stay protected.
Clean Up Your Resources
Clean Up Your Resources
Choose whether to keep the local security resources, pause the cluster for later, or remove this project's additions. This work runs in your existing local kind cluster through Docker Desktop at no ongoing cost.
Resources you used:
- The hello-app Deployment configured to use hello-app-sa.
- Tokenless ServiceAccount hello-app-sa in default.
- Tokenless ServiceAccount ops-auditor in default.
- The pod-observer Role in default.
- The ops-auditor-pod-observer RoleBinding in default.
- The staging namespace containing its Pod observer Role and RoleBinding.
Keep everything running
No resource cleanup is needed. Choose this option if you want to continue testing workload identities or authorization rules.
- Press Ctrl+C in PowerShell if the port-forward is active.
- Leave Docker Desktop running.
Your dev-cluster remains available with every security resource from this project.
Pause - I'll come back to this later
Shut down active processes to free up memory while keeping the local cluster containers and project files for later.
- Press Ctrl+C in PowerShell if the port-forward is active.
- Quit Docker Desktop.
Your local files remain unchanged. The kind cluster containers stay available for your next session.
Delete - I don't want to use this again
Removing these additions looks final. Your inherited hello-app and dev-cluster stay in place because you first restore the Deployment's original ServiceAccount.
- Press Ctrl+C in PowerShell if the port-forward is active.
- Restore the hello-app Deployment to the default ServiceAccount by running:
kubectl set serviceaccount deployment hello-app default
Why Revert First?
This command changes the hello-app Pod template back to the inherited default identity before hello-app-sa is removed.
- Confirm PowerShell reports that the Deployment's ServiceAccount was updated.
- Delete the three main security manifests by running:
kubectl delete -f k8s/ops-auditor-rbac.yaml
kubectl delete -f k8s/ops-auditor-serviceaccount.yaml
kubectl delete -f k8s/hello-app-serviceaccount.yaml
What Do These Commands Remove?
The first command removes the pod-observer Role and ops-auditor-pod-observer RoleBinding from default.
The second command removes the ops-auditor ServiceAccount.
The third command removes the hello-app-sa ServiceAccount.
- Confirm PowerShell reports that each default-namespace resource was deleted.
- Delete the Secret Mission's staging namespace by running:
kubectl delete namespace staging
Why Delete the Namespace?
This removes the staging namespace. Its namespaced pod-observer Role and ops-auditor-pod-observer RoleBinding are removed with it.
- Confirm PowerShell reports that the staging namespace was deleted.
Cleanup complete. Your inherited app and local cluster remain ready for future Kubernetes practice.
Nice Work!
Nice Work!
Mission complete! Your live Kubernetes application now uses deliberate workload identities under least-privilege RBAC.
You've learned how to:
- Give the hello-app Deployment a dedicated tokenless ServiceAccount named hello-app-sa. You also verified that the browser app keeps serving traffic.
- Expose the default-deny starting point with the ops-auditor identity. You then granted Pod observation through the namespace-scoped pod-observer Role. You connected that grant to ops-auditor with the ops-auditor-pod-observer RoleBinding.
- Build an authorization test matrix that proves Pod observation is allowed. The matrix also proves Pod-log reads are allowed. Pod deletion remains denied. Secret reads remain denied. The default grant does not reach kube-system.
- Secret Mission: grant controlled Pod observation in staging to the existing ops-auditor ServiceAccount from default. You used a target-namespace RoleBinding. Secret access stays denied. No cluster-wide binding exists.
Ready to quiz yourself?