mirror of
https://github.com/ivuorinen/actions.git
synced 2026-01-26 03:23:59 +00:00
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
264 lines
9.0 KiB
YAML
264 lines
9.0 KiB
YAML
---
|
|
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
|
|
name: Action Security
|
|
|
|
on:
|
|
push:
|
|
paths:
|
|
- '**/action.yml'
|
|
- '**/action.yaml'
|
|
pull_request:
|
|
paths:
|
|
- '**/action.yml'
|
|
- '**/action.yaml'
|
|
merge_group:
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
permissions:
|
|
contents: read
|
|
actions: read
|
|
pull-requests: read
|
|
|
|
jobs:
|
|
analyze:
|
|
name: Analyze Action Security
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
|
|
permissions:
|
|
security-events: write
|
|
statuses: write
|
|
issues: write
|
|
|
|
steps:
|
|
- name: Checkout Repository
|
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Check Required Configurations
|
|
id: check-configs
|
|
shell: bash
|
|
run: |
|
|
# Initialize all flags as false
|
|
{
|
|
echo "run_gitleaks=false"
|
|
echo "run_trivy=true"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
# Check Gitleaks configuration and license
|
|
if [ -f ".gitleaks.toml" ] && [ -n "${{ secrets.GITLEAKS_LICENSE }}" ]; then
|
|
echo "Gitleaks config and license found"
|
|
echo "run_gitleaks=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "::warning::Gitleaks config or license missing - skipping Gitleaks scan"
|
|
fi
|
|
|
|
- name: Run actionlint
|
|
uses: raven-actions/actionlint@3a24062651993d40fed1019b58ac6fbdfbf276cc # v2.0.1
|
|
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: ${{ secrets.GITHUB_TOKEN }}
|
|
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
|
|
with:
|
|
config-path: .gitleaks.toml
|
|
report-format: sarif
|
|
report-path: gitleaks-report.sarif
|
|
|
|
- name: Run Trivy vulnerability scanner
|
|
uses: aquasecurity/trivy-action@a11da62073708815958ea6d84f5650c78a3ef85b # master
|
|
with:
|
|
scan-type: 'fs'
|
|
scanners: 'vuln,config,secret'
|
|
format: 'sarif'
|
|
output: 'trivy-results.sarif'
|
|
severity: 'CRITICAL,HIGH'
|
|
timeout: '10m'
|
|
|
|
- name: Verify SARIF files
|
|
id: verify-sarif
|
|
shell: bash
|
|
run: |
|
|
# Initialize outputs
|
|
{
|
|
echo "has_trivy=false"
|
|
echo "has_gitleaks=false"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
# Check Trivy results
|
|
if [ -f "trivy-results.sarif" ]; then
|
|
if jq -e . </dev/null 2>&1 <"trivy-results.sarif"; then
|
|
echo "has_trivy=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "::warning::Trivy SARIF file exists but is not valid JSON"
|
|
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 . </dev/null 2>&1 <"gitleaks-report.sarif"; then
|
|
echo "has_gitleaks=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "::warning::Gitleaks SARIF file exists but is not valid JSON"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
- name: Upload Trivy results
|
|
if: steps.verify-sarif.outputs.has_trivy == 'true'
|
|
uses: github/codeql-action/upload-sarif@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19
|
|
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@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19
|
|
with:
|
|
sarif_file: 'gitleaks-report.sarif'
|
|
category: 'gitleaks'
|
|
|
|
- name: Archive security reports
|
|
if: always()
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
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
|
|
if: always()
|
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
|
with:
|
|
script: |
|
|
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 }} ?
|
|
analyzeSarif('trivy-results.sarif', 'trivy') : null,
|
|
gitleaks: ${{ steps.verify-sarif.outputs.has_gitleaks }} ?
|
|
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
|
|
- Trivy: ${{ steps.verify-sarif.outputs.has_trivy }}
|
|
- Gitleaks: ${{ steps.check-configs.outputs.run_gitleaks }}
|
|
`;
|
|
|
|
// Set output
|
|
core.setOutput('total_issues', totalIssues);
|
|
core.setOutput('critical_issues', criticalIssues);
|
|
|
|
// Add job summary
|
|
await core.summary
|
|
.addRaw(summary)
|
|
.write();
|
|
|
|
// Fail if critical issues found
|
|
if (criticalIssues > 0) {
|
|
core.setFailed(`Found ${criticalIssues} critical security issues`);
|
|
}
|
|
} catch (error) {
|
|
core.setFailed(`Analysis failed: ${error.message}`);
|
|
}
|
|
|
|
- name: Notify on Critical Issues
|
|
if: failure()
|
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
|
with:
|
|
script: |
|
|
const { repo, owner } = context.repo;
|
|
const critical = core.getInput('critical_issues');
|
|
|
|
const body = `🚨 Critical security issues found in GitHub Actions
|
|
|
|
${critical} critical security issues were found during the security scan.
|
|
|
|
### Scan Results
|
|
- Trivy: ${{ steps.verify-sarif.outputs.has_trivy == 'true' && 'Completed' || 'Skipped/Failed' }}
|
|
- Gitleaks: ${{ steps.check-configs.outputs.run_gitleaks == 'true' && 'Completed' || 'Skipped' }}
|
|
|
|
[View detailed scan results](https://github.com/${owner}/${repo}/actions/runs/${context.runId})
|
|
|
|
Please address these issues immediately.
|
|
|
|
> Note: Some security tools might have been skipped due to missing configurations.
|
|
> Check the workflow run for details.`;
|
|
|
|
await github.rest.issues.create({
|
|
owner,
|
|
repo,
|
|
title: '🚨 Critical Security Issues in Actions',
|
|
body,
|
|
labels: ['security', 'critical', 'actions'],
|
|
assignees: ['ivuorinen']
|
|
});
|