Skip to content

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

rp-portal

A two-tier resume submission portal - Nginx frontend + Flask backend - packaged as containers, secured through a DevSecOps CI pipeline, and deployed to Kubernetes on AWS EKS. Images are built and scanned in GitHub Actions and pushed to ECR using GitHub OIDC (no long-lived AWS keys). Deployment to the cluster is done manually, by hand, as a learning exercise in Kubernetes objects.

This repo is intentionally infrastructure-light: no Terraform. The AWS prerequisites (OIDC role, ECR) are a small one-time bootstrap, and the EKS cluster is assumed to exist. The focus is a solid CI security pipeline + understanding k8s objects.


Architecture - data flow, frontend → backend

flowchart TB
    user([User browser])

    subgraph eks["EKS cluster (namespace: rp-portal)"]
        fsvc["Service: rp-portal-frontend :80"]
        subgraph fe["Frontend Deployment - Nginx"]
            direction LR
            fp1["Pod 1"] ~~~ fp2["Pod 2"]
        end
        bsvc["Service: rp-portal-backend :80"]
        subgraph be["Backend Deployment - Flask"]
            direction LR
            bp1["Pod 1"] ~~~ bp2["Pod 2"] ~~~ bp3["Pod 3"] ~~~ bp4["Pod 4"]
        end
    end

    subgraph aws["AWS managed services (outside the cluster)"]
        direction LR
        rds[("RDS PostgreSQL")]
        s3[("S3 resume PDFs")]
        ses["SES email"]
        rds ~~~ s3 ~~~ ses
    end

    user --> fsvc
    fsvc --> fe
    fe --> bsvc
    bsvc --> be
    be --> aws

    classDef store fill:#f5f0ff,stroke:#7c5cff,color:#000;
    class rds,s3,ses store;
Loading

Traffic flows one way through the cluster: the frontend Service load-balances across the 2 frontend pods (Nginx), which call the backend Service, which load-balances across the 4 backend pods (Flask). The backend pods reach RDS, S3, and SES using an IRSA-scoped IAM role, so no static credentials live in the pod.


Tech stack

Layer Technology
Frontend HTML/CSS/JS served by Nginx
Backend Python Flask + Gunicorn
Database PostgreSQL on AWS RDS
Orchestration Kubernetes on AWS EKS
Registry AWS ECR (image scan + cosign signature)
CI / DevSecOps GitHub Actions
CI → AWS auth GitHub OIDC (no stored keys)
Pod → AWS auth IRSA

Repository structure

rp-portal/
├── backend/                 # Flask backend (app.py, Dockerfile, requirements.txt)
├── frontend/                # Nginx frontend (index.html, nginx.conf, Dockerfile)
├── k8s/
│   ├── namespace.yaml           # rp-portal namespace (apply first, never use default)
│   ├── frontend/frontend.yaml   # simple, commented manifest (start here)
│   ├── flask/                   # backend manifests (deployment, service, hpa, config, secret)
│   ├── nginx/                   # original frontend manifests
│   └── ingress.yaml             # ALB ingress rules
├── .github/workflows/ci.yml # DevSecOps pipeline (both images)
└── docs/
    ├── deploy-frontend-to-eks.md  # manual build → ECR → deploy walkthrough
    └── devsecops-pipeline.md      # pipeline stages + one-time AWS bootstrap

DevSecOps CI pipeline

Workflow: .github/workflows/ci.yml · Full write-up: docs/devsecops-pipeline.md

Stage 1 - source security gates (every PR and push):

Check Tool Blocks build?
Secret scan gitleaks Yes
SAST Bandit, Semgrep Reports (soft)
Dependency + IaC scan Trivy fs Yes on CRITICAL
Dockerfile lint hadolint Yes on errors

Stage 2 - per image (backend + frontend), only after Stage 1 passes:

build → Trivy image scan (blocks on HIGH/CRITICAL) → push to ECR → SBOM (CycloneDX) → cosign keyless signature

  • OIDC, no keys - the only GitHub secret is AWS_ROLE_ARN.
  • PRs scan but never push - a vulnerable branch can't publish an image.
  • Images publish as rp-portal-backend and rp-portal-frontend.

Findings appear under GitHub → Security → Code scanning (SARIF).


Getting started

1. One-time AWS bootstrap

Create the OIDC provider, the rp-portal-gha IAM role, and the AWS_ROLE_ARN GitHub secret. Copy-paste commands: docs/devsecops-pipeline.md.

2. Build & publish images

Push to main (or run the workflow manually) - CI scans and pushes both images to ECR. To do it by hand instead, follow docs/deploy-frontend-to-eks.md.

3. Deploy to the cluster (manual)

Start with the frontend - the simplest object set - using the commented manifest k8s/frontend/frontend.yaml and the step-by-step guide docs/deploy-frontend-to-eks.md.


Security summary

Practice How
No long-lived CI keys GitHub OIDC → short-lived role session
No static pod credentials IRSA least-privilege role
Secrets never committed gitleaks gate in CI
Known-vuln images blocked Trivy image scan gate (HIGH/CRITICAL)
Vulnerable deps blocked Trivy fs gate (CRITICAL)
Supply-chain provenance SBOM + cosign keyless signatures
Private database RDS in private subnet
Container hardening Non-root user in backend image
Namespace isolation App runs in the rp-portal namespace, never default

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages