Enforce Kubernetes Pod Security Standards
Lock down a Kubernetes cluster by enforcing Pod Security Standards.
Introduction
30 Second Summary
Every container running in a Kubernetes cluster is a potential entry point for attackers. A single misconfigured pod running as root can compromise an entire system.
In this project, you will take a Kubernetes cluster from zero security to enforced baselines. You will scan for vulnerabilities using Kubescape, implement Pod Security Standards to automatically reject insecure workloads, and deploy a hardened pod that passes all compliance checks.
What You'll Build
Picture running a deploy command and watching Kubernetes instantly reject an insecure workload, listing every policy violation in the error message. Then you fix the spec, redeploy, and watch it sail through.
By the end of this project, you'll have:
- A compliance scan report showing your cluster's security posture scored against the NSA-CISA hardening framework.
- A locked-down namespace that automatically rejects any pod violating the restricted Pod Security Standard.
- A hardened pod spec that passes all Pod Security Admission checks and runs successfully in the enforced namespace.
- Secret Mission: Add a NetworkPolicy to restrict traffic flow between pods and further improve your compliance score.
Are there any prerequisites?
No prior Kubernetes experience is required. This project walks you through every tool installation and concept from the ground up. You just need a Windows machine with VS Code and Docker Desktop installed.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters.
Set Up Your Environment
To enforce security on a Kubernetes cluster, you first need the cluster itself and a tool to scan it.
In this step, you will confirm Docker Desktop is running with Linux containers, enable Kubernetes to get a local cluster, and install Kubescape as your compliance scanner.
In this step, get ready to:
- Verify Docker Desktop is running with Linux containers enabled.
- Enable Kubernetes in Docker Desktop and confirm the cluster responds.
- Install Kubescape and verify it runs.
Verify Docker Desktop is running with Linux containers
Kubernetes on Docker Desktop requires the Docker engine to be running in Linux container mode. The Kubernetes settings tab is not available in Windows container mode.
- Press the Windows key to open your search bar.
- Type Docker Desktop and press Enter to open it.
- Wait for Docker Desktop to fully load. The whale icon in your system tray will stop animating when it is ready.
Now confirm Docker is in Linux container mode.
- Right-click the Docker whale icon in the system tray (bottom-right of your taskbar).
- If you see Switch to Windows containers... in the menu, you are already on Linux containers. No action needed.
- If you see Switch to Linux containers... in the menu, click it and wait for Docker to restart.
Why Linux containers?
Docker Desktop can run either Windows containers or Linux containers. Kubernetes on Docker Desktop only works in Linux container mode because it runs a Linux-based control plane. The Kubernetes settings tab will not appear if you are in Windows container mode.
Next, open PowerShell to verify the Docker engine is responding.
- Press the Windows key to open your search bar.
- Type PowerShell and press Enter to open it.
- Confirm Docker is running by running this command:
docker version
You should see both a Client section and a Server section in the output. If you only see the Client section, Docker Desktop has not finished starting.
Only seeing the Client section?
Wait 30 seconds and try docker version again. Docker Desktop can take a minute to fully start the engine after launch.
If the command is not recognized at all, Docker Desktop may not have added itself to your PATH. Close and reopen PowerShell, then try again.
help me troubleshoot Docker Desktop not starting
Enable Kubernetes in Docker Desktop
Docker Desktop includes a single-node Kubernetes cluster that you can enable from Settings. Once enabled, you get kubectl ready to use without any extra installation.
- In the Docker Desktop Dashboard window, click the Settings gear icon (top-right).
- Select the Kubernetes tab in the left sidebar.
- Check the box labeled Enable Kubernetes.
- Click Apply & Restart.
What is happening behind the scenes?
Docker Desktop downloads and starts the Kubernetes control plane components (API server, scheduler, controller manager, etcd) as containers. This takes one to three minutes on the first run.
Watch for the green light at the bottom-left of Docker Desktop that says Kubernetes running. Do not proceed until you see it.
Before you run the verification command, what do you think kubectl get nodes will show for a single-node local cluster?
- Return to your PowerShell window and verify the cluster is ready by running:
kubectl get nodes
You should see a node named docker-desktop with a status of Ready.
✔️ I see docker-desktop in Ready state
Your local Kubernetes cluster is running. Move on to the next substep to install Kubescape.
ⓧ I see NotReady or no output
Kubernetes is still starting. Wait another minute for the green Kubernetes running light in Docker Desktop, then run the command again.
If you have used other Kubernetes tools before, your kubectl context may be pointing to a different cluster. Switch to the Docker Desktop context by running:
kubectl config use-context docker-desktop
Then run kubectl get nodes again.
ⓧ kubectl is not recognized
Docker Desktop installs kubectl automatically when Kubernetes is enabled, but your PowerShell session may need to be restarted to pick up the updated PATH.
- Close PowerShell completely.
- Reopen PowerShell from the Windows search bar.
- Run kubectl get nodes again.
If it still fails, confirm Kubernetes is enabled (green light visible) and that Docker Desktop finished restarting.
Install Kubescape
Kubescape is an open-source security scanner that checks your cluster against compliance frameworks like the NSA-CISA hardening guidelines. You will install it directly into PowerShell using the official install script.
- In your PowerShell window, install Kubescape by running this command:
iwr -useb https://raw.githubusercontent.com/kubescape/kubescape/master/install.ps1 | iex
What does this command do?
iwr -useb downloads the install script from the Kubescape repository.
iex executes the downloaded script, which places the kubescape binary on your system and adds it to your PATH.
The script may take a minute to download and extract the binary. Wait for it to finish before continuing.
- Verify the installation succeeded by running:
kubescape version
✔️ I see a version number
Kubescape is installed and ready. You are all set to move on.
ⓧ The command is not recognized
The install script may have added Kubescape to your PATH without refreshing your current session.
- Close PowerShell completely.
- Reopen PowerShell from the Windows search bar.
- Run kubescape version again.
If it still fails, the install script may not have completed successfully. Re-run the install command above and watch for error messages in the output.
help me troubleshoot Kubescape installation on Windows
Your environment is ready. You have a running Kubernetes cluster and a compliance scanner installed. Next up, you will deploy an intentionally insecure workload so you have something to scan and secure.
Deploy an Insecure Workload
Your cluster is up and Kubescape is ready to scan it. Now you need something to actually scan and secure.
In this step, you'll deploy a pod that has zero security restrictions. Starting with an intentionally insecure workload lets you see exactly what "bad" looks like, so when you enforce Pod Security Standards later, you'll understand what they're protecting against.
In this step, get ready to:
- Create a dedicated Kubernetes namespace for your insecure workload.
- Deploy a pod with no security context restrictions.
- Verify the pod is running and inspect its security posture.
Create a namespace for your insecure workload
A namespace is a virtual partition inside your Kubernetes cluster. Resources in one namespace are invisible to resources in another by default. You'll create one specifically for your insecure experiment so it stays separate from system-level components.
- Create the insecure-ns namespace by running this command in PowerShell:
kubectl create namespace insecure-ns
You should see namespace/insecure-ns created in the output.
What is a Kubernetes namespace?
Think of namespaces as separate rooms inside one building. Each room can have its own rules and access controls. By putting your insecure pod in its own namespace, you keep it isolated from the kube-system namespace where Kubernetes runs its own critical components.
Seeing an error about the namespace already existing?
If you see already exists in the output, that means the namespace was created in a previous attempt. You can safely move on to the next substep.
If you see a connection error, make sure Docker Desktop is running and Kubernetes shows a green light in the Docker Desktop dashboard. Help me fix my kubectl connection to Docker Desktop
Create and deploy the insecure pod
Now you need a pod manifest. This is a YAML file that tells Kubernetes what container to run and how to configure it. Your manifest deliberately omits all security settings, creating the worst-case scenario you'll fix later.
- Move to your Desktop and create a project folder by running these commands in PowerShell:
cd ~/Desktop
mkdir kubernetes-security
cd kubernetes-security
- Confirm the folder was created by running:
ls
You should see an empty directory listing (the folder exists but has no files yet).
- Open this folder in VS Code by running:
code .
VS Code opens with the kubernetes-security folder loaded in the Explorer sidebar.
- In VS Code, create a new file by pressing Ctrl+N.
- Save it immediately with Ctrl+Shift+S and name it insecure-pod.yaml.
- Paste the following pod manifest into the file:
apiVersion: v1
kind: Pod
metadata:
name: insecure-nginx
labels:
app: insecure-nginx
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
What does this manifest do?
- apiVersion: v1 and kind: Pod tell Kubernetes you're defining a Pod resource.
- metadata.name gives the pod the name insecure-nginx so you can reference it later.
- image: nginx:1.27 pulls the nginx web server image from Docker Hub.
- There is no securityContext field anywhere. That's the point. Without explicit security settings, the container runs as root with full privileges.
- Save the file with Ctrl+S.
✔️ Awesome, I've got everything!
Great. Make sure you've saved insecure-pod.yaml and it's visible in VS Code's Explorer sidebar.
ⓧ I'd like to double check the full code
Your insecure-pod.yaml should contain exactly this:
apiVersion: v1
kind: Pod
metadata:
name: insecure-nginx
labels:
app: insecure-nginx
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
- Deploy the pod into your insecure-ns namespace by running this command in PowerShell:
kubectl apply -f insecure-pod.yaml -n insecure-ns
You should see pod/insecure-nginx created in the output.
Seeing a file not found error?
Make sure your PowerShell working directory is the same folder where you saved the file. Run ls to confirm insecure-pod.yaml appears in the listing.
If you're running the command from VS Code's integrated terminal, it should already be in the kubernetes-security folder. If you're using a separate PowerShell window, run cd ~/Desktop/kubernetes-security first. Help me fix my kubectl apply file path issue
Verify the pod is running and inspect its security context
Your pod should now be running in the cluster. Before you check, think about what you expect to see when you query a pod that has no security restrictions defined at all.
- Check the pod status by running:
kubectl get pods -n insecure-ns
You should see output like this, with your pod in Running status:
NAME READY STATUS RESTARTS AGE
insecure-nginx 1/1 Running 0 30s
What do the columns mean?
- READY 1/1 means one container is running out of one expected.
- STATUS Running confirms the container started successfully.
- RESTARTS 0 means the container hasn't crashed since creation.
Now inspect the pod's security context. What do you think you'll see for a pod that defined no security settings?
- Inspect the container's security context by running:
kubectl get pod insecure-nginx -n insecure-ns -o jsonpath="{.spec.containers[0].securityContext}"
The command returns nothing. An empty result means there is no securityContext defined on this container at all. No restrictions on running as root, no capability limits, no seccomp profile.
Why is an empty security context dangerous?
When a container has no security context, Kubernetes applies no restrictions. The container process can run as root (user ID 0), escalate privileges, access all Linux capabilities, and interact with the host kernel without constraints.
This is the default behavior for any pod you deploy without explicitly locking it down. In a production environment, this would be a critical security gap.
Pod stuck in ContainerCreating or Pending?
This usually means Docker Desktop is still pulling the nginx:1.27 image. Wait 30-60 seconds and run kubectl get pods -n insecure-ns again.
If the pod shows ErrImagePull or ImagePullBackOff, check your internet connection. Docker Desktop needs to download the image from Docker Hub. Help me fix my pod image pull error
You now have a running workload with zero security controls. Next up, you'll scan your cluster with Kubescape to see exactly how many security controls this pod violates.
Scan Your Cluster with Kubescape
Your insecure pod is up and running. But how insecure is it, exactly?
Before you can enforce a security baseline, you need to know where you stand. In this step, you will scan your cluster against the NSA-CISA hardening framework using Kubescape. The scan produces a scored compliance report that shows exactly which security controls pass and which ones fail.
In this step, get ready to:
- Run a full cluster compliance scan against the NSA-CISA framework.
- Run a targeted scan with verbose output to pinpoint exact failures in your insecure namespace.
- Identify the specific security gaps you will close in the next steps.
Run a full cluster compliance scan
Before you run the scan, think about your insecure-nginx pod. It has no security context at all. Do you think it will pass or fail a government security framework?
- Scan your entire cluster against the NSA-CISA framework by running this command in PowerShell:
kubescape scan framework nsa
The scan takes a moment to download the framework definitions and evaluate every resource in your cluster. You will see a table of results scroll by, followed by a summary.
What does this output show?
The output has three sections:
- An overview showing how many controls passed, failed, and need configuration.
- A table with columns for Severity, Control name, Failed resources, All resources, and % compliance score.
- A resource summary with your total compliance score for the entire NSA framework.
Look at the bottom of the output. You will see a compliance score well below 100%. Multiple controls are marked as failed across various severity levels.
Scan hangs or fails to download?
Kubescape downloads framework definitions on first run. If the scan hangs, check your internet connection. If you see a connection timeout, try running the command again.
If you see a message about kubectl context, make sure your context is set to docker-desktop by running:
kubectl config use-context docker-desktop
help me troubleshoot Kubescape scan failures
Run a targeted scan with verbose output
The full cluster scan includes system namespaces like kube-system which add noise to your results. You can scope the scan to just your insecure workload and get detailed per-resource failure information.
- Run a targeted scan against your insecure-ns namespace with verbose output by running:
kubescape scan framework nsa --include-namespaces insecure-ns --verbose
What do these flags do?
- --include-namespaces insecure-ns scopes the scan to only evaluate resources in your insecure namespace, filtering out system components.
- --verbose shows exactly which resources caused each control to fail, along with remediation suggestions for each one.
This time the output lists your insecure-nginx pod by name under each failed control. You can see precisely what is wrong and what to fix.
Not seeing your pod in the results?
Make sure you spelled the namespace correctly. Run kubectl get pods -n insecure-ns to confirm your pod is still running in that namespace.
help me figure out why Kubescape doesn't show my pod
Identify the security gaps
Look through your verbose scan output. The failed controls tell you exactly what is missing from your pod spec.
- Containers running as root. Your pod has no runAsNonRoot restriction, so the container runs with full root privileges.
- Missing seccomp profiles. No seccompProfile is defined, leaving system calls unrestricted.
- Unrestricted capabilities. The container has not dropped any Linux capabilities, giving it access to operations it does not need.
- No resource limits. Without CPU and memory limits, a compromised container could consume all node resources.
Why do these failures matter?
Each of these failures represents an attack surface. A container running as root can modify the host filesystem. Unrestricted capabilities let an attacker escalate privileges. Missing resource limits enable denial-of-service attacks.
The NSA-CISA framework flags these because they represent the minimum security controls any production workload should have. In the next steps, you will enforce these controls at the namespace level so Kubernetes rejects any pod that does not meet them.
You now have a clear picture of your cluster's security gaps. Next up, you will use Kubernetes' built-in Pod Security Admission controller to enforce the restricted standard and automatically reject any workload that has these vulnerabilities.
Enforce Pod Security Standards
Your Kubescape scan revealed the gaps. Containers running as root, no seccomp profiles, unrestricted capabilities. You know what is wrong. Now you need to prevent it from happening again.
Kubernetes has a built-in enforcement mechanism called Pod Security Admission. It checks every pod spec against a security standard before allowing it into a namespace. In this step, you will configure a namespace to enforce the restricted standard and prove it works by watching Kubernetes reject your insecure pod.
In this step, get ready to:
- Create a namespace that enforces the restricted Pod Security Standard.
- Attempt to deploy the insecure pod and watch Kubernetes reject it.
- Understand how enforce, audit, and warn modes differ.
Create a namespace with the restricted standard enforced
Pod Security Admission works through labels on namespaces. When you label a namespace with a specific security level, Kubernetes checks every pod created in that namespace against that level's requirements.
- Switch back to VS Code.
- Create a new file called secure-namespace.yaml in the same folder as your insecure-pod.yaml file.
- Add the following namespace manifest with Pod Security Admission labels:
apiVersion: v1
kind: Namespace
metadata:
name: secure-ns
labels:
# Enforce mode: reject pods that violate the restricted standard
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
# Audit mode: log violations to the audit log
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
# Warn mode: show warnings to the user
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
What do these labels do?
Each pair of labels sets one of three enforcement modes for this namespace. The label format is pod-security.kubernetes.io/<MODE>: <LEVEL> where MODE is enforce, audit, or warn.
- enforce rejects pods that violate the standard. They never run.
- audit records violations in the cluster audit log but still allows the pod.
- warn shows a warning to the user but still allows the pod.
- restricted is the most locked-down level, requiring non-root execution, dropped capabilities, and seccomp profiles.
- Save secure-namespace.yaml.
- Apply the namespace manifest by running this command in PowerShell:
kubectl apply -f secure-namespace.yaml
- Confirm the namespace was created by running:
kubectl get ns secure-ns
You should see secure-ns listed with Active status.
Namespace not created?
- Make sure your YAML indentation uses spaces, not tabs. YAML is whitespace-sensitive.
- Check that the file is saved before running kubectl apply.
- Verify your PowerShell is in the same directory as secure-namespace.yaml by running ls to list files.
Help me troubleshoot my secure-namespace.yaml not being applied
Attempt to deploy the insecure pod into the enforced namespace
The enforcement is set. Now you will prove it works by attempting to deploy your original insecure pod into the locked-down namespace.
Before you run this, think about what you expect to happen. Your insecure-pod.yaml has no security context at all. The restricted standard requires non-root execution, dropped capabilities, and a seccomp profile. Do you think Kubernetes will accept or reject this pod?
- Try deploying your insecure pod into the secured namespace by running:
kubectl apply -f insecure-pod.yaml -n secure-ns
Kubernetes rejects the pod. You will see an error that starts with Error from server (Forbidden) and lists every specific violation. The error mentions allowPrivilegeEscalation, runAsNonRoot, capabilities, and seccompProfile.
Why is this rejection useful?
The error message is not just a wall of text. It tells you exactly what to fix. Each violation maps to a field in the pod's securityContext that you need to set.
This is the enforce mode in action. In a real cluster, this prevents any developer from accidentally deploying an insecure workload into a production namespace. The admission controller catches it before the pod ever runs.
Understand how the three enforcement modes work together
Your namespace already has all three modes configured. You saw enforce in action when the pod was rejected. The audit and warn modes provide additional visibility without blocking.
- Verify all three modes are applied to your namespace by running:
kubectl get ns secure-ns -o jsonpath="{.metadata.labels}"
You should see all six Pod Security Admission labels in the output, confirming enforce, audit, and warn are all set to restricted.
When would you use warn without enforce?
In a real organization, you often roll out Pod Security Standards gradually. Start with warn mode so developers see warnings without their deployments breaking. Then move to audit to capture violations in logs. Finally, flip to enforce once teams have fixed their workloads.
You can also apply these labels imperatively with kubectl label. For example:
kubectl label --overwrite ns secure-ns pod-security.kubernetes.io/warn=restricted pod-security.kubernetes.io/warn-version=latest
This achieves the same result as declaring labels in the YAML file. The declarative approach is preferred for repeatability.
Not seeing all six labels?
- Re-apply your secure-namespace.yaml file with kubectl apply -f secure-namespace.yaml to ensure all labels are set.
- Check that your YAML has exactly six label entries under metadata.labels.
Help me check my namespace labels are correct
✔️ Awesome, I've got everything!
Great. Double check you have saved secure-namespace.yaml and that your secure-ns namespace shows all six Pod Security Admission labels.
ⓧ I'd like to double check the full code
Here is the complete secure-namespace.yaml file:
apiVersion: v1
kind: Namespace
metadata:
name: secure-ns
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
Your namespace is now a security gate. Any pod that does not meet the restricted standard will be rejected before it ever runs. In the next step, you will fix your pod spec to pass these checks and deploy it successfully.
Fix and Deploy a Compliant Workload
In the previous step, you saw Kubernetes reject the insecure pod with a wall of violation messages. That is enforcement in action.
Enforcement alone is only half the picture. Without remediation, your namespace is locked but empty. In this step, you will write a pod spec that satisfies every requirement of the restricted Pod Security Standard, deploy it successfully, and re-scan with Kubescape to prove your compliance score improved.
In this step, get ready to:
- Create a hardened pod spec that passes all restricted PSS admission checks.
- Deploy the compliant pod and confirm it runs in the enforced namespace.
- Re-scan with Kubescape and compare your compliance score to the earlier results.
Create a compliant pod spec
The restricted Pod Security Standard requires specific security fields declared in the pod spec. Your insecure pod had none of them, so it was rejected.
- Switch back to VS Code.
- Create a new file called secure-pod.yaml in the same folder as your other YAML files by pasting the following:
apiVersion: v1
kind: Pod
metadata:
name: secure-nginx
labels:
app: secure-nginx
spec:
# Pod-level security: no root, use seccomp
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 8080
# Container-level security: no escalation, drop all caps
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
capabilities:
drop:
- ALL
# Resource limits satisfy best-practice controls
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "250m"
What does this spec do differently?
- runAsNonRoot: true at the pod level tells the kubelet to reject any container that tries to start as UID 0.
- seccompProfile: RuntimeDefault applies the node's default syscall filter, blocking dangerous system calls.
- allowPrivilegeEscalation: false prevents the container process from gaining more privileges than its parent.
- capabilities: drop: [ALL] removes every Linux capability. The container runs with zero special privileges.
- The resources block sets CPU and memory limits, which also satisfies Kubescape controls for resource management.
- Port 8080 instead of 80 avoids requiring the NET_BIND_SERVICE capability (ports below 1024 are privileged).
- Save secure-pod.yaml.
✔️ Awesome, I've got everything!
Great. Double check you have saved the file before moving on.
ⓧ I'd like to double check the full code
Your secure-pod.yaml should look exactly like this:
apiVersion: v1
kind: Pod
metadata:
name: secure-nginx
labels:
app: secure-nginx
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 8080
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
capabilities:
drop:
- ALL
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "250m"
Deploy the hardened pod
Last time you applied a pod to secure-ns, Kubernetes rejected it immediately. This time, the spec declares every field the restricted standard demands.
Before you run the next command, what do you think will happen? Will the admission controller accept or reject this pod?
- Deploy the compliant pod into the enforced namespace by running:
kubectl apply -f secure-pod.yaml -n secure-ns
This time, Kubernetes accepts the pod. The admission controller inspects the spec, confirms every restricted-profile field is present and correct, and allows creation.
- Confirm the pod is running by checking its status:
kubectl get pods -n secure-ns
You should see secure-nginx with status Running (or transitioning through ContainerCreating first).
Pod not reaching Running status?
- If you see CreateContainerConfigError or CrashLoopBackOff: the official nginx:1.27 image runs as root by default, which conflicts with runAsNonRoot at runtime. The key learning moment is that Pod Security Admission accepted the spec (no rejection error). Runtime enforcement by the kubelet is a separate layer. If the pod does not reach Running, it still passed admission, which is the goal of this step.
- If you see the original rejection error listing policy violations, double check that your secure-pod.yaml has runAsNonRoot: true under spec.securityContext and allowPrivilegeEscalation: false under the container's securityContext.
- Make sure you specified -n secure-ns in the apply command. Without it, the pod deploys to the default namespace which has no enforcement.
Help me troubleshoot my secure pod not starting in the restricted namespace.
Re-scan and compare compliance scores
Your earlier Kubescape scan flagged containers running as root, missing seccomp profiles, unrestricted capabilities, and no resource limits. You have now addressed all of those in the new pod spec.
Before running the scan, what do you expect happened to your compliance score for this namespace?
- Re-run the Kubescape NSA framework scan targeting only the secure namespace by running:
kubescape scan framework nsa --include-namespaces secure-ns
Compare the output table to your earlier scan. The controls that previously failed (containers running as root, missing seccomp profiles, unrestricted capabilities, missing resource limits) should now pass for the secure-nginx pod.
What should I see in the scan results?
The compliance score for secure-ns is significantly higher than what you saw for insecure-ns earlier. Controls like "Containers should run as non-root" and "Containers should have a seccomp profile" now show passing resources instead of failures.
Some controls may still show warnings (for example, network policies or image scanning). Those are expected gaps you have not addressed yet.
Score looks unchanged?
- Make sure you included --include-namespaces secure-ns in the command. Without it, you scan the full cluster and the insecure-ns results dilute the improvement.
- Confirm secure-nginx is actually running in secure-ns with kubectl get pods -n secure-ns. If the pod was rejected or is not present, there is nothing for Kubescape to evaluate.
Help me understand why my Kubescape compliance score did not improve after deploying a hardened pod.
Your namespace now enforces the restricted Pod Security Standard and contains a workload that complies with it. You have completed the full assess, enforce, remediate, verify loop. Next up: a Secret Mission to push your compliance score even higher with network segmentation.
Secret mission
Add a NetworkPolicy
Pod Security Standards lock down what your workloads can do, but they don't control who can talk to whom. In this mission, you'll write a Kubernetes NetworkPolicy from scratch to restrict network traffic and push your compliance score even higher.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- Kubernetes cluster running inside Docker Desktop.
- Namespace insecure-ns with pod insecure-nginx.
- Namespace secure-ns with pod secure-nginx and NetworkPolicy restrict-ingress.
- YAML manifest files (insecure-pod.yaml, secure-namespace.yaml, secure-pod.yaml, network-policy.yaml).
- Kubescape CLI installed to ~/.kubescape.
Keep everything running
No action needed. Choose this if you are still actively building or want to continue experimenting with Pod Security Standards and Kubescape scans.
- Docker Desktop Kubernetes stays active and uses some system memory and CPU.
- Your namespaces, pods, and NetworkPolicy remain deployed.
- Kubescape stays installed and ready for future scans.
Pause - I'll come back to this later
Shut down Docker Desktop to free up system memory and CPU. Kubernetes will resume exactly where you left off when you restart Docker Desktop.
- Right-click the Docker Desktop icon in the system tray.
- Click Quit Docker Desktop.
- Your namespaces, pods, and YAML files remain intact for when you return.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow each step below.
- Delete both Kubernetes namespaces (this removes all pods and the NetworkPolicy inside them) by running:
kubectl delete namespace insecure-ns
kubectl delete namespace secure-ns
- Delete your YAML manifest files. Navigate to the folder where you saved them and run:
Remove-Item insecure-pod.yaml, secure-namespace.yaml, secure-pod.yaml, network-policy.yaml
- Remove Kubescape by deleting its installation directory:
Remove-Item -Recurse -Force ~/.kubescape
- To disable Kubernetes entirely, open the Docker Desktop Dashboard.
- Go to Settings, then select the Kubernetes tab.
- Uncheck Enable Kubernetes and click Apply & restart.
Nice Work!
Nice Work!
You just took a Kubernetes cluster from zero security to enforced baselines. This is the exact assess-enforce-remediate workflow you would perform on day one of a DevSecOps role.
You've learned how to:
- Run a compliance scan against the NSA-CISA hardening framework using Kubescape and interpret a scored report showing exactly which security controls pass or fail.
- Enforce the restricted Pod Security Standard at the namespace level so Kubernetes automatically rejects any workload that does not meet the security baseline.
- Diagnose and fix a non-compliant pod spec to pass all Pod Security Admission checks and deploy successfully into a locked-down namespace.
- Secret Mission: Created a NetworkPolicy to segment network traffic and further improved your compliance posture.
Ready to quiz yourself?