Skip to content

SumonMSelim/zero-trust-eks-demo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Zero Trust on EKS – URL Shortener Demo

Polyglot stack demonstrating Zero Trust on Amazon EKS with SPIFFE/SPIRE mTLS, Kubernetes NetworkPolicy (AWS VPC CNI), and AWS IRSA.

Architecture

┌────────────────────────────────────────────────────────────────────────────┐
│                              AWS EKS Cluster                               │
│                                                                            │
│  ┌─────────────┐         ┌─────────────────────────────────────────────┐   │
│  │   public    │         │              services namespace             │   │
│  │  namespace  │         │                                             │   │
│  │             │         │  ┌───────────┐  mTLS  ┌─────────────┐       │   │
│  │ ┌─────────┐ │  HTTP   │  │    api    │◄──────►│ url-service │       │   │
│  │ │frontend │─┼────────►│  │  gateway  │ SPIFFE │   (Go)      │       │   │
│  │ │ (React) │ │  :8000  │  │ (Laravel) │        └──────┬──────┘       │   │
│  │ └─────────┘ │         │  └───────────┘               │              │   │
│  │             │         │                              │ :5432        │   │
│  └─────────────┘         │  ┌───────────┐               ▼              │   │
│                          │  │  worker   │         ┌──────────┐         │   │
│                          │  │   (Go)    │────────►│   RDS    │         │   │
│                          │  └─────┬─────┘  IRSA   │ Postgres │         │   │
│                          │        │               └──────────┘         │   │
│                          │        │ :443                               │   │
│                          │        ▼                                    │   │
│                          │   ┌─────────┐                               │   │
│                          │   │   SQS   │                               │   │
│                          │   └─────────┘                               │   │
│                          └─────────────────────────────────────────────┘   │
└────────────────────────────────────────────────────────────────────────────┘

Components

Service Technology Namespace Authentication
Frontend React + Vite + Nginx public Network Policy
API Gateway Laravel 12 (PHP) services SPIFFE mTLS (client)
URL Service Go services SPIFFE mTLS (server)
Worker Go services AWS IRSA
Database RDS PostgreSQL AWS Secrets Manager
Queue SQS AWS IRSA

Security Architecture

This demo implements defense in depth using two complementary security layers:

┌──────────────────────────────────────────────────────────────────┐
│                     IDENTITY LAYER (L7)                          │
│                      SPIFFE/SPIRE mTLS                           │
│    ────────────────────────────────────────────────────────      │
│    • X.509 SVID certificates per workload                        │
│    • Cryptographic identity verification                         │
│    • Mutual TLS between api-gateway ↔ url-service                │
│    • No sidecar proxy - certificates used directly by app        │
│    • Controls: CRYPTOGRAPHIC PROOF of caller identity            │
│                                                                  │    
│    api-gateway ◄──────── mTLS (X.509 SVID) ────────► url-service │
└──────────────────────────────────────────────────────────────────┘
                               +
┌──────────────────────────────────────────────────────────────────┐
│                   NETWORK LAYER (L3/L4) - Swappable              │
│                                                                  │
│   ┌─────────────────────┐    OR    ┌─────────────────────┐       │
│   │  AWS VPC CNI        │          │      Cilium         │       │
│   │  + Network Policy   │          │                     │       │
│   │    Agent (current)  │          │                     │       │
│   │                     │          │                     │       │
│   │  • eBPF enforcement │          │  • eBPF enforcement │       │
│   │  • K8s NetworkPolicy│          │  • K8s NetworkPolicy│       │
│   │  • AWS native       │          │  • L7 policy option │       │
│   │  • Low complexity   │          │  • Hubble observ.   │       │
│   └─────────────────────┘          └─────────────────────┘       │
│                                                                  │
│          Same NetworkPolicy YAML manifests work with both!       │
│                                                                  │
│    • Default deny all traffic                                    │
│    • Explicit allow rules per service                            │
│    • IP/Port based filtering                                     │
│    • Controls: WHO can connect to WHOM at network level          │
└──────────────────────────────────────────────────────────────────┘

