Skip to content

Latest commit

Β 

History

50 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Hardened Headless E-Commerce REST API

Production-grade headless commerce backend engineered for automated DevSecOps pipelines

Java Spring Boot PostgreSQL AWS Terraform OWASP ZAP License: MIT


Table of Contents


Overview

This project is a REST API backend for a headless e-commerce platform, designed as a full DevSecOps reference implementation. Security is treated as a first-class citizen β€” not a post-deployment afterthought.

Every pull request into main triggers a fully automated 6-job DAG validation pipeline that runs secret scanning, SAST analysis, infrastructure compliance checks, SCA/container hardening, ephemeral AWS deployment, OWASP ZAP DAST validation, guaranteed teardown, and a final branch-protection gate β€” all before a single line of code can be merged.

Tech Stack

Layer Technology
Runtime Java 17 (Eclipse Temurin), Spring Boot 3.5.x
Database AWS RDS PostgreSQL 16, HikariCP connection pool
Infrastructure AWS VPC Β· ALB Β· EC2 Auto Scaling Group Β· RDS
IaC Terraform (modular, remote S3 backend state)
CI/CD GitHub Actions (DAG multi-job pipeline)
Security Tools Trufflehog Β· Semgrep Β· Checkov Β· Trivy Β· OWASP ZAP
Container Docker (eclipse-temurin:17-jre-alpine base)
Auth AWS OIDC Keyless Federation (no static credentials)

DevSecOps Pipeline Architecture

Pull requests trigger a fully parallelized Directed Acyclic Graph (DAG) workflow in GitHub Actions. Uncoupled static checks run in parallel; rigid compliance gates block cloud provisioning until all upstream jobs pass.

[ git push β†’ pull_request on main ]
               β”‚
               β–Ό
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚  JOB 1 β€” Static Security Scans β”‚  ← Runs parallel to nothing; blocks all downstream
 β”‚                                 β”‚
 β”‚  ● Trufflehog  (Secret Scan)    β”‚
 β”‚  ● Semgrep     (SAST / OWASP)   β”‚
 β”‚  ● Checkov     (IaC Compliance) β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚ needs: static-security-scans
                 β–Ό
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚  JOB 2 β€” Build & Verify         β”‚
 β”‚                                 β”‚
 β”‚  ● Maven compile + package      β”‚
 β”‚  ● Trivy FS   (SCA scan)        β”‚
 β”‚  ● Docker build                 β”‚
 β”‚  ● Trivy Image (container scan) β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚ needs: build-and-verify
                 β–Ό
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚  JOB 3 β€” Ephemeral AWS Deploy   β”‚
 β”‚                                 β”‚
 β”‚  ● Two-phase S3 staging         β”‚
 β”‚  ● VPC Β· ALB Β· ASG Β· RDS map   β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚ needs: ephemeral-deploy
                 β–Ό
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚  JOB 4 β€” OWASP ZAP DAST         β”‚
 β”‚                                 β”‚
 β”‚  ● ALB health-check polling     β”‚
 β”‚  ● OWASP ZAP baseline scan      β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
            β”‚ if: always()β”‚ needs: ephemeral-deploy + dast
            β–Ό             β–Ό
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚  JOB 5        β”‚  β”‚  JOB 6 β€” Branch Gate        β”‚
 β”‚  Teardown     β”‚  β”‚                             β”‚
 β”‚  (guaranteed) β”‚  β”‚  ● Atomic compliance status β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Security Gates in Detail

Job 1 β€” Static Security Scans

Tool Purpose Failure Behavior
Trufflehog Scans full git commit history for verified secrets (API keys, tokens, passwords) Hard fail β€” blocks pipeline
Semgrep SAST against p/java + p/owasp-top-ten rulesets Hard fail β€” blocks pipeline
Checkov Terraform IaC compliance (IMDSv2, S3 encryption, security group lockdown) Hard fail with curated skip_check profile for ephemeral sandbox constraints

Job 2 β€” Compilation & Supply Chain Vetting

