Skip to content

Emmsfay/DevSecMovieApp

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

78 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DevSecMovieApp

Getting Started

Project Overview

DevSecMovie integrates security directly into the software development lifecycle — shifting security left rather than treating it as an afterthought. Every code push triggers an automated pipeline that runs secret detection, static analysis, dependency scanning, container scanning, and infrastructure scanning before deploying the application.

The base application is a movie and TV series discovery platform powered by the TMDB API, built with React, TypeScript, and Tailwind CSS. It supports search, trailers, and browsing by category.

What makes this a DevSecOps project:

  • Secrets are never hardcoded — all sensitive values are stored in Jenkins credentials and injected at build time
  • Security scanning happens at every layer: source code, dependencies, Docker image, and Terraform infrastructure
  • The pipeline fails fast — if a critical vulnerability is detected, the deployment is blocked
  • All scan reports are archived as Jenkins artifacts for audit trails
  • Infrastructure is fully reproducible via Terraform — no manual console clicks

📷 Screenshots

Screenshot (356)

Screenshot (357)

Screenshot (369)

Screenshot (370)



A production-grade DevSecOps project built on top of a React + TypeScript movie discovery app. This project demonstrates a complete CI/CD pipeline with security scanning at every stage, deployed to AWS using infrastructure-as-code.

Live App: http://34.206.161.111 Pipeline: Jenkins CI/CD with 11 automated security stages Infrastructure: Terraform-provisioned AWS EC2, ECR, VPC


Table of Contents


Architecture

Developer (local)
      │
      │  git push
      ▼
GitHub (source of truth)
      │
      │  webhook / manual trigger
      ▼
┌─────────────────────────────────────────────┐
│         Jenkins CI/CD Server                │
│         EC2 t2.large (98.88.241.84)         │
│                                             │
│  ┌─────────┐  ┌──────────┐  ┌──────────┐   │
│  │SonarQube│  │Prometheus│  │ Grafana  │   │
│  │:9000    │  │:9090     │  │:3000     │   │
│  └─────────┘  └──────────┘  └──────────┘   │
└─────────────────────────────────────────────┘
      │
      │  docker push
      ▼
AWS ECR (container registry)
      │
      │  docker pull + run
      ▼
┌─────────────────────────────┐
│     App Server              │
│     EC2 t2.medium           │
│     (34.206.161.111)        │
│     nginx:80 → React SPA    │
└─────────────────────────────┘

Both EC2 instances live inside a custom VPC with public subnets, internet gateway, and security groups that follow least-privilege principles.


Tech Stack

Application

Layer Technology
Frontend framework React 18 + TypeScript
Styling Tailwind CSS
Build tool Vite
API TMDB (The Movie Database)
Web server nginx (alpine)
Container Docker (multi-stage build)

DevSecOps

Category Tool Purpose
CI/CD Jenkins 2.541 Pipeline orchestration
Secret scanning GitLeaks 8.18 Detect hardcoded secrets in git history
SAST SonarQube 9.9 LTS Static code analysis, quality gate
Dependency scanning OWASP Dependency-Check CVE scanning of npm packages
Filesystem scanning Trivy 0.69 Scan source files for vulnerabilities
Image scanning Trivy 0.69 Scan Docker image layers for CVEs
IaC scanning Checkov 3.2 Terraform misconfiguration detection
Container registry AWS ECR Private Docker image storage
Infrastructure Terraform 1.6 Infrastructure as Code
Monitoring Prometheus + Grafana Metrics and dashboards

AWS Services

Service Usage
EC2 (t2.large) Jenkins server
EC2 (t2.medium) Application server
ECR Docker image registry
VPC Network isolation
Security Groups Firewall rules
Elastic IPs Static public IPs
IAM Instance profiles for ECR access
S3 Terraform remote state
DynamoDB Terraform state locking

Pipeline Stages

The Jenkins pipeline consists of 11 stages that run automatically on every build:

Stage 1 — Clean Workspace

Wipes the Jenkins workspace before each build to ensure a clean slate and prevent stale file contamination between builds.

Stage 2 — Checkout

Clones the repository from GitHub using the branch specified in the pipeline configuration.

Stage 3 — GitLeaks (Secret Scanning)

Runs GitLeaks against the entire git history (all commits) to detect hardcoded secrets such as API keys, tokens, passwords, and private keys. Reports are archived as JSON artifacts.

Tool: GitLeaks 8.18.2
Scope: All 69 commits in history
Output: gitleaks-report.json

Stage 4 — SonarQube SAST

Performs static application security testing on the TypeScript source code. Analyses 45 source files for bugs, vulnerabilities, security hotspots, and code smells. Results are sent to the SonarQube server running on the same EC2 instance.

Tool: SonarQube 9.9 LTS
Files analysed: 45 TypeScript/CSS files
Quality Gate: Passed
Result: 0 bugs, 0 vulnerabilities, 0 security hotspots, 21 code smells

Stage 5 — Quality Gate

Waits for SonarQube to complete processing the analysis report and checks whether the quality gate conditions are met. Uses a SonarQube webhook to avoid polling. Pipeline proceeds only if the gate passes.

Stage 6 — Install Dependencies

Runs npm ci --frozen-lockfile to install exact versions from package-lock.json. This ensures reproducible builds and surfaces any installation issues early.

Stage 7 — OWASP Dependency-Check

Scans all npm dependencies against the NVD (National Vulnerability Database) for known CVEs. Generates both HTML and XML reports archived as Jenkins artifacts.

Tool: OWASP Dependency-Check (latest)
Database: NVD CVE database (342,280 records)
Output: dependency-check-report.html, dependency-check-report.xml

Stage 8 — Trivy Filesystem Scan

Scans the source code filesystem for HIGH and CRITICAL severity vulnerabilities before building the Docker image. This catches issues in the codebase before containerisation.

Tool: Trivy 0.69.3
Severity filter: HIGH, CRITICAL
Output: trivy-fs-report.txt

Stage 9 — Docker Build

Builds the production Docker image using a multi-stage Dockerfile. Stage 1 uses node:20-alpine to compile the React app with Vite. Stage 2 copies the compiled dist/ folder into nginx:1.27-alpine. The TMDB API key is injected as a build argument and baked into the JavaScript bundle at compile time.

Base images: node:20-alpine (builder), nginx:1.27-alpine (final)
Build args: VITE_API_KEY, VITE_TMDB_API_BASE_URL
Final image size: ~50MB

Stage 10 — Trivy Image Scan

Scans the built Docker image for HIGH and CRITICAL CVEs across all layers including the OS packages, language runtimes, and application dependencies.

Tool: Trivy 0.69.3
Target: ECR image (build number tag)
Severity filter: HIGH, CRITICAL
Output: trivy-image-report.txt

Stage 11 — Checkov IaC Scan

Scans all Terraform files in the terraform/ directory for security misconfigurations and compliance violations. Uses --soft-fail so findings are reported but do not block deployment — allowing teams to review and remediate iteratively.

