Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions .github/workflows/build-containers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ jobs:
packages: write
id-token: write # Needed for OIDC token (sigstore)
attestations: write # Needed for attestations
security-events: write # Needed for Trivy SARIF upload
security-events: write # Needed for Grype SARIF upload

steps:
- name: Checkout repository
Expand Down Expand Up @@ -444,7 +444,7 @@ jobs:
sbom: true
provenance: true

- name: Build single-platform image for Trivy scan
- name: Build single-platform image for Grype scan
id: build-for-scan
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
with:
Expand Down Expand Up @@ -534,20 +534,20 @@ jobs:
# Clean up
rm -f /tmp/security-attestation.json

- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
- name: Run Grype vulnerability scanner
id: grype-scan
uses: anchore/scan-action@7037fa011853d5a11690026fb85feee79f4c946c # v7.3.2
with:
image-ref: local-scan:${{ steps.meta.outputs.server_name }}-${{ steps.meta.outputs.version }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH,MEDIUM'
image: "local-scan:${{ steps.meta.outputs.server_name }}-${{ steps.meta.outputs.version }}"
severity-cutoff: "medium"
output-format: "sarif"

- name: Upload Trivy results to GitHub Security
- name: Upload Grype results to GitHub Security
uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4
if: always()
with:
sarif_file: 'trivy-results.sarif'
category: 'trivy-${{ steps.meta.outputs.server_name }}'
sarif_file: ${{ steps.grype-scan.outputs.sarif }}
category: 'grype-${{ steps.meta.outputs.server_name }}'

- name: Generate image summary
env:
Expand All @@ -572,14 +572,14 @@ jobs:
echo "- **Build Provenance**: ✅ Attested" >> $GITHUB_STEP_SUMMARY
echo "- **Security Scan**: ✅ Attested" >> $GITHUB_STEP_SUMMARY
echo "- **Signatures**: ✅ Signed with Sigstore/Cosign" >> $GITHUB_STEP_SUMMARY
echo "- **Trivy Scan**: ✅ Completed (see Security tab)" >> $GITHUB_STEP_SUMMARY
echo "- **Grype Scan**: ✅ Completed (see Security tab)" >> $GITHUB_STEP_SUMMARY
echo "- **Status**: ✅ Built, pushed, signed, and attested" >> $GITHUB_STEP_SUMMARY
echo "- **Tags**:" >> $GITHUB_STEP_SUMMARY
echo " - ${IMAGE_NAME}:${VERSION}" >> $GITHUB_STEP_SUMMARY
echo " - ${IMAGE_NAME}:latest" >> $GITHUB_STEP_SUMMARY
echo "- **Digest**: $DIGEST" >> $GITHUB_STEP_SUMMARY
else
echo "- **Trivy Scan**: ✅ Completed (see Security tab)" >> $GITHUB_STEP_SUMMARY
echo "- **Grype Scan**: ✅ Completed (see Security tab)" >> $GITHUB_STEP_SUMMARY
echo "- **Status**: ✅ Built (not pushed - PR)" >> $GITHUB_STEP_SUMMARY
fi

Expand Down
72 changes: 31 additions & 41 deletions .github/workflows/periodic-security-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,45 +87,39 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Run Trivy comprehensive scan
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
- name: Run Grype vulnerability scan (SARIF)
id: grype-scan
uses: anchore/scan-action@7037fa011853d5a11690026fb85feee79f4c946c # v7.3.2
with:
image-ref: ${{ steps.meta.outputs.image_ref }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH,MEDIUM,LOW'
scanners: 'vuln,secret,config,license'
timeout: '15m'
image: "${{ steps.meta.outputs.image_ref }}"
severity-cutoff: "low"
output-format: "sarif"

- name: Upload SARIF to GitHub Security
uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4
if: always()
with:
sarif_file: 'trivy-results.sarif'
category: 'periodic-trivy-${{ steps.meta.outputs.server_name }}'
sarif_file: ${{ steps.grype-scan.outputs.sarif }}
category: 'periodic-grype-${{ steps.meta.outputs.server_name }}'

- name: Run Trivy for detailed JSON report
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
- name: Run Grype vulnerability scan (JSON)
id: grype-scan-json
uses: anchore/scan-action@7037fa011853d5a11690026fb85feee79f4c946c # v7.3.2
with:
image-ref: ${{ steps.meta.outputs.image_ref }}
format: 'json'
output: 'trivy-results.json'
severity: 'CRITICAL,HIGH,MEDIUM,LOW,UNKNOWN'
scanners: 'vuln,secret,config,license'
timeout: '15m'
image: "${{ steps.meta.outputs.image_ref }}"
severity-cutoff: "low"
output-format: "json"

- name: Check for critical issues
id: check-critical
run: |
critical=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length' trivy-results.json)
high=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length' trivy-results.json)
secrets=$(jq '[.Results[]?.Secrets[]?] | length' trivy-results.json)
critical=$(jq '[.matches[]? | select(.vulnerability.severity == "Critical")] | length' ${{ steps.grype-scan-json.outputs.json }})
high=$(jq '[.matches[]? | select(.vulnerability.severity == "High")] | length' ${{ steps.grype-scan-json.outputs.json }})

echo "critical=$critical" >> $GITHUB_OUTPUT
echo "high=$high" >> $GITHUB_OUTPUT
echo "secrets=$secrets" >> $GITHUB_OUTPUT

if [ "$critical" -gt 0 ] || [ "$secrets" -gt 0 ]; then
if [ "$critical" -gt 0 ]; then
echo "should_create_issue=true" >> $GITHUB_OUTPUT
else
echo "should_create_issue=false" >> $GITHUB_OUTPUT
Expand All @@ -134,44 +128,40 @@ jobs:
- name: Create issue for critical findings
if: steps.check-critical.outputs.should_create_issue == 'true'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GRYPE_JSON_PATH: ${{ steps.grype-scan-json.outputs.json }}
with:
script: |
const fs = require('fs');
const results = JSON.parse(fs.readFileSync('trivy-results.json', 'utf8'));
const results = JSON.parse(fs.readFileSync(process.env.GRYPE_JSON_PATH, 'utf8'));

const critical = ${{ steps.check-critical.outputs.critical }};
const high = ${{ steps.check-critical.outputs.high }};
const secrets = ${{ steps.check-critical.outputs.secrets }};

let body = `## 🚨 Security Scan Alert\n\n`;
body += `A periodic security scan found critical issues in the container image:\n\n`;
body += `- **Image**: \`${{ steps.meta.outputs.image_ref }}\`\n`;
body += `- **Critical vulnerabilities**: ${critical}\n`;
body += `- **High vulnerabilities**: ${high}\n`;
body += `- **Secrets detected**: ${secrets}\n\n`;
body += `- **High vulnerabilities**: ${high}\n\n`;

body += `### Details\n\n`;
body += `See the [Security tab](../../security/code-scanning) for full details.\n\n`;

if (critical > 0) {
body += `#### Critical Vulnerabilities\n\n`;
const criticalVulns = results.Results.flatMap(r =>
(r.Vulnerabilities || []).filter(v => v.Severity === 'CRITICAL').slice(0, 5)
);
const criticalVulns = (results.matches || [])
.filter(m => m.vulnerability.severity === 'Critical')
.slice(0, 5);

for (const vuln of criticalVulns) {
body += `- **${vuln.VulnerabilityID}** in \`${vuln.PkgName}\`: ${vuln.Title || 'No title'}\n`;
for (const match of criticalVulns) {
body += `- **${match.vulnerability.id}** in \`${match.artifact.name}@${match.artifact.version}\`: ${match.vulnerability.description || 'No description'}\n`;
}

if (critical > 5) {
body += `\n_... and ${critical - 5} more. See Security tab for complete list._\n`;
}
}

if (secrets > 0) {
body += `\n⚠️ **${secrets} potential secret(s) detected in the image!**\n`;
}

body += `\n---\n`;
body += `_Automated security scan from [periodic-security-scan workflow](../actions/workflows/periodic-security-scan.yml)_`;

Expand All @@ -180,7 +170,7 @@ jobs:
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'security,trivy',
labels: 'security,grype',
});

const existingIssue = issues.find(issue =>
Expand All @@ -203,7 +193,7 @@ jobs:
repo: context.repo.repo,
title: `🚨 Security: Critical issues in ${{ steps.meta.outputs.server_name }} container`,
body: body,
labels: ['security', 'trivy', 'critical']
labels: ['security', 'grype', 'critical']
});
console.log('Created new security issue');
}
Expand All @@ -214,8 +204,8 @@ jobs:
with:
name: periodic-scan-${{ steps.meta.outputs.server_name }}
path: |
trivy-results.json
trivy-results.sarif
${{ steps.grype-scan-json.outputs.json }}
${{ steps.grype-scan.outputs.sarif }}
retention-days: 90

summary:
Expand All @@ -227,7 +217,7 @@ jobs:
- name: Generate summary
run: |
echo "## Periodic Security Scan Complete" >> $GITHUB_STEP_SUMMARY
echo "- **Scan Type**: Comprehensive (vulnerabilities, secrets, configs, licenses)" >> $GITHUB_STEP_SUMMARY
echo "- **Scan Type**: Vulnerability scan (Grype)" >> $GITHUB_STEP_SUMMARY
echo "- **Severity Levels**: CRITICAL, HIGH, MEDIUM, LOW" >> $GITHUB_STEP_SUMMARY
echo "- **Status**: ${{ needs.scan-images.result }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
Expand Down
Loading