Add Buy Me a Coffee username #14
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 | ||
| on: | ||
| push: | ||
| branches: [ "main", "master" ] | ||
| pull_request: | ||
| branches: [ "main", "master" ] | ||
| schedule: | ||
| - cron: '0 0 * * 0' # Weekly baseline scan | ||
| workflow_dispatch: | ||
| permissions: | ||
| security-events: write | ||
| contents: read | ||
| packages: write # For pushing images/signatures | ||
| id-token: write # For Cosign OIDC (optional) | ||
| pull-requests: write # For posting comments | ||
| jobs: | ||
| secret-scan: | ||
| name: Secret Scan (TruffleHog) | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| - name: TruffleHog (Diff Mode) | ||
| if: github.event_name == 'push' || github.event_name == 'pull_request' | ||
| uses: trufflesecurity/trufflehog@main | ||
| with: | ||
| extra_args: --only-verified --fail --no-update | ||
| - name: TruffleHog (Baseline Mode) | ||
| if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' | ||
| uses: trufflesecurity/trufflehog@main | ||
| with: | ||
| extra_args: --only-verified --fail --no-update --since-commit HEAD | ||
| sast-scan: | ||
| name: SAST (Semgrep) | ||
| runs-on: ubuntu-latest | ||
| container: | ||
| image: returntocorp/semgrep | ||
| if: (github.actor != 'dependabot[bot]') | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Semgrep Scan | ||
| run: semgrep scan --config auto --sarif --output semgrep.sarif | ||
| continue-on-error: true | ||
| - name: Check for Critical/High Failures | ||
| run: semgrep scan --config auto --error --severity ERROR --severity CRITICAL | ||
| - name: Upload SARIF | ||
| uses: actions/upload-artifact@v4 | ||
| if: always() | ||
| with: | ||
| name: semgrep.sarif | ||
| path: semgrep.sarif | ||
| - name: Upload Security SARIF (CodeQL) | ||
| uses: github/codeql-action/upload-sarif@v3 | ||
| if: always() | ||
| continue-on-error: true | ||
| with: | ||
| sarif_file: semgrep.sarif | ||
| sca-scan: | ||
| name: SCA (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: '16' | ||
| - name: Install dependencies | ||
| run: npm install || true | ||
| - name: Snyk Scan | ||
| uses: snyk/actions/node@master | ||
| continue-on-error: true | ||
| env: | ||
| SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} | ||
| with: | ||
| args: --severity-threshold=high --sarif-file-output=snyk.sarif | ||
| - name: Upload SARIF | ||
| uses: actions/upload-artifact@v4 | ||
| if: always() | ||
| with: | ||
| name: snyk.sarif | ||
| path: snyk.sarif | ||
| - name: Upload Security SARIF (CodeQL) | ||
| uses: github/codeql-action/upload-sarif@v3 | ||
| if: always() | ||
| continue-on-error: true | ||
| with: | ||
| sarif_file: snyk.sarif | ||
| - name: Snyk Gatekeeper | ||
| uses: snyk/actions/node@master | ||
| if: ${{ env.SNYK_TOKEN != '' }} | ||
| env: | ||
| SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} | ||
| with: | ||
| args: --severity-threshold=high | ||
| iac-scan: | ||
| name: IaC Scan (Checkov) | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Checkov Scan | ||
| uses: bridgecrewio/checkov-action@master | ||
| with: | ||
| directory: ./terraform | ||
| quiet: true | ||
| soft_fail: false | ||
| output_format: sarif | ||
| output_file_path: checkov.sarif | ||
| - name: Upload SARIF | ||
| uses: actions/upload-artifact@v4 | ||
| if: always() | ||
| with: | ||
| name: checkov.sarif | ||
| path: checkov.sarif | ||
| - name: Upload Security SARIF (CodeQL) | ||
| uses: github/codeql-action/upload-sarif@v3 | ||
| if: always() | ||
| continue-on-error: true | ||
| with: | ||
| sarif_file: checkov.sarif | ||
| cost-estimation: | ||
| name: FinOps (Infracost) | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| pull-requests: write # Required to post comments | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Setup Infracost | ||
| uses: infracost/actions/setup@v3 | ||
| with: | ||
| api-key: ${{ secrets.INFRACOST_API_KEY }} | ||
| - name: Infracost Breakdown | ||
| if: ${{ secrets.INFRACOST_API_KEY != '' }} | ||
| run: | | ||
| infracost breakdown --path ./terraform --format json --out-file infracost-usage.json | ||
| - name: Post Infracost Comment | ||
| if: github.event_name == 'pull_request' && secrets.INFRACOST_API_KEY != '' | ||
| env: | ||
| INFRACOST_API_KEY: ${{ secrets.INFRACOST_API_KEY }} | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| infracost comment github --path infracost-usage.json --repo $GITHUB_REPOSITORY --pull-request ${{ github.event.pull_request.number }} --behavior update | ||
| container-build-sign: | ||
| name: Build & Sign (Trivy + Cosign) | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Build Docker Image | ||
| run: docker build -t my-app:${{ github.sha }} . | ||
| # 1. Scan (Trivy) | ||
| - name: Trivy Image Scan (SARIF) | ||
| uses: aquasecurity/trivy-action@master | ||
| with: | ||
| image-ref: 'my-app:${{ github.sha }}' | ||
| format: 'sarif' | ||
| output: 'trivy-results.sarif' | ||
| ignore-unfixed: true | ||
| vuln-type: 'os,library' | ||
| - name: Upload SARIF | ||
| uses: actions/upload-artifact@v4 | ||
| if: always() | ||
| with: | ||
| name: trivy-results.sarif | ||
| path: trivy-results.sarif | ||
| - name: Upload Security SARIF (CodeQL) | ||
| uses: github/codeql-action/upload-sarif@v3 | ||
| if: always() | ||
| continue-on-error: true | ||
| with: | ||
| sarif_file: trivy-results.sarif | ||
| - name: Trivy Gatekeeper | ||
| uses: aquasecurity/trivy-action@master | ||
| with: | ||
| image-ref: 'my-app:${{ github.sha }}' | ||
| exit-code: '1' | ||
| ignore-unfixed: true | ||
| severity: 'CRITICAL,HIGH' | ||
| # 2. Sign (Cosign) - Only runs if Trivy passes | ||
| - name: Install Cosign | ||
| uses: sigstore/cosign-installer@main | ||
| - name: Sign Image | ||
| if: github.event_name != 'pull_request' # Don't sign PR builds typically | ||
| env: | ||
| COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} | ||
| COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} | ||
| run: | | ||
| if [[ -z "$COSIGN_PRIVATE_KEY" ]]; then | ||
| echo "Skipping signing: COSIGN_PRIVATE_KEY not set." | ||
| else | ||
| # In a real pipeline, you'd push to a registry first. | ||
| # For this demo, we mock the signing step or sign a local tag if supported (Cosign usually needs a registry). | ||
| echo "Cosign key detected. In a real pipeline, we would run:" | ||
| echo "cosign sign --key env://COSIGN_PRIVATE_KEY my-registry/my-app:${{ github.sha }}" | ||
| # To demonstrate, we'll create a dummy signature file | ||
| echo "signature" > cosign.sig | ||
| fi | ||
| - name: Generate SBOM (SPDX) | ||
| uses: aquasecurity/trivy-action@master | ||
| with: | ||
| image-ref: 'my-app:${{ github.sha }}' | ||
| format: 'spdx-json' | ||
| output: 'sbom.spdx.json' | ||
| - name: Upload SBOM | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: sbom | ||
| path: sbom.spdx.json | ||
| dast-scan: | ||
| name: Runtime Security (DAST) | ||
| runs-on: ubuntu-latest | ||
| needs: [container-build-sign] | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Build & Run Container | ||
| run: | | ||
| docker build -t my-app:${{ github.sha }} . | ||
| # Run detached, map internal 3000 to host 3000 | ||
| docker run -d -p 3000:3000 --name test-app my-app:${{ github.sha }} | ||
| # Wait for app to start (simple sleep or healthcheck loop) | ||
| sleep 5 | ||
| - name: OWASP ZAP Baseline Scan | ||
| uses: zaproxy/action-baseline@v0.9.0 | ||
| continue-on-error: true # DAST is noisy; often we want results without blocking initially | ||
| with: | ||
| target: 'http://localhost:3000' | ||
| fail_action: false # Don't fail the action immediately, check report later | ||
| # Note: ZAP action outputs artifacts automatically (zap_report.html) | ||
| compliance-audit: | ||
| name: Generate Compliance Artifacts | ||
| runs-on: ubuntu-latest | ||
| needs: [secret-scan, sast-scan, sca-scan, iac-scan, container-build-sign, dast-scan] | ||
| if: always() | ||
| steps: | ||
| - name: Generate Audit Record | ||
| run: | | ||
| echo "{" > deployment-audit.json | ||
| echo " \"timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\"," >> deployment-audit.json | ||
| echo " \"repository\": \"$GITHUB_REPOSITORY\"," >> deployment-audit.json | ||
| echo " \"commit_sha\": \"$GITHUB_SHA\"," >> deployment-audit.json | ||
| echo " \"actor\": \"$GITHUB_ACTOR\"," >> deployment-audit.json | ||
| echo " \"workflow_run_id\": \"$GITHUB_RUN_ID\"," >> deployment-audit.json | ||
| echo "}" >> deployment-audit.json | ||
| - name: Upload Audit Artifact | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: deployment-audit | ||
| path: deployment-audit.json | ||
| pr-feedback: | ||
| name: Developer Feedback (PR Comments) | ||
| runs-on: ubuntu-latest | ||
| needs: [sast-scan, sca-scan, iac-scan, container-build-sign] # Wait for scans to finish | ||
| if: always() && github.event_name == 'pull_request' | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Download All Artifacts | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| merge-multiple: true | ||
| - name: Post Summary to PR | ||
| uses: actions/github-script@v6 | ||
| with: | ||
| script: | | ||
| const script = require('./.github/scripts/post_summary.js') | ||
| await script({github, context}) | ||