Initial commit
This commit is contained in:
257
skills/devsecops/secrets-gitleaks/assets/gitlab-ci.yml
Normal file
257
skills/devsecops/secrets-gitleaks/assets/gitlab-ci.yml
Normal file
@@ -0,0 +1,257 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user