480 lines
11 KiB
YAML
480 lines
11 KiB
YAML
# Complete DevSecOps Security Scanning Pipeline for GitLab CI
|
|
# SAST, DAST, SCA, Container Scanning, Secret Scanning
|
|
|
|
stages:
|
|
- secret-scan
|
|
- sast
|
|
- sca
|
|
- build
|
|
- container-scan
|
|
- dast
|
|
- compliance
|
|
- report
|
|
|
|
variables:
|
|
SECURE_LOG_LEVEL: "info"
|
|
# Enable Auto DevOps security scanners
|
|
SAST_EXCLUDED_PATHS: "spec, test, tests, tmp"
|
|
SCAN_KUBERNETES_MANIFESTS: "false"
|
|
|
|
# Stage 1: Secret Scanning
|
|
secret-scan:trufflehog:
|
|
stage: secret-scan
|
|
image: trufflesecurity/trufflehog:latest
|
|
script:
|
|
- trufflehog filesystem . --json --fail > trufflehog-report.json || true
|
|
- |
|
|
if [ -s trufflehog-report.json ]; then
|
|
echo "❌ Secrets detected!"
|
|
cat trufflehog-report.json
|
|
exit 1
|
|
fi
|
|
artifacts:
|
|
when: always
|
|
paths:
|
|
- trufflehog-report.json
|
|
expire_in: 30 days
|
|
allow_failure: false
|
|
|
|
secret-scan:gitleaks:
|
|
stage: secret-scan
|
|
image: zricethezav/gitleaks:latest
|
|
script:
|
|
- gitleaks detect --source . --report-format json --report-path gitleaks-report.json
|
|
artifacts:
|
|
when: always
|
|
paths:
|
|
- gitleaks-report.json
|
|
expire_in: 30 days
|
|
allow_failure: true
|
|
|
|
# Stage 2: SAST (Static Application Security Testing)
|
|
sast:semgrep:
|
|
stage: sast
|
|
image: returntocorp/semgrep
|
|
script:
|
|
- semgrep scan --config=auto --sarif --output=semgrep.sarif .
|
|
- semgrep scan --config=p/owasp-top-ten --json --output=semgrep-owasp.json .
|
|
artifacts:
|
|
reports:
|
|
sast: semgrep.sarif
|
|
paths:
|
|
- semgrep.sarif
|
|
- semgrep-owasp.json
|
|
expire_in: 30 days
|
|
allow_failure: false
|
|
|
|
sast:nodejs:
|
|
stage: sast
|
|
image: node:20-alpine
|
|
script:
|
|
- npm install -g eslint eslint-plugin-security
|
|
- eslint . --plugin=security --format=json --output-file=eslint-security.json || true
|
|
artifacts:
|
|
paths:
|
|
- eslint-security.json
|
|
expire_in: 30 days
|
|
only:
|
|
exists:
|
|
- package.json
|
|
allow_failure: true
|
|
|
|
sast:python:
|
|
stage: sast
|
|
image: python:3.11-alpine
|
|
script:
|
|
- pip install bandit
|
|
- bandit -r . -f json -o bandit-report.json -ll || true
|
|
- bandit -r . -ll # Fail on high severity
|
|
artifacts:
|
|
reports:
|
|
sast: bandit-report.json
|
|
paths:
|
|
- bandit-report.json
|
|
expire_in: 30 days
|
|
only:
|
|
exists:
|
|
- requirements.txt
|
|
allow_failure: false
|
|
|
|
sast:go:
|
|
stage: sast
|
|
image: securego/gosec:latest
|
|
script:
|
|
- gosec -fmt json -out gosec-report.json ./... || true
|
|
- gosec ./... # Fail on findings
|
|
artifacts:
|
|
reports:
|
|
sast: gosec-report.json
|
|
paths:
|
|
- gosec-report.json
|
|
expire_in: 30 days
|
|
only:
|
|
exists:
|
|
- go.mod
|
|
allow_failure: false
|
|
|
|
# GitLab built-in SAST
|
|
include:
|
|
- template: Security/SAST.gitlab-ci.yml
|
|
|
|
sast:
|
|
variables:
|
|
SAST_EXCLUDED_ANALYZERS: ""
|
|
|
|
# Stage 3: SCA (Software Composition Analysis)
|
|
sca:npm-audit:
|
|
stage: sca
|
|
image: node:20-alpine
|
|
before_script:
|
|
- npm ci
|
|
script:
|
|
- npm audit --audit-level=moderate --json > npm-audit.json || true
|
|
- npm audit --audit-level=high # Fail on high severity
|
|
artifacts:
|
|
paths:
|
|
- npm-audit.json
|
|
expire_in: 30 days
|
|
only:
|
|
exists:
|
|
- package.json
|
|
allow_failure: false
|
|
|
|
sca:python:
|
|
stage: sca
|
|
image: python:3.11-alpine
|
|
script:
|
|
- pip install pip-audit
|
|
- pip-audit --requirement requirements.txt --format json --output pip-audit.json || true
|
|
- pip-audit --requirement requirements.txt --vulnerability-service osv # Fail on vulns
|
|
artifacts:
|
|
paths:
|
|
- pip-audit.json
|
|
expire_in: 30 days
|
|
only:
|
|
exists:
|
|
- requirements.txt
|
|
allow_failure: false
|
|
|
|
# GitLab built-in Dependency Scanning
|
|
include:
|
|
- template: Security/Dependency-Scanning.gitlab-ci.yml
|
|
|
|
dependency_scanning:
|
|
variables:
|
|
DS_EXCLUDED_PATHS: "test/,tests/,spec/,vendor/"
|
|
|
|
# Stage 4: Build
|
|
build:app:
|
|
stage: build
|
|
image: node:20-alpine
|
|
script:
|
|
- npm ci
|
|
- npm run build
|
|
artifacts:
|
|
paths:
|
|
- dist/
|
|
expire_in: 7 days
|
|
only:
|
|
- branches
|
|
- merge_requests
|
|
|
|
build:docker:
|
|
stage: build
|
|
image: docker:24-cli
|
|
services:
|
|
- docker:24-dind
|
|
variables:
|
|
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
|
|
before_script:
|
|
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
|
|
script:
|
|
- docker build --tag $IMAGE_TAG .
|
|
- docker push $IMAGE_TAG
|
|
- echo "IMAGE_TAG=$IMAGE_TAG" > build.env
|
|
artifacts:
|
|
reports:
|
|
dotenv: build.env
|
|
only:
|
|
- branches
|
|
- merge_requests
|
|
|
|
# Stage 5: Container Security Scanning
|
|
container:trivy:
|
|
stage: container-scan
|
|
image: aquasec/trivy:latest
|
|
needs: [build:docker]
|
|
dependencies:
|
|
- build:docker
|
|
variables:
|
|
GIT_STRATEGY: none
|
|
script:
|
|
# Scan for vulnerabilities
|
|
- trivy image --severity HIGH,CRITICAL --format json --output trivy-report.json $IMAGE_TAG
|
|
- trivy image --severity HIGH,CRITICAL --exit-code 1 $IMAGE_TAG
|
|
artifacts:
|
|
reports:
|
|
container_scanning: trivy-report.json
|
|
paths:
|
|
- trivy-report.json
|
|
expire_in: 30 days
|
|
allow_failure: false
|
|
|
|
container:grype:
|
|
stage: container-scan
|
|
image: anchore/grype:latest
|
|
needs: [build:docker]
|
|
dependencies:
|
|
- build:docker
|
|
variables:
|
|
GIT_STRATEGY: none
|
|
before_script:
|
|
- echo $CI_REGISTRY_PASSWORD | grype registry login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
|
|
script:
|
|
- grype $IMAGE_TAG --fail-on high --output json --file grype-report.json
|
|
artifacts:
|
|
paths:
|
|
- grype-report.json
|
|
expire_in: 30 days
|
|
allow_failure: true
|
|
|
|
container:sbom:
|
|
stage: container-scan
|
|
image: anchore/syft:latest
|
|
needs: [build:docker]
|
|
dependencies:
|
|
- build:docker
|
|
variables:
|
|
GIT_STRATEGY: none
|
|
before_script:
|
|
- echo $CI_REGISTRY_PASSWORD | syft registry login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
|
|
script:
|
|
- syft $IMAGE_TAG -o spdx-json > sbom.spdx.json
|
|
- syft $IMAGE_TAG -o cyclonedx-json > sbom.cyclonedx.json
|
|
artifacts:
|
|
paths:
|
|
- sbom.spdx.json
|
|
- sbom.cyclonedx.json
|
|
expire_in: 90 days
|
|
only:
|
|
- main
|
|
- tags
|
|
|
|
# GitLab built-in Container Scanning
|
|
include:
|
|
- template: Security/Container-Scanning.gitlab-ci.yml
|
|
|
|
container_scanning:
|
|
needs: [build:docker]
|
|
dependencies:
|
|
- build:docker
|
|
variables:
|
|
CS_IMAGE: $IMAGE_TAG
|
|
GIT_STRATEGY: none
|
|
|
|
# Stage 6: DAST (Dynamic Application Security Testing)
|
|
dast:zap-baseline:
|
|
stage: dast
|
|
image: owasp/zap2docker-stable
|
|
needs: [build:docker]
|
|
services:
|
|
- name: $IMAGE_TAG
|
|
alias: testapp
|
|
script:
|
|
# Wait for app to be ready
|
|
- sleep 10
|
|
# Run baseline scan
|
|
- zap-baseline.py -t http://testapp:8080 -r zap-baseline-report.html -J zap-baseline.json
|
|
artifacts:
|
|
when: always
|
|
paths:
|
|
- zap-baseline-report.html
|
|
- zap-baseline.json
|
|
expire_in: 30 days
|
|
only:
|
|
- main
|
|
- schedules
|
|
allow_failure: true
|
|
|
|
dast:zap-full:
|
|
stage: dast
|
|
image: owasp/zap2docker-stable
|
|
script:
|
|
# Full scan on staging environment
|
|
- zap-full-scan.py -t https://staging.example.com -r zap-full-report.html -J zap-full.json
|
|
artifacts:
|
|
when: always
|
|
paths:
|
|
- zap-full-report.html
|
|
- zap-full.json
|
|
reports:
|
|
dast: zap-full.json
|
|
expire_in: 30 days
|
|
only:
|
|
- schedules # Run on schedule only (slow)
|
|
allow_failure: true
|
|
|
|
# GitLab built-in DAST
|
|
include:
|
|
- template: DAST.gitlab-ci.yml
|
|
|
|
dast:
|
|
variables:
|
|
DAST_WEBSITE: https://staging.example.com
|
|
DAST_FULL_SCAN_ENABLED: "false"
|
|
only:
|
|
- schedules
|
|
- main
|
|
|
|
# Stage 7: License Compliance
|
|
license:check:
|
|
stage: compliance
|
|
image: node:20-alpine
|
|
needs: [build:app]
|
|
script:
|
|
- npm ci
|
|
- npm install -g license-checker
|
|
- license-checker --production --onlyAllow "MIT;Apache-2.0;BSD-2-Clause;BSD-3-Clause;ISC;0BSD" --json --out license-report.json
|
|
artifacts:
|
|
paths:
|
|
- license-report.json
|
|
expire_in: 30 days
|
|
only:
|
|
exists:
|
|
- package.json
|
|
allow_failure: false
|
|
|
|
# GitLab built-in License Scanning
|
|
include:
|
|
- template: Security/License-Scanning.gitlab-ci.yml
|
|
|
|
license_scanning:
|
|
only:
|
|
- main
|
|
- merge_requests
|
|
|
|
# Stage 8: Security Report & Gate
|
|
security:gate:
|
|
stage: report
|
|
image: alpine:latest
|
|
needs:
|
|
- secret-scan:trufflehog
|
|
- sast:semgrep
|
|
- sca:npm-audit
|
|
- container:trivy
|
|
- license:check
|
|
before_script:
|
|
- apk add --no-cache jq curl
|
|
script:
|
|
- |
|
|
echo "==================================="
|
|
echo "🔒 Security Gate Evaluation"
|
|
echo "==================================="
|
|
|
|
GATE_PASSED=true
|
|
|
|
# Check Trivy results
|
|
if [ -f trivy-report.json ]; then
|
|
CRITICAL=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity=="CRITICAL")] | length' trivy-report.json)
|
|
HIGH=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity=="HIGH")] | length' trivy-report.json)
|
|
|
|
echo "Container Vulnerabilities:"
|
|
echo " - Critical: $CRITICAL"
|
|
echo " - High: $HIGH"
|
|
|
|
if [ "$CRITICAL" -gt 0 ]; then
|
|
echo "❌ CRITICAL vulnerabilities found in container"
|
|
GATE_PASSED=false
|
|
fi
|
|
|
|
if [ "$HIGH" -gt 10 ]; then
|
|
echo "⚠️ Too many HIGH vulnerabilities: $HIGH"
|
|
GATE_PASSED=false
|
|
fi
|
|
fi
|
|
|
|
# Final gate decision
|
|
if [ "$GATE_PASSED" = true ]; then
|
|
echo ""
|
|
echo "✅ Security gate PASSED"
|
|
echo "All security checks completed successfully"
|
|
exit 0
|
|
else
|
|
echo ""
|
|
echo "❌ Security gate FAILED"
|
|
echo "Critical security issues detected"
|
|
exit 1
|
|
fi
|
|
allow_failure: false
|
|
|
|
security:report:
|
|
stage: report
|
|
image: alpine:latest
|
|
needs:
|
|
- security:gate
|
|
when: always
|
|
script:
|
|
- |
|
|
cat << EOF > security-report.md
|
|
# Security Scan Report - $(date +%Y-%m-%d)
|
|
|
|
## Summary
|
|
- **Project:** $CI_PROJECT_NAME
|
|
- **Branch:** $CI_COMMIT_BRANCH
|
|
- **Commit:** $CI_COMMIT_SHORT_SHA
|
|
- **Pipeline:** $CI_PIPELINE_URL
|
|
- **Scan Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
|
|
|
## Scans Performed
|
|
1. ✅ Secret Scanning (TruffleHog, Gitleaks)
|
|
2. ✅ SAST (Semgrep, Language-specific)
|
|
3. ✅ SCA (npm audit, pip-audit)
|
|
4. ✅ Container Scanning (Trivy, Grype)
|
|
5. ✅ SBOM Generation (Syft)
|
|
6. ✅ License Compliance
|
|
|
|
## Security Gate
|
|
Status: See job logs for details
|
|
|
|
## Artifacts
|
|
- Trivy container scan report
|
|
- Semgrep SAST report
|
|
- SBOM (SPDX & CycloneDX)
|
|
- License compliance report
|
|
- ZAP DAST report (if applicable)
|
|
|
|
## Next Steps
|
|
1. Review all security findings in artifacts
|
|
2. Address critical and high severity vulnerabilities
|
|
3. Update dependencies with known vulnerabilities
|
|
4. Re-run pipeline to verify fixes
|
|
EOF
|
|
|
|
cat security-report.md
|
|
artifacts:
|
|
paths:
|
|
- security-report.md
|
|
expire_in: 90 days
|
|
|
|
# Workflow rules
|
|
workflow:
|
|
rules:
|
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
- if: '$CI_COMMIT_BRANCH == "develop"'
|
|
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
|
- if: '$CI_COMMIT_TAG'
|
|
|
|
# Interruptible jobs for MR pipelines
|
|
.interruptible:
|
|
interruptible: true
|
|
|
|
secret-scan:trufflehog:
|
|
extends: .interruptible
|
|
|
|
sast:semgrep:
|
|
extends: .interruptible
|
|
|
|
sca:npm-audit:
|
|
extends: .interruptible
|