Skip to content

Lab 02 — Deploy Your First Secure Application

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

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.

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

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 Client

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

Before beginning this lab, ensure that you have:

  • Completed Lab 01
  • Docker Desktop installed and running
  • kubectl installed
  • kind installed
  • Visual Studio Code installed
  • PowerShell or Git Bash available
  • A running kind Kubernetes cluster
  • Basic understanding of Pods, Deployments, Services and Namespaces
Terminal window
docker version

Both the Docker client and server should respond successfully.

Terminal window
kind get clusters

Expected cluster:

cloudnova-security-lab

Step 3 — Check the Current kubectl Context

Section titled “Step 3 — Check the Current kubectl Context”
Terminal window
kubectl config current-context

Expected context:

kind-cloudnova-security-lab
Terminal window
kubectl get nodes -o wide

Both nodes should show:

STATUS: Ready

Expected architecture:

cloudnova-security-lab-control-plane
cloudnova-security-lab-worker

Lab 01 included an optional cluster cleanup.

When the cluster no longer exists, recreate it using the kind-cluster.yaml file from Lab 01.

Terminal window
Set-Location C:\GoHackersCloud-Labs\kubernetes\lab-01
kind create cluster --config .\kind-cluster.yaml
Terminal window
cd /c/GoHackersCloud-Labs/kubernetes/lab-01
kind create cluster --config kind-cluster.yaml

Verify:

Terminal window
kubectl get nodes

Create a dedicated directory for the secure application files.

Terminal window
New-Item -ItemType Directory -Path C:\GoHackersCloud-Labs\kubernetes\lab-02 -Force
Set-Location C:\GoHackersCloud-Labs\kubernetes\lab-02
Terminal window
mkdir -p /c/GoHackersCloud-Labs/kubernetes/lab-02
cd /c/GoHackersCloud-Labs/kubernetes/lab-02

Verify the current directory.

Terminal window
Get-Location
Terminal window
pwd

Create a file named:

01-namespace.yaml

Add:

apiVersion: v1
kind: Namespace
metadata:
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: latest

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 user

The restricted profile is designed for strongly hardened workloads.

Terminal window
kubectl apply -f .\01-namespace.yaml
Terminal window
kubectl apply -f 01-namespace.yaml

Expected result:

namespace/cloudnova-secure-app created
Terminal window
kubectl get namespace cloudnova-secure-app
Terminal window
kubectl get namespace cloudnova-secure-app --show-labels

Confirm that the output includes:

pod-security.kubernetes.io/enforce=restricted
security-tier=restricted

Task 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.yaml

Add:

apiVersion: v1
kind: ServiceAccount
metadata:
name: cloudnova-web-sa
namespace: cloudnova-secure-app
labels:
app: cloudnova-secure-web
owner: cloud-security-team
automountServiceAccountToken: false

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: false

prevents Kubernetes from automatically mounting an API token inside the Pod.

This reduces the impact of a container compromise.

Terminal window
kubectl apply -f .\02-service-account.yaml
Terminal window
kubectl apply -f 02-service-account.yaml
Terminal window
kubectl get serviceaccount -n cloudnova-secure-app

Expected Service Accounts:

cloudnova-web-sa
default
Terminal window
kubectl describe serviceaccount cloudnova-web-sa -n cloudnova-secure-app

Create:

03-secure-deployment.yaml

Add:

apiVersion: apps/v1
kind: Deployment
metadata:
name: cloudnova-secure-web
namespace: cloudnova-secure-app
labels:
app: cloudnova-secure-web
environment: development
security-tier: restricted
spec:
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: 20Mi
serviceAccountName: cloudnova-web-sa

The workload uses its own identity instead of relying on the default Service Account.

automountServiceAccountToken: false

The application does not receive unnecessary Kubernetes API credentials.

runAsNonRoot: true

Kubernetes rejects the workload when the container attempts to run as the root user.

seccompProfile:
type: RuntimeDefault

The container runtime applies its default seccomp profile to restrict dangerous Linux system calls.

allowPrivilegeEscalation: false

Processes inside the container cannot gain more privileges than their parent process.

capabilities:
drop:
- ALL

All optional Linux capabilities are removed from the container.

readOnlyRootFilesystem: true

