Centralized Secret Scanning Report #14
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Centralized Secret Scanning Report | |
on: | |
workflow_dispatch: | |
inputs: | |
include_inactive: | |
description: 'Include inactive alerts in report' | |
required: false | |
type: boolean | |
default: false | |
max_workers: | |
description: 'Maximum number of concurrent workers' | |
required: false | |
type: number | |
default: 10 | |
log_level: | |
description: 'Logging level' | |
required: false | |
type: choice | |
options: | |
- INFO | |
- DEBUG | |
- WARNING | |
- ERROR | |
default: 'INFO' | |
alert_threshold: | |
description: 'Number of active alerts to trigger issue creation' | |
required: false | |
type: number | |
default: 10 | |
schedule: | |
- cron: '0 0 * * 1' | |
permissions: | |
security-events: read | |
contents: write # Corrected: covers both reading and writing | |
actions: write | |
issues: write | |
jobs: | |
generate-report: | |
runs-on: ubuntu-latest | |
steps: | |
- name: Checkout .github repo | |
uses: actions/checkout@v4 | |
with: | |
repository: ${{ github.repository_owner }}/.github # Checkout the .github repo! | |
ref: main # Or your default branch | |
path: .github # Checkout into a directory named .github | |
token: ${{ secrets.SECRET_SCANNING_TOKEN }} # Use PAT for org-level access. | |
- name: Set up Python | |
uses: actions/setup-python@v4 | |
with: | |
python-version: '3.11' | |
cache: 'pip' | |
cache-dependency-path: .github/scripts/requirements.txt | |
- name: Install dependencies | |
run: | | |
python -m pip install --upgrade pip | |
pip install -r .github/scripts/requirements.txt | |
- name: Generate timestamp | |
id: timestamp | |
run: echo "timestamp=$(date +%Y%m%d_%H%M%S)" >> $GITHUB_OUTPUT | |
- name: Generate Secret Report | |
id: generate-report | |
env: | |
GITHUB_TOKEN: ${{ secrets.SECRET_SCANNING_TOKEN }} # Use PAT for org-level access | |
ORGANIZATION: ${{ github.repository_owner }} | |
REPORT_FILE: "secret_report_${{ steps.timestamp.outputs.timestamp }}.csv" | |
run: | | |
# No need to create 'reports' dir here; it will be in the checked-out .github repo | |
python .github/scripts/github_secret_scanner.py \ | |
--org "$ORGANIZATION" \ | |
--token "$GITHUB_TOKEN" \ | |
--output "reports/${REPORT_FILE}" \ # Path relative to workflow | |
--log-level ${{ inputs.log_level || 'INFO' }} \ | |
--max-workers ${{ inputs.max_workers || 10 }} \ | |
--max-retries 3 \ | |
${{ inputs.include_inactive && '--include-inactive' || '' }} | |
echo "report_path=reports/${REPORT_FILE}" >> $GITHUB_OUTPUT # Path still correct. | |
- name: Check for No Repositories | |
id: check-repos | |
if: success() | |
run: | | |
if grep -q "__NO_REPOS__" ${{ steps.generate-report.outputs.report_path }}/../output.txt; then # Adjust the path | |
echo "No repositories found in the organization. Exiting." | |
exit 1 | |
fi | |
- name: Process report statistics (inline) | |
id: stats | |
if: success() && steps.check-repos.outcome == 'success' | |
run: | | |
STATS=$(grep "__STATS_START__" ${{ steps.generate-report.outputs.report_path }}/../output.txt | sed 's/__STATS_START__//' | sed 's/__STATS_END__//') # Adjust path | |
echo "total_alerts=$(echo $STATS | cut -d',' -f1 | cut -d'=' -f2)" >> $GITHUB_OUTPUT | |
echo "active_alerts=$(echo $STATS | cut -d',' -f2 | cut -d'=' -f2)" >> $GITHUB_OUTPUT | |
echo "inactive_alerts=$(echo $STATS | cut -d',' -f3 | cut -d'=' -f2)" >> $GITHUB_OUTPUT | |
echo "Total alerts found: $(echo $STATS | cut -d',' -f1 | cut -d'=' -f2)" | |
echo "Active alerts: $(echo $STATS | cut -d',' -f2 | cut -d'=' -f2)" | |
echo "Inactive alerts: $(echo $STATS | cut -d',' -f3 | cut -d'=' -f2)" | |
- name: Create summary issue (using github-script) | |
if: success() && steps.check-repos.outcome == 'success' && steps.stats.outputs.active_alerts > inputs.alert_threshold | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const stats = { | |
total: '${{ steps.stats.outputs.total_alerts }}', | |
active: '${{ steps.stats.outputs.active_alerts }}', | |
inactive: '${{ steps.stats.outputs.inactive_alerts }}', | |
}; | |
const now = new Date(); | |
const formattedDate = now.toLocaleDateString('en-US', { | |
year: 'numeric', | |
month: 'long', | |
day: 'numeric' | |
}); | |
const body = ` | |
# Secret Scanning Report Summary | |
Report generated on: ${now.toISOString()} | |
## Statistics | |
- Total alerts analyzed: ${stats.total} | |
- Active alerts found: ${stats.active} | |
- Inactive alerts found: ${stats.inactive} | |
## Details | |
- Report artifact: [Download report](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}) | |
- Workflow run: [View details](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}) | |
## Configuration | |
- Include inactive alerts: ${{ inputs.include_inactive || 'false' }} | |
- Max workers: ${{ inputs.max_workers || '10' }} | |
- Log level: ${{ inputs.log_level || 'INFO' }} | |
- Alert threshold: ${{ inputs.alert_threshold || '10'}} | |
`; | |
await github.rest.issues.create({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
title: \`📊 Secret Scanning Report - \${formattedDate}\`, | |
body: body, | |
labels: ['secret-scanning', 'report'] | |
}); | |
- name: Commit and Push Report | |
if: success() && steps.check-repos.outcome == 'success' | |
uses: stefanzweifel/git-auto-commit-action@v5 | |
with: | |
commit_message: "Add secret scanning report: ${{ steps.timestamp.outputs.timestamp }}" | |
repository: ./.github # Commit to the .github directory | |
file_pattern: reports/*.csv # Commit the report file | |
commit_user_name: GitHub Actions # Set committer name | |
commit_user_email: [email protected] # Set committer email | |
commit_author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com> # Set author. | |
push_options: '--force' # Use force push (be careful!) | |
- name: Notify on failure | |
if: failure() | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const body = ` | |
# 🚨 Secret Scanning Report Generation Failed | |
Workflow run failed at ${new Date().toISOString()} | |
## Details | |
- Run ID: \`${context.runId}\` | |
- Trigger: ${context.eventName} | |
- Actor: @${context.actor} | |
## Links | |
- [View run details](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}) | |
- [View workflow file](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/blob/main/.github/workflows/secret-scanning-report.yml) | |
Please check the workflow logs for detailed error information. | |
`; | |
await github.rest.issues.create({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
title: '🚨 Secret Scanning Report Generation Failed', | |
body: body, | |
labels: ['secret-scanning', 'failed'] | |
}); | |
- name: Clean up | |
if: always() | |
run: | | |
# No need to clean reports dir now it is part of .github repo | |
echo "No clean up required." | |
concurrency: | |
group: ${{ github.workflow }}-${{ github.ref }} | |
cancel-in-progress: true | |