Skip to content

Add Buy Me a Coffee username #14

Add Buy Me a Coffee username

Add Buy Me a Coffee username #14

Workflow file for this run

name: DevSecOps Pipeline

Check failure on line 1 in .github/workflows/devsecops.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/devsecops.yml

Invalid workflow file

(Line: 163, Col: 13): Unrecognized named-value: 'secrets'. Located at position 1 within expression: secrets.INFRACOST_API_KEY != ''
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})