The container cannot modify its root filesystem.

Temporary writable locations are provided using controlled emptyDir volumes.

resources:
requests:
limits:

Requests assist scheduling, while limits restrict maximum resource consumption.

Readiness Probe
└── Determines when the Pod can receive traffic
Liveness Probe
└── Determines whether Kubernetes should restart the container
maxUnavailable: 0
maxSurge: 1

This 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.

Terminal window
kubectl apply --dry-run=client -f .\03-secure-deployment.yaml
Terminal window
kubectl apply --dry-run=client -f 03-secure-deployment.yaml

Expected result:

deployment.apps/cloudnova-secure-web created (dry run)

The dry run confirms that the YAML structure is valid.

Terminal window
kubectl apply -f .\03-secure-deployment.yaml
Terminal window
kubectl apply -f 03-secure-deployment.yaml

Expected result:

deployment.apps/cloudnova-secure-web created
Terminal window
kubectl rollout status deployment/cloudnova-secure-web -n cloudnova-secure-app

Expected result:

deployment "cloudnova-secure-web" successfully rolled out
Terminal window
kubectl get deployments -n cloudnova-secure-app

Expected result:

NAME READY UP-TO-DATE AVAILABLE
cloudnova-secure-web 2/2 2 2
Terminal window
kubectl get pods -n cloudnova-secure-app

Expected status:

Running
Terminal window
kubectl get pods -n cloudnova-secure-app -o wide

Review:

  • 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.yaml

Add:

apiVersion: v1
kind: Pod
metadata:
name: insecure-test-pod
namespace: cloudnova-secure-app
spec:
containers:
- name: insecure-container
image: nginx:stable-alpine

Step 1 — Attempt to Create the Insecure Pod

Section titled “Step 1 — Attempt to Create the Insecure Pod”
Terminal window
kubectl apply -f .\insecure-test-pod.yaml
Terminal window
kubectl apply -f insecure-test-pod.yaml

The 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

The admission control operated before the Pod was created.

Developer Request
Kubernetes API Server
Pod Security Admission
├── Compliant → Allowed
└── Non-compliant → Rejected

This 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”
Terminal window
kubectl get pod insecure-test-pod -n cloudnova-secure-app

Expected result:

NotFound

Keep the rejected command output as evidence.

Task 09 — Inspect the Running Security Context

Section titled “Task 09 — Inspect the Running Security Context”
Terminal window
kubectl get deployment cloudnova-secure-web -n cloudnova-secure-app -o yaml

Locate:

securityContext:

List the Pods:

Terminal window
kubectl get pods -n cloudnova-secure-app

Copy one Pod name and run:

Terminal window
kubectl describe pod <POD-NAME> -n cloudnova-secure-app

Review:

  • Service Account
  • Container image
  • Container port
  • Resource requests
  • Resource limits
  • Readiness probe
  • Liveness probe
  • Mounted volumes
  • Pod events
Terminal window
kubectl get pod <POD-NAME> -n cloudnova-secure-app -o jsonpath="{.spec.serviceAccountName}"

Expected result:

cloudnova-web-sa

Step 4 — Confirm Token Automount Is Disabled

Section titled “Step 4 — Confirm Token Automount Is Disabled”
Terminal window
kubectl get pod <POD-NAME> -n cloudnova-secure-app -o jsonpath="{.spec.automountServiceAccountToken}"

Expected result:

false

Step 5 — Confirm the Container Cannot Run as Root

Section titled “Step 5 — Confirm the Container Cannot Run as Root”
Terminal window
kubectl get pod <POD-NAME> -n cloudnova-secure-app -o jsonpath="{.spec.securityContext.runAsNonRoot}"

Expected result:

true

Step 6 — Confirm Privilege Escalation Is Disabled

Section titled “Step 6 — Confirm Privilege Escalation Is Disabled”
Terminal window
kubectl get pod <POD-NAME> -n cloudnova-secure-app -o jsonpath="{.spec.containers[0].securityContext.allowPrivilegeEscalation}"

Expected result:

false

Step 7 — Confirm the Root Filesystem Is Read-Only

Section titled “Step 7 — Confirm the Root Filesystem Is Read-Only”
Terminal window
kubectl get pod <POD-NAME> -n cloudnova-secure-app -o jsonpath="{.spec.containers[0].securityContext.readOnlyRootFilesystem}"