Tool Purpose Failure Behavior
Maven Compiles with pinned tomcat.version: 10.1.55, postgresql.version: 42.7.11 and jackson-bom.version: 2.21.4 to block transitive RCE vulnerabilities Hard fail
Trivy FS SCA scan of all file system dependencies β€” zero tolerance for CRITICAL/HIGH CVEs Hard fail
Trivy Image Container layer analysis on eclipse-temurin:17-jre-alpine Hard fail

Job 6 β€” Branch Protection Gate

An umbrella job (secure-validation-gate) aggregates the outcome of all upstream jobs into a single atomic status check that GitHub's branch protection rules evaluate before allowing a merge to main.


Two-Phase Ephemeral Infrastructure

The pipeline solves a non-trivial distribution problem: GitHub Actions runner nodes are ephemeral VMs with no network path into the private AWS subnets where compute lives. The Two-Phase Staging Bucket Pattern bridges this gap cleanly.

Phase 1 β€” Storage Provisioning

Terraform targets only the S3 deployment bucket (module.compute.aws_s3_bucket.app_deploy in terraform/environments/staging):

  • Server-side encryption enabled (aws:kms)
  • Public access fully blocked
  • 24-hour lifecycle expiry rule (ephemeral data hygiene)

The runner then streams three artifacts into the bucket:

  • app.jar β€” the pre-vetted, pre-compiled Spring Boot binary
  • Dockerfile β€” a staging-optimized image built from the JAR, not from source
  • docker-compose.yml β€” the container orchestration spec

Why a separate Dockerfile? The development Dockerfile rebuilds from src/ and pom.xml, which don't exist on the EC2 host. The staging variant uses COPY app.jar directly, keeping the image minimal and reproducible.

Phase 2 β€” Compute Bootstrapping

Terraform applies the full network topology:

Internet
   β”‚
   β–Ό
AWS ALB (Public Subnets, HTTP:80)
   β”‚
   β–Ό
EC2 Auto Scaling Group (Private Subnets)
   β”‚ user_data bootstrap script:
   β”‚  1. Pull app.jar + Dockerfile + docker-compose.yml from S3 (via IAM profile)
   β”‚  2. Write /app/.env from Terraform-injected RDS coordinates
   β”‚  3. docker compose up
   β”‚
   β–Ό
AWS RDS PostgreSQL 16 (Isolated Subnet Group)

The database password is generated at apply time by Terraform (random_password), published to SSM Parameter Store as a KMS-encrypted SecureString, and injected into the launch template so the boot script can write it into /app/.env β€” no static credentials in source control, ever. The instance's IAM profile is scoped to read-only access on the artifact bucket only.

Credential-safe by construction. The .env is written with single-quoted shell echoes and the generated password excludes $ and other shell/Compose metacharacters, so the secret survives the shell β†’ .env β†’ Docker Compose round-trip intact instead of being silently mangled at boot.

Why decouple the database? Running PostgreSQL alongside the Java application inside a t3.micro (1 GB RAM) instance triggers the Linux OOM killer. Routing connections to managed RDS keeps the compute layer stateless and horizontally scalable.


Infrastructure Pattern Library

The Terraform footprint is a reusable module library, not a monolith. Small, single-responsibility, security-hardened modules are composed by thin per-environment roots β€” each with its own remote state. Adding an environment is configuration, not code duplication.

terraform/
β”œβ”€β”€ bootstrap/              # One-time backend: state bucket, KMS, GitHub OIDC role
β”œβ”€β”€ modules/               # Reusable building blocks (the pattern library)
β”‚   β”œβ”€β”€ network/           # VPC Β· 3 subnet tiers Β· routing Β· NAT Β· edge SGs
β”‚   β”œβ”€β”€ iam/               # EC2 instance role + profile (least-privilege S3 read)
β”‚   β”œβ”€β”€ database/          # Encrypted Multi-AZ RDS PostgreSQL 16 + generated secret
β”‚   β”œβ”€β”€ compute/           # ALB Β· Auto Scaling Group Β· ephemeral artifact bucket
β”‚   └── observability/     # VPC flow logs β†’ locked-down, self-expiring S3
└── environments/          # Composition roots (one state file each)
    β”œβ”€β”€ staging/           # Production-representative; deployed by the pipeline
    └── dev/               # Low-cost sandbox twin (single-AZ, no ASG burst)
