Lab 02 — Deploy Your First Secure Application
Mission Information
Section titled “Mission Information”| Item | Details |
|---|---|
| Lab ID | K8S-FND-LAB-02 |
| Difficulty | Beginner to Intermediate |
| Estimated Time | 90–120 minutes |
| Environment | Local Kubernetes cluster |
| Platform | Docker Desktop and kind |
| Cost | Free |
| Primary Role | Kubernetes Security Engineer |
| Module | Module 01 — Kubernetes Fundamentals for Security Engineers |
| Previous Lab | Lab 01 — Build Your First Kubernetes Cluster |
Mission Scenario
Section titled “Mission Scenario”CloudNova Technologies has successfully created its first Kubernetes development cluster.
The application team now wants to deploy an internal web application for testing. During an initial security review, the Cloud Security team identified several weaknesses in the proposed deployment:
- The container would run without a defined security context.
- The default Service Account would be used.
- Service Account tokens could be mounted unnecessarily.
- The container filesystem would remain writable.
- Linux capabilities would not be restricted.
- Resource limits would not be configured.
- Network communication would be unrestricted.
- Application health checks would be missing.
- The workload would use a floating container image tag.
- Security-related Namespace controls would not be enabled.
You have been assigned to create a more secure deployment standard.
Your mission is to deploy the application using Kubernetes security controls that reduce privilege, restrict network access, improve availability, and provide evidence that the controls are operating correctly.
Learning Objectives
Section titled “Learning Objectives”By completing this lab, you will be able to:
- Deploy a container using a dedicated Namespace
- Apply Kubernetes Pod Security Admission labels
- Create and use a dedicated Service Account
- Disable unnecessary Service Account token mounting
- Configure Pod-level and container-level security contexts
- Run a container as a non-root user
- Prevent privilege escalation
- remove unnecessary Linux capabilities
- Use a read-only root filesystem
- Configure resource requests and limits
- Add readiness and liveness probes
- Create an internal ClusterIP Service
- Apply default-deny Network Policies
- Allow only approved application communication
- Perform positive and negative network tests
- Validate workload security controls
- Document security findings and evidence
Architecture
Section titled “Architecture”You will build the following environment:
Windows Workstation │ ▼Docker Desktop │ ▼kind Kubernetes Cluster │ ├── Control Plane Node │ └── Worker Node │ ▼ cloudnova-secure-app Namespace │ ├── Pod Security Admission Labels │ ├── Dedicated Service Account │ ├── Secure Web Deployment │ ├── Non-root execution │ ├── Read-only root filesystem │ ├── No privilege escalation │ ├── Dropped Linux capabilities │ ├── Seccomp profile │ ├── Resource controls │ └── Health probes │ ├── ClusterIP Service │ ├── Default-Deny Network Policy │ └── Approved Test ClientSecurity Controls Implemented
Section titled “Security Controls Implemented”This lab introduces the following controls:
| Control | Security Benefit |
|---|---|
| Dedicated Namespace | Separates application resources logically |
| Pod Security labels | Enforces workload security standards |
| Dedicated Service Account | Separates workload identity |
| Disabled token automount | Reduces unnecessary API credentials |
| Non-root execution | Reduces container privilege |
| Privilege escalation disabled | Prevents gaining additional privileges |
| Dropped capabilities | Removes unnecessary Linux privileges |
| Read-only root filesystem | Reduces unauthorised file modification |
| RuntimeDefault seccomp | Restricts dangerous system calls |
| Resource requests and limits | Reduces resource exhaustion risk |
| Health probes | Improves availability and recovery |
| ClusterIP Service | Prevents direct public exposure |
| Default-deny Network Policy | Blocks unapproved communication |
| Approved client policy | Allows only authorised application access |
Prerequisites
Section titled “Prerequisites”Before beginning this lab, ensure that you have:
- Completed Lab 01
- Docker Desktop installed and running
kubectlinstalledkindinstalled- Visual Studio Code installed
- PowerShell or Git Bash available
- A running kind Kubernetes cluster
- Basic understanding of Pods, Deployments, Services and Namespaces
Task 01 — Verify the Kubernetes Cluster
Section titled “Task 01 — Verify the Kubernetes Cluster”Step 1 — Confirm Docker Is Running
Section titled “Step 1 — Confirm Docker Is Running”docker versionBoth the Docker client and server should respond successfully.
Step 2 — Check Existing kind Clusters
Section titled “Step 2 — Check Existing kind Clusters”kind get clustersExpected cluster:
cloudnova-security-labStep 3 — Check the Current kubectl Context
Section titled “Step 3 — Check the Current kubectl Context”kubectl config current-contextExpected context:
kind-cloudnova-security-labStep 4 — Verify the Nodes
Section titled “Step 4 — Verify the Nodes”kubectl get nodes -o wideBoth nodes should show:
STATUS: ReadyExpected architecture:
cloudnova-security-lab-control-planecloudnova-security-lab-workerRecreate the Cluster if It Was Deleted
Section titled “Recreate the Cluster if It Was Deleted”Lab 01 included an optional cluster cleanup.
When the cluster no longer exists, recreate it using the kind-cluster.yaml file from Lab 01.
PowerShell
Section titled “PowerShell”Set-Location C:\GoHackersCloud-Labs\kubernetes\lab-01kind create cluster --config .\kind-cluster.yamlGit Bash
Section titled “Git Bash”cd /c/GoHackersCloud-Labs/kubernetes/lab-01kind create cluster --config kind-cluster.yamlVerify:
kubectl get nodesTask 02 — Create the Lab Workspace
Section titled “Task 02 — Create the Lab Workspace”Create a dedicated directory for the secure application files.
PowerShell
Section titled “PowerShell”New-Item -ItemType Directory -Path C:\GoHackersCloud-Labs\kubernetes\lab-02 -ForceSet-Location C:\GoHackersCloud-Labs\kubernetes\lab-02Git Bash
Section titled “Git Bash”mkdir -p /c/GoHackersCloud-Labs/kubernetes/lab-02cd /c/GoHackersCloud-Labs/kubernetes/lab-02Verify the current directory.
PowerShell
Section titled “PowerShell”Get-LocationGit Bash
Section titled “Git Bash”pwdTask 03 — Create a Secure Namespace
Section titled “Task 03 — Create a Secure Namespace”Create a file named:
01-namespace.yamlAdd:
apiVersion: v1kind: Namespacemetadata: name: cloudnova-secure-app labels: environment: development owner: cloud-security-team business-unit: cloudnova-technologies security-tier: restricted 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: latestUnderstanding the Pod Security Labels
Section titled “Understanding the Pod Security Labels”The Namespace uses the Kubernetes restricted Pod Security Standard.
Enforce │ └── Rejects workloads that violate the selected standard
Audit │ └── Records policy violations in audit information
Warn │ └── Displays warnings to the userThe restricted profile is designed for strongly hardened workloads.
Step 1 — Apply the Namespace
Section titled “Step 1 — Apply the Namespace”PowerShell
Section titled “PowerShell”kubectl apply -f .\01-namespace.yamlGit Bash
Section titled “Git Bash”kubectl apply -f 01-namespace.yamlExpected result:
namespace/cloudnova-secure-app createdStep 2 — Verify the Namespace
Section titled “Step 2 — Verify the Namespace”kubectl get namespace cloudnova-secure-appStep 3 — Review the Labels
Section titled “Step 3 — Review the Labels”kubectl get namespace cloudnova-secure-app --show-labelsConfirm that the output includes:
pod-security.kubernetes.io/enforce=restrictedsecurity-tier=restrictedTask 04 — Create a Dedicated Service Account
Section titled “Task 04 — Create a Dedicated Service Account”Applications should not rely automatically on the default Service Account.
Create:
02-service-account.yamlAdd:
apiVersion: v1kind: ServiceAccountmetadata: name: cloudnova-web-sa namespace: cloudnova-secure-app labels: app: cloudnova-secure-web owner: cloud-security-teamautomountServiceAccountToken: falseSecurity Explanation
Section titled “Security Explanation”Every Kubernetes Namespace contains a default Service Account.
A workload that uses the Kubernetes API may require a Service Account token. However, this application only serves web content and does not need to communicate with the Kubernetes API.
Therefore:
automountServiceAccountToken: falseprevents Kubernetes from automatically mounting an API token inside the Pod.
This reduces the impact of a container compromise.
Step 1 — Apply the Service Account
Section titled “Step 1 — Apply the Service Account”PowerShell
Section titled “PowerShell”kubectl apply -f .\02-service-account.yamlGit Bash
Section titled “Git Bash”kubectl apply -f 02-service-account.yamlStep 2 — Verify the Service Account
Section titled “Step 2 — Verify the Service Account”kubectl get serviceaccount -n cloudnova-secure-appExpected Service Accounts:
cloudnova-web-sadefaultStep 3 — Inspect the Service Account
Section titled “Step 3 — Inspect the Service Account”kubectl describe serviceaccount cloudnova-web-sa -n cloudnova-secure-appTask 05 — Create the Secure Deployment
Section titled “Task 05 — Create the Secure Deployment”Create:
03-secure-deployment.yamlAdd:
apiVersion: apps/v1kind: Deploymentmetadata: name: cloudnova-secure-web namespace: cloudnova-secure-app labels: app: cloudnova-secure-web environment: development security-tier: restrictedspec: replicas: 2
strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 0 maxSurge: 1
selector: matchLabels: app: cloudnova-secure-web
template: metadata: labels: app: cloudnova-secure-web environment: development security-tier: restricted
spec: serviceAccountName: cloudnova-web-sa automountServiceAccountToken: false
securityContext: runAsNonRoot: true seccompProfile: type: RuntimeDefault
containers: - name: secure-nginx image: nginxinc/nginx-unprivileged:stable-alpine imagePullPolicy: IfNotPresent
ports: - name: http containerPort: 8080 protocol: TCP
securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsNonRoot: true capabilities: drop: - ALL
resources: requests: cpu: 100m memory: 64Mi ephemeral-storage: 50Mi limits: cpu: 250m memory: 128Mi ephemeral-storage: 100Mi
readinessProbe: httpGet: path: / port: http scheme: HTTP initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 2 failureThreshold: 3 successThreshold: 1
livenessProbe: httpGet: path: / port: http scheme: HTTP initialDelaySeconds: 15 periodSeconds: 20 timeoutSeconds: 2 failureThreshold: 3
volumeMounts: - name: nginx-cache mountPath: /var/cache/nginx
- name: nginx-run mountPath: /var/run
- name: temporary-files mountPath: /tmp
volumes: - name: nginx-cache emptyDir: sizeLimit: 20Mi
- name: nginx-run emptyDir: sizeLimit: 10Mi
- name: temporary-files emptyDir: sizeLimit: 20MiUnderstanding the Deployment Controls
Section titled “Understanding the Deployment Controls”Dedicated Service Account
Section titled “Dedicated Service Account”serviceAccountName: cloudnova-web-saThe workload uses its own identity instead of relying on the default Service Account.
Service Account Token Disabled
Section titled “Service Account Token Disabled”automountServiceAccountToken: falseThe application does not receive unnecessary Kubernetes API credentials.
Non-Root Execution
Section titled “Non-Root Execution”runAsNonRoot: trueKubernetes rejects the workload when the container attempts to run as the root user.
RuntimeDefault Seccomp
Section titled “RuntimeDefault Seccomp”seccompProfile: type: RuntimeDefaultThe container runtime applies its default seccomp profile to restrict dangerous Linux system calls.
Privilege Escalation Disabled
Section titled “Privilege Escalation Disabled”allowPrivilegeEscalation: falseProcesses inside the container cannot gain more privileges than their parent process.
Linux Capabilities Removed
Section titled “Linux Capabilities Removed”capabilities: drop: - ALLAll optional Linux capabilities are removed from the container.
Read-Only Root Filesystem
Section titled “Read-Only Root Filesystem”readOnlyRootFilesystem: trueThe container cannot modify its root filesystem.
Temporary writable locations are provided using controlled emptyDir volumes.
Resource Controls
Section titled “Resource Controls”resources: requests: limits:Requests assist scheduling, while limits restrict maximum resource consumption.
Health Checks
Section titled “Health Checks”Readiness Probe │ └── Determines when the Pod can receive traffic
Liveness Probe │ └── Determines whether Kubernetes should restart the containerRolling Update Strategy
Section titled “Rolling Update Strategy”maxUnavailable: 0maxSurge: 1This configuration helps maintain application availability during updates.
Task 06 — Validate the Manifest Before Deployment
Section titled “Task 06 — Validate the Manifest Before Deployment”Use client-side validation before sending the manifest to the cluster.
PowerShell
Section titled “PowerShell”kubectl apply --dry-run=client -f .\03-secure-deployment.yamlGit Bash
Section titled “Git Bash”kubectl apply --dry-run=client -f 03-secure-deployment.yamlExpected result:
deployment.apps/cloudnova-secure-web created (dry run)The dry run confirms that the YAML structure is valid.
Task 07 — Deploy the Secure Application
Section titled “Task 07 — Deploy the Secure Application”Step 1 — Apply the Deployment
Section titled “Step 1 — Apply the Deployment”PowerShell
Section titled “PowerShell”kubectl apply -f .\03-secure-deployment.yamlGit Bash
Section titled “Git Bash”kubectl apply -f 03-secure-deployment.yamlExpected result:
deployment.apps/cloudnova-secure-web createdStep 2 — Watch the Rollout
Section titled “Step 2 — Watch the Rollout”kubectl rollout status deployment/cloudnova-secure-web -n cloudnova-secure-appExpected result:
deployment "cloudnova-secure-web" successfully rolled outStep 3 — Verify the Deployment
Section titled “Step 3 — Verify the Deployment”kubectl get deployments -n cloudnova-secure-appExpected result:
NAME READY UP-TO-DATE AVAILABLEcloudnova-secure-web 2/2 2 2Step 4 — Verify the Pods
Section titled “Step 4 — Verify the Pods”kubectl get pods -n cloudnova-secure-appExpected status:
RunningStep 5 — Review Pod Placement
Section titled “Step 5 — Review Pod Placement”kubectl get pods -n cloudnova-secure-app -o wideReview:
- Pod name
- Pod IP address
- Node assignment
- Readiness status
- Restart count
Task 08 — Investigate Pod Security Admission
Section titled “Task 08 — Investigate Pod Security Admission”The Namespace enforces the restricted Pod Security Standard.
Create an intentionally insecure Pod to confirm that the admission policy rejects it.
Create:
insecure-test-pod.yamlAdd:
apiVersion: v1kind: Podmetadata: name: insecure-test-pod namespace: cloudnova-secure-appspec: containers: - name: insecure-container image: nginx:stable-alpineStep 1 — Attempt to Create the Insecure Pod
Section titled “Step 1 — Attempt to Create the Insecure Pod”PowerShell
Section titled “PowerShell”kubectl apply -f .\insecure-test-pod.yamlGit Bash
Section titled “Git Bash”kubectl apply -f insecure-test-pod.yamlThe request should be rejected because the Pod does not meet the restricted security requirements.
The error may identify missing controls such as:
allowPrivilegeEscalation: false- Dropped capabilities
runAsNonRoot: true- A seccomp profile
Security Observation
Section titled “Security Observation”The admission control operated before the Pod was created.
Developer Request │ ▼Kubernetes API Server │ ▼Pod Security Admission │ ├── Compliant → Allowed │ └── Non-compliant → RejectedThis prevents insecure workloads from entering the cluster.
Step 2 — Confirm the Pod Was Not Created
Section titled “Step 2 — Confirm the Pod Was Not Created”kubectl get pod insecure-test-pod -n cloudnova-secure-appExpected result:
NotFoundKeep the rejected command output as evidence.
Task 09 — Inspect the Running Security Context
Section titled “Task 09 — Inspect the Running Security Context”Step 1 — View the Deployment YAML
Section titled “Step 1 — View the Deployment YAML”kubectl get deployment cloudnova-secure-web -n cloudnova-secure-app -o yamlLocate:
securityContext:Step 2 — Inspect One Pod
Section titled “Step 2 — Inspect One Pod”List the Pods:
kubectl get pods -n cloudnova-secure-appCopy one Pod name and run:
kubectl describe pod <POD-NAME> -n cloudnova-secure-appReview:
- Service Account
- Container image
- Container port
- Resource requests
- Resource limits
- Readiness probe
- Liveness probe
- Mounted volumes
- Pod events
Step 3 — Confirm the Service Account
Section titled “Step 3 — Confirm the Service Account”kubectl get pod <POD-NAME> -n cloudnova-secure-app -o jsonpath="{.spec.serviceAccountName}"Expected result:
cloudnova-web-saStep 4 — Confirm Token Automount Is Disabled
Section titled “Step 4 — Confirm Token Automount Is Disabled”kubectl get pod <POD-NAME> -n cloudnova-secure-app -o jsonpath="{.spec.automountServiceAccountToken}"Expected result:
falseStep 5 — Confirm the Container Cannot Run as Root
Section titled “Step 5 — Confirm the Container Cannot Run as Root”kubectl get pod <POD-NAME> -n cloudnova-secure-app -o jsonpath="{.spec.securityContext.runAsNonRoot}"Expected result:
trueStep 6 — Confirm Privilege Escalation Is Disabled
Section titled “Step 6 — Confirm Privilege Escalation Is Disabled”kubectl get pod <POD-NAME> -n cloudnova-secure-app -o jsonpath="{.spec.containers[0].securityContext.allowPrivilegeEscalation}"Expected result:
falseStep 7 — Confirm the Root Filesystem Is Read-Only
Section titled “Step 7 — Confirm the Root Filesystem Is Read-Only”kubectl get pod <POD-NAME> -n cloudnova-secure-app -o jsonpath="{.spec.containers[0].securityContext.readOnlyRootFilesystem}"Expected result:
trueStep 8 — Confirm the Seccomp Profile
Section titled “Step 8 — Confirm the Seccomp Profile”kubectl get pod <POD-NAME> -n cloudnova-secure-app -o jsonpath="{.spec.securityContext.seccompProfile.type}"Expected result:
RuntimeDefaultStep 9 — Confirm Linux Capabilities Are Dropped
Section titled “Step 9 — Confirm Linux Capabilities Are Dropped”kubectl get pod <POD-NAME> -n cloudnova-secure-app -o jsonpath="{.spec.containers[0].securityContext.capabilities.drop}"Expected result:
["ALL"]Task 10 — Verify the Container User
Section titled “Task 10 — Verify the Container User”Execute the id command inside one application Pod.
kubectl exec -n cloudnova-secure-app <POD-NAME> -- idExpected output should show a non-root user.
Example:
uid=101(...) gid=101(...) groups=...The UID should not be:
0UID 0 represents the root user.
Security Observation
Section titled “Security Observation”Root UserUID 0High Privilege
Non-Root UserUID greater than 0Reduced PrivilegeRunning as non-root does not eliminate every risk, but it reduces the potential impact of a container compromise.
Task 11 — Validate the Read-Only Root Filesystem
Section titled “Task 11 — Validate the Read-Only Root Filesystem”Attempt to create a file in the root filesystem.
kubectl exec -n cloudnova-secure-app <POD-NAME> -- sh -c "touch /security-test.txt"Expected result:
Read-only file systemThe command should fail.
Test an Approved Writable Directory
Section titled “Test an Approved Writable Directory”Run:
kubectl exec -n cloudnova-secure-app <POD-NAME> -- sh -c "touch /tmp/approved-test.txt && ls -l /tmp/approved-test.txt"This command should succeed because /tmp is backed by a controlled emptyDir volume.
Security Observation
Section titled “Security Observation”The application cannot modify the container image filesystem, but it can use explicitly approved temporary storage.
Root Filesystem │ └── Read-Only
Approved Temporary Directories │ └── Writable emptyDir volumesTask 12 — Create an Internal Service
Section titled “Task 12 — Create an Internal Service”Create:
04-service.yamlAdd:
apiVersion: v1kind: Servicemetadata: name: cloudnova-secure-web-service namespace: cloudnova-secure-app labels: app: cloudnova-secure-webspec: type: ClusterIP selector: app: cloudnova-secure-web ports: - name: http protocol: TCP port: 80 targetPort: httpStep 1 — Apply the Service
Section titled “Step 1 — Apply the Service”PowerShell
Section titled “PowerShell”kubectl apply -f .\04-service.yamlGit Bash
Section titled “Git Bash”kubectl apply -f 04-service.yamlStep 2 — Verify the Service
Section titled “Step 2 — Verify the Service”kubectl get service -n cloudnova-secure-appExpected Service type:
ClusterIPStep 3 — Inspect the Service
Section titled “Step 3 — Inspect the Service”kubectl describe service cloudnova-secure-web-service -n cloudnova-secure-appReview:
- Selector
- Cluster IP
- Port
- Target port
- Endpoints
Step 4 — Review Endpoints
Section titled “Step 4 — Review Endpoints”kubectl get endpoints -n cloudnova-secure-appThe endpoints should correspond to the application Pod IP addresses.
Task 13 — Test the Application Before Network Restrictions
Section titled “Task 13 — Test the Application Before Network Restrictions”Use local port forwarding to verify that the application is operational.
kubectl port-forward service/cloudnova-secure-web-service 8080:80 -n cloudnova-secure-appExpected output:
Forwarding from 127.0.0.1:8080 -> 80Open:
http://localhost:8080You should see the NGINX welcome page.
Stop port forwarding:
Ctrl + CTask 14 — Create a Default-Deny Network Policy
Section titled “Task 14 — Create a Default-Deny Network Policy”Create:
05-default-deny-network-policy.yamlAdd:
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: default-deny-all namespace: cloudnova-secure-appspec: podSelector: {} policyTypes: - Ingress - EgressUnderstanding Default Deny
Section titled “Understanding Default Deny”Before Default Deny
Any Pod │ └── May communicate with application Pods
After Default Deny
Any Pod │ └── Blocked unless explicitly allowedThe policy selects every Pod in the Namespace and provides no allow rules.
Step 1 — Apply the Policy
Section titled “Step 1 — Apply the Policy”PowerShell
Section titled “PowerShell”kubectl apply -f .\05-default-deny-network-policy.yamlGit Bash
Section titled “Git Bash”kubectl apply -f 05-default-deny-network-policy.yamlStep 2 — Verify the Policy
Section titled “Step 2 — Verify the Policy”kubectl get networkpolicy -n cloudnova-secure-appExpected result:
default-deny-allStep 3 — Inspect the Policy
Section titled “Step 3 — Inspect the Policy”kubectl describe networkpolicy default-deny-all -n cloudnova-secure-appImportant kind Networking Note
Section titled “Important kind Networking Note”Network Policy enforcement depends on the cluster’s Container Network Interface plugin.
Some local kind configurations use networking that does not enforce Kubernetes Network Policies.
If the traffic tests do not behave as expected, record:
NetworkPolicy resource created successfully, but enforcement requires a compatible CNI such as Calico or Cilium.The Kubernetes resource can still be created and inspected even when the current local CNI does not enforce it.
Task 15 — Create an Approved Ingress Policy
Section titled “Task 15 — Create an Approved Ingress Policy”Only Pods carrying the following label will be permitted to access the secure web application:
access=cloudnova-secure-webCreate:
06-allow-approved-client.yamlAdd:
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: allow-approved-web-client namespace: cloudnova-secure-appspec: podSelector: matchLabels: app: cloudnova-secure-web
policyTypes: - Ingress
ingress: - from: - podSelector: matchLabels: access: cloudnova-secure-web
ports: - protocol: TCP port: 8080Step 1 — Apply the Policy
Section titled “Step 1 — Apply the Policy”PowerShell
Section titled “PowerShell”kubectl apply -f .\06-allow-approved-client.yamlGit Bash
Section titled “Git Bash”kubectl apply -f 06-allow-approved-client.yamlStep 2 — Review Both Policies
Section titled “Step 2 — Review Both Policies”kubectl get networkpolicy -n cloudnova-secure-appExpected policies:
default-deny-allallow-approved-web-clientTask 16 — Create an Approved Test Client
Section titled “Task 16 — Create an Approved Test Client”Create:
07-approved-client.yamlAdd:
apiVersion: v1kind: Podmetadata: name: approved-client namespace: cloudnova-secure-app labels: access: cloudnova-secure-webspec: automountServiceAccountToken: false
securityContext: runAsNonRoot: true seccompProfile: type: RuntimeDefault
containers: - name: curl image: curlimages/curl:latest
command: - sh - -c - sleep 3600
securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsNonRoot: true capabilities: drop: - ALL
resources: requests: cpu: 25m memory: 16Mi limits: cpu: 100m memory: 32MiStep 1 — Apply the Approved Client
Section titled “Step 1 — Apply the Approved Client”PowerShell
Section titled “PowerShell”kubectl apply -f .\07-approved-client.yamlGit Bash
Section titled “Git Bash”kubectl apply -f 07-approved-client.yamlStep 2 — Wait for the Client
Section titled “Step 2 — Wait for the Client”kubectl wait --for=condition=Ready pod/approved-client -n cloudnova-secure-app --timeout=90sStep 3 — Test Approved Access
Section titled “Step 3 — Test Approved Access”kubectl exec approved-client -n cloudnova-secure-app -- curl --max-time 5 -I http://cloudnova-secure-web-serviceExpected response:
HTTP/1.1 200 OKThis demonstrates that the approved client can communicate with the application.
Task 17 — Perform a Negative Network Test
Section titled “Task 17 — Perform a Negative Network Test”Create an unapproved client without the required access label.
Create:
08-unapproved-client.yamlAdd:
apiVersion: v1kind: Podmetadata: name: unapproved-client namespace: cloudnova-secure-app labels: access: deniedspec: automountServiceAccountToken: false
securityContext: runAsNonRoot: true seccompProfile: type: RuntimeDefault
containers: - name: curl image: curlimages/curl:latest
command: - sh - -c - sleep 3600
securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsNonRoot: true capabilities: drop: - ALL
resources: requests: cpu: 25m memory: 16Mi limits: cpu: 100m memory: 32MiStep 1 — Apply the Unapproved Client
Section titled “Step 1 — Apply the Unapproved Client”PowerShell
Section titled “PowerShell”kubectl apply -f .\08-unapproved-client.yamlGit Bash
Section titled “Git Bash”kubectl apply -f 08-unapproved-client.yamlStep 2 — Wait for the Pod
Section titled “Step 2 — Wait for the Pod”kubectl wait --for=condition=Ready pod/unapproved-client -n cloudnova-secure-app --timeout=90sStep 3 — Attempt Unapproved Access
Section titled “Step 3 — Attempt Unapproved Access”kubectl exec unapproved-client -n cloudnova-secure-app -- curl --max-time 5 -I http://cloudnova-secure-web-serviceWhen the CNI enforces Network Policies, the request should time out or fail.
Expected behaviour:
Connection blockedSecurity Observation
Section titled “Security Observation”Approved ClientLabel: access=cloudnova-secure-web │ └── Allowed
Unapproved ClientLabel: access=denied │ └── BlockedIf both clients can connect, document that the current kind CNI does not enforce Network Policies and recommend installing a policy-capable CNI.
Task 18 — Validate Resource Controls
Section titled “Task 18 — Validate Resource Controls”Step 1 — Review Deployment Resources
Section titled “Step 1 — Review Deployment Resources”kubectl describe deployment cloudnova-secure-web -n cloudnova-secure-appLocate:
RequestsLimitsStep 2 — Extract Resource Configuration
Section titled “Step 2 — Extract Resource Configuration”kubectl get deployment cloudnova-secure-web -n cloudnova-secure-app -o jsonpath="{.spec.template.spec.containers[0].resources}"Confirm that CPU, memory and ephemeral storage controls are configured.
Security Importance
Section titled “Security Importance”Without limits, a compromised or malfunctioning container could consume excessive resources.
Possible consequences include:
- Node resource exhaustion
- Application outages
- Denial of service
- Pod eviction
- Reduced cluster stability
Task 19 — Validate Health Probes
Section titled “Task 19 — Validate Health Probes”Step 1 — Inspect Probe Configuration
Section titled “Step 1 — Inspect Probe Configuration”kubectl describe pod <POD-NAME> -n cloudnova-secure-appLocate:
LivenessReadinessStep 2 — Review Pod Readiness
Section titled “Step 2 — Review Pod Readiness”kubectl get pods -n cloudnova-secure-appThe application Pods should show:
READY: 1/1Step 3 — Review Pod Events
Section titled “Step 3 — Review Pod Events”kubectl get events -n cloudnova-secure-app --sort-by=.metadata.creationTimestampLook for events related to:
- Scheduling
- Image pulling
- Container startup
- Readiness
- Liveness
- Network Policy creation
Task 20 — Test Application Self-Healing
Section titled “Task 20 — Test Application Self-Healing”Step 1 — List Application Pods
Section titled “Step 1 — List Application Pods”kubectl get pods -l app=cloudnova-secure-web -n cloudnova-secure-appStep 2 — Delete One Application Pod
Section titled “Step 2 — Delete One Application Pod”kubectl delete pod <APPLICATION-POD-NAME> -n cloudnova-secure-appStep 3 — Watch Recovery
Section titled “Step 3 — Watch Recovery”kubectl get pods -l app=cloudnova-secure-web -n cloudnova-secure-app --watchObserve Kubernetes creating a replacement Pod.
Press:
Ctrl + Cafter the new Pod is ready.
Security and Availability Observation
Section titled “Security and Availability Observation”Security controls should not remove Kubernetes resilience.
The replacement Pod should receive the same:
- Service Account
- Security context
- Resource controls
- Labels
- Health probes
- Network restrictions
This is because the Deployment’s Pod template defines the required state.
Task 21 — Review Application Logs
Section titled “Task 21 — Review Application Logs”Step 1 — Read Logs from the Deployment
Section titled “Step 1 — Read Logs from the Deployment”kubectl logs deployment/cloudnova-secure-web -n cloudnova-secure-appStep 2 — Read Logs from One Pod
Section titled “Step 2 — Read Logs from One Pod”kubectl logs <POD-NAME> -n cloudnova-secure-appStep 3 — Review Previous Logs When Applicable
Section titled “Step 3 — Review Previous Logs When Applicable”When a container restarts, previous logs may be available using:
kubectl logs <POD-NAME> -n cloudnova-secure-app --previousLogs are important for:
- Troubleshooting
- Incident investigation
- Application monitoring
- Detecting unusual requests
- Supporting compliance requirements
Task 22 — Review Workload Permissions
Section titled “Task 22 — Review Workload Permissions”The secure application does not require Kubernetes API permissions.
Step 1 — Check Service Account Permissions
Section titled “Step 1 — Check Service Account Permissions”kubectl auth can-i --list --as=system:serviceaccount:cloudnova-secure-app:cloudnova-web-sa -n cloudnova-secure-appReview the available permissions.
The Service Account has not been assigned a Role or RoleBinding in this lab.
Step 2 — Test Secret Access
Section titled “Step 2 — Test Secret Access”kubectl auth can-i get secrets --as=system:serviceaccount:cloudnova-secure-app:cloudnova-web-sa -n cloudnova-secure-appExpected result:
noStep 3 — Test Pod Creation
Section titled “Step 3 — Test Pod Creation”kubectl auth can-i create pods --as=system:serviceaccount:cloudnova-secure-app:cloudnova-web-sa -n cloudnova-secure-appExpected result:
noSecurity Observation
Section titled “Security Observation”The Service Account follows least privilege because it has not been granted unnecessary access to Kubernetes resources.
Task 23 — Perform the Security Validation
Section titled “Task 23 — Perform the Security Validation”Complete the following validation table.
| Security Requirement | Validation Command | Expected Result |
|---|---|---|
| Dedicated Namespace | kubectl get ns cloudnova-secure-app |
Namespace exists |
| Restricted Pod Security | kubectl get ns cloudnova-secure-app --show-labels |
Restricted labels present |
| Dedicated Service Account | Inspect Pod specification | cloudnova-web-sa |
| Token automount disabled | JSONPath query | false |
| Non-root execution | kubectl exec ... -- id |
UID is not 0 |
| Privilege escalation disabled | JSONPath query | false |
| Read-only root filesystem | Attempt touch /security-test.txt |
Command fails |
| Capabilities dropped | JSONPath query | ["ALL"] |
| Seccomp enabled | JSONPath query | RuntimeDefault |
| Resource limits | Inspect Deployment | Limits present |
| Readiness probe | Describe Pod | Probe present |
| Liveness probe | Describe Pod | Probe present |
| Internal Service | Get Service | ClusterIP |
| Default-deny policy | Get Network Policies | Policy exists |
| Approved client | Curl application | Request succeeds |
| Unapproved client | Curl application | Request blocked when CNI supports enforcement |
| Secret access | kubectl auth can-i |
no |
Task 24 — Collect Lab Evidence
Section titled “Task 24 — Collect Lab Evidence”Capture screenshots or command output for the following evidence.
Evidence 01 — Namespace Security Labels
Section titled “Evidence 01 — Namespace Security Labels”kubectl get namespace cloudnova-secure-app --show-labelsEvidence 02 — Rejected Insecure Pod
Section titled “Evidence 02 — Rejected Insecure Pod”Capture the error returned by:
kubectl apply -f insecure-test-pod.yamlEvidence 03 — Secure Deployment
Section titled “Evidence 03 — Secure Deployment”kubectl get deployment cloudnova-secure-web -n cloudnova-secure-appEvidence 04 — Running Application Pods
Section titled “Evidence 04 — Running Application Pods”kubectl get pods -l app=cloudnova-secure-web -n cloudnova-secure-app -o wideEvidence 05 — Dedicated Service Account
Section titled “Evidence 05 — Dedicated Service Account”kubectl get serviceaccount cloudnova-web-sa -n cloudnova-secure-appEvidence 06 — Non-Root User
Section titled “Evidence 06 — Non-Root User”kubectl exec -n cloudnova-secure-app <POD-NAME> -- idEvidence 07 — Read-Only Filesystem
Section titled “Evidence 07 — Read-Only Filesystem”Capture the failed output from:
kubectl exec -n cloudnova-secure-app <POD-NAME> -- sh -c "touch /security-test.txt"Evidence 08 — Security Context
Section titled “Evidence 08 — Security Context”kubectl get deployment cloudnova-secure-web -n cloudnova-secure-app -o yamlEvidence 09 — Resource Controls
Section titled “Evidence 09 — Resource Controls”kubectl describe deployment cloudnova-secure-web -n cloudnova-secure-appEvidence 10 — Service
Section titled “Evidence 10 — Service”kubectl get service cloudnova-secure-web-service -n cloudnova-secure-appEvidence 11 — Network Policies
Section titled “Evidence 11 — Network Policies”kubectl get networkpolicy -n cloudnova-secure-appEvidence 12 — Approved Client Test
Section titled “Evidence 12 — Approved Client Test”Capture the successful HTTP response.
Evidence 13 — Unapproved Client Test
Section titled “Evidence 13 — Unapproved Client Test”Capture the failed request or document the local CNI limitation.
Evidence 14 — Least Privilege Test
Section titled “Evidence 14 — Least Privilege Test”kubectl auth can-i get secrets --as=system:serviceaccount:cloudnova-secure-app:cloudnova-web-sa -n cloudnova-secure-appTask 25 — Create the Security Assessment Report
Section titled “Task 25 — Create the Security Assessment Report”Create:
secure-application-assessment.mdUse:
# CloudNova Secure Application Assessment
## Assessment Information
- Lab ID: K8S-FND-LAB-02- Application:- Namespace:- Cluster:- Assessment date:- Assessor:
## Application Architecture
Describe the Deployment, Pods, Service, Service Account and Network Policies.
## Implemented Security Controls
### Namespace Security
- Dedicated Namespace:- Pod Security Standard:- Environment labels:- Ownership labels:
### Identity Security
- Dedicated Service Account:- Service Account token automount:- Kubernetes API permissions:
### Container Security
- Non-root execution:- Privilege escalation:- Linux capabilities:- Root filesystem:- Seccomp profile:
### Resource Security
- CPU requests:- CPU limits:- Memory requests:- Memory limits:- Ephemeral storage limits:
### Availability Controls
- Number of replicas:- Readiness probe:- Liveness probe:- Rolling update strategy:- Self-healing result:
### Network Security
- Service type:- Default-deny policy:- Approved client policy:- Positive connectivity result:- Negative connectivity result:- CNI enforcement status:
## Security Gaps
Identify controls that remain outside the scope of this lab.
Examples:
- Container image signature verification- Image vulnerability scanning- Runtime threat detection- TLS encryption- External secrets management- Centralised logging- Admission policy management- Production-grade RBAC- Network Policy monitoring- Supply-chain security
## Risk Assessment
### Risk 1
- Finding:- Impact:- Likelihood:- Severity:- Recommendation:
### Risk 2
- Finding:- Impact:- Likelihood:- Severity:- Recommendation:
### Risk 3
- Finding:- Impact:- Likelihood:- Severity:- Recommendation:
## Overall Assessment
State whether the deployment is suitable for:
- Local learning:- Development testing:- Production:
## Recommended Next Actions
1.2.3.4.5.Suggested Conclusion
Section titled “Suggested Conclusion”A suitable conclusion is:
The application demonstrates a significantly improved Kubernetes security baseline and is suitable for local learning and controlled development testing.
It should not yet be approved for production because image assurance, TLS, secrets management, centralised monitoring, runtime protection and formal production governance have not been implemented.Task 26 — Review All Lab Resources
Section titled “Task 26 — Review All Lab Resources”Run:
kubectl get all -n cloudnova-secure-appReview:
- Deployment
- ReplicaSet
- Pods
- Service
Run:
kubectl get serviceaccounts,networkpolicies -n cloudnova-secure-appReview:
- Dedicated Service Account
- Default Service Account
- Default-deny policy
- Approved client policy
Task 27 — Clean Up the Lab
Section titled “Task 27 — Clean Up the Lab”Delete resources in reverse order.
Step 1 — Delete the Test Clients
Section titled “Step 1 — Delete the Test Clients”PowerShell
Section titled “PowerShell”kubectl delete -f .\08-unapproved-client.yamlkubectl delete -f .\07-approved-client.yamlGit Bash
Section titled “Git Bash”kubectl delete -f 08-unapproved-client.yamlkubectl delete -f 07-approved-client.yamlStep 2 — Delete the Network Policies
Section titled “Step 2 — Delete the Network Policies”PowerShell
Section titled “PowerShell”kubectl delete -f .\06-allow-approved-client.yamlkubectl delete -f .\05-default-deny-network-policy.yamlGit Bash
Section titled “Git Bash”kubectl delete -f 06-allow-approved-client.yamlkubectl delete -f 05-default-deny-network-policy.yamlStep 3 — Delete the Service
Section titled “Step 3 — Delete the Service”PowerShell
Section titled “PowerShell”kubectl delete -f .\04-service.yamlGit Bash
Section titled “Git Bash”kubectl delete -f 04-service.yamlStep 4 — Delete the Deployment
Section titled “Step 4 — Delete the Deployment”PowerShell
Section titled “PowerShell”kubectl delete -f .\03-secure-deployment.yamlGit Bash
Section titled “Git Bash”kubectl delete -f 03-secure-deployment.yamlStep 5 — Delete the Service Account
Section titled “Step 5 — Delete the Service Account”PowerShell
Section titled “PowerShell”kubectl delete -f .\02-service-account.yamlGit Bash
Section titled “Git Bash”kubectl delete -f 02-service-account.yamlStep 6 — Delete the Namespace
Section titled “Step 6 — Delete the Namespace”PowerShell
Section titled “PowerShell”kubectl delete -f .\01-namespace.yamlGit Bash
Section titled “Git Bash”kubectl delete -f 01-namespace.yamlStep 7 — Confirm Cleanup
Section titled “Step 7 — Confirm Cleanup”kubectl get namespace cloudnova-secure-appExpected result:
NotFoundOptional Cluster Cleanup
Section titled “Optional Cluster Cleanup”Keep the cluster for the next lab.
When the cluster is no longer required, delete it using:
kind delete cluster --name cloudnova-security-labTroubleshooting Guide
Section titled “Troubleshooting Guide”Issue 01 — Deployment Is Rejected by Pod Security Admission
Section titled “Issue 01 — Deployment Is Rejected by Pod Security Admission”Review the error carefully.
Common missing controls include:
allowPrivilegeEscalation must be falsecapabilities must drop ALLrunAsNonRoot must be trueseccompProfile must be RuntimeDefaultConfirm that the Deployment security contexts match the provided manifest.
Issue 02 — Pod Shows ImagePullBackOff
Section titled “Issue 02 — Pod Shows ImagePullBackOff”Check:
kubectl describe pod <POD-NAME> -n cloudnova-secure-appPossible causes:
- No internet access
- Container registry unavailable
- Incorrect image name
- Registry rate limiting
- Proxy configuration problems
Issue 03 — Pod Shows CrashLoopBackOff
Section titled “Issue 03 — Pod Shows CrashLoopBackOff”Check:
kubectl logs <POD-NAME> -n cloudnova-secure-appThen:
kubectl describe pod <POD-NAME> -n cloudnova-secure-appPossible causes include:
- The application attempted to write to a read-only path.
- A required writable volume is missing.
- The container is incompatible with non-root execution.
- The application cannot bind to its configured port.
Issue 04 — Application Does Not Respond
Section titled “Issue 04 — Application Does Not Respond”Check:
kubectl get pods -n cloudnova-secure-appkubectl get service -n cloudnova-secure-appkubectl get endpoints -n cloudnova-secure-appkubectl describe service cloudnova-secure-web-service -n cloudnova-secure-appConfirm:
- Pods are ready
- Service selector matches Pod labels
- Service endpoints exist
- Target port matches container port
8080
Issue 05 — Approved Client Cannot Connect
Section titled “Issue 05 — Approved Client Cannot Connect”Check the following:
kubectl get pod approved-client -n cloudnova-secure-app --show-labelskubectl get networkpolicy -n cloudnova-secure-appkubectl describe networkpolicy allow-approved-web-client -n cloudnova-secure-appConfirm that the client contains:
access=cloudnova-secure-webIssue 06 — Unapproved Client Can Still Connect
Section titled “Issue 06 — Unapproved Client Can Still Connect”The local cluster CNI may not enforce Network Policies.
Record this finding and recommend using:
- Calico
- Cilium
- Another Network Policy-capable CNI
The policy resource may exist even when enforcement is unavailable.
Issue 07 — Port 8080 Is Already Used
Section titled “Issue 07 — Port 8080 Is Already Used”Use another local port:
kubectl port-forward service/cloudnova-secure-web-service 8081:80 -n cloudnova-secure-appOpen:
http://localhost:8081Issue 08 — The kubectl exec Command Fails
Section titled “Issue 08 — The kubectl exec Command Fails”Confirm the Pod name:
kubectl get pods -n cloudnova-secure-appEnsure the selected Pod is in the Running state.
Issue 09 — Test Client Is Rejected
Section titled “Issue 09 — Test Client Is Rejected”Because the Namespace enforces the restricted Pod Security Standard, test Pods must also include:
- Non-root execution
- Seccomp
- Dropped capabilities
- Privilege escalation disabled
Use the complete client manifests provided in this lab.
Validation Checklist
Section titled “Validation Checklist”Confirm that you completed the following:
- Verified the kind cluster
- Created the Lab 02 workspace
- Created a dedicated Namespace
- Applied restricted Pod Security labels
- Created a dedicated Service Account
- Disabled Service Account token automount
- Created the secure Deployment
- Validated the manifest using dry run
- Deployed two application replicas
- Confirmed the application runs as non-root
- Disabled privilege escalation
- Dropped all Linux capabilities
- Applied the RuntimeDefault seccomp profile
- Enabled the read-only root filesystem
- Verified controlled writable directories
- Configured CPU and memory controls
- Configured ephemeral storage controls
- Added readiness and liveness probes
- Created a ClusterIP Service
- Tested the application
- Applied a default-deny Network Policy
- Applied an approved-client Network Policy
- Tested approved communication
- Tested unapproved communication
- Validated least-privilege Service Account access
- Tested application self-healing
- Collected security evidence
- Completed the assessment report
- Cleaned up the lab resources
Knowledge Check
Section titled “Knowledge Check”Question 1
Section titled “Question 1”Why should an application use a dedicated Service Account?
- A. To increase container storage
- B. To provide a separate workload identity
- C. To replace the Kubernetes API Server
- D. To expose the application publicly
Answer: B
Question 2
Section titled “Question 2”Why was automatic Service Account token mounting disabled?
- A. The application does not need Kubernetes API credentials.
- B. Kubernetes does not support Service Accounts.
- C. Tokens prevent Pods from starting.
- D. Tokens increase CPU consumption.
Answer: A
Question 3
Section titled “Question 3”What does runAsNonRoot: true accomplish?
- A. It requires the container to run as UID 0.
- B. It prevents the container from running as the root user.
- C. It encrypts application traffic.
- D. It creates a Network Policy.
Answer: B
Question 4
Section titled “Question 4”What is the purpose of allowPrivilegeEscalation: false?
- A. It prevents a process from gaining additional privileges.
- B. It disables application logging.
- C. It creates a new Namespace.
- D. It increases container memory.
Answer: A
Question 5
Section titled “Question 5”Why were all Linux capabilities dropped?
- A. To remove unnecessary operating-system privileges
- B. To create additional Worker Nodes
- C. To increase network speed
- D. To make the Service publicly accessible
Answer: A
Question 6
Section titled “Question 6”What is the security benefit of a read-only root filesystem?
- A. It prevents all network traffic.
- B. It reduces unauthorised modification of the container filesystem.
- C. It encrypts Kubernetes Secrets.
- D. It replaces the container image.
Answer: B
Question 7
Section titled “Question 7”What does a readiness probe determine?
- A. Whether a Pod should receive application traffic
- B. Whether the cluster should be deleted
- C. Whether a user can create a Namespace
- D. Whether a container image is signed
Answer: A
Question 8
Section titled “Question 8”What does a liveness probe determine?
- A. Whether a container should be restarted
- B. Whether a user has RBAC permissions
- C. Whether storage is encrypted
- D. Whether the Service should become public
Answer: A
Question 9
Section titled “Question 9”What is the purpose of a default-deny Network Policy?
- A. Allow every connection automatically
- B. Block traffic unless it is explicitly allowed
- C. Delete all application Pods
- D. Prevent resource limits
Answer: B
Question 10
Section titled “Question 10”Why was the insecure test Pod rejected?
- A. The cluster had no Worker Node.
- B. The Namespace enforced the restricted Pod Security Standard.
- C. The Pod used a container image.
- D. The Service Account was deleted.
Answer: B
Skills Developed
Section titled “Skills Developed”By completing this lab, you practised:
- Secure Namespace configuration
- Pod Security Admission
- Service Account management
- Workload identity separation
- Non-root container execution
- Linux capability management
- Seccomp configuration
- Read-only filesystem implementation
- Temporary volume configuration
- CPU and memory governance
- Health probe configuration
- Internal Service deployment
- Network Policy creation
- Positive connectivity testing
- Negative connectivity testing
- Kubernetes permission validation
- Workload inspection
- Application self-healing
- Evidence collection
- Security assessment documentation
Lab Summary
Section titled “Lab Summary”In this lab, you deployed the first security-hardened Kubernetes application for CloudNova Technologies.
The application was protected using:
- A dedicated Namespace
- Restricted Pod Security Admission
- A dedicated Service Account
- Disabled token automounting
- Non-root execution
- Disabled privilege escalation
- Removed Linux capabilities
- A read-only root filesystem
- A RuntimeDefault seccomp profile
- Controlled temporary storage
- Resource requests and limits
- Readiness and liveness probes
- An internal ClusterIP Service
- Default-deny network controls
- Approved-client communication rules
You also attempted to deploy an insecure Pod and confirmed that Kubernetes admission controls could prevent non-compliant workloads from entering the Namespace.
This lab demonstrated an important enterprise security principle:
Secure Kubernetes workloads should not depend only on developer awareness.
Security requirements should be defined in configuration, enforced by the platform, validated continuously, and supported by evidence.What’s Next?
Section titled “What’s Next?”In the next lab, you will inspect the Kubernetes architecture in greater depth, identify Control Plane and Worker Node components, trace workload execution, review cluster networking, and document the Kubernetes attack surface.
➡️ Next Lab: Lab 03 — Explore Kubernetes Architecture and Cluster Components