Files
gh-ahmedasmar-devops-claude…/assets/templates/gitlab-ci/security-scan.yml
2025-11-29 17:51:12 +08:00

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