Files
actions/security-scan/action.yml
renovate[bot] a8031d3922 chore(deps): update raven-actions/actionlint action (v2.0.1 → v2.1.0) (#400)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-16 13:05:24 +02:00

283 lines
9.7 KiB
YAML

# yaml-language-server: $schema=https://json.schemastore.org/github-action.json
#
# REQUIRED PERMISSIONS (set these in your workflow file):
# permissions:
# security-events: write # Required for SARIF uploads
# contents: read # Required for repository access
#
---
name: Security Scan
description: |
Comprehensive security scanning for GitHub Actions including actionlint,
Gitleaks (optional), and Trivy vulnerability scanning. Requires
'security-events: write' and 'contents: read' permissions in the workflow.
author: Ismo Vuorinen
branding:
icon: shield
color: red
inputs:
gitleaks-license:
description: 'Gitleaks license key (required for Gitleaks scanning)'
required: false
default: ''
gitleaks-config:
description: 'Path to Gitleaks config file'
required: false
default: '.gitleaks.toml'
trivy-severity:
description: 'Severity levels to scan for (comma-separated)'
required: false
default: 'CRITICAL,HIGH'
trivy-scanners:
description: 'Types of scanners to run (comma-separated)'
required: false
default: 'vuln,config,secret'
trivy-timeout:
description: 'Timeout for Trivy scan'
required: false
default: '10m'
actionlint-enabled:
description: 'Enable actionlint scanning'
required: false
default: 'true'
token:
description: 'GitHub token for authentication'
required: false
default: ''
outputs:
has_trivy_results:
description: 'Whether Trivy scan produced valid results'
value: ${{ steps.verify-sarif.outputs.has_trivy }}
has_gitleaks_results:
description: 'Whether Gitleaks scan produced valid results'
value: ${{ steps.verify-sarif.outputs.has_gitleaks }}
total_issues:
description: 'Total number of security issues found'
value: ${{ steps.analyze.outputs.total_issues }}
critical_issues:
description: 'Number of critical security issues found'
value: ${{ steps.analyze.outputs.critical_issues }}
runs:
using: composite
steps:
- name: Validate Inputs
id: validate
uses: ivuorinen/actions/validate-inputs@5cc7373a22402ee8985376bc713f00e09b5b2edb
with:
action-type: security-scan
gitleaks-license: ${{ inputs.gitleaks-license }}
gitleaks-config: ${{ inputs.gitleaks-config }}
trivy-severity: ${{ inputs.trivy-severity }}
trivy-scanners: ${{ inputs.trivy-scanners }}
trivy-timeout: ${{ inputs.trivy-timeout }}
actionlint-enabled: ${{ inputs.actionlint-enabled }}
token: ${{ inputs.token }}
- name: Check Required Configurations
id: check-configs
shell: sh
run: |
set -eu
# Initialize all flags as false
{
printf '%s\n' "run_gitleaks=false"
printf '%s\n' "run_trivy=true"
printf '%s\n' "run_actionlint=${{ inputs.actionlint-enabled }}"
} >> "$GITHUB_OUTPUT"
# Check Gitleaks configuration and license
if [ -f "${{ inputs.gitleaks-config }}" ] && [ -n "${{ inputs.gitleaks-license }}" ]; then
printf 'Gitleaks config and license found\n'
printf '%s\n' "run_gitleaks=true" >> "$GITHUB_OUTPUT"
else
printf '::warning::Gitleaks config or license missing - skipping Gitleaks scan\n'
fi
- name: Run actionlint
if: steps.check-configs.outputs.run_actionlint == 'true'
uses: raven-actions/actionlint@963d4779ef039e217e5d0e6fd73ce9ab7764e493 # v2.1.0
with:
cache: true
fail-on-error: true
shellcheck: false
- name: Run Gitleaks
if: steps.check-configs.outputs.run_gitleaks == 'true'
uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2.3.9
env:
GITHUB_TOKEN: ${{ inputs.token || github.token }}
GITLEAKS_LICENSE: ${{ inputs.gitleaks-license }}
with:
config-path: ${{ inputs.gitleaks-config }}
report-format: sarif
report-path: gitleaks-report.sarif
- name: Run Trivy vulnerability scanner
if: steps.check-configs.outputs.run_trivy == 'true'
uses: aquasecurity/trivy-action@a11da62073708815958ea6d84f5650c78a3ef85b # master
with:
scan-type: 'fs'
scanners: ${{ inputs.trivy-scanners }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: ${{ inputs.trivy-severity }}
timeout: ${{ inputs.trivy-timeout }}
- name: Verify SARIF files
id: verify-sarif
shell: sh
run: |
set -eu
# Initialize outputs
{
printf '%s\n' "has_trivy=false"
printf '%s\n' "has_gitleaks=false"
} >> "$GITHUB_OUTPUT"
# Check Trivy results
if [ -f "trivy-results.sarif" ]; then
if jq -e . <"trivy-results.sarif" >/dev/null 2>&1; then
printf '%s\n' "has_trivy=true" >> "$GITHUB_OUTPUT"
else
printf '::warning::Trivy SARIF file exists but is not valid JSON\n'
fi
fi
# Check Gitleaks results if it ran
if [ "${{ steps.check-configs.outputs.run_gitleaks }}" = "true" ]; then
if [ -f "gitleaks-report.sarif" ]; then
if jq -e . <"gitleaks-report.sarif" >/dev/null 2>&1; then
printf '%s\n' "has_gitleaks=true" >> "$GITHUB_OUTPUT"
else
printf '::warning::Gitleaks SARIF file exists but is not valid JSON\n'
fi
fi
fi
- name: Upload Trivy results
if: steps.verify-sarif.outputs.has_trivy == 'true'
uses: github/codeql-action/upload-sarif@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7
with:
sarif_file: 'trivy-results.sarif'
category: 'trivy'
- name: Upload Gitleaks results
if: steps.verify-sarif.outputs.has_gitleaks == 'true'
uses: github/codeql-action/upload-sarif@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7
with:
sarif_file: 'gitleaks-report.sarif'
category: 'gitleaks'
- name: Archive security reports
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: security-reports-${{ github.run_id }}
path: |
${{ steps.verify-sarif.outputs.has_trivy == 'true' && 'trivy-results.sarif' || '' }}
${{ steps.verify-sarif.outputs.has_gitleaks == 'true' && 'gitleaks-report.sarif' || '' }}
retention-days: 30
- name: Analyze Results
id: analyze
if: always()
shell: node {0}
run: |
const fs = require('fs');
try {
let totalIssues = 0;
let criticalIssues = 0;
const analyzeSarif = (file, tool) => {
if (!fs.existsSync(file)) {
console.log(`No results file found for ${tool}`);
return null;
}
try {
const sarif = JSON.parse(fs.readFileSync(file, 'utf8'));
return sarif.runs.reduce((acc, run) => {
if (!run.results) return acc;
const critical = run.results.filter(r =>
r.level === 'error' ||
r.level === 'critical' ||
(r.ruleId || '').toLowerCase().includes('critical')
).length;
return {
total: acc.total + run.results.length,
critical: acc.critical + critical
};
}, { total: 0, critical: 0 });
} catch (error) {
console.log(`Error analyzing ${tool} results: ${error.message}`);
return null;
}
};
// Only analyze results from tools that ran successfully
const results = {
trivy: '${{ steps.verify-sarif.outputs.has_trivy }}' === 'true' ?
analyzeSarif('trivy-results.sarif', 'trivy') : null,
gitleaks: '${{ steps.verify-sarif.outputs.has_gitleaks }}' === 'true' ?
analyzeSarif('gitleaks-report.sarif', 'gitleaks') : null
};
// Aggregate results
Object.entries(results).forEach(([tool, result]) => {
if (result) {
totalIssues += result.total;
criticalIssues += result.critical;
console.log(`${tool}: ${result.total} total, ${result.critical} critical issues`);
}
});
// Create summary
const summary = `## Security Scan Summary
- Total Issues Found: ${totalIssues}
- Critical Issues: ${criticalIssues}
### Tool Breakdown
${Object.entries(results)
.filter(([_, r]) => r)
.map(([tool, r]) =>
`- ${tool}: ${r.total} total, ${r.critical} critical`
).join('\n')}
### Tools Run Status
- Actionlint: ${{ steps.check-configs.outputs.run_actionlint }}
- Trivy: ${{ steps.verify-sarif.outputs.has_trivy }}
- Gitleaks: ${{ steps.check-configs.outputs.run_gitleaks }}
`;
// Set outputs using GITHUB_OUTPUT
const outputFile = process.env.GITHUB_OUTPUT;
if (outputFile) {
fs.appendFileSync(outputFile, `total_issues=${totalIssues}\n`);
fs.appendFileSync(outputFile, `critical_issues=${criticalIssues}\n`);
}
// Add job summary using GITHUB_STEP_SUMMARY
const summaryFile = process.env.GITHUB_STEP_SUMMARY;
if (summaryFile) {
fs.appendFileSync(summaryFile, summary + '\n');
}
// Fail if critical issues found
if (criticalIssues > 0) {
console.error(`Found ${criticalIssues} critical security issues`);
process.exit(1);
}
} catch (error) {
console.error(`Analysis failed: ${error.message}`);
process.exit(1);
}