Expected result:

true
Terminal window
kubectl get pod <POD-NAME> -n cloudnova-secure-app -o jsonpath="{.spec.securityContext.seccompProfile.type}"

Expected result:

RuntimeDefault

Step 9 — Confirm Linux Capabilities Are Dropped

Section titled “Step 9 — Confirm Linux Capabilities Are Dropped”
Terminal window
kubectl get pod <POD-NAME> -n cloudnova-secure-app -o jsonpath="{.spec.containers[0].securityContext.capabilities.drop}"

Expected result:

["ALL"]

Execute the id command inside one application Pod.

Terminal window
kubectl exec -n cloudnova-secure-app <POD-NAME> -- id

Expected output should show a non-root user.

Example:

uid=101(...) gid=101(...) groups=...

The UID should not be:

0

UID 0 represents the root user.

Root User
UID 0
High Privilege
Non-Root User
UID greater than 0
Reduced Privilege

Running 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.

Terminal window
kubectl exec -n cloudnova-secure-app <POD-NAME> -- sh -c "touch /security-test.txt"

Expected result:

Read-only file system

The command should fail.

Run:

Terminal window
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.

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 volumes

Create:

04-service.yaml

Add:

apiVersion: v1
kind: Service
metadata:
name: cloudnova-secure-web-service
namespace: cloudnova-secure-app
labels:
app: cloudnova-secure-web
spec:
type: ClusterIP
selector:
app: cloudnova-secure-web
ports:
- name: http
protocol: TCP
port: 80
targetPort: http
Terminal window
kubectl apply -f .\04-service.yaml
Terminal window
kubectl apply -f 04-service.yaml
Terminal window
kubectl get service -n cloudnova-secure-app

Expected Service type:

ClusterIP
Terminal window
kubectl describe service cloudnova-secure-web-service -n cloudnova-secure-app

Review:

  • Selector
  • Cluster IP
  • Port
  • Target port
  • Endpoints
Terminal window
kubectl get endpoints -n cloudnova-secure-app

The 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.

Terminal window
kubectl port-forward service/cloudnova-secure-web-service 8080:80 -n cloudnova-secure-app

Expected output:

Forwarding from 127.0.0.1:8080 -> 80

Open:

http://localhost:8080

You should see the NGINX welcome page.

Stop port forwarding:

Ctrl + C

Task 14 — Create a Default-Deny Network Policy

Section titled “Task 14 — Create a Default-Deny Network Policy”

Create:

05-default-deny-network-policy.yaml

Add:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: cloudnova-secure-app
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Before Default Deny
Any Pod
└── May communicate with application Pods
After Default Deny
Any Pod
└── Blocked unless explicitly allowed

The policy selects every Pod in the Namespace and provides no allow rules.

Terminal window
kubectl apply -f .\05-default-deny-network-policy.yaml
Terminal window
kubectl apply -f 05-default-deny-network-policy.yaml
Terminal window
kubectl get networkpolicy -n cloudnova-secure-app

Expected result:

default-deny-all
Terminal window
kubectl describe networkpolicy default-deny-all -n cloudnova-secure-app

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-web

Create:

06-allow-approved-client.yaml

Add:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-approved-web-client
namespace: cloudnova-secure-app
spec:
podSelector:
matchLabels:
app: cloudnova-secure-web
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
access: cloudnova-secure-web
ports:
- protocol: TCP
port: 8080
Terminal window
kubectl apply -f .\06-allow-approved-client.yaml
Terminal window
kubectl apply -f 06-allow-approved-client.yaml
Terminal window
kubectl get networkpolicy -n cloudnova-secure-app

Expected policies:

default-deny-all
allow-approved-web-client

Task 16 — Create an Approved Test Client

Section titled “Task 16 — Create an Approved Test Client”

Create:

07-approved-client.yaml

Add:

apiVersion: v1
kind: Pod
metadata:
name: approved-client
namespace: cloudnova-secure-app
labels:
access: cloudnova-secure-web
spec:
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: 32Mi
Terminal window
kubectl apply -f .\07-approved-client.yaml
Terminal window
kubectl apply -f 07-approved-client.yaml
Terminal window
kubectl wait --for=condition=Ready pod/approved-client -n cloudnova-secure-app --timeout=90s
Terminal window
kubectl exec approved-client -n cloudnova-secure-app -- curl --max-time 5 -I http://cloudnova-secure-web-service

Expected response:

HTTP/1.1 200 OK

This 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.yaml

Add:

apiVersion: v1
kind: Pod
metadata:
name: unapproved-client
namespace: cloudnova-secure-app
labels:
access: denied
spec:
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: 32Mi
Terminal window
kubectl apply -f .\08-unapproved-client.yaml
Terminal window
kubectl apply -f 08-unapproved-client.yaml
Terminal window
kubectl wait --for=condition=Ready pod/unapproved-client -n cloudnova-secure-app --timeout=90s
Terminal window
kubectl exec unapproved-client -n cloudnova-secure-app -- curl --max-time 5 -I http://cloudnova-secure-web-service

When the CNI enforces Network Policies, the request should time out or fail.

Expected behaviour:

Connection blocked
Approved Client
Label: access=cloudnova-secure-web
└── Allowed
Unapproved Client
Label: access=denied
└── Blocked

If both clients can connect, document that the current kind CNI does not enforce Network Policies and recommend installing a policy-capable CNI.

Terminal window
kubectl describe deployment cloudnova-secure-web -n cloudnova-secure-app

Locate:

Requests
Limits
Terminal window
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.

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
Terminal window
kubectl describe pod <POD-NAME> -n cloudnova-secure-app

Locate:

Liveness
Readiness
Terminal window
kubectl get pods -n cloudnova-secure-app

The application Pods should show:

READY: 1/1
Terminal window
kubectl get events -n cloudnova-secure-app --sort-by=.metadata.creationTimestamp

Look for events related to:

  • Scheduling
  • Image pulling
  • Container startup
  • Readiness
  • Liveness
  • Network Policy creation
Terminal window
kubectl get pods -l app=cloudnova-secure-web -n cloudnova-secure-app
Terminal window
kubectl delete pod <APPLICATION-POD-NAME> -n cloudnova-secure-app
Terminal window
kubectl get pods -l app=cloudnova-secure-web -n cloudnova-secure-app --watch

Observe Kubernetes creating a replacement Pod.

Press:

Ctrl + C

after the new Pod is ready.

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.

Terminal window
kubectl logs deployment/cloudnova-secure-web -n cloudnova-secure-app
Terminal window
kubectl logs <POD-NAME> -n cloudnova-secure-app

Step 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:

Terminal window
kubectl logs <POD-NAME> -n cloudnova-secure-app --previous

Logs are important for:

  • Troubleshooting
  • Incident investigation
  • Application monitoring
  • Detecting unusual requests
  • Supporting compliance requirements

The secure application does not require Kubernetes API permissions.

Step 1 — Check Service Account Permissions

Section titled “Step 1 — Check Service Account Permissions”
Terminal window
kubectl auth can-i --list --as=system:serviceaccount:cloudnova-secure-app:cloudnova-web-sa -n cloudnova-secure-app

Review the available permissions.

The Service Account has not been assigned a Role or RoleBinding in this lab.

Terminal window
kubectl auth can-i get secrets --as=system:serviceaccount:cloudnova-secure-app:cloudnova-web-sa -n cloudnova-secure-app

Expected result:

no
Terminal window
kubectl auth can-i create pods --as=system:serviceaccount:cloudnova-secure-app:cloudnova-web-sa -n cloudnova-secure-app

Expected result:

no

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

Capture screenshots or command output for the following evidence.

Terminal window
kubectl get namespace cloudnova-secure-app --show-labels

Capture the error returned by:

Terminal window
kubectl apply -f insecure-test-pod.yaml
Terminal window
kubectl get deployment cloudnova-secure-web -n cloudnova-secure-app
Terminal window
kubectl get pods -l app=cloudnova-secure-web -n cloudnova-secure-app -o wide
Terminal window
kubectl get serviceaccount cloudnova-web-sa -n cloudnova-secure-app
Terminal window
kubectl exec -n cloudnova-secure-app <POD-NAME> -- id

Capture the failed output from:

Terminal window
kubectl exec -n cloudnova-secure-app <POD-NAME> -- sh -c "touch /security-test.txt"
Terminal window
kubectl get deployment cloudnova-secure-web -n cloudnova-secure-app -o yaml
Terminal window
kubectl describe deployment cloudnova-secure-web -n cloudnova-secure-app
Terminal window
kubectl get service cloudnova-secure-web-service -n cloudnova-secure-app
Terminal window
kubectl get networkpolicy -n cloudnova-secure-app

Capture the successful HTTP response.

Capture the failed request or document the local CNI limitation.

Terminal window
kubectl auth can-i get secrets --as=system:serviceaccount:cloudnova-secure-app:cloudnova-web-sa -n cloudnova-secure-app

Task 25 — Create the Security Assessment Report

Section titled “Task 25 — Create the Security Assessment Report”

Create:

secure-application-assessment.md

Use:

# 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.

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.

Run:

Terminal window
kubectl get all -n cloudnova-secure-app

Review:

  • Deployment
  • ReplicaSet
  • Pods
  • Service

Run:

Terminal window
kubectl get serviceaccounts,networkpolicies -n cloudnova-secure-app

Review:

  • Dedicated Service Account
  • Default Service Account
  • Default-deny policy
  • Approved client policy

Delete resources in reverse order.

Terminal window
kubectl delete -f .\08-unapproved-client.yaml
kubectl delete -f .\07-approved-client.yaml
Terminal window
kubectl delete -f 08-unapproved-client.yaml
kubectl delete -f 07-approved-client.yaml
Terminal window
kubectl delete -f .\06-allow-approved-client.yaml
kubectl delete -f .\05-default-deny-network-policy.yaml
Terminal window
kubectl delete -f 06-allow-approved-client.yaml
kubectl delete -f 05-default-deny-network-policy.yaml
Terminal window
kubectl delete -f .\04-service.yaml
Terminal window
kubectl delete -f 04-service.yaml
Terminal window
kubectl delete -f .\03-secure-deployment.yaml
Terminal window
kubectl delete -f 03-secure-deployment.yaml
Terminal window
kubectl delete -f .\02-service-account.yaml
Terminal window
kubectl delete -f 02-service-account.yaml
Terminal window
kubectl delete -f .\01-namespace.yaml
Terminal window
kubectl delete -f 01-namespace.yaml
Terminal window
kubectl get namespace cloudnova-secure-app

Expected result:

NotFound

Keep the cluster for the next lab.

When the cluster is no longer required, delete it using:

Terminal window
kind delete cluster --name cloudnova-security-lab

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 false
capabilities must drop ALL
runAsNonRoot must be true
seccompProfile must be RuntimeDefault

Confirm that the Deployment security contexts match the provided manifest.

Check:

Terminal window
kubectl describe pod <POD-NAME> -n cloudnova-secure-app

Possible causes:

  • No internet access
  • Container registry unavailable
  • Incorrect image name
  • Registry rate limiting
  • Proxy configuration problems

Check:

Terminal window
kubectl logs <POD-NAME> -n cloudnova-secure-app

Then:

Terminal window
kubectl describe pod <POD-NAME> -n cloudnova-secure-app

Possible 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.

Check:

Terminal window
kubectl get pods -n cloudnova-secure-app
kubectl get service -n cloudnova-secure-app
kubectl get endpoints -n cloudnova-secure-app
kubectl describe service cloudnova-secure-web-service -n cloudnova-secure-app

Confirm:

  • 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:

Terminal window
kubectl get pod approved-client -n cloudnova-secure-app --show-labels
kubectl get networkpolicy -n cloudnova-secure-app
kubectl describe networkpolicy allow-approved-web-client -n cloudnova-secure-app

Confirm that the client contains:

access=cloudnova-secure-web

Issue 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.

Use another local port:

Terminal window
kubectl port-forward service/cloudnova-secure-web-service 8081:80 -n cloudnova-secure-app

Open:

http://localhost:8081

Issue 08 — The kubectl exec Command Fails

Section titled “Issue 08 — The kubectl exec Command Fails”

Confirm the Pod name:

Terminal window
kubectl get pods -n cloudnova-secure-app

Ensure the selected Pod is in the Running state.

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.

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

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

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

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

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

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

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

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

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

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

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

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

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.

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