fix: Update security remediation plan and QA report for Grype SBOM implementation
- Removed outdated security remediation plan for DoD failures, indicating no active specifications. - Documented recent completion of Grype SBOM remediation, including implementation summary and QA report. - Updated QA report to reflect successful validation of security scans with zero HIGH/CRITICAL findings. - Deleted the previous QA report file as its contents are now integrated into the current report.
This commit is contained in:
225
.github/workflows/supply-chain-verify.yml
vendored
225
.github/workflows/supply-chain-verify.yml
vendored
@@ -52,53 +52,182 @@ jobs:
|
||||
fi
|
||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Check Image Availability
|
||||
id: image-check
|
||||
env:
|
||||
IMAGE: ghcr.io/${{ github.repository_owner }}/charon:${{ steps.tag.outputs.tag }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo "Checking if image exists: ${IMAGE}"
|
||||
|
||||
# Authenticate with GHCR using GitHub token
|
||||
echo "${GH_TOKEN}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||
|
||||
if docker manifest inspect ${IMAGE} >/dev/null 2>&1; then
|
||||
echo "✅ Image exists and is accessible"
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "⚠️ Image not found - likely not built yet"
|
||||
echo "This is normal for PR workflows before docker-build completes"
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Verify SBOM Completeness
|
||||
if: steps.image-check.outputs.exists == 'true'
|
||||
env:
|
||||
IMAGE: ghcr.io/${{ github.repository_owner }}/charon:${{ steps.tag.outputs.tag }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo "Verifying SBOM for ${IMAGE}..."
|
||||
echo ""
|
||||
|
||||
# Generate fresh SBOM
|
||||
syft ${IMAGE} -o spdx-json > sbom-generated.json || {
|
||||
echo "⚠️ Failed to generate SBOM - image may not exist yet"
|
||||
exit 0
|
||||
}
|
||||
# Log Syft version for debugging
|
||||
echo "Syft version:"
|
||||
syft version
|
||||
echo ""
|
||||
|
||||
# Semantic comparison
|
||||
GENERATED_COUNT=$(jq '.packages | length' sbom-generated.json)
|
||||
# Generate fresh SBOM in CycloneDX format (aligned with docker-build.yml)
|
||||
echo "Generating SBOM in CycloneDX JSON format..."
|
||||
if ! syft ${IMAGE} -o cyclonedx-json > sbom-generated.json; then
|
||||
echo "❌ Failed to generate SBOM"
|
||||
echo ""
|
||||
echo "Debug information:"
|
||||
echo "Image: ${IMAGE}"
|
||||
echo "Syft exit code: $?"
|
||||
exit 1 # Fail on real errors, not silent exit
|
||||
fi
|
||||
|
||||
echo "Generated SBOM packages: ${GENERATED_COUNT}"
|
||||
# Check SBOM content
|
||||
GENERATED_COUNT=$(jq '.components | length' sbom-generated.json 2>/dev/null || echo "0")
|
||||
|
||||
echo "Generated SBOM components: ${GENERATED_COUNT}"
|
||||
|
||||
if [[ ${GENERATED_COUNT} -eq 0 ]]; then
|
||||
echo "⚠️ SBOM contains no packages - may indicate an issue"
|
||||
echo "⚠️ SBOM contains no components - may indicate an issue"
|
||||
else
|
||||
echo "✅ SBOM contains ${GENERATED_COUNT} packages"
|
||||
echo "✅ SBOM contains ${GENERATED_COUNT} components"
|
||||
fi
|
||||
|
||||
- name: Upload SBOM Artifact
|
||||
if: steps.image-check.outputs.exists == 'true' && always()
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
with:
|
||||
name: sbom-${{ steps.tag.outputs.tag }}
|
||||
path: sbom-generated.json
|
||||
retention-days: 30
|
||||
|
||||
- name: Validate SBOM File
|
||||
id: validate-sbom
|
||||
if: steps.image-check.outputs.exists == 'true'
|
||||
run: |
|
||||
echo "Validating SBOM file..."
|
||||
echo ""
|
||||
|
||||
# Check jq availability
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "❌ jq is not available"
|
||||
echo "valid=false" >> $GITHUB_OUTPUT
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check file exists
|
||||
if [[ ! -f sbom-generated.json ]]; then
|
||||
echo "❌ SBOM file does not exist"
|
||||
echo "valid=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check file is non-empty
|
||||
if [[ ! -s sbom-generated.json ]]; then
|
||||
echo "❌ SBOM file is empty"
|
||||
echo "valid=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Validate JSON structure
|
||||
if ! jq empty sbom-generated.json 2>/dev/null; then
|
||||
echo "❌ SBOM file contains invalid JSON"
|
||||
echo "SBOM content:"
|
||||
cat sbom-generated.json
|
||||
echo "valid=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Validate CycloneDX structure
|
||||
BOMFORMAT=$(jq -r '.bomFormat // "missing"' sbom-generated.json)
|
||||
SPECVERSION=$(jq -r '.specVersion // "missing"' sbom-generated.json)
|
||||
COMPONENTS=$(jq '.components // [] | length' sbom-generated.json)
|
||||
|
||||
echo "SBOM Format: ${BOMFORMAT}"
|
||||
echo "Spec Version: ${SPECVERSION}"
|
||||
echo "Components: ${COMPONENTS}"
|
||||
echo ""
|
||||
|
||||
if [[ "${BOMFORMAT}" != "CycloneDX" ]]; then
|
||||
echo "❌ Invalid bomFormat: expected 'CycloneDX', got '${BOMFORMAT}'"
|
||||
echo "valid=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "${COMPONENTS}" == "0" ]]; then
|
||||
echo "⚠️ SBOM has no components - may indicate incomplete scan"
|
||||
echo "valid=partial" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "✅ SBOM is valid with ${COMPONENTS} components"
|
||||
echo "valid=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Scan for Vulnerabilities
|
||||
if: steps.validate-sbom.outputs.valid == 'true'
|
||||
env:
|
||||
IMAGE: ghcr.io/${{ github.repository_owner }}/charon:${{ steps.tag.outputs.tag }}
|
||||
run: |
|
||||
echo "Scanning for vulnerabilities..."
|
||||
echo "Scanning for vulnerabilities with Grype..."
|
||||
echo "SBOM format: CycloneDX JSON"
|
||||
echo "SBOM size: $(wc -c < sbom-generated.json) bytes"
|
||||
echo ""
|
||||
|
||||
if [[ ! -f sbom-generated.json ]]; then
|
||||
echo "⚠️ No SBOM found, skipping vulnerability scan"
|
||||
exit 0
|
||||
# Update Grype vulnerability database
|
||||
echo "Updating Grype vulnerability database..."
|
||||
grype db update
|
||||
echo ""
|
||||
|
||||
# Run Grype with explicit path and better error handling
|
||||
if ! grype sbom:./sbom-generated.json --output json --file vuln-scan.json; then
|
||||
echo ""
|
||||
echo "❌ Grype scan failed"
|
||||
echo ""
|
||||
echo "Debug information:"
|
||||
echo "Grype version:"
|
||||
grype version
|
||||
echo ""
|
||||
echo "SBOM preview (first 1000 characters):"
|
||||
head -c 1000 sbom-generated.json
|
||||
echo ""
|
||||
exit 1 # Fail the step to surface the issue
|
||||
fi
|
||||
|
||||
grype sbom:sbom-generated.json -o json > vuln-scan.json || {
|
||||
echo "⚠️ Grype scan failed"
|
||||
exit 0
|
||||
}
|
||||
echo "✅ Grype scan completed successfully"
|
||||
echo ""
|
||||
|
||||
grype sbom:sbom-generated.json -o table || true
|
||||
# Display human-readable results
|
||||
echo "Vulnerability summary:"
|
||||
grype sbom:./sbom-generated.json --output table || true
|
||||
|
||||
# Parse and categorize results
|
||||
CRITICAL=$(jq '[.matches[] | select(.vulnerability.severity == "Critical")] | length' vuln-scan.json 2>/dev/null || echo "0")
|
||||
HIGH=$(jq '[.matches[] | select(.vulnerability.severity == "High")] | length' vuln-scan.json 2>/dev/null || echo "0")
|
||||
MEDIUM=$(jq '[.matches[] | select(.vulnerability.severity == "Medium")] | length' vuln-scan.json 2>/dev/null || echo "0")
|
||||
LOW=$(jq '[.matches[] | select(.vulnerability.severity == "Low")] | length' vuln-scan.json 2>/dev/null || echo "0")
|
||||
|
||||
echo "Critical: ${CRITICAL}, High: ${HIGH}"
|
||||
echo ""
|
||||
echo "Vulnerability counts:"
|
||||
echo " Critical: ${CRITICAL}"
|
||||
echo " High: ${HIGH}"
|
||||
echo " Medium: ${MEDIUM}"
|
||||
echo " Low: ${LOW}"
|
||||
|
||||
# Set warnings for critical vulnerabilities
|
||||
if [[ ${CRITICAL} -gt 0 ]]; then
|
||||
echo "::warning::${CRITICAL} critical vulnerabilities found"
|
||||
fi
|
||||
@@ -106,20 +235,72 @@ jobs:
|
||||
# Store for PR comment
|
||||
echo "CRITICAL_VULNS=${CRITICAL}" >> $GITHUB_ENV
|
||||
echo "HIGH_VULNS=${HIGH}" >> $GITHUB_ENV
|
||||
echo "MEDIUM_VULNS=${MEDIUM}" >> $GITHUB_ENV
|
||||
echo "LOW_VULNS=${LOW}" >> $GITHUB_ENV
|
||||
|
||||
- name: Report Skipped Scan
|
||||
if: steps.image-check.outputs.exists != 'true' || steps.validate-sbom.outputs.valid != 'true'
|
||||
run: |
|
||||
echo "## ⚠️ Vulnerability Scan Skipped" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [[ "${{ steps.image-check.outputs.exists }}" != "true" ]]; then
|
||||
echo "**Reason**: Docker image not available yet" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "This is expected for PR workflows. The image will be scanned" >> $GITHUB_STEP_SUMMARY
|
||||
echo "after it's built by the docker-build workflow." >> $GITHUB_STEP_SUMMARY
|
||||
elif [[ "${{ steps.validate-sbom.outputs.valid }}" != "true" ]]; then
|
||||
echo "**Reason**: SBOM validation failed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Check the 'Validate SBOM File' step for details." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ Workflow completed successfully (scan skipped)" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Comment on PR
|
||||
if: github.event_name == 'pull_request' && env.CRITICAL_VULNS != ''
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
with:
|
||||
script: |
|
||||
const imageExists = '${{ steps.image-check.outputs.exists }}' === 'true';
|
||||
const sbomValid = '${{ steps.validate-sbom.outputs.valid }}';
|
||||
const critical = process.env.CRITICAL_VULNS || '0';
|
||||
const high = process.env.HIGH_VULNS || '0';
|
||||
const medium = process.env.MEDIUM_VULNS || '0';
|
||||
const low = process.env.LOW_VULNS || '0';
|
||||
|
||||
let body = '## 🔒 Supply Chain Verification\n\n';
|
||||
|
||||
if (!imageExists) {
|
||||
body += '⏭️ **Status**: Image not yet available\n\n';
|
||||
body += 'Verification will run automatically after the docker-build workflow completes.\n';
|
||||
body += 'This is normal for PR workflows.\n';
|
||||
} else if (sbomValid !== 'true') {
|
||||
body += '⚠️ **Status**: SBOM validation failed\n\n';
|
||||
body += `[Check workflow logs for details](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})\n`;
|
||||
} else {
|
||||
body += '✅ **Status**: SBOM verified and scanned\n\n';
|
||||
body += '### Vulnerability Summary\n\n';
|
||||
body += `| Severity | Count |\n`;
|
||||
body += `|----------|-------|\n`;
|
||||
body += `| Critical | ${critical} |\n`;
|
||||
body += `| High | ${high} |\n`;
|
||||
body += `| Medium | ${medium} |\n`;
|
||||
body += `| Low | ${low} |\n\n`;
|
||||
|
||||
if (parseInt(critical) > 0) {
|
||||
body += `⚠️ **Action Required**: ${critical} critical vulnerabilities found\n\n`;
|
||||
}
|
||||
|
||||
body += `[View full report](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})\n`;
|
||||
}
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: `## 🔒 Supply Chain Verification\n\n✅ SBOM verified\n📊 Vulnerabilities: ${critical} Critical, ${high} High\n\n[View full report](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})`
|
||||
body: body
|
||||
});
|
||||
|
||||
verify-docker-image:
|
||||
|
||||
Reference in New Issue
Block a user