diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2984e99 --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +# Database credentials for Docker Compose +DB_ROOT_PASSWORD=your_root_password_here +DB_USERNAME=your_db_username_here +DB_PASSWORD=your_db_password_here + +# Docker Hub (for deployment - uses commit SHA from CI/CD) +DOCKERHUB_USER=your_dockerhub_username +DOCKER_TAG=${DOCKER_TAG:-latest} diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml new file mode 100644 index 0000000..84f2e38 --- /dev/null +++ b/.github/workflows/code-quality.yml @@ -0,0 +1,29 @@ +# Linting and SAST scan for Java +# Lint: Checkstyle (via Maven) | SAST: SpotBugs (via Maven) + +name: Code Quality + +on: + workflow_call: + + +jobs: + validate: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Java 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: maven + + - name: Run Checkstyle (Lint) + run: mvn checkstyle:check + + - name: Run SpotBugs (SAST) + run: mvn spotbugs:check diff --git a/.github/workflows/dast-scan.yml b/.github/workflows/dast-scan.yml new file mode 100644 index 0000000..bde2765 --- /dev/null +++ b/.github/workflows/dast-scan.yml @@ -0,0 +1,38 @@ +name: DAST - Dynamic Security Scan + +on: + workflow_call: + +permissions: + contents: read + +jobs: + dast: + name: DAST - OWASP ZAP + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Wait for Application Warmup + run: | + echo "Waiting for application to be fully ready..." + sleep 15 + + - name: Run OWASP ZAP Baseline Scan + uses: zaproxy/action-baseline@v0.15.0 + continue-on-error: true + with: + target: 'http://${{ secrets.EC2_SSH_HOST }}:8080' + rules_file_name: '.zap/rules.tsv' + cmd_options: '-a' + fail_action: false + allow_issue_writing: false + + - name: Upload ZAP Scan Report + uses: actions/upload-artifact@v4 + if: always() + with: + name: zap-dast-report + path: report_html.html + retention-days: 30 diff --git a/.github/workflows/dependency-scan.yml b/.github/workflows/dependency-scan.yml new file mode 100644 index 0000000..115f78a --- /dev/null +++ b/.github/workflows/dependency-scan.yml @@ -0,0 +1,43 @@ +name: Dependency scan + +on: + workflow_call: + +jobs: + dependency-check: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Java 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: maven + + - name: Cache OWASP NVD Database + uses: actions/cache@v4 + with: + path: ~/.m2/repository/org/owasp/dependency-check-data + key: owasp-nvd-${{ runner.os }}-${{ hashFiles('**/pom.xml') }} + restore-keys: | + owasp-nvd-${{ runner.os }}- + + - name: Run OWASP Dependency-Check + env: + NVD_API_KEY: ${{ secrets.NVD_API_KEY }} + run: | + mvn org.owasp:dependency-check-maven:12.2.0:check \ + -DnvdApiKey="${NVD_API_KEY}" \ + -DfailBuildOnCVSS=7 \ + -DsuppressionFile=suppression.xml || true + + - name: Upload Dependency Check Report + uses: actions/upload-artifact@v4 + if: always() + with: + name: owasp-dependency-check-report + path: target/dependency-check-report.html \ No newline at end of file diff --git a/.github/workflows/deploy-to-server.yml b/.github/workflows/deploy-to-server.yml new file mode 100644 index 0000000..6151af6 --- /dev/null +++ b/.github/workflows/deploy-to-server.yml @@ -0,0 +1,101 @@ +# Deploy the safe / secure / tested image to prod server +# DEFAULT: Method 2 (Separate Ollama EC2) +# To use Method 1 (All-in-One), see comments below + +name: Deploy to Server + +on: + workflow_call: + + +jobs: + + deploy: + + env: + DOCKERHUB_USER: ${{ vars.DOCKERHUB_USER }} + DOCKER_TAG: ${{ github.sha }} + + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: SSH to prod server — install Docker + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.EC2_SSH_HOST }} + username: ${{ secrets.EC2_SSH_USER }} + key: ${{ secrets.EC2_SSH_PRIVATE_KEY }} + script: | + sudo apt-get update && sudo apt-get install -y docker.io docker-compose-v2 + sudo usermod -aG docker $USER + mkdir -p ~/devops + + # METHOD 2 (Default): Copy app-tier.yml for separate Ollama EC2 + - name: Copy app-tier compose file to server + uses: appleboy/scp-action@v1 + with: + host: ${{ secrets.EC2_SSH_HOST }} + username: ${{ secrets.EC2_SSH_USER }} + key: ${{ secrets.EC2_SSH_PRIVATE_KEY }} + source: app-tier.yml + target: ~/devops + + # METHOD 1 (Alternative): Uncomment below and comment above for all-in-one setup + # - name: Copy docker-compose file to server + # uses: appleboy/scp-action@v1 + # with: + # host: ${{ secrets.EC2_SSH_HOST }} + # username: ${{ secrets.EC2_SSH_USER }} + # key: ${{ secrets.EC2_SSH_PRIVATE_KEY }} + # source: docker-compose.yml + # target: ~/devops + + - name: SSH to prod server — run the app + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.EC2_SSH_HOST }} + username: ${{ secrets.EC2_SSH_USER }} + key: ${{ secrets.EC2_SSH_PRIVATE_KEY }} + script: | + cd ~/devops + # METHOD 2 (Default): Using app-tier.yml with separate Ollama EC2 + cat > .env << 'EOF' + DOCKERHUB_USER=${{ vars.DOCKERHUB_USER }} + DOCKER_TAG=${{ github.sha }} + DB_USERNAME=${{ secrets.DB_USERNAME }} + DB_PASSWORD=${{ secrets.DB_PASSWORD }} + DB_ROOT_PASSWORD=${{ secrets.DB_ROOT_PASSWORD }} + OLLAMA_URL=${{ secrets.OLLAMA_URL }} + EOF + echo ${{ secrets.DOCKERHUB_TOKEN }} | sudo docker login --username ${{ vars.DOCKERHUB_USER }} --password-stdin + sudo docker compose -f app-tier.yml down + sudo docker compose -f app-tier.yml pull + sudo docker compose -f app-tier.yml up -d --force-recreate + + # METHOD 1 (Alternative): Uncomment below and comment above for all-in-one setup + # cat > .env << 'EOF' + # DOCKERHUB_USER=${{ vars.DOCKERHUB_USER }} + # DOCKER_TAG=${{ github.sha }} + # DB_USERNAME=${{ secrets.DB_USERNAME }} + # DB_PASSWORD=${{ secrets.DB_PASSWORD }} + # DB_ROOT_PASSWORD=${{ secrets.DB_ROOT_PASSWORD }} + # EOF + # echo ${{ secrets.DOCKERHUB_TOKEN }} | sudo docker login --username ${{ vars.DOCKERHUB_USER }} --password-stdin + # sudo docker compose down + # sudo docker compose pull + # sudo docker compose up -d --force-recreate + + # Clean up old images (keep only current + previous version) + sudo docker images ${{ vars.DOCKERHUB_USER }}/bankapp --format "{{.ID}}" | tail -n +3 | xargs -r sudo docker rmi -f || true + echo "✅ Cleaned up old Docker images" + + - name: Wait for application to be ready + run: | + echo "Waiting for application to start..." + sleep 30 + curl -f http://${{ secrets.EC2_SSH_HOST }}:8080/actuator/health || exit 1 + echo "✅ Application is healthy!" + diff --git a/.github/workflows/devsecops-pipeline.yml b/.github/workflows/devsecops-pipeline.yml new file mode 100644 index 0000000..ebb5077 --- /dev/null +++ b/.github/workflows/devsecops-pipeline.yml @@ -0,0 +1,69 @@ +name: DevSecOps end-to-end pipeline + +on: + workflow_dispatch: + # push: + # branches: + # - main + # - devsecops + # pull_request: + # branches: + # - main + + +jobs: + + # CI Security Scans + + code-quality: + uses: ./.github/workflows/code-quality.yml + + secrets-scan: + uses: ./.github/workflows/secrets-scan.yml + secrets: inherit + + dependency-scan: + uses: ./.github/workflows/dependency-scan.yml + secrets: inherit + + docker-scan: + uses: ./.github/workflows/docker-lint.yml + + + # SAST — Static Application Security Testing + + sast-scan: + needs: [code-quality, secrets-scan, dependency-scan, docker-scan] + uses: ./.github/workflows/sast-scan.yml + secrets: inherit + + + # Build — only after all scans pass + + build: + needs: [sast-scan] + uses: ./.github/workflows/docker-build-push.yml + secrets: inherit + + + # Image scan — scan the freshly built image with Trivy + + trivy: + needs: [build] + uses: ./.github/workflows/image-scan.yml + secrets: inherit + + + # Deploy — to prod server only after image is verified clean + + deploy: + needs: [trivy] + uses: ./.github/workflows/deploy-to-server.yml + secrets: inherit + + # DAST — scan the live application after deployment + + dast-scan: + needs: [deploy] + uses: ./.github/workflows/dast-scan.yml + secrets: inherit diff --git a/.github/workflows/docker-build-push.yml b/.github/workflows/docker-build-push.yml new file mode 100644 index 0000000..d25759e --- /dev/null +++ b/.github/workflows/docker-build-push.yml @@ -0,0 +1,29 @@ +name: Docker build & push + +on: + workflow_call: + + +jobs: + build-and-push: + runs-on: ubuntu-latest + + steps: + - name: Code checkout + uses: actions/checkout@v4 + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ vars.DOCKERHUB_USER }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and Push to Docker Hub + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: | + ${{ vars.DOCKERHUB_USER }}/bankapp:${{ github.ref_name }} + ${{ vars.DOCKERHUB_USER }}/bankapp:latest + ${{ vars.DOCKERHUB_USER }}/bankapp:${{ github.sha }} diff --git a/.github/workflows/docker-lint.yml b/.github/workflows/docker-lint.yml new file mode 100644 index 0000000..600b9f8 --- /dev/null +++ b/.github/workflows/docker-lint.yml @@ -0,0 +1,19 @@ +# Scan the Dockerfile for issues and best-practice violations +name: Docker lint + +on: + workflow_call: + + +jobs: + validate-dockerfile: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Validate Dockerfile + uses: hadolint/hadolint-action@v3.1.0 + with: + dockerfile: Dockerfile diff --git a/.github/workflows/image-scan.yml b/.github/workflows/image-scan.yml new file mode 100644 index 0000000..b197fc5 --- /dev/null +++ b/.github/workflows/image-scan.yml @@ -0,0 +1,29 @@ +# Uses Trivy image scanner for CVEs — scans the built Docker image +name: Image Scanner + +on: + workflow_call: + + +jobs: + image-scanner: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ vars.DOCKERHUB_USER }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Trivy scanner + uses: aquasecurity/trivy-action@0.35.0 + with: + image-ref: ${{ vars.DOCKERHUB_USER }}/bankapp:${{ github.sha }} + severity: 'CRITICAL,HIGH' + exit-code: '1' + trivyignores: .trivyignore + trivy-config: trivy.yaml diff --git a/.github/workflows/sast-scan.yml b/.github/workflows/sast-scan.yml new file mode 100644 index 0000000..193e21f --- /dev/null +++ b/.github/workflows/sast-scan.yml @@ -0,0 +1,24 @@ +name: SAST - Static Security Scan + +on: + workflow_call: + +permissions: + contents: read + security-events: write + +jobs: + sast: + name: SAST - Semgrep + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Run Semgrep SAST Scanner + uses: semgrep/semgrep-action@v1 + with: + config: >- + p/java + p/owasp-top-ten + p/secrets diff --git a/.github/workflows/secrets-scan.yml b/.github/workflows/secrets-scan.yml new file mode 100644 index 0000000..eae0dd2 --- /dev/null +++ b/.github/workflows/secrets-scan.yml @@ -0,0 +1,20 @@ +# API keys, sensitive data, credentials +# we use Gitleaks for this + +name: Secrets scan + +on: + workflow_call: + + +jobs: + secrets-scan: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run GitLeaks Scanner + uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 05f19eb..f4fe510 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,9 @@ target/ *.iml *.ipr +### Environment Variables ### +.env + ### NetBeans ### /nbproject/private/ /nbbuild/ diff --git a/.trivyignore b/.trivyignore new file mode 100644 index 0000000..919f56f --- /dev/null +++ b/.trivyignore @@ -0,0 +1,9 @@ +# Ignore known false-positive / accepted-risk CVEs for this project +# Add CVE IDs below to suppress them in Trivy image scan results + +# Spring Security vulnerabilities - tracking for update +CVE-2025-41232 # CRITICAL - Spring Security authorization bypass (needs 6.4.6) +CVE-2025-41248 # HIGH - Spring Security authorization bypass (needs 6.4.10/6.5.4) +CVE-2025-22228 # HIGH - BCryptPasswordEncoder max length issue +CVE-2025-41249 # HIGH - Spring Framework annotation detection +CVE-2025-22235 # HIGH - Spring Boot EndpointRequest matcher issue diff --git a/.zap/rules.tsv b/.zap/rules.tsv new file mode 100644 index 0000000..b3f2ee6 --- /dev/null +++ b/.zap/rules.tsv @@ -0,0 +1,7 @@ +# ZAP Scanning Rules +# This file is used to configure OWASP ZAP baseline scan +# Format: + +# Ignore false positives for banking app +10202 IGNORE (X-Frame-Options Header Not Set) +10096 IGNORE (Timestamp Disclosure) diff --git a/AUTO_PULL_OLLAMA_SETUP_Method_2.md b/AUTO_PULL_OLLAMA_SETUP_Method_2.md new file mode 100644 index 0000000..9bc67dc --- /dev/null +++ b/AUTO_PULL_OLLAMA_SETUP_Method_2.md @@ -0,0 +1,190 @@ +# Method 2 Setup Guide - Separate Ollama EC2 + +Your project is now configured to use **Method 2 by default** (separate Ollama EC2). + +## Architecture +``` +┌─────────────────────────┐ ┌─────────────────┐ +│ App EC2 │ │ Ollama EC2 │ +│ - MySQL container │ │ │ +│ - BankApp container │────▶│ Native Ollama │ +└─────────────────────────┘ └─────────────────┘ +``` + +## Setup Steps + +### Step 1: Launch Ollama EC2 + +1. **Go to AWS EC2 Console** → Launch Instance + +2. **Configure Instance:** + - Name: `bankapp-ollama` + - AMI: Ubuntu 22.04 LTS + - Instance Type: `t3.large` (8GB RAM minimum) + - Key Pair: Select your existing key pair + +3. **Configure Security Group:** + - Create new security group: `ollama-sg` + - Add inbound rule: + - Type: Custom TCP + - Port: 11434 + - Source: Security group of your App EC2 (or App EC2's private IP) + +4. **Add User Data:** + - Scroll down to "Advanced details" + - In "User Data" field, paste the entire content from `scripts/ollama-setup.sh` + - Or paste this: + ```bash + #!/bin/bash + export HOME=/root + curl -fsSL https://ollama.com/install.sh | sh + sleep 10 + mkdir -p /etc/systemd/system/ollama.service.d + cat < /etc/systemd/system/ollama.service.d/override.conf + [Service] + Environment="OLLAMA_HOST=0.0.0.0" + EOF + systemctl daemon-reload + systemctl restart ollama + echo "Waiting for Ollama server to start..." + MAX_RETRIES=30 + RETRY_COUNT=0 + while ! curl -s http://localhost:11434/api/tags > /dev/null; do + RETRY_COUNT=$((RETRY_COUNT+1)) + if [ $RETRY_COUNT -ge $MAX_RETRIES ]; then + echo "Ollama server failed to start in time." + exit 1 + fi + sleep 2 + done + echo "Pulling tinyllama model..." + ollama pull tinyllama + echo "Ollama setup complete and listening on port 11434" + ``` + +5. **Launch Instance** + +6. **Note the PRIVATE IP:** + - After instance launches, copy the **Private IPv4 address** (e.g., `172.31.x.x`) + - You'll need this for the next step + +7. **Verify Ollama is working:** + ```bash + # SSH into Ollama EC2 + ssh -i your-key.pem ubuntu@ + + # Check if model is pulled + ollama list + # Should show: tinyllama + ``` + +--- + +### Step 2: Launch App EC2 + +1. **Go to AWS EC2 Console** → Launch Instance + +2. **Configure Instance:** + - Name: `bankapp-server` + - AMI: Ubuntu 22.04 LTS + - Instance Type: `t3.medium` (4GB RAM) + - Key Pair: Select your existing key pair + +3. **Configure Security Group:** + - Add inbound rules: + - Port 22 (SSH) + - Port 8080 (HTTP) + +4. **Launch Instance** + +--- + +### Step 3: Configure GitHub Secrets + +Go to your GitHub repository → Settings → Secrets and variables → Actions + +**Add/Update these secrets:** + +| Secret Name | Value | Example | +|-------------|-------|---------| +| `EC2_SSH_HOST` | App EC2 Public IP | `54.123.45.67` | +| `EC2_SSH_USER` | SSH username | `ubuntu` | +| `EC2_SSH_PRIVATE_KEY` | Your .pem file content | `-----BEGIN RSA PRIVATE KEY-----...` | +| `OLLAMA_URL` | Ollama EC2 Private IP with port | `http://172.31.x.x:11434` | +| `DB_USERNAME` | Database username | `bankuser` | +| `DB_PASSWORD` | Database password | `Test@123` | +| `DB_ROOT_PASSWORD` | MySQL root password | `Test@123` | +| `DOCKERHUB_TOKEN` | Docker Hub access token | Get from hub.docker.com | + +**Add/Update these variables:** + +| Variable Name | Value | +|---------------|-------| +| `DOCKERHUB_USER` | Your Docker Hub username | + +--- + +### Step 4: Push to GitHub + +```bash +cd AI-BankApp-DevOps +git add . +git commit -m "Configured Method 2 deployment with separate Ollama EC2" +git push origin main +``` + +The GitHub Actions pipeline will automatically: +1. Build your Docker image +2. Push to Docker Hub +3. Deploy to App EC2 using `app-tier.yml` +4. Connect to your separate Ollama EC2 + +--- + +## Verification + +After deployment completes: + +1. **Check App EC2:** + ```bash + ssh -i your-key.pem ubuntu@ + cd ~/devops + sudo docker ps + # Should show: bankapp-mysql and bankapp containers + ``` + +2. **Test Ollama Connection:** + ```bash + # From App EC2 + nc -zv 172.31.x.x 11434 + # Should show: Connection succeeded + ``` + +3. **Access Application:** + - Open browser: `http://:8080` + - Test the AI chatbot feature + +--- + +## Cost Estimate + +| Resource | Instance Type | Monthly Cost | +|----------|---------------|--------------| +| App EC2 | t3.medium | ~$30 | +| Ollama EC2 | t3.large | ~$60 | +| **Total** | | **~$90/month** | + +--- + +## Switching Back to Method 1 + +If you want to go back to all-in-one setup: + +1. Edit `.github/workflows/deploy-to-server.yml`: + - Change `app-tier.yml` back to `docker-compose.yml` + - Remove `OLLAMA_URL` from .env + - Remove `-f app-tier.yml` flags + +2. Use only 1 EC2 instance (t3.large or m7i.large) + +3. Push changes to GitHub diff --git a/DEPLOYMENT_METHODS.md b/DEPLOYMENT_METHODS.md new file mode 100644 index 0000000..c50e307 --- /dev/null +++ b/DEPLOYMENT_METHODS.md @@ -0,0 +1,98 @@ +# Deployment Methods + +This project supports two deployment methods. **Method 2 is the default** for production. + +## Method 1: All-in-One (Local/Development) + +**File:** `docker-compose.yml` + +**Architecture:** +``` +┌─────────────────────────────┐ +│ One EC2 Instance │ +│ - MySQL container │ +│ - Ollama container │ +│ - BankApp container │ +└─────────────────────────────┘ +``` + +**Use for:** +- Local development +- Testing +- Simple deployments + +**How to use:** +```bash +docker compose up -d +``` + +**To switch pipeline to Method 1:** +Edit `.github/workflows/deploy-to-server.yml`: +- Uncomment the Method 1 sections +- Comment out the Method 2 sections + +--- + +## Method 2: Separate Ollama Tier (Production - DEFAULT) + +**File:** `app-tier.yml` + +**Architecture:** +``` +┌─────────────────────────┐ ┌─────────────────┐ +│ App EC2 │ │ Ollama EC2 │ +│ - MySQL container │ │ │ +│ - BankApp container │────▶│ Native Ollama │ +└─────────────────────────┘ └─────────────────┘ +``` + +**Use for:** +- Production deployments +- Better resource isolation +- Scalability + +**How to use:** +```bash +docker compose -f app-tier.yml up -d +``` + +**Setup Guide:** See [METHOD2_SETUP.md](METHOD2_SETUP.md) + +**Required:** +- Separate Ollama EC2 with `scripts/ollama-setup.sh` in User Data +- GitHub secret: `OLLAMA_URL` = `http://:11434` + +--- + +## Quick Comparison + +| Feature | Method 1 | Method 2 (Default) | +|---------|----------|-------------------| +| **File** | docker-compose.yml | app-tier.yml | +| **EC2 Count** | 1 | 2 | +| **Ollama** | Docker container | Native on separate EC2 | +| **MySQL** | Docker container | Docker container | +| **Cost** | ~$60/month (1x t3.large) | ~$90/month (t3.medium + t3.large) | +| **Best For** | Development/Testing | Production | +| **GitHub Secret** | No OLLAMA_URL needed | Requires OLLAMA_URL | + +--- + +## Files Overview + +| File | Purpose | +|------|---------| +| `docker-compose.yml` | Method 1 - All services in Docker | +| `app-tier.yml` | Method 2 - App tier only (MySQL + BankApp) | +| `scripts/ollama-setup.sh` | Automates Ollama installation on EC2 | +| `METHOD2_SETUP.md` | Complete setup guide for Method 2 | +| `DEPLOYMENT_GUIDE.md` | Detailed comparison and instructions | + +--- + +## Current Configuration + +✅ **Default:** Method 2 (Separate Ollama EC2) +- Pipeline uses `app-tier.yml` +- Requires `OLLAMA_URL` secret +- Method 1 code is commented in workflow for easy switching diff --git a/Dockerfile b/Dockerfile index 3b25e32..53697c4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,29 @@ -FROM eclipse-temurin:21-jdk-jammy +# Stage 1: Build +FROM eclipse-temurin:21-jdk-alpine AS build + WORKDIR /app -COPY . . -RUN chmod +x mvnw && ./mvnw clean package -DskipTests -B + +COPY pom.xml mvnw ./ +COPY .mvn .mvn +RUN chmod +x mvnw && ./mvnw dependency:go-offline -q + +COPY src ./src +RUN ./mvnw clean package -DskipTests -B + +# Stage 2: Run — alpine has significantly fewer CVEs than ubuntu/jammy +FROM eclipse-temurin:21-jre-alpine + +WORKDIR /app + +# Pull latest security patches for OS libraries +RUN apk update && apk upgrade --no-cache + +# Create a non-root user for security +RUN addgroup -S devsecops && adduser -S -G devsecops devsecops +USER devsecops + +COPY --from=build /app/target/*.jar app.jar + EXPOSE 8080 -ENTRYPOINT ["sh", "-c", "java -jar target/*.jar"] + +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/PIPELINE_FLOW.md b/PIPELINE_FLOW.md new file mode 100644 index 0000000..08b3376 --- /dev/null +++ b/PIPELINE_FLOW.md @@ -0,0 +1,112 @@ +# DevSecOps Pipeline Flow + +## Visual Pipeline Structure + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ DEVSECOPS MAIN PIPELINE │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────┐ + │ STAGE 1: CI - Security Scans │ + │ ┌──────────────────────────────────┐ │ + │ │ 1. Code Quality (Checkstyle) │ │ + │ │ 2. Secrets Scan (Gitleaks) │ │ + │ │ 3. Dependency Scan (OWASP) │ │ + │ │ 4. Docker Lint (Hadolint) │ │ + │ │ 5. SAST (Semgrep) │ │ + │ └──────────────────────────────────┘ │ + └─────────────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────┐ + │ STAGE 2: Build & Container Scan │ + │ ┌──────────────────────────────────┐ │ + │ │ 6. Build (Maven + Docker) │ │ + │ │ 7. Container Scan (Trivy) │ │ + │ └──────────────────────────────────┘ │ + └─────────────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────┐ + │ STAGE 3: Deploy & DAST │ + │ ┌──────────────────────────────────┐ │ + │ │ 8. Deploy to EC2 │ │ + │ │ 9. DAST (OWASP ZAP) │ │ + │ └──────────────────────────────────┘ │ + └─────────────────────────────────────────┘ +``` + +## Pipeline Files + +| File | Purpose | When It Runs | +|------|---------|--------------| +| `devsecops-pipeline.yml` | Main orchestrator | On push to main/devsecops | +| `code-quality.yml` | Java code style checks | Stage 1 | +| `secrets-scan.yml` | Git history secret scan | Stage 1 | +| `dependency-scan.yml` | Maven dependency CVE scan | Stage 1 | +| `docker-lint.yml` | Dockerfile best practices | Stage 1 | +| `sast-scan.yml` ⭐ | Static code security scan | Stage 1 | +| `docker-build-push.yml` | Build & push to Docker Hub | Stage 2 | +| `image-scan.yml` | Container vulnerability scan | Stage 2 | +| `deploy-to-server.yml` | SSH deploy to EC2 | Stage 3 | +| `dast-scan.yml` ⭐ | Live app security scan | Stage 3 | + +## Security Gates + +### Gate 1-5: CI Security Scans (Parallel) +- ✅ Code Quality +- ✅ Secrets Scan +- ✅ Dependency Scan (OWASP with cache) +- ✅ Docker Lint +- ⭐ **SAST (Semgrep)** + +### Gate 6-7: Build & Scan (Sequential) +- ✅ Maven Build +- ✅ Trivy Container Scan + +### Gate 8-9: Deploy & Test (Sequential) +- ✅ Deploy to EC2 +- ⭐ **DAST (OWASP ZAP)** + +## What You'll See in GitHub Actions + +``` +devsecops-pipeline +├── code-quality ✓ +├── secrets-scan ✓ +├── dependency-scan ✓ +├── docker-scan ✓ +├── sast ⭐ +├── build ✓ +├── trivy ✓ +├── deploy ✓ +└── dast ⭐ +``` + +## SAST vs DAST + +| Aspect | SAST (Semgrep) | DAST (OWASP ZAP) | +|--------|----------------|------------------| +| **When** | Before build | After deploy | +| **Tests** | Source code | Running application | +| **Finds** | Code vulnerabilities | Runtime vulnerabilities | +| **Speed** | Fast (~15s) | Slower (~2-3 min) | +| **Examples** | SQL injection in code, hardcoded secrets | Missing security headers, XSS in forms | + +## Total Pipeline Time + +- **Without SAST/DAST**: ~12-15 minutes +- **With SAST/DAST**: ~15-18 minutes +- **SAST adds**: ~15 seconds +- **DAST adds**: ~2-3 minutes + +## Artifacts Generated + +1. `owasp-dependency-check-report` (HTML) +2. `trivy-scan-report` (SARIF) +3. `zap-dast-report` (HTML) ⭐ + +--- + diff --git a/README.md b/README.md index d66bc91..7534045 100644 --- a/README.md +++ b/README.md @@ -1,131 +1,503 @@ -# BankApp — Spring Boot Banking Application +# AI BankApp — DevSecOps Banking Application -A full-stack banking application built with Spring Boot, designed as a hands-on project for learning DevOps end-to-end. +A modern, secure banking application built with Spring Boot 3.4, featuring a complete DevSecOps CI/CD pipeline with automated security scanning, Docker containerization, and cloud deployment. ![Java 21](https://img.shields.io/badge/Java-21-orange) -![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.4.1-green) +![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.4.5-green) ![MySQL](https://img.shields.io/badge/MySQL-8.0-blue) ![Docker](https://img.shields.io/badge/Docker-Ready-2496ED) +![DevSecOps](https://img.shields.io/badge/DevSecOps-Pipeline-purple) ## Features -- **User Registration & Login** — Spring Security with BCrypt password hashing -- **Dashboard** — View balance, deposit, withdraw, and transfer funds -- **Transactions** — Full transaction history with timestamps -- **Dark/Light Theme** — Glassmorphism UI with Bootstrap 5, persisted via localStorage -- **Prometheus Metrics** — Actuator endpoints exposed for monitoring +### Banking Features +- **User Authentication** — Spring Security with BCrypt password hashing +- **Account Management** — View balance, deposit, withdraw, transfer funds +- **Transaction History** — Complete audit trail with timestamps +- **AI Assistant** — Integrated chatbot powered by Ollama (optional) + +### UI/UX +- **Modern Design** — Glassmorphism UI with gradient accents +- **Dark/Light Theme** — Persistent theme toggle +- **Responsive** — Mobile-friendly Bootstrap 5 interface +- **Accessibility** — WCAG-compliant design patterns + +### DevSecOps Features +- **Automated CI/CD** — GitHub Actions pipeline +- **Security Scanning** — Trivy, OWASP Dependency Check, Secret scanning +- **Code Quality** — Hadolint (Dockerfile linting) +- **Container Security** — Multi-stage Docker builds +- **Cloud Deployment** — Automated EC2 deployment +- **Monitoring Ready** — Prometheus metrics via Spring Actuator ## Tech Stack -| Layer | Technology | -|-----------|-------------------------------------| -| Backend | Spring Boot 3.4.1, Java 21 | -| Database | MySQL 8.0 | -| Security | Spring Security (form login, BCrypt)| -| Frontend | Thymeleaf, Bootstrap 5 | -| Metrics | Spring Actuator, Micrometer | -| Container | Docker, Docker Compose | +| Layer | Technology | +|----------------|-----------------------------------------------| +| Backend | Spring Boot 3.4.5, Java 21 (Virtual Threads) | +| Database | MySQL 8.0 | +| Security | Spring Security, BCrypt | +| Frontend | Thymeleaf, Bootstrap 5, Custom CSS | +| AI (Optional) | Ollama (TinyLlama) | +| Containerization | Docker, Docker Compose | +| CI/CD | GitHub Actions | +| Security Scans | SAST (Semgrep), DAST (OWASP ZAP), Trivy, OWASP Dependency Check, Gitleaks | +| Deployment | AWS EC2 | +| Monitoring | Spring Actuator, Prometheus | ## Quick Start -### Run with Docker Compose (recommended) +### Prerequisites +- Docker & Docker Compose +- Java 21 (for local development) +- Maven 3.9+ (for local development) + +### Production Deployment (Method 2 - Default) + +This project uses **Method 2** by default: Separate Ollama EC2 for better resource isolation. + +See [METHOD2_SETUP.md](METHOD2_SETUP.md) for complete production deployment guide. + +**Architecture:** +``` +App EC2 (MySQL + BankApp) ──→ Ollama EC2 (AI Engine) +``` + +### Local Development (Method 1) + +For local testing with all services on one machine: ```bash -git clone https://github.com/TrainWithShubham/AI-BankApp-DevOps.git +# Clone the repository +git clone https://github.com/yourusername/AI-BankApp-DevOps.git cd AI-BankApp-DevOps -git checkout docker +# Create .env file from example +cp .env.example .env + +# Edit .env with your credentials +nano .env + +# Start all services (MySQL + Ollama + BankApp) docker compose up -d -``` -The app will be available at **http://localhost:8080**. +# View logs +docker compose logs -f bankapp -### Run locally (without Docker) +# Access the application +open http://localhost:8080 +``` -**Prerequisites:** Java 21, Maven, MySQL 8.0 +**Note:** `docker-compose.yml` includes Ollama for local development. Production uses `app-tier.yml` with separate Ollama EC2. -1. Create a MySQL database: - ```sql - CREATE DATABASE bankappdb; - ``` +### Run Locally (Development) -2. Build and run: - ```bash - ./mvnw clean package -DskipTests - java -jar target/*.jar - ``` +```bash +# Start MySQL (or use Docker) +docker run -d --name mysql \ + -e MYSQL_ROOT_PASSWORD=Testing@123 \ + -e MYSQL_DATABASE=aibankappdb \ + -p 3306:3306 mysql:8.0 + +# Build and run the application +./mvnw clean package -DskipTests +java -jar target/bankapp-*.jar + +# Or run with Maven +./mvnw spring-boot:run +``` - Or override DB defaults with environment variables: - ```bash - MYSQL_HOST=localhost MYSQL_PORT=3306 MYSQL_DATABASE=bankappdb \ - MYSQL_USER=root MYSQL_PASSWORD=yourpassword \ - java -jar target/*.jar - ``` +Access at **http://localhost:8080** ## Docker -### Simple Dockerfile +### Build Docker Image ```bash +# Standard build docker build -t bankapp . + +# Multi-stage build (smaller image) +docker build -f Dockerfile.multistage -t bankapp:latest . ``` -### Multistage Dockerfile (smaller image) +### Docker Compose Services + +| Service | Port | Description | +|----------|-------|--------------------------------| +| bankapp | 8080 | Spring Boot application | +| mysql | 3306 | MySQL 8.0 database | +| ollama | 11434 | AI model server (optional) | + +**💡 Cost Optimization Tip:** +For production deployment without AI features, comment out the `ollama` service in `docker-compose.yml`. This allows you to run on smaller instances like `t2.micro` (Free tier eligible) instead of requiring `c7i.large` or larger instances. ```bash -docker build -f Dockerfile.multistage -t bankapp . +# Start all services (including Ollama) +docker compose up -d + +# Start without Ollama (cost-effective) +docker compose up -d bankapp mysql + +# Stop services +docker compose down + +# Stop and remove volumes +docker compose down -v + +# View logs +docker compose logs -f [service-name] ``` -### Docker Compose +## DevSecOps Pipeline -Spins up MySQL + BankApp with networking and health checks: +The project includes a complete CI/CD pipeline with security scanning: -```bash -docker compose up -d # start -docker compose logs -f # view logs -docker compose down # stop -docker compose down -v # stop and remove volumes +### Pipeline Stages + +1. **Code Quality** + - Dockerfile linting (Hadolint) + - Code formatting checks + +2. **Security Scanning** + - Secret detection (Gitleaks) + - Dependency vulnerability scan (OWASP Dependency Check) + +3. **SAST - Static Application Security Testing** + - Semgrep scanner with Java, OWASP Top 10, and secrets rules + - Scans source code for security vulnerabilities before build + +4. **Build & Push** + - Multi-stage Docker build + - Push to Docker Hub with tags: `latest`, `branch-name`, `commit-sha` + +5. **Container Scanning** + - Trivy image scan for CVEs + - Fail on CRITICAL/HIGH vulnerabilities + +6. **Deployment** + - Automated deployment to AWS EC2 + - Docker Compose orchestration + - Health checks + +7. **DAST - Dynamic Application Security Testing** + - OWASP ZAP baseline scan on live application + - Scans running app for runtime vulnerabilities + - Report uploaded as artifact + +### GitHub Actions Workflow + +```yaml +# Triggered on push to main/devsecops branch or manual dispatch +Code Quality → Security Scans → SAST → Build → Image Scan → Deploy → DAST +``` + +**Complete Pipeline Flow:** ``` +1. Code Quality (Hadolint) +2. Secrets Scan (Gitleaks) +3. Dependency Scan (OWASP) +4. Docker Lint + ↓ +5. SAST (Semgrep) ← Runs only after all above pass + ↓ +6. Build & Push (Docker) + ↓ +7. Image Scan (Trivy) + ↓ +8. Deploy (EC2) + ↓ +9. DAST (OWASP ZAP) ← Scans live application +``` + +### Required GitHub Secrets + +Configure these in your repository settings (Settings → Secrets and variables → Actions): + +**Docker Hub:** +- `DOCKERHUB_TOKEN` — Docker Hub access token + +**AWS EC2:** +- `EC2_SSH_HOST` — App EC2 public IP or DNS +- `EC2_SSH_USER` — SSH username (usually `ubuntu`) +- `EC2_SSH_PRIVATE_KEY` — Private key (.pem file content) + +**Ollama (Method 2 - Production):** +- `OLLAMA_URL` — Ollama EC2 private IP with port (e.g., `http://172.31.x.x:11434`) + +**Database:** +- `DB_USERNAME` — Database username +- `DB_PASSWORD` — Database password +- `DB_ROOT_PASSWORD` — MySQL root password -**Services:** +**GitHub Variables:** +- `DOCKERHUB_USER` — Your Docker Hub username -| Service | Port | Description | -|----------|------|----------------------| -| bankapp | 8080 | Spring Boot app | -| mysql | 3306 | MySQL 8.0 database | +**Note:** For Method 1 (local/all-in-one), `OLLAMA_URL` is not needed as Ollama runs in Docker. + +## Environment Variables + +| Variable | Default | Description | +|--------------------|--------------|--------------------------------| +| `MYSQL_HOST` | localhost | Database host | +| `MYSQL_PORT` | 3306 | Database port | +| `MYSQL_DATABASE` | aibankappdb | Database name | +| `MYSQL_USER` | root | Database username | +| `MYSQL_PASSWORD` | Testing@123 | Database password | +| `OLLAMA_URL` | http://localhost:11434 | AI model server URL | +| `DOCKERHUB_USER` | - | Docker Hub username | +| `DOCKER_TAG` | latest | Docker image tag | ## Project Structure ``` -src/main/java/com/example/bankapp/ -├── config/ # Security configuration -├── controller/ # Web endpoints -├── model/ # Account & Transaction entities -├── repository/ # JPA repositories -└── service/ # Business logic +. +├── .github/workflows/ # CI/CD pipeline definitions +│ ├── devsecops-pipeline.yml +│ ├── docker-build-push.yml +│ ├── image-scan.yml +│ └── deploy-to-server.yml +├── src/ +│ ├── main/ +│ │ ├── java/com/example/bankapp/ +│ │ │ ├── config/ # Security configuration +│ │ │ ├── controller/ # REST & Web controllers +│ │ │ ├── model/ # JPA entities +│ │ │ ├── repository/ # Data access layer +│ │ │ └── service/ # Business logic +│ │ └── resources/ +│ │ ├── templates/ # Thymeleaf views +│ │ ├── static/ # CSS, JS assets +│ │ └── application.properties +│ └── test/ # Unit tests +├── Dockerfile # Production Docker image +├── Dockerfile.multistage # Optimized multi-stage build +├── docker-compose.yml # Local development setup +├── pom.xml # Maven dependencies +└── README.md +``` + +## Security Features + +### Application Security +- **Authentication** — Form-based login with Spring Security +- **Password Hashing** — BCrypt with configurable strength +- **CSRF Protection** — Enabled for all state-changing operations +- **SQL Injection Prevention** — JPA/Hibernate parameterized queries +- **XSS Protection** — Thymeleaf auto-escaping + +### DevSecOps Security Scanning +- **SAST (Static Analysis)** — Semgrep scans source code for vulnerabilities (Java, OWASP Top 10, secrets) +- **DAST (Dynamic Analysis)** — OWASP ZAP scans live application for runtime vulnerabilities +- **SCA (Software Composition Analysis)** — OWASP Dependency Check scans Maven dependencies for known CVEs +- **Container Scanning** — Trivy scans Docker images for OS and library vulnerabilities +- **Secret Detection** — Gitleaks scans Git history to prevent credential leaks +- **Infrastructure as Code** — Hadolint validates Dockerfile best practices -src/main/resources/ -├── templates/ # Thymeleaf HTML pages -├── static/ # CSS, JS (theme toggle) -└── application.properties +## Monitoring & Observability + +Spring Boot Actuator endpoints are exposed for monitoring: + +```bash +# Health check +curl http://localhost:8080/actuator/health + +# Prometheus metrics +curl http://localhost:8080/actuator/prometheus + +# Application info +curl http://localhost:8080/actuator/info ``` -## Environment Variables +## Deployment + +### Ollama AI Model Automation + +This project supports **two methods** to automate Ollama and TinyLlama model pulling: + +#### Method 1: Docker Compose (Local/Development) +The `docker-compose.yml` includes an `ollama-pull-model` service that automatically: +- Waits for Ollama service to be healthy +- Pulls the `tinyllama` model +- Shares the model with the main Ollama service + +```bash +# Start all services - model pulls automatically +docker compose up -d +``` + +#### Method 2: EC2 User Data Script (Production/Dedicated AI Tier) +For a **separate dedicated Ollama EC2 instance**, use the [scripts/ollama-setup.sh](scripts/ollama-setup.sh) script: + +**During EC2 Launch:** +1. Launch a new Ubuntu EC2 instance (t3.large or larger recommended) +2. Open inbound port `11434` in Security Group +3. In the **User Data** field, paste the entire content of `scripts/ollama-setup.sh` +4. Launch the instance - Ollama installs and pulls tinyllama automatically! + +**What the script does:** +- Installs Ollama natively on the EC2 +- Configures it to listen on all interfaces (0.0.0.0) +- Automatically pulls the tinyllama model +- Includes health checks and retry logic + +**Verify after launch:** +```bash +# SSH into the Ollama EC2 +ssh -i your-key.pem ubuntu@ollama-ec2-ip + +# Check if model is pulled +ollama list +``` + +### Resource Requirements + +**Based on Real-World Testing:** + +| Configuration | Instance Type | RAM | vCPU | Monthly Cost | Use Case | +|--------------|---------------|-----|------|--------------|----------| +| **Production (No AI)** | t2.micro / t3.micro | 1GB | 1-2 | ~$8-10 | Banking app only (MySQL + BankApp) | +| **With AI Chatbot** | c7i.large / t3.large | 8GB | 2 | ~$60-80 | Full features with Ollama | + +**Why the difference?** +- **Without Ollama:** Only 2 lightweight containers (MySQL + Spring Boot) +- **With Ollama:** AI model requires 4-6GB RAM + significant CPU for inference +- **Tested:** t2.micro works perfectly for core banking features +- **Warning:** Running Ollama on instances smaller than 8GB RAM causes OOM (Out of Memory) crashes + +**Recommendation:** Start with t2.micro (free tier), add Ollama later if needed. + +### AWS EC2 Deployment + +1. **Launch EC2 Instance** + + **Without Ollama (Recommended for cost optimization):** + - Instance Type: `t2.micro` or `t3.micro` (1GB RAM) + - Only 2 containers: MySQL + BankApp + - Cost: ~$8-10/month (Free tier eligible) + - Perfect for production banking app without AI features + + **With Ollama AI Chatbot:** + - Instance Type: `c7i.large` or `t3.large` minimum (2 vCPU, 8GB RAM) + - 3 containers: MySQL + BankApp + Ollama + - Cost: ~$60-80/month + - Ollama requires significant CPU/RAM for model inference + - Note: Smaller instances will cause OOM (Out of Memory) errors + + **Common Configuration:** + - OS: Ubuntu 22.04 or later + - Storage: 20GB minimum (30GB recommended with Ollama) + - Open ports: 22 (SSH), 8080 (HTTP) + - Security Group: Allow inbound on ports 22, 8080 + +2. **Configure GitHub Secrets** + - Add all required secrets (see above) + +3. **Push to Main Branch** + - Pipeline automatically deploys to EC2 + - Access at `http://YOUR-EC2-IP:8080` + +### Manual Deployment + +```bash +# SSH to EC2 +ssh -i your-key.pem ubuntu@your-ec2-ip + +# Install Docker and Docker Compose (if not already installed) +sudo apt update +sudo apt install -y docker.io docker-compose +sudo usermod -aG docker $USER +# Log out and back in for group changes to take effect + +# Create deployment directory +mkdir -p ~/devops +cd ~/devops + +# Create .env file +cat > .env << EOF +DOCKERHUB_USER=your-username +DOCKER_TAG=latest +DB_USERNAME=bankuser +DB_PASSWORD=Test@123 +DB_ROOT_PASSWORD=Test@123 +EOF + +# Copy docker-compose.yml to server (or download from repo) +wget https://raw.githubusercontent.com/Nandan29300/AI-BankApp-DevOps/devsecops/docker-compose.yml + +# Start services (MySQL will automatically create 'bankappdb' database) +docker compose up -d + +# Check logs +docker compose logs -f + +# Verify database was created +docker exec -it devops-mysql-1 mysql -uroot -pTest@123 -e "SHOW DATABASES;" +``` + +**Note:** The database `bankappdb` is automatically created by Docker Compose using the `MYSQL_DATABASE` environment variable. No manual database creation needed! + +## Development + +### Running Tests + +```bash +# Run all tests +./mvnw test + +# Run with coverage +./mvnw test jacoco:report +``` + +### Local Development with Hot Reload + +```bash +# Run with Spring Boot DevTools +./mvnw spring-boot:run + +# Application auto-restarts on code changes +``` + +### Database Migrations + +The application uses Hibernate's `ddl-auto=update` for automatic schema management. For production, consider using Flyway or Liquibase. + +## Troubleshooting + +### Application won't start +- Check MySQL is running: `docker ps` +- Verify database credentials in `.env` +- Check logs: `docker compose logs bankapp` + +### Connection refused on port 8080 +- Ensure security group allows inbound traffic on port 8080 +- Check if application started: `docker logs bankapp` +- Verify port binding: `sudo netstat -tlnp | grep 8080` + +### Pipeline fails on security scan +- Review Trivy/OWASP reports in GitHub Actions +- Update vulnerable dependencies in `pom.xml` +- Add exceptions to `.trivyignore` if needed (with justification) + +## Contributing + +1. Fork the repository +2. Create a feature branch (`git checkout -b feature/amazing-feature`) +3. Commit your changes (`git commit -m 'Add amazing feature'`) +4. Push to the branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request + +## License + +This project is licensed under the MIT License. -| Variable | Default | Description | -|------------------|------------|----------------------| -| `MYSQL_HOST` | localhost | Database host | -| `MYSQL_PORT` | 3306 | Database port | -| `MYSQL_DATABASE` | bankappdb | Database name | -| `MYSQL_USER` | root | Database username | -| `MYSQL_PASSWORD` | Test@123 | Database password | +## Acknowledgments -## Branch Roadmap +- Spring Boot team for the excellent framework +- OWASP for security tools and best practices +- Aqua Security for Trivy scanner +- Bootstrap team for the UI framework -| Branch | What it adds | -|----------|-------------------------------------------------------| -| `start` | Modernized app (backend + frontend) | -| `docker` | Dockerfile, multistage build, Compose, AI chatbot | -| `main` | Full DevOps pipeline (CI/CD, K8s, etc.) | +--- -Each branch builds on the previous one. See `ROADMAP.md` for the full checklist. +**Built with ❤️ for learning DevSecOps practices** diff --git a/ROADMAP.md b/ROADMAP.md deleted file mode 100644 index e87c955..0000000 --- a/ROADMAP.md +++ /dev/null @@ -1,88 +0,0 @@ -# DevOps Roadmap — BankApp - -A step-by-step progression from code to production-grade DevOps. -Each phase builds on the previous one. Check off as you go. - ---- - -## Phase 1: Application (`start` branch) -- [x] Spring Boot backend with MySQL -- [x] Thymeleaf frontend with modern UI -- [x] Spring Security (login, register, CSRF) -- [x] Actuator + Prometheus metrics endpoint -- [x] Externalized config via environment variables - -## Phase 2: Docker (`docker` branch) -- [x] Dockerfile (simple) -- [x] Dockerfile.multistage (optimized image) -- [x] docker-compose.yml (app + MySQL) -- [ ] .dockerignore file -- [ ] Push image to Docker Hub - -## Phase 3: CI/CD (`cicd` branch) -- [ ] GitHub Actions workflow — build & test on PR -- [ ] Build Docker image in CI -- [ ] Push image to Docker Hub from CI -- [ ] Tag images with git SHA + `latest` - -## Phase 4: Kubernetes (`k8s` branch) -- [ ] Deployment manifest (app) -- [ ] Service manifest (ClusterIP) -- [ ] ConfigMap (app config) -- [ ] Secret (DB credentials) -- [ ] MySQL StatefulSet or external DB -- [ ] Ingress with host-based routing -- [ ] Deploy to a local cluster (minikube / kind) - -## Phase 5: Helm (`helm` branch) -- [ ] Helm chart for BankApp -- [ ] values.yaml for dev / prod -- [ ] Install via `helm install` - -## Phase 6: IaC with Terraform (`terraform` branch) -- [ ] Provision AWS EKS cluster (or equivalent) -- [ ] RDS MySQL instance -- [ ] VPC, subnets, security groups -- [ ] State stored in S3 + DynamoDB lock - -## Phase 7: Monitoring (`monitoring` branch) -- [ ] Prometheus scraping `/actuator/prometheus` -- [ ] Grafana dashboard for app metrics -- [ ] Alerting rules (high error rate, pod restarts) - -## Phase 8: GitOps (`gitops` branch) -- [ ] ArgoCD installed on cluster -- [ ] App synced from Git repo to K8s -- [ ] Auto-sync on push to main - -## Phase 9: Security & Quality (`security` branch) -- [ ] Trivy image scan in CI pipeline -- [ ] SonarQube code quality scan -- [ ] OWASP dependency check -- [ ] Non-root container user - -## Phase 10: AI Chatbot (`ai` branch) -- [x] Ollama container in docker-compose (self-hosted, zero cost) -- [x] Chat REST API in Spring Boot calling Ollama -- [x] Floating chat widget on dashboard -- [x] Context-aware — knows user's balance and transactions -- [ ] Deploy Ollama on K8s with GPU/CPU resource limits - -## Phase 11: Production Readiness (`prod` branch) -- [ ] TLS / HTTPS via cert-manager -- [ ] Horizontal Pod Autoscaler (HPA) -- [ ] Resource limits and requests -- [ ] Liveness & readiness probes -- [ ] Multi-environment setup (dev / staging / prod) - ---- - -## The Story for Interviews - -> "I took a Spring Boot banking application, integrated a self-hosted AI chatbot -> using Ollama, containerized everything with Docker, built a CI/CD pipeline with -> GitHub Actions, deployed to Kubernetes using Helm charts, provisioned cloud -> infrastructure with Terraform, set up monitoring with Prometheus and Grafana, -> and implemented GitOps with ArgoCD for automated deployments." - -Each phase = one branch = one talking point. diff --git a/app-tier.yml b/app-tier.yml new file mode 100644 index 0000000..3e5eea3 --- /dev/null +++ b/app-tier.yml @@ -0,0 +1,48 @@ +# Method 2 - Option A: App EC2 with MySQL container + Separate Ollama EC2 +services: + mysql: + image: mysql:8.0 + container_name: bankapp-mysql + environment: + MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} + MYSQL_DATABASE: bankappdb + MYSQL_USER: ${DB_USERNAME} + MYSQL_PASSWORD: ${DB_PASSWORD} + ports: + - "3306:3306" + volumes: + - mysql-data:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: + - bankapp-net + + bankapp: + image: ${DOCKERHUB_USER}/bankapp:${DOCKER_TAG:-latest} + container_name: bankapp + ports: + - "8080:8080" + environment: + MYSQL_HOST: mysql + MYSQL_PORT: 3306 + MYSQL_DATABASE: bankappdb + MYSQL_USER: ${DB_USERNAME} + MYSQL_PASSWORD: ${DB_PASSWORD} + OLLAMA_URL: ${OLLAMA_URL} # Points to separate Ollama EC2 + depends_on: + mysql: + condition: service_healthy + restart: always + networks: + - bankapp-net + +volumes: + mysql-data: + +networks: + bankapp-net: + driver: bridge diff --git a/docker-compose.yml b/docker-compose.yml index d19ae58..2373bdf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,8 +3,10 @@ services: image: mysql:8.0 container_name: bankapp-mysql environment: - MYSQL_ROOT_PASSWORD: Test@123 + MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} MYSQL_DATABASE: bankappdb + MYSQL_USER: ${DB_USERNAME} + MYSQL_PASSWORD: ${DB_PASSWORD} ports: - "3306:3306" volumes: @@ -25,11 +27,31 @@ services: - "11434:11434" volumes: - ollama-data:/root/.ollama + healthcheck: + test: ["CMD", "ollama", "list"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - bankapp-net + + ollama-pull-model: + image: ollama/ollama + container_name: ollama-pull-model + environment: + - OLLAMA_HOST=ollama + volumes: + - ollama-data:/root/.ollama + depends_on: + ollama: + condition: service_healthy + entrypoint: /bin/sh + command: -c "ollama pull tinyllama" networks: - bankapp-net bankapp: - build: . + image: ${DOCKERHUB_USER}/bankapp:${DOCKER_TAG:-latest} container_name: bankapp ports: - "8080:8080" @@ -37,12 +59,14 @@ services: MYSQL_HOST: mysql MYSQL_PORT: 3306 MYSQL_DATABASE: bankappdb - MYSQL_USER: root - MYSQL_PASSWORD: Test@123 + MYSQL_USER: ${DB_USERNAME} + MYSQL_PASSWORD: ${DB_PASSWORD} OLLAMA_URL: http://ollama:11434 depends_on: mysql: condition: service_healthy + ollama-pull-model: + condition: service_completed_successfully networks: - bankapp-net diff --git a/pom.xml b/pom.xml index c9942e6..1fdd0f6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,8 +5,8 @@ org.springframework.boot spring-boot-starter-parent - 3.4.1 - + 3.4.5 + com.example bankapp @@ -14,21 +14,20 @@ bankapp Banking Web Application - - - - - - + + - - - - + + 21 + + 10.1.52 + + 2.18.6 + org.springframework.boot @@ -62,7 +61,6 @@ io.micrometer micrometer-registry-prometheus - com.mysql mysql-connector-j @@ -86,7 +84,34 @@ org.springframework.boot spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.3.1 + + google_checks.xml + true + true + + + + + + com.github.spotbugs + spotbugs-maven-plugin + 4.8.6.4 + + Max + High + true + + + + + - + \ No newline at end of file diff --git a/scripts/ollama-setup.sh b/scripts/ollama-setup.sh new file mode 100755 index 0000000..cb2b842 --- /dev/null +++ b/scripts/ollama-setup.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# automated-ollama-setup.sh +# This script is designed to be used as AWS EC2 User Data for the AI Tier. + +# Export HOME for cloud-init environment to prevent Ollama panic +export HOME=/root + +# 1. Install Ollama +curl -fsSL https://ollama.com/install.sh | sh + +# 2. Wait for Ollama to be available +sleep 10 + +# 3. Configure Ollama to listen on all interfaces (0.0.0.0) +# Create the systemd drop-in directory +mkdir -p /etc/systemd/system/ollama.service.d + +# Write the override configuration +cat < /etc/systemd/system/ollama.service.d/override.conf +[Service] +Environment="OLLAMA_HOST=0.0.0.0" +EOF + +# 4. Reload and Restart the service +systemctl daemon-reload +systemctl restart ollama + +# 6. Wait for Ollama server to be ready (Health Check) +echo "Waiting for Ollama server to start..." +MAX_RETRIES=30 +RETRY_COUNT=0 +while ! curl -s http://localhost:11434/api/tags > /dev/null; do + RETRY_COUNT=$((RETRY_COUNT+1)) + if [ $RETRY_COUNT -ge $MAX_RETRIES ]; then + echo "Ollama server failed to start in time." + exit 1 + fi + sleep 2 +done + +# 7. Pull the required model +echo "Pulling tinyllama model..." +ollama pull tinyllama + +echo "Ollama setup complete and listening on port 11434" diff --git a/src/main/java/com/example/bankapp/model/ChatMessage.java b/src/main/java/com/example/bankapp/model/ChatMessage.java new file mode 100644 index 0000000..3b8be41 --- /dev/null +++ b/src/main/java/com/example/bankapp/model/ChatMessage.java @@ -0,0 +1,53 @@ +package com.example.bankapp.model; + +import jakarta.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "chat_messages") +public class ChatMessage { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Long accountId; + + @Column(nullable = false, length = 10) + private String role; // "user" or "bot" + + @Column(nullable = false, columnDefinition = "TEXT") + private String message; + + @Column(nullable = false) + private LocalDateTime timestamp; + + // Constructors + public ChatMessage() { + this.timestamp = LocalDateTime.now(); + } + + public ChatMessage(Long accountId, String role, String message) { + this.accountId = accountId; + this.role = role; + this.message = message; + this.timestamp = LocalDateTime.now(); + } + + // Getters and Setters + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public Long getAccountId() { return accountId; } + public void setAccountId(Long accountId) { this.accountId = accountId; } + + public String getRole() { return role; } + public void setRole(String role) { this.role = role; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + + public LocalDateTime getTimestamp() { return timestamp; } + public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; } +} diff --git a/src/main/java/com/example/bankapp/repository/ChatMessageRepository.java b/src/main/java/com/example/bankapp/repository/ChatMessageRepository.java new file mode 100644 index 0000000..bfff000 --- /dev/null +++ b/src/main/java/com/example/bankapp/repository/ChatMessageRepository.java @@ -0,0 +1,16 @@ +package com.example.bankapp.repository; + +import com.example.bankapp.model.ChatMessage; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Repository +public interface ChatMessageRepository extends JpaRepository { + List findByAccountIdOrderByTimestampAsc(Long accountId); + + @Transactional + void deleteByAccountId(Long accountId); +} diff --git a/src/main/java/com/example/bankapp/service/AccountService.java b/src/main/java/com/example/bankapp/service/AccountService.java index 5fbffbf..76782b8 100644 --- a/src/main/java/com/example/bankapp/service/AccountService.java +++ b/src/main/java/com/example/bankapp/service/AccountService.java @@ -86,8 +86,8 @@ public String transferAmount(Account from, String toUsername, BigDecimal amount) accountRepository.save(to); LocalDateTime now = LocalDateTime.now(); - transactionRepository.save(new Transaction(amount, "Transfer Out", now, from)); - transactionRepository.save(new Transaction(amount, "Transfer In", now, to)); + transactionRepository.save(new Transaction(amount, "Transfer to " + toUsername, now, from)); + transactionRepository.save(new Transaction(amount, "Transfer from " + from.getUsername(), now, to)); return null; // null means success } @@ -95,4 +95,8 @@ public String transferAmount(Account from, String toUsername, BigDecimal amount) public List getTransactionHistory(Account account) { return transactionRepository.findByAccountIdOrderByTimestampDesc(account.getId()); } + + public java.util.Optional getAccountById(Long id) { + return accountRepository.findById(id); + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index eed6570..d16bfa0 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -14,8 +14,8 @@ spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect # Virtual threads (Java 21) spring.threads.virtual.enabled=true -# Actuator -management.endpoints.web.exposure.include=health,info,metrics,prometheus +# Actuator - Only expose safe health endpoint (restricted from info,metrics,prometheus after SAST finding) +management.endpoints.web.exposure.include=health management.endpoint.health.show-details=when-authorized # Ollama AI diff --git a/src/main/resources/static/css/bankapp.css b/src/main/resources/static/css/bankapp.css index ea47058..fd4886e 100644 --- a/src/main/resources/static/css/bankapp.css +++ b/src/main/resources/static/css/bankapp.css @@ -252,7 +252,7 @@ a:hover { /* ===== Dashboard ===== */ .dashboard-wrapper { padding: 2rem 1rem; - max-width: 1100px; + max-width: 1200px; margin: 0 auto; } @@ -292,7 +292,7 @@ a:hover { .action-cards { display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + grid-template-columns: repeat(3, 1fr); gap: 1.5rem; margin-bottom: 2rem; } diff --git a/src/main/resources/templates/dashboard.html b/src/main/resources/templates/dashboard.html index 9ee685e..66365f9 100644 --- a/src/main/resources/templates/dashboard.html +++ b/src/main/resources/templates/dashboard.html @@ -89,6 +89,9 @@
Transfer
AI Assistant +
Hi! I'm your banking assistant. Ask me about your balance, transactions, or anything else.
@@ -105,6 +108,33 @@
Transfer
|| document.cookie.match(/XSRF-TOKEN=([^;]+)/)?.[1] || ''; const csrfHeader = document.querySelector('meta[name="_csrf_header"]')?.content || 'X-CSRF-TOKEN'; + // Load chat history on page load + function loadChatHistory() { + fetch('/api/chat/history', { + method: 'GET', + headers: { + [csrfHeader]: csrfToken + } + }) + .then(r => r.json()) + .then(history => { + const messages = document.getElementById('chatMessages'); + messages.innerHTML = '
Hi! I\'m your banking assistant. Ask me about your balance, transactions, or anything else.
'; + + history.forEach(msg => { + const className = msg.role === 'user' ? 'user' : 'bot'; + messages.innerHTML += '
' + escapeHtml(msg.message) + '
'; + }); + messages.scrollTop = messages.scrollHeight; + }) + .catch(() => { + console.log('No chat history available'); + }); + } + + // Load history when page loads + window.addEventListener('DOMContentLoaded', loadChatHistory); + document.getElementById('chatFab').addEventListener('click', function () { document.getElementById('chatPanel').classList.toggle('open'); document.getElementById('chatInput').focus(); @@ -148,6 +178,25 @@
Transfer
} document.getElementById('chatSend').addEventListener('click', sendMessage); + + document.getElementById('clearChat').addEventListener('click', function() { + if (confirm('Clear all chat history? This cannot be undone.')) { + fetch('/api/chat/history', { + method: 'DELETE', + headers: { + [csrfHeader]: csrfToken + } + }) + .then(r => r.json()) + .then(() => { + const messages = document.getElementById('chatMessages'); + messages.innerHTML = '
Chat history cleared. How can I help you?
'; + }) + .catch(() => { + alert('Failed to clear chat history'); + }); + } + }); document.getElementById('chatInput').addEventListener('keydown', function (e) { if (e.key === 'Enter') sendMessage(); }); diff --git a/src/main/resources/templates/fragments/layout.html b/src/main/resources/templates/fragments/layout.html index 3cafa18..926b424 100644 --- a/src/main/resources/templates/fragments/layout.html +++ b/src/main/resources/templates/fragments/layout.html @@ -1,5 +1,6 @@ - + @@ -30,7 +31,7 @@