Initial commit
This commit is contained in:
479
assets/templates/gitlab-ci/security-scan.yml
Normal file
479
assets/templates/gitlab-ci/security-scan.yml
Normal file
@@ -0,0 +1,479 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user