Principle How it shows up
Single responsibility One concern per module; clean input/output contracts
No provider/backend in modules Environments own state + provider; modules own resources
DRY environments dev and staging share every module; differ only in tfvars
Cycle-free wiring iam ⇄ compute decoupled via a shared bucket name string
Namespaced by project_name dev and staging coexist in one account without collisions
Compliance built in Each module ships its own Checkov fixes (IMDSv2, KMS, PAB, …)

Full catalog, dependency graph, and how-to (new module / new environment): docs/terraform-module-usage.md.


Security Hardening

All security measures are implemented in code β€” no manual server configuration required.

1. HTTP Security Headers β€” SecurityHeadersFilter

A global jakarta.servlet.Filter (priority @Order(1)) intercepts every outbound HTTP response β€” regardless of controller, path, or status code β€” and injects the following headers:

Header Value Fixes
X-Content-Type-Options nosniff ZAP [10021] β€” MIME sniffing attacks
Cross-Origin-Resource-Policy same-origin ZAP [90004] β€” Cross-origin resource leakage
Cache-Control no-store ZAP [10049] β€” Sensitive response caching
X-Frame-Options DENY Clickjacking / UI redressing
X-XSS-Protection 0 Disables legacy broken browser XSS filter (OWASP recommendation)

2. Information Disclosure Controls β€” BaseUtilityController

Unmapped administrative paths that would otherwise trigger Spring Boot's default 500 error page (leaking framework details) are explicitly intercepted and return sanitized JSON responses:

Path Behavior HTTP Status
/actuator/ {"error": "Not Found"} β€” path existence is not confirmed 404
/api/v1/admin/ {"error": "Forbidden"} β€” structured, non-disclosive 403

3. Error Payload Sanitization β€” application.properties

Framework error signatures are stripped from all client-facing responses:

# Disable Spring Boot white-label error page
server.error.whitelabel.enabled=false

# Strip all framework debug information from error responses
server.error.include-message=never
server.error.include-binding-errors=never
server.error.include-stacktrace=never
server.error.include-exception=false

4. Structured Exception Handling β€” GlobalExceptionHandler

A @RestControllerAdvice handler produces consistent, OWASP-compliant error payloads for all exception types without leaking stack traces or internal class names:

{
  "timestamp": "2026-06-23T14:00:00Z",
  "status": 404,
  "error": "Not Found",
  "message": "Product with id 42 was not found.",
  "path": "/api/products/42"
}

5. OWASP ZAP DAST Scan Results

OWASP ZAP baseline scan results after hardening (production target):

PASS: Loosely Scoped Cookie          [90033]
PASS: X-Content-Type-Options         [10021]  ← Fixed by SecurityHeadersFilter
PASS: Information Disclosure         [10023]  ← Fixed by explicit endpoint handlers
PASS: Cross-Origin-Resource-Policy   [90004]  ← Fixed by SecurityHeadersFilter
PASS: Application Error Disclosure   [90022]  ← Fixed by explicit endpoint handlers
IGNORE: Non-Storable Content         [10049]  ← HTTP 500/204 are non-cacheable by spec

FAIL-NEW: 0  WARN-NEW: 0  PASS: 62+

Domain Data Model

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      1:1       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      1:N      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   User   β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ίβ”‚   Cart   β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ίβ”‚  CartItem  β”‚
β”‚          β”‚                β”‚          β”‚                β”‚            β”‚
β”‚ id       β”‚                β”‚ id       β”‚                β”‚ id         β”‚
β”‚ username β”‚                β”‚ user_id  β”‚                β”‚ cart_id    β”‚
β”‚ email    β”‚                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β”‚ product_id β”‚
β”‚createdAt β”‚                                            β”‚ quantity   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                            β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
     β”‚                                                        β”‚
     β”‚ 1:N                                                    β”‚ N:1
     β–Ό                                                        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      1:N      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      N:1     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Order   β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ίβ”‚ OrderItem β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ίβ”‚   Product   β”‚
