deploy stage added #16
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: DevSecOps Pipeline | ||
|
Check failure on line 1 in .github/workflows/devsecops-pipeline.yml
|
||
| 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: Generate Snyk HTML report | ||
| if: always() | ||
| continue-on-error: true | ||
| run: | | ||
| npx snyk test --severity-threshold=high > snyk-results.txt 2>&1 || true | ||
| npx snyk-to-html -i snyk-results.json -o snyk-results.html || echo "HTML generation skipped" | ||
| env: | ||
| SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} | ||
| - name: Upload Snyk results | ||
| if: always() | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: snyk-results | ||
| path: | | ||
| snyk-results.json | ||
| snyk-results.html | ||
| snyk-results.txt | ||
| - 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: Run Trivy for HTML output | ||
| uses: aquasecurity/trivy-action@master | ||
| continue-on-error: true | ||
| with: | ||
| image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} | ||
| format: 'template' | ||
| template: '@$HOME/.local/bin/trivy-bin/contrib/html.tpl' | ||
| output: 'trivy-results.html' | ||
| - name: Upload Trivy results | ||
| if: always() | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: trivy-results | ||
| path: | | ||
| trivy-results.json | ||
| trivy-results.html | ||
| # 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/' | ||
| cmd_options: '-a' | ||
| allow_issue_writing: false | ||
| artifact_name: '' # Disable ZAP's internal artifact upload (we handle it manually) | ||
| continue-on-error: true # Don't fail pipeline on warnings | ||
| - name: Upload ZAP scan results | ||
| if: always() | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: zap-dast-results | ||
| path: | | ||
| report_json.json | ||
| report_md.md | ||
| report_html.html | ||
| - name: Stop application container | ||
| if: always() | ||
| run: docker stop solar-system-app && docker rm solar-system-app | ||
| # Stage 11: Deploy Infrastructure and Application to Azure | ||
| deploy-azure: | ||
| name: Deploy to Azure Web App | ||
| 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: https://${{ secrets.AZURE_WEBAPP_NAME }}.azurewebsites.net | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Log in to Azure | ||
| uses: azure/login@v1 | ||
| with: | ||
| creds: ${{ secrets.AZURE_CREDENTIALS }} | ||
| - name: Setup Terraform | ||
| uses: hashicorp/setup-terraform@v3 | ||
| with: | ||
| terraform_version: 1.6.0 | ||
| - name: Terraform Init | ||
| run: terraform init | ||
| working-directory: ./terraform | ||
| - name: Terraform Plan | ||
| run: | | ||
| terraform plan \ | ||
| -var="app_name=${{ secrets.AZURE_WEBAPP_NAME }}" \ | ||
| -var="github_username=${{ github.repository_owner }}" \ | ||
| -var="github_token=${{ secrets.GITHUB_TOKEN }}" \ | ||
| -var="mongo_uri=${{ secrets.MONGO_URI }}" \ | ||
| -var="docker_image=${{ github.repository_owner }}/solar-system:${{ github.ref_name }}" \ | ||
| -out=tfplan | ||
| working-directory: ./terraform | ||
| env: | ||
| ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} | ||
| ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} | ||
| ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} | ||
| ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} | ||
| - name: Terraform Apply | ||
| run: terraform apply -auto-approve tfplan | ||
| working-directory: ./terraform | ||
| env: | ||
| ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} | ||
| ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} | ||
| ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} | ||
| ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} | ||
| - name: Get Web App URL | ||
| id: webapp | ||
| run: | | ||
| WEBAPP_URL=$(terraform output -raw webapp_url) | ||
| echo "url=$WEBAPP_URL" >> $GITHUB_OUTPUT | ||
| working-directory: ./terraform | ||
| - name: Restart Web App (force pull latest image) | ||
| run: | | ||
| az webapp restart \ | ||
| --name ${{ secrets.AZURE_WEBAPP_NAME }} \ | ||
| --resource-group $(terraform output -raw resource_group_name) | ||
| working-directory: ./terraform | ||
| - name: Health check | ||
| run: | | ||
| sleep 60 | ||
| curl -f ${{ steps.webapp.outputs.url }}/ready || exit 1 | ||
| echo "Application is healthy and running on Azure Web App!" | ||