258 lines
6.7 KiB
YAML
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
|