Tool: Checkov 3.2.513
Target: terraform/ directory
Mode: --soft-fail (report, don't block)
Output: checkov-report.txt

Stage 12 — Push to ECR

Authenticates with AWS ECR using the EC2 instance's IAM role (no static credentials required), then pushes the image with both a build number tag and latest tag.

Registry: 757760338065.dkr.ecr.us-east-1.amazonaws.com/devsecmovie
Tags: :<build_number>, :latest
Auth: IAM instance profile (no stored credentials)

Stage 13 — Deploy to App EC2

SSHs into the app server using a stored Jenkins SSH credential, pulls the latest image from ECR, stops and removes the old container, and starts a new one on port 80.

docker pull $ECR_REPO:latest
docker stop devsecmovie
docker rm devsecmovie
docker run -d --name devsecmovie --restart unless-stopped -p 80:80 $ECR_REPO:latest

Infrastructure

All infrastructure is provisioned and managed by Terraform. No manual AWS Console configuration is required after initial setup.

Resources Created

VPC
├── Public subnet (10.0.1.0/24)
├── Internet Gateway
└── Route table

EC2: jenkins-server (t2.large)
├── Security group (ports: 22, 8080, 9000, 3000, 9090)
├── Elastic IP
├── IAM instance profile (ECR access)
├── 30GB gp3 EBS volume
└── User data script (auto-installs all tools on boot)

EC2: app-server (t2.medium)
├── Security group (ports: 22, 80, 443)
├── Elastic IP
├── IAM instance profile (ECR access)
├── 15GB gp3 EBS volume
└── User data script (installs Docker + AWS CLI)

ECR: devsecmovie
├── Image scanning on push enabled
└── Lifecycle policy (keep last 10 images)

S3: devsecmovie-tfstate (remote state)
DynamoDB: devsecmovie-tflock (state locking)

Terraform Commands

# Bootstrap (run once)
aws s3 mb s3://devsecmovie-tfstate --region us-east-1
aws dynamodb create-table \
  --table-name devsecmovie-tflock \
  --attribute-definitions AttributeName=LockID,AttributeType=S \
  --key-schema AttributeName=LockID,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST \
  --region us-east-1

# Deploy infrastructure
terraform init
terraform plan
terraform apply

# Destroy when done
terraform destroy

Project Structure

DevSecMovieApp/
├── src/                        # React + TypeScript source
│   ├── components/
│   ├── pages/
│   ├── hooks/
│   └── utils/
│       └── config.ts           # Environment variable exports
├── Dockerfile                  # Multi-stage production build
├── nginx.conf                  # nginx SPA routing + security headers
├── docker-compose.yml          # Local development
├── Jenkinsfile                 # 11-stage DevSecOps pipeline
├── .gitignore
└── terraform/
    ├── main.tf                 # Provider + S3 backend
    ├── variables.tf
    ├── vpc.tf                  # VPC, subnet, IGW, routes
    ├── sg.tf                   # Security groups
    ├── iam.tf                  # EC2 instance profiles
    ├── ecr.tf                  # Container registry
    ├── ec2.tf                  # Jenkins + App servers
    ├── outputs.tf              # IPs, URLs, ECR URL
    ├── terraform.tfvars        # Your values (gitignored)
    └── scripts/
        ├── jenkins_install.sh  # Bootstrap: Java, Jenkins, Docker, Trivy, etc.
        └── app_install.sh      # Bootstrap: Docker, AWS CLI

Getting Started

Prerequisites

  • AWS account with programmatic access (Access Key + Secret Key)
  • Terraform >= 1.6 installed locally
  • AWS CLI v2 configured (aws configure)
  • Docker Desktop installed locally
  • Node.js 18+ installed locally
  • TMDB API key (free at themoviedb.org)

Local Development

# Clone the repo
git clone https://github.com/Emmsfay/DevSecMovieApp.git
cd DevSecMovieApp

# Install dependencies
npm install

# Create .env file
echo "VITE_API_KEY=your_tmdb_api_key" > .env
echo "VITE_TMDB_API_BASE_URL=https://api.themoviedb.org/3" >> .env

# Start dev server
npm run dev
# App runs at http://localhost:5173

Local Docker Build

docker build \
  --build-arg VITE_API_KEY=your_tmdb_api_key \
  --build-arg VITE_TMDB_API_BASE_URL=https://api.themoviedb.org/3 \
  -t devsecmovie:local .

docker run -d --name devsecmovie -p 8080:80 devsecmovie:local
# App runs at http://localhost:8080

Deploy to AWS

# 1. Create bootstrap resources
aws s3 mb s3://devsecmovie-tfstate --region us-east-1

# 2. Configure variables
cd terraform
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars with your key_name and my_ip

# 3. Provision infrastructure
terraform init
terraform plan
terraform apply

# 4. Note the outputs
# jenkins_url, sonarqube_url, app_url, ecr_repository_url

# 5. Configure Jenkins (see Jenkins Setup section)
# 6. Create pipeline pointing to this repo
# 7. Click Build Now

Environment Variables

Variable Description Required
VITE_API_KEY TMDB API key (v3 auth) Yes
VITE_TMDB_API_BASE_URL TMDB base URL Yes
VITE_GA_MEASUREMENT_ID Google Analytics ID No
VITE_GOOGLE_AD_SLOT Google Ad slot No
VITE_GOOGLE_AD_CLIENT Google Ad client No

The VITE_ prefix is required by Vite to expose variables to the browser bundle. Variables without this prefix are not accessible in the frontend code.

Important: Never commit .env files to git. The .gitignore excludes them. In the pipeline, values are stored as Jenkins credentials and injected at build time.


Docker

The Dockerfile uses a multi-stage build to keep the final image small and secure:

Stage 1 (builder): Uses node:20-alpine to install dependencies and compile the React app. The TMDB API key is baked into the JavaScript bundle during this stage via Vite's environment variable system.

Stage 2 (final): Uses nginx:1.27-alpine — a minimal web server image. Only the compiled dist/ folder is copied from Stage 1. No Node.js, no npm, no source code exists in the final image.

Security headers are configured in nginx.conf:

X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin

Static asset caching is set to 1 year for all hashed filenames (Vite fingerprints all assets).


Jenkins Setup

Required Plugins

Plugin Purpose
Eclipse Temurin Installer JDK 21 management
NodeJS Node 18 in pipeline
SonarQube Scanner SAST integration
OWASP Dependency-Check CVE scanning
Pipeline AWS Steps ECR authentication
SSH Agent Deploy to app server
Docker Pipeline Docker build steps
Prometheus Metrics Expose metrics for Grafana

Required Credentials

ID Kind Value
Sonar-token Secret text SonarQube user token
TMDB_API_KEY Secret text TMDB API key
ECR_REPO Secret text Full ECR repository URL
APP_SERVER_IP Secret text App server public IP
app-server-ssh-key SSH username + private key EC2 key pair (.pem contents)

SonarQube Configuration

SonarQube runs as a Docker container on the Jenkins server:

docker run -d \
  --name sonarqube \
  --restart unless-stopped \
  -p 9000:9000 \
  sonarqube:lts-community

Access at http://<jenkins_ip>:9000 — default credentials: admin/admin.

A webhook must be configured in SonarQube pointing to Jenkins:

Name: jenkins
URL: http://<jenkins_ip>:8080/sonarqube-webhook/

This allows the Quality Gate stage to complete quickly instead of polling.


Security Tools

GitLeaks

Scans git history for secrets using pattern matching and entropy analysis. Detects API keys, tokens, passwords, private keys, and connection strings. Configured with --exit-code 0 so findings are reported but don't fail the build — allowing teams to review and rotate secrets without blocking deployment.

SonarQube

Performs deep static analysis of TypeScript source code. The Quality Gate checks:

  • 0 new bugs
  • 0 new vulnerabilities
  • 0 new security hotspots unreviewed
  • Code coverage threshold

Results are visible at http://<jenkins_ip>:9000/dashboard?id=DevSecMovie.

OWASP Dependency-Check

Cross-references all npm packages against the NVD CVE database. Particularly effective at catching known vulnerabilities in third-party libraries that developers may have missed. The NVD database is downloaded on first run (~342,000 records) and cached for subsequent runs.

Trivy

Aqua Security's open-source vulnerability scanner. Used at two points in the pipeline:

  1. Filesystem scan — catches vulnerabilities before building
  2. Image scan — catches vulnerabilities in the final container layers including OS packages

The nginx:1.27-alpine base image is chosen for minimal attack surface.

Checkov

Scans Terraform infrastructure code for security misconfigurations against hundreds of built-in policies. Checks for issues like:

  • Overly permissive security groups
  • Unencrypted storage
  • Missing logging
  • Public access misconfigurations
  • IAM policy violations

Monitoring

Prometheus and Grafana run as Docker containers on the Jenkins server.

Prometheus scrapes metrics from:

  • Jenkins (via Prometheus metrics plugin at /prometheus)
  • Node Exporter (system metrics: CPU, RAM, disk, network)

Grafana dashboards:

  • Jenkins performance dashboard (ID: 9964) — build duration, queue depth, success rate
  • Node Exporter full dashboard (ID: 1860) — system resources

Access Grafana at http://<jenkins_ip>:3000 — default credentials: admin/admin.


Cost Optimisation

This project is designed for learning and portfolio purposes. Stop instances when not in use to avoid unnecessary charges.

Estimated cost (running 8 hours/day):

Resource Monthly cost
Jenkins EC2 (t2.large) ~$12–15
App EC2 (t2.medium) ~$6–8
ECR storage ~$0.10
Elastic IPs (attached) $0
S3 remote state ~$0.01
Total ~$18–24

Stop instances when done:

aws ec2 stop-instances \
  --instance-ids \
  $(aws ec2 describe-instances \
    --filters "Name=tag:Name,Values=devsecmovie-jenkins" \
    --query "Reservations[0].Instances[0].InstanceId" \
    --output text --region us-east-1) \
  $(aws ec2 describe-instances \
    --filters "Name=tag:Name,Values=devsecmovie-app" \
    --query "Reservations[0].Instances[0].InstanceId" \
    --output text --region us-east-1) \
  --region us-east-1

Start instances again:

aws ec2 start-instances \
  --instance-ids \
  $(aws ec2 describe-instances \
    --filters "Name=tag:Name,Values=devsecmovie-jenkins" \
    --query "Reservations[0].Instances[0].InstanceId" \
    --output text --region us-east-1) \
  $(aws ec2 describe-instances \
    --filters "Name=tag:Name,Values=devsecmovie-app" \
    --query "Reservations[0].Instances[0].InstanceId" \
    --output text --region us-east-1) \
  --region us-east-1

Note: Elastic IPs are free while attached to running instances but incur a small charge (~$0.005/hour) when the instance is stopped. For longer breaks, consider releasing the EIPs and using terraform apply to reassign new ones when resuming.


Lessons Learned

Jenkins GPG key rotation — The Jenkins Debian repository rotated its GPG key from jenkins.io-2023.key to jenkins.io-2026.key. Bootstrap scripts must use the latest key URL or installation fails silently.

Vite environment variables — Vite requires all browser-accessible variables to be prefixed with VITE_. Variables are baked into the JavaScript bundle at build time, not injected at runtime. Both the variable name in the Dockerfile and the --build-arg in the Jenkinsfile must match exactly.

Multi-stage Docker builds — The API key only exists in the builder stage. The final nginx image contains only compiled static files — making it smaller, faster, and more secure.

Git history hygiene — Large binaries committed to git (such as Terraform provider plugins) bloat the repository significantly. The .gitignore must exclude terraform/.terraform/ before the first commit. Use git filter-repo to remove large files from history.

Checkov PATH issues — Tools installed via pipx install into user-specific directories not accessible by service accounts like jenkins. The binary must be copied (not symlinked) to a system-wide location with world-executable permissions, and the shebang must point to a Python interpreter accessible by all users.

Dynamic IPs in AWS — EC2 instances get new public IPs on stop/start unless an Elastic IP is attached. Security group rules that restrict SSH by IP need updating when the client's ISP assigns a new address. For development, using 0.0.0.0/0 for SSH is acceptable but should never be done in production.

SonarQube quality gate webhook — Without a webhook, the Jenkins quality gate stage polls SonarQube repeatedly and can time out. Adding a webhook at Administration → Webhooks pointing to http://<jenkins_ip>:8080/sonarqube-webhook/ makes the gate complete in seconds.


Acknowledgements

About

A Simple Movie Application built with React JS, Typescript and Tailwind CSS which allows user to search and view the trailer of both movies and TV series.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • TypeScript 72.4%
  • HCL 11.3%
  • Shell 7.8%
  • CSS 4.7%
  • Dockerfile 1.7%
  • JavaScript 1.1%
  • HTML 1.0%