Why Both Layers?

NetworkPolicy Alone SPIFFE/SPIRE Alone Both Together
Can't verify WHO is calling Can't block at network level Defense in depth
IP spoofing possible Pod could bypass if network allows Network + Identity
No cryptographic identity Relies on app to enforce Layered security

Example Attack Scenario:

  1. Attacker compromises a pod in services namespace
  2. NetworkPolicy blocks - attacker can't reach url-service (wrong pod label)
  3. Even if attacker bypasses NetworkPolicy, SPIFFE/SPIRE blocks - no valid SVID certificate

Security Layers

1. SPIFFE/SPIRE mTLS (Identity Layer)

  • Workload identity via X.509 SVIDs
  • Trust domain: mol.la
  • Automatic certificate rotation
  • api-gateway → url-service authenticated via SPIFFE ID
  • url-service only accepts calls from spiffe://mol.la/ns/services/sa/api-gateway

2. Kubernetes NetworkPolicy (Network Layer)

  • AWS VPC CNI with Network Policy Agent (eBPF)
  • Default deny all in services namespace
  • Explicit allow rules for each traffic flow
  • Enforced at kernel level before traffic reaches pods

3. AWS IAM (Cloud Layer)

  • IRSA (IAM Roles for Service Accounts)
  • Secrets Manager for database credentials
  • Least-privilege access
  • No long-lived credentials in pods

Network Policies

All policies are in kubernetes/network-policies/. The services namespace uses a default-deny baseline.

Policy Summary

# File Purpose From To Port
00 default-deny-all.yaml Block all traffic by default * * *
01 allow-frontend-to-api-gateway.yaml Frontend → API Gateway public/frontend api-gateway TCP 8000
02 allow-api-gateway-to-url-service.yaml API Gateway → URL Service (ingress) api-gateway url-service TCP 8443
03 allow-url-service-egress-to-rds.yaml URL Service → RDS url-service 10.0.0.0/16 TCP 5432
04 allow-worker-egress-to-sqs.yaml Worker → AWS SQS worker any TCP 443
05 allow-kubelet-probes.yaml Health checks from kubelet 10.0.0.0/16 pods TCP 8000/8080
06 allow-dns-egress.yaml DNS resolution all pods kube-system UDP/TCP 53
07 allow-api-gateway-egress-to-url-service.yaml API Gateway → URL Service (egress) api-gateway url-service TCP 8443

Traffic Flow

                         ┌─────────────────────────────────────────┐
                         │            services namespace           │
                         │                                         │
┌──────────┐             │  ┌─────────────┐      ┌──────────────┐  │     ┌─────────┐
│ frontend │──TCP:8000──▶│  │ api-gateway │─8443─│ url-service  │──┼────▶│   RDS   │
│ (public) │             │  └─────────────┘ mTLS └──────────────┘  │     │  :5432  │
└──────────┘             │         │                    │          │     └─────────┘
                         │         │                    │          │
                         │         ▼                    ▼          │     ┌─────────┐
                         │     DNS:53              DNS:53          │     │   SQS   │
                         │                                         │     │  :443   │
                         │  ┌─────────────┐                        │     └────▲────┘
                         │  │   worker    │────────────────────────┼──────────┘
                         │  └─────────────┘                        │
                         │                                         │
                         └─────────────────────────────────────────┘

Blocked Traffic (Security Guarantees)

Blocked Path Reason
worker → api-gateway No policy allows it
worker → url-service No policy allows it
frontend → url-service No policy allows it (must go through api-gateway)
url-service → api-gateway No policy allows it
External → any pod No ingress from outside cluster

Quick Start

Prerequisites

  • AWS CLI configured with credentials
  • Terraform >= 1.6
  • kubectl
  • Docker with buildx

1. Infrastructure (Terraform)

# Set environment variables
export AWS_PROFILE=your-profile
export AWS_REGION=us-east-1
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

# Apply Terraform
./scripts/terraform-apply.sh

2. Build & Push Images

# Build all images for linux/amd64 and push to ECR
AWS_ACCOUNT_ID=$AWS_ACCOUNT_ID AWS_REGION=$AWS_REGION ./scripts/build-and-push.sh

3. Deploy to Kubernetes

# Deploy all manifests (namespaces, identity, deployments, services, network policies)
AWS_ACCOUNT_ID=$AWS_ACCOUNT_ID AWS_REGION=$AWS_REGION ./scripts/deploy-k8s.sh

4. Enable Network Policy (if not already)

# Install VPC CNI as managed add-on with network policy
aws eks create-addon \
  --cluster-name zero-trust-eks-cluster \
  --addon-name vpc-cni \
  --configuration-values '{"enableNetworkPolicy": "true"}' \
  --resolve-conflicts OVERWRITE

5. Verify Deployment

# Check pods
kubectl get pods -n services
kubectl get pods -n public

# Check network policies
kubectl get networkpolicies -n services

# Get ALB hostname
kubectl get ingress -n public

SPIFFE/SPIRE Identity

Trust Domain

mol.la

SPIFFE IDs

Service SPIFFE ID
api-gateway spiffe://mol.la/ns/services/sa/api-gateway
url-service spiffe://mol.la/ns/services/sa/url-service
worker spiffe://mol.la/ns/services/sa/worker

mTLS Flow

  1. SPIRE agent provides X.509 SVID to each workload
  2. api-gateway presents its SVID when calling url-service
  3. url-service validates api-gateway's SPIFFE ID
  4. Only spiffe://mol.la/ns/services/sa/api-gateway is authorized

Endpoints

Service Endpoint Port Protocol
Frontend / 80 HTTP
API Gateway /api/health 8000 HTTP
API Gateway /api/shorten 8000 HTTP
API Gateway /api/u/{code} 8000 HTTP
API Gateway /api/identities 8000 HTTP
URL Service /shorten 8443 HTTPS/mTLS
URL Service /u/{code} 8443 HTTPS/mTLS
URL Service /identity 8443 HTTPS/mTLS
URL Service /health 8080 HTTP
Worker /health 8080 HTTP

Demo

Test URL Shortening

# Get ALB hostname
ALB=$(kubectl get ingress -n public -o jsonpath='{.items[0].status.loadBalancer.ingress[0].hostname}')

# Shorten a URL
curl -X POST "http://$ALB/api/shorten" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com"}'

# Response: {"code":"abc123","url":"https://example.com"}

# Resolve the code
curl "http://$ALB/api/u/abc123"

View SPIFFE Identities

# Get all workload identities and trust chain
curl "http://$ALB/api/identities"

Test Network Policy Enforcement

# This should work (allowed by policy)
kubectl exec -n public deployment/frontend -- \
  curl -s http://api-gateway.services.svc.cluster.local:80/health

# This should be BLOCKED (no policy)
kubectl exec -n services deployment/worker -- \
  wget -q -O- --timeout=5 http://api-gateway.services.svc.cluster.local:80/health

Notes

  • SPIFFE Trust Domain: Uses mol.la. Update kubernetes/identity/spire-registration-entries.yaml if using a different trust domain.
  • IRSA Roles: Provisioned by Terraform. Service accounts are annotated with IAM role ARNs.
  • Network Policy: Requires AWS VPC CNI add-on with enableNetworkPolicy: true.
  • ECR Repositories: Created by Terraform with names zero-trust-eks-{api-gateway,url-service,worker,frontend}.

About

Polyglot stack demonstrating Zero Trust on Amazon EKS with SPIFFE/SPIRE mTLS, Kubernetes NetworkPolicy (AWS VPC CNI), and AWS IRSA.

Topics

Resources

Stars

Watchers

Forks

Contributors