Back to path
AdvancedStorefront · Project 5 of 11 ~10h· 5 milestones

Lock it down: least-privilege RBAC, hardened pods, network policy

Continues from the last build: Storefront now has durable state, but its pods run as root, its RBAC is wide open, and any pod can reach any other pod.

Storefront works, and that is exactly the problem. It runs on Kubernetes defaults, which means every container starts as root with a full Linux capability set, every workload uses the namespace’s default ServiceAccount that can be widened to anything, and the cluster’s flat network lets any pod open a socket to any other pod.

Pod securityContext (runAsNonRoot, readOnlyRootFilesystem)Linux capabilities & seccompKubernetes RBAC (ServiceAccount/Role/RoleBinding)Least-privilege designNetworkPolicy (default-deny + allowlists)Threat modelling a workloadkubectl auth can-iYAML

What you'll build

A Storefront namespace where pods run as a non-root user with a read-only root filesystem, dropped capabilities, and a seccomp profile; where a dedicated ServiceAccount is bound to a least-privilege Role (no cluster-admin, no wildcard verbs); and where a default-deny NetworkPolicy plus explicit allowlists mean each service can reach only the peers it actually needs, all proven by a request that is allowed and a request that is blocked.

See how we teach, before you sign up

You don't just get code dumped on you. Every starter file and every solution is explained line-by-line, in plain English. Here's one real file from this project:

base/catalog.yamlyaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: catalog
  namespace: storefront
  labels:
    app: catalog
spec:
  replicas: 2
  selector:
    matchLabels:
      app: catalog
  template:
    metadata:
      labels:
        app: catalog
    spec:
      # no securityContext yet -> runs as root
      # no serviceAccountName yet -> uses the default SA
      containers:
        - name: catalog
          image: ghcr.io/your-org/catalog:2.0.0
          ports:
            - containerPort: 8080

Reading this file

  • kind: DeploymentA Deployment keeps the catalog pods running, today it inherits every insecure cluster default because nothing overrides them.
  • image: ghcr.io/your-org/catalog:2.0.0The catalog image, by default its process runs as UID 0 (root) inside the container unless you say otherwise.
  • containerPort: 8080The port the catalog serves on, you will later allow only the gateway to reach this port.

The catalog deployment as it runs today: no securityContext, default ServiceAccount, nothing stopping it talking to anything.

That's 1 of 8 explained code blocks in this single project.

The build, milestone by milestone

  1. 1

    Threat-model the namespace before you touch a control

    5 guided steps

    Hardening without a model is how you both over-restrict (break the app) and under-restrict (miss the real path). Knowing who runs as what, who can call the API, and who talks to whom turns the next three milestones into filling in a plan rather than guessing.

  2. 2

    Harden the pods: non-root, read-only, no capabilities

    6 guided steps

    A root container with a writable filesystem and full capabilities is a single dependency CVE away from a full host foothold. Non-root plus read-only plus dropped caps removes most of an attacker’s post-exploitation toolkit, this is the highest-leverage control on the rung.

  3. 3

    Give each workload a least-privilege identity

    6 guided steps

    The default ServiceAccount is shared and easy to over-grant, and a token that can list secrets or create pods turns a single compromise into lateral movement across the namespace. A dedicated SA with a tight Role means a stolen token can do almost nothing.

  4. 4

    Default-deny the network, then allow only Storefront flows

    6 guided steps

    A flat cluster network means one compromised pod can scan and reach every other pod, including the database and the API server. Default-deny plus an explicit allowlist turns the network into a least-privilege surface where lateral movement has to be specifically permitted.

  5. 5

    Prove the hardening with allowed-vs-blocked tests

    5 guided steps

    A control you have not tested is a control you do not have. Hardening that quietly breaks the app gets reverted, and hardening with a gap gives false confidence, only an allowed-vs-blocked pair proves both that the app works and that the attack path is closed.

What's inside when you start

3 starter files, ready to clone
5 guided milestones
5 full reference solutions
8 code blocks explained line-by-line
5 "is it working?" checks
4 interview questions it prepares you for

You'll walk away with

A security/ folder with the hardened securityContext, the ServiceAccount + Role + RoleBinding, and the default-deny + allow NetworkPolicies
A one-page threat model of the Storefront namespace: identities, run-as users, legitimate flows, and blast radius
A before/after evidence table: app-still-works plus a denied negative test for each of the three controls
A README note explaining what each control buys you and which attack path it closes

This is portfolio-grade. Build it free.

Sign up to unlock every milestone step-by-step, the code skeletons, full reference solutions, and checkable tasks, with your progress saved as you build.

Start building