β”‚           β”‚               β”‚           β”‚               β”‚             β”‚
β”‚ id        β”‚               β”‚ id        β”‚               β”‚ id          β”‚
β”‚ user_id   β”‚               β”‚ order_id  β”‚               β”‚ name        β”‚
β”‚ status    β”‚               β”‚ product_idβ”‚               β”‚ description β”‚
β”‚totalAmountβ”‚               β”‚ quantity  β”‚               β”‚ price       β”‚
β”‚ createdAt β”‚               β”‚priceAtPurchase            β”‚ category    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β”‚stockQuantityβ”‚
                                                        β”‚ createdAt   β”‚
                                                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Design decisions:

  • CartItem enforces a unique constraint on (cart_id, product_id) β€” duplicate POST calls increment quantity rather than creating duplicate rows.
  • OrderItem.priceAtPurchase snapshots the price at checkout time β€” product price changes never retroactively affect historical order data.
  • Order.@PrePersist null-guards createdAt to allow the DataSeeder to inject realistic historical timestamps for demo data.
  • All lazy-loaded associations use JOIN FETCH in repository queries to eliminate N+1 query patterns.

API Reference

All endpoints return application/json. Endpoints that operate on cart and order data require an X-User-Id header.

Users β€” /api/users

GET /api/users/{id} β€” Retrieve a user profile
curl http://localhost:8080/api/users/1
{
  "id": 1,
  "username": "alice_dev",
  "email": "alice.devlin@techcorp.io",
  "createdAt": "2026-06-16T17:00:00"
}
POST /api/users β€” Create a new user account
curl -X POST http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{ "username": "newuser", "email": "new@example.com" }'

Products β€” /api/products

GET /api/products β€” List all products with optional filters

Supports ?category= (exact match) and ?search= (case-insensitive substring).

curl "http://localhost:8080/api/products?category=Fitness&search=yoga"
POST /api/products β€” Create a new product

Validates: price β‰₯ 0.01, non-blank name, stock quantity β‰₯ 0.

curl -X POST http://localhost:8080/api/products \
  -H "Content-Type: application/json" \
  -d '{ "name": "Yoga Mat Pro", "price": 49.99, "category": "Fitness", "stockQuantity": 100 }'

Cart β€” /api/carts

Requires X-User-Id: {userId} header on all requests.

GET /api/carts β€” Retrieve the active cart with computed totals
curl http://localhost:8080/api/carts -H "X-User-Id: 1"
POST /api/carts/items β€” Add an item to the cart

Validates stock availability in real time. Duplicate product adds increment quantity.

curl -X POST http://localhost:8080/api/carts/items \
  -H "X-User-Id: 1" \
  -H "Content-Type: application/json" \
  -d '{ "productId": 3, "quantity": 2 }'

Orders β€” /api/orders

POST /api/orders/checkout β€” Atomic checkout transaction

Executes inside a single @Transactional boundary:

  1. Validates stock levels for every cart item
  2. Decrements inventory counters atomically
  3. Snapshots priceAtPurchase for each line item
  4. Creates the Order record with status PENDING
  5. Clears the user's cart
curl -X POST http://localhost:8080/api/orders/checkout \
  -H "X-User-Id: 1"

Project Structure

