Skip to content

container scanning

container scanning #11

name: DevSecOps Pipeline
on:
push:
branches: [ main, master, develop ]
pull_request:
branches: [ main, master, develop ]
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/solar-system
DOCKER_IMAGE_TAG: ${{ github.sha }}
jobs:
# Stage 1 & 2: Code Checkout and Dependency Installation
build-and-test:
name: Build and Unit Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Run unit tests
run: npm test
continue-on-error: false # Fail pipeline if tests fail
env:
MONGO_URI: ${{ secrets.MONGO_URI }}
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: test-results.xml
# Stage 4: Code Coverage
code-coverage:
name: Code Coverage Analysis
runs-on: ubuntu-latest
needs: build-and-test
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Run coverage analysis
run: npm run coverage
env:
MONGO_URI: ${{ secrets.MONGO_URI }}
- name: Upload coverage reports
uses: actions/upload-artifact@v4
with:
name: coverage-reports
path: |
coverage/
.nyc_output/
# Stage 5: SAST - Static Application Security Testing
sast-semgrep:
name: SAST - Semgrep
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/nodejs
p/owasp-top-ten
p/javascript
- name: Upload Semgrep results
if: always()
uses: actions/upload-artifact@v4
with:
name: semgrep-results
path: semgrep-results.json
# Stage 6: Dependency Scanning
dependency-scan:
name: Dependency Scanning - Snyk
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm install
- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/node@master
continue-on-error: true
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high --json-file-output=snyk-results.json
- name: Upload Snyk results
if: always()
uses: actions/upload-artifact@v4
with:
name: snyk-results
path: snyk-results.json
- name: Run npm audit
run: npm audit --json > npm-audit-results.json
continue-on-error: true
- name: Upload npm audit results
if: always()
uses: actions/upload-artifact@v4
with:
name: npm-audit-results
path: npm-audit-results.json
# Stage 7: Secret Detection
secret-scan:
name: Secret Detection - TruffleHog
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for secret scanning
- name: TruffleHog OSS
uses: trufflesecurity/trufflehog@main
continue-on-error: true # Don't fail pipeline on secrets found
with:
path: ./
base: ${{ github.event.before || '' }}
head: ${{ github.sha }}
extra_args: --only-verified
# Stage 8: Docker Build and Push
docker-build:
name: Docker Build and Push
runs-on: ubuntu-latest
needs: [build-and-test, code-coverage, sast-semgrep, dependency-scan, secret-scan]
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix={{branch}}-
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Save image name for later stages
run: |
echo "FULL_IMAGE_NAME=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-${{ github.sha }}" >> $GITHUB_ENV
echo "Image pushed: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-${{ github.sha }}"
# Stage 9: Container Scanning
container-scan:
name: Container Scanning - Trivy
runs-on: ubuntu-latest
needs: docker-build
permissions:
contents: read
packages: read
security-events: write
steps:
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH,MEDIUM'
- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: 'trivy-results.sarif'
- name: Run Trivy for JSON output
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
format: 'json'
output: 'trivy-results.json'
- name: Upload Trivy results
if: always()
uses: actions/upload-artifact@v4
with:
name: trivy-results
path: trivy-results.json
# Stage 10: DAST - Dynamic Application Security Testing
dast-zap:
name: DAST - OWASP ZAP
runs-on: ubuntu-latest
needs: docker-build
permissions:
contents: read
packages: read
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Start application container
run: |
docker run -d --name solar-system-app \
-p 3000:3000 \
-e MONGO_URI="${{ secrets.MONGO_URI }}" \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
# Wait for application to be ready
sleep 10
curl -f http://localhost:3000/ready || exit 1
- name: Run OWASP ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.12.0
with:
target: 'http://localhost:3000/'
rules_file_name: '.zap/rules.tsv'
cmd_options: '-a'
allow_issue_writing: false
- name: Stop application container
if: always()
run: docker stop solar-system-app && docker rm solar-system-app
# Stage 11: Deploy to Azure VM
# deploy-azure:
# name: Deploy to Azure VM
# runs-on: ubuntu-latest
# needs: [container-scan, dast-zap]
# if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
# permissions:
# contents: read
# packages: read
# environment:
# name: production
# url: http://${{ secrets.AZURE_VM_IP }}:3000
# steps:
# - name: Deploy on Azure VM
# uses: appleboy/ssh-action@master
# with:
# host: ${{ secrets.AZURE_VM_IP }}
# username: ${{ secrets.AZURE_VM_USERNAME }}
# key: ${{ secrets.AZURE_VM_SSH_KEY }}
# script: |
# # Log in to GitHub Container Registry
# echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
# # Pull the latest image
# docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-${{ github.sha }}
# # Stop and remove old container if exists
# docker stop solar-system || true
# docker rm solar-system || true
# # Run new container
# docker run -d --name solar-system \
# -p 3000:3000 \
# --restart unless-stopped \
# -e MONGO_URI="${{ secrets.MONGO_URI }}" \
# ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-${{ github.sha }}
# # Clean up old images (keep last 3)
# docker image prune -af --filter "until=72h"
# # Verify deployment
# sleep 5
# curl -f http://localhost:3000/ready || exit 1
# echo "Deployment successful!"
# docker ps | grep solar-system
# - name: Health check
# run: |
# sleep 10
# curl -f http://${{ secrets.AZURE_VM_IP }}:3000/ready || exit 1
# echo "Application is healthy and running!"