fix: Robust workflow secret handling and artifact management #18
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@6961f2bace57ab32b23b3ba40f8f420f6bc7e004 # 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@6961f2bace57ab32b23b3ba40f8f420f6bc7e004 # 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: semgrep/semgrep # Updated from 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 | |
| env: | |
| SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} | |
| 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 | |
| if: ${{ env.SNYK_TOKEN != '' }} | |
| uses: snyk/actions/node@b9830575092c6dac41351111608672074e54e445 # v0.10.0 | |
| continue-on-error: true | |
| with: | |
| args: --severity-threshold=high --sarif-file-output=snyk.sarif | |
| - name: Upload SARIF | |
| uses: actions/upload-artifact@6027bc5291242e2308107931669864239f884a44 # v4.4.0 | |
| if: always() && env.SNYK_TOKEN != '' | |
| with: | |
| name: snyk.sarif | |
| path: snyk.sarif | |
| - name: Upload Security SARIF (CodeQL) | |
| uses: github/codeql-action/upload-sarif@66115715ae9c0202956f4d546f14066914995955 # v3.25.11 | |
| if: always() && env.SNYK_TOKEN != '' | |
| continue-on-error: true | |
| with: | |
| sarif_file: snyk.sarif | |
| - name: Snyk Gatekeeper | |
| if: ${{ env.SNYK_TOKEN != '' }} | |
| uses: snyk/actions/node@b9830575092c6dac41351111608672074e54e445 # v0.10.0 | |
| 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 | |
| env: | |
| INFRACOST_API_KEY: ${{ secrets.INFRACOST_API_KEY }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Setup Infracost | |
| if: ${{ env.INFRACOST_API_KEY != '' }} | |
| uses: infracost/actions/setup@91f7a6021289569735d487f98f6d525790a6e60b # v3.0.0 | |
| with: | |
| api-key: ${{ env.INFRACOST_API_KEY }} | |
| - name: Infracost Breakdown | |
| if: ${{ env.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' && env.INFRACOST_API_KEY != '' | |
| env: | |
| 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 | |
| echo "Cosign key detected. Mock signing for demo." | |
| 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 }} . | |
| docker run -d -p 3000:3000 --name test-app my-app:${{ github.sha }} | |
| sleep 5 | |
| - name: OWASP ZAP Baseline Scan | |
| uses: zaproxy/action-baseline@290a611c039d91f868c6e26487e0766324263720 # v0.14.0 | |
| continue-on-error: true | |
| with: | |
| target: 'http://localhost:3000' | |
| fail_action: false | |
| 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 }} | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Download All Artifacts | |
| uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 | |
| with: | |
| path: results | |
| merge-multiple: true | |
| - 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 "{" > results/deployment-audit.json | |
| echo " \"timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\"," >> results/deployment-audit.json | |
| echo " \"repository\": \"$GITHUB_REPOSITORY\"," >> results/deployment-audit.json | |
| echo " \"commit_sha\": \"$GITHUB_SHA\"," >> results/deployment-audit.json | |
| echo " \"actor\": \"$GITHUB_ACTOR\"," >> results/deployment-audit.json | |
| echo " \"workflow_run_id\": \"$GITHUB_RUN_ID\"," >> results/deployment-audit.json | |
| echo "}" >> results/deployment-audit.json | |
| - name: Upload Audit Artifact | |
| uses: actions/upload-artifact@6027bc5291242e2308107931669864239f884a44 # v4.4.0 | |
| with: | |
| name: deployment-audit | |
| path: results/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 != '' | |
| 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] | |
| 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 |