devsecops_project02/
β”‚
β”œβ”€β”€ .github/workflows/
β”‚   └── devsecops-pipeline.yml       # 6-job parallel DAG CI/CD pipeline
β”‚
β”œβ”€β”€ .zap/
β”‚   └── rules.tsv                    # OWASP ZAP custom rule overrides
β”‚
β”œβ”€β”€ docs/                            # Self-service, ops & IaC documentation
β”‚   β”œβ”€β”€ developer-self-service-guide.md
β”‚   β”œβ”€β”€ runbook.md
β”‚   β”œβ”€β”€ troubleshooting.md
β”‚   └── terraform-module-usage.md
β”‚
β”œβ”€β”€ terraform/                        # IaC pattern library (modules + environments)
β”‚   β”œβ”€β”€ bootstrap/                   # One-time backend: state bucket, KMS, OIDC role
β”‚   β”œβ”€β”€ modules/                     # Reusable, single-responsibility building blocks
β”‚   β”‚   β”œβ”€β”€ network/                 # VPC, subnets, routing, NAT, edge security groups
β”‚   β”‚   β”œβ”€β”€ iam/                     # EC2 instance role + profile (least-privilege S3)
β”‚   β”‚   β”œβ”€β”€ database/                # Encrypted Multi-AZ RDS PostgreSQL + secret
β”‚   β”‚   β”œβ”€β”€ compute/                 # ALB + Auto Scaling Group + artifact bucket
β”‚   β”‚   └── observability/           # VPC flow logs β†’ locked-down S3
β”‚   └── environments/                # Composition roots (one state file each)
β”‚       β”œβ”€β”€ staging/                 # Production-representative; deployed by CI
β”‚       └── dev/                     # Low-cost sandbox twin
β”‚
β”œβ”€β”€ Dockerfile                       # Multi-stage container build (local)
β”œβ”€β”€ docker-compose.yml               # Local development stack
β”œβ”€β”€ pom.xml                          # Dependency management with version pins
β”‚
└── src/main/
    β”œβ”€β”€ java/com/ecommerce/api/
    β”‚   β”œβ”€β”€ config/
    β”‚   β”‚   β”œβ”€β”€ SecurityHeadersFilter.java   # Global HTTP security header injector
    β”‚   β”‚   └── DataSeeder.java              # Idempotent demo data seeder
    β”‚   β”‚
    β”‚   β”œβ”€β”€ controller/
    β”‚   β”‚   β”œβ”€β”€ BaseUtilityController.java   # Root, robots.txt, sitemap, safe fallbacks
    β”‚   β”‚   β”œβ”€β”€ UserController.java
    β”‚   β”‚   β”œβ”€β”€ ProductController.java
    β”‚   β”‚   β”œβ”€β”€ CartController.java
    β”‚   β”‚   └── OrderController.java
    β”‚   β”‚
    β”‚   β”œβ”€β”€ exception/
    β”‚   β”‚   └── GlobalExceptionHandler.java  # Unified OWASP-compliant error responses
    β”‚   β”‚
    β”‚   β”œβ”€β”€ model/                           # JPA entities (User, Product, Cart, Order…)
    β”‚   β”œβ”€β”€ repository/                      # Spring Data JPA repositories
    β”‚   └── service/                         # Business logic layer
    β”‚
    └── resources/
        └── application.properties           # Hardened production configuration

Documentation

Operational and self-service documentation lives in docs/:

Guide For Covers
Developer Self-Service Any developer Local loop, standing up your own dev sandbox, the paved road to staging, changing infra safely
Terraform Module Usage Anyone touching IaC Module catalog, dependency graph, adding a new module/environment
Runbook On-call / operators Deploy, promote, roll back, tear down, secret rotation, on-call triage
Troubleshooting Everyone Symptom β†’ cause β†’ fix across IaC, runtime, and pipeline gates

Local Development

Prerequisites

Requirement Minimum Version
JDK 17 (Eclipse Temurin recommended)
Maven 3.9+
Docker Engine 24+ with Compose Plugin

Start the Full Stack

# Start PostgreSQL 16 + Spring Boot API in isolated containers
docker compose up -d --build

# Stream live application logs
docker compose logs -f api

# Stop and remove containers
docker compose down

Run Tests & Build

# Run the full test suite
mvn test

# Compile and package the JAR (skip tests)
mvn clean package -DskipTests=true

Built with security-first principles Β· Designed for automated validation pipelines

About

Production-inspired DevSecOps CI/CD pipeline with GitHub Actions, Terraform, AWS, Maven, Docker and security gates.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages