docs: Update documentation with cross-repo analyzer details #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 | ||
| 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
| with: | ||
| fetch-depth: 0 | ||
| - name: TruffleHog (Diff Mode) | ||
| if: github.event_name == 'push' || github.event_name == 'pull_request' | ||
| uses: trufflesecurity/trufflehog@895cc72a44b8b6a386993a4046bc77884784a929 # v3.82.12 | ||
| 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@895cc72a44b8b6a386993a4046bc77884784a929 # v3.82.12 | ||
| 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
| - 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@6027bc5291242e2308107931669864239f884a44 # v4.4.0 | ||
| if: always() | ||
| with: | ||
| name: semgrep.sarif | ||
| path: semgrep.sarif | ||
| - name: Upload Security SARIF (CodeQL) | ||
| uses: github/codeql-action/upload-sarif@66115715ae9c0202956f4d546f14066914995955 # v3.25.11 | ||
| 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
| - name: Setup Node.js | ||
| uses: actions/setup-node@39225597dea6c34d620298be596b72fe05224403 # v4.1.0 | ||
| with: | ||
| node-version: '16' | ||
| - name: Install dependencies | ||
| run: npm install || true | ||
| - name: Snyk Scan | ||
| uses: snyk/actions/node@b9830575092c6dac41351111608672074e54e445 # v0.10.0 | ||
| 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@6027bc5291242e2308107931669864239f884a44 # v4.4.0 | ||
| if: always() | ||
| with: | ||
| name: snyk.sarif | ||
| path: snyk.sarif | ||
| - name: Upload Security SARIF (CodeQL) | ||
| uses: github/codeql-action/upload-sarif@66115715ae9c0202956f4d546f14066914995955 # v3.25.11 | ||
| if: always() | ||
| continue-on-error: true | ||
| with: | ||
| sarif_file: snyk.sarif | ||
| - name: Snyk Gatekeeper | ||
| uses: snyk/actions/node@b9830575092c6dac41351111608672074e54e445 # v0.10.0 | ||
| 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
| - name: Checkov Scan | ||
| uses: bridgecrewio/checkov-action@486183e91f1a1c93a0228308436440c313203f7e # v12.2882.0 | ||
| with: | ||
| directory: ./terraform | ||
| quiet: true | ||
| soft_fail: false | ||
| output_format: sarif | ||
| output_file_path: checkov.sarif | ||
| - name: Upload SARIF | ||
| uses: actions/upload-artifact@6027bc5291242e2308107931669864239f884a44 # v4.4.0 | ||
| if: always() | ||
| with: | ||
| name: checkov.sarif | ||
| path: checkov.sarif | ||
| - name: Upload Security SARIF (CodeQL) | ||
| uses: github/codeql-action/upload-sarif@66115715ae9c0202956f4d546f14066914995955 # v3.25.11 | ||
| 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
| - name: Setup Infracost | ||
| uses: infracost/actions/setup@91f7a6021289569735d487f98f6d525790a6e60b # v3.0.0 | ||
| 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
| - name: Build Docker Image | ||
| run: docker build -t my-app:${{ github.sha }} . | ||
| # 1. Scan (Trivy) | ||
| - name: Trivy Image Scan (SARIF) | ||
| uses: aquasecurity/trivy-action@d43c1f16c000d89748ed7a5cc2a74043e803b00e # v0.24.0 | ||
| 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@6027bc5291242e2308107931669864239f884a44 # v4.4.0 | ||
| if: always() | ||
| with: | ||
| name: trivy-results.sarif | ||
| path: trivy-results.sarif | ||
| - name: Upload Security SARIF (CodeQL) | ||
| uses: github/codeql-action/upload-sarif@66115715ae9c0202956f4d546f14066914995955 # v3.25.11 | ||
| if: always() | ||
| continue-on-error: true | ||
| with: | ||
| sarif_file: trivy-results.sarif | ||
| - name: Trivy Gatekeeper | ||
| uses: aquasecurity/trivy-action@d43c1f16c000d89748ed7a5cc2a74043e803b00e # v0.24.0 | ||
| 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@59ac5dcde5aa5408e01ad4409589d283b9d7a287 # v3.5.0 | ||
| - 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@d43c1f16c000d89748ed7a5cc2a74043e803b00e # v0.24.0 | ||
| with: | ||
| image-ref: 'my-app:${{ github.sha }}' | ||
| format: 'spdx-json' | ||
| output: 'sbom.spdx.json' | ||
| - name: Upload SBOM | ||
| uses: actions/upload-artifact@6027bc5291242e2308107931669864239f884a44 # v4.4.0 | ||
| 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
| - 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@290a611c039d91f868c6e26487e0766324263720 # v0.14.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() | ||
| outputs: | ||
| hashes: ${{ steps.hash.outputs.hashes }} | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
| - name: Generate Source SBOM | ||
| run: | | ||
| mkdir -p results | ||
| curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin | ||
| /usr/local/bin/syft dir:. -o spdx-json > results/sbom-source.spdx.json | ||
| /usr/local/bin/syft dir:. -o cyclonedx-json > results/sbom-source.cdx.json | ||
| - name: Upload SBOM Artifacts | ||
| uses: actions/upload-artifact@6027bc5291242e2308107931669864239f884a44 # v4.4.0 | ||
| with: | ||
| name: sbom-reports | ||
| path: results/sbom-* | ||
| - 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@6027bc5291242e2308107931669864239f884a44 # v4.4.0 | ||
| with: | ||
| name: deployment-audit | ||
| path: deployment-audit.json | ||
| - name: Run Policy Check | ||
| run: ./scripts/fortressci-policy-check.sh .security/policy.yml results/ | ||
| - name: Generate Compliance Report | ||
| run: python3 scripts/generate-compliance-report.py results/ .security/compliance-mappings.yml | ||
| - name: AI Triage | ||
| if: always() | ||
| env: | ||
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | ||
| run: python3 scripts/ai-triage.py --results-dir results/ --config .fortressci.yml | ||
| - name: Generate Security Badge | ||
| if: always() | ||
| run: python3 scripts/generate-badge.py results/ | ||
| - name: Build Attack Graph | ||
| if: always() | ||
| run: python3 scripts/build-attack-graph.py results/ | ||
| - name: Generate Hashes | ||
| id: hash | ||
| run: | | ||
| cd results | ||
| echo "hashes=$(sha256sum sbom-source.spdx.json sbom-source.cdx.json ../deployment-audit.json | base64 -w0)" >> "$GITHUB_OUTPUT" | ||
| provenance: | ||
| needs: [compliance-audit] | ||
| permissions: | ||
| actions: read | ||
| id-token: write | ||
| contents: write | ||
| uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 | ||
| with: | ||
| base64-subjects: "${{ needs.compliance-audit.outputs.hashes }}" | ||
| upload-assets: true | ||
| 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
| - name: Download All Artifacts | ||
| uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 | ||
| with: | ||
| merge-multiple: true | ||
| - name: Post Summary to PR | ||
| uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | ||
| with: | ||
| script: | | ||
| const script = require('./.github/scripts/post_summary.js') | ||
| await script({github, context}) | ||
| auto-remediation: | ||
| name: Auto-Remediation (PR) | ||
| runs-on: ubuntu-latest | ||
| needs: [sast-scan, sca-scan, iac-scan] | ||
| if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' | ||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
| - name: Run Auto-Fix | ||
| run: ./scripts/auto-fix.sh | ||
| - name: Create Pull Request | ||
| uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84617901 # v6.0.1 | ||
| with: | ||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
| commit-message: "fix: auto-remediation of security findings" | ||
| title: "🏰 FortressCI Auto-Remediation" | ||
| body: | | ||
| This is an automated pull request from FortressCI to fix security findings. | ||
| Scanners: Snyk, Checkov | ||
| branch: fortressci/auto-remediation | ||
| base: main | ||
| labels: | | ||
| security | ||
| automated | ||