Files
2025-11-29 17:51:02 +08:00

258 lines
6.7 KiB
YAML

# GitLab CI Pipeline for Gitleaks Secret Scanning
# Save as: .gitlab-ci.yml or include in existing pipeline
# Define stages
stages:
- security
- report
# Default Docker image for security jobs
image: docker:latest
services:
- docker:dind
variables:
# Gitleaks Docker image
GITLEAKS_IMAGE: zricethezav/gitleaks:latest
# Report output path
REPORT_PATH: gitleaks-report.json
# SARIF output for GitLab Security Dashboard
SARIF_PATH: gl-secret-detection-report.json
# Secret scanning job
gitleaks-scan:
stage: security
image: $GITLEAKS_IMAGE
# Run on all branches and merge requests
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
- if: '$CI_COMMIT_BRANCH =~ /^(develop|release)/'
script:
# Run Gitleaks scan
- echo "Running Gitleaks secret detection..."
- |
gitleaks detect \
--source . \
--report-path $REPORT_PATH \
--report-format json \
--verbose || true
# Convert to GitLab SARIF format for Security Dashboard
- |
gitleaks detect \
--source . \
--report-path $SARIF_PATH \
--report-format sarif \
--verbose || true
# Check if secrets were found
- |
if [ -s "$REPORT_PATH" ] && [ "$(cat $REPORT_PATH)" != "null" ]; then
echo "⚠️ Secrets detected! Review findings below."
cat $REPORT_PATH | jq -r '.[] | "File: \(.File)\nLine: \(.StartLine)\nRule: \(.RuleID)\n"'
exit 1
else
echo "✅ No secrets detected"
fi
artifacts:
paths:
- $REPORT_PATH
- $SARIF_PATH
reports:
# GitLab Security Dashboard integration
secret_detection: $SARIF_PATH
when: always
expire_in: 30 days
# Allow failure for initial rollout, then set to false
allow_failure: false
# Optional: Incremental scanning with baseline
gitleaks-incremental:
stage: security
image: $GITLEAKS_IMAGE
# Only run on merge requests
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
script:
# Download baseline from artifacts or storage
- echo "Downloading baseline..."
- |
if [ -f ".gitleaks-baseline.json" ]; then
echo "Using baseline from repository"
else
echo "No baseline found, running full scan"
fi
# Run incremental scan
- |
if [ -f ".gitleaks-baseline.json" ]; then
gitleaks detect \
--source . \
--baseline-path .gitleaks-baseline.json \
--report-path new-findings.json \
--report-format json \
--exit-code 1 || true
if [ -s "new-findings.json" ] && [ "$(cat new-findings.json)" != "null" ]; then
echo "⚠️ New secrets detected since baseline!"
cat new-findings.json | jq .
exit 1
fi
fi
artifacts:
paths:
- new-findings.json
when: always
expire_in: 7 days
# Optional: Create baseline on main branch
create-baseline:
stage: security
image: $GITLEAKS_IMAGE
# Only run on main/master branch
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
when: manual # Manual trigger to avoid overwriting
script:
- echo "Creating new baseline..."
- |
gitleaks detect \
--source . \
--report-path .gitleaks-baseline.json \
--report-format json \
--exit-code 0 || true
artifacts:
paths:
- .gitleaks-baseline.json
expire_in: 365 days
# Optional: Generate human-readable report
generate-report:
stage: report
image: python:3.11-slim
dependencies:
- gitleaks-scan
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
script:
- pip install jinja2
- |
python3 << 'EOF'
import json
import sys
from datetime import datetime
try:
with open('gitleaks-report.json', 'r') as f:
findings = json.load(f)
if not findings:
print("✅ No secrets detected")
sys.exit(0)
print("# Gitleaks Secret Detection Report")
print(f"\n**Generated**: {datetime.now().isoformat()}")
print(f"**Total Findings**: {len(findings)}\n")
for idx, finding in enumerate(findings, 1):
print(f"\n## Finding {idx}")
print(f"- **File**: {finding.get('File', 'unknown')}")
print(f"- **Line**: {finding.get('StartLine', 'unknown')}")
print(f"- **Rule**: {finding.get('RuleID', 'unknown')}")
print(f"- **Description**: {finding.get('Description', 'unknown')}")
print(f"- **Commit**: {finding.get('Commit', 'N/A')}\n")
except FileNotFoundError:
print("No report file found")
except json.JSONDecodeError:
print("No findings in report")
EOF
artifacts:
paths:
- gitleaks-report.json
# Optional: Comment on merge request
comment-mr:
stage: report
image: alpine:latest
dependencies:
- gitleaks-scan
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
before_script:
- apk add --no-cache curl jq
script:
- |
if [ -s "$REPORT_PATH" ] && [ "$(cat $REPORT_PATH)" != "null" ]; then
FINDING_COUNT=$(cat $REPORT_PATH | jq '. | length')
COMMENT="## 🔒 Secret Scanning Results\n\n"
COMMENT="${COMMENT}⚠️ **${FINDING_COUNT} potential secret(s) detected!**\n\n"
COMMENT="${COMMENT}Please review the findings and take immediate action:\n"
COMMENT="${COMMENT}1. **Do not merge** this MR until secrets are removed\n"
COMMENT="${COMMENT}2. Rotate any exposed credentials immediately\n"
COMMENT="${COMMENT}3. Remove secrets from code and use CI/CD variables\n\n"
COMMENT="${COMMENT}See pipeline artifacts for detailed findings."
# Post comment to merge request
curl --request POST \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
--data-urlencode "body=$COMMENT" \
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes"
fi
allow_failure: true
# Optional: Scheduled nightly scan
nightly-scan:
stage: security
image: $GITLEAKS_IMAGE
# Run on schedule only
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
script:
- echo "Running comprehensive nightly secret scan..."
- |
gitleaks detect \
--source . \
--report-path nightly-scan.json \
--report-format json \
--verbose
artifacts:
paths:
- nightly-scan.json
when: always
expire_in: 90 days
# Send notifications on failure
after_script:
- |
if [ $? -ne 0 ]; then
echo "Secrets detected in nightly scan!"
# Add notification logic (email, Slack, etc.)
fi