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:
GitHub Actions
2026-01-10 05:40:56 +00:00
parent 18d1294c24
commit e95590a727
9 changed files with 4221 additions and 462 deletions

View File

@@ -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: