You've already forked devops-pipeline-template
162 lines
6.0 KiB
YAML
162 lines
6.0 KiB
YAML
name: Trivy Security Scan (Template)
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
images:
|
|
description: 'JSON array of images to build and scan. Each entry: {"name": "my-app", "dockerfile": "Dockerfile", "context": "."}'
|
|
required: false
|
|
type: string
|
|
default: '[]'
|
|
secrets:
|
|
GITHUB_TOKEN:
|
|
required: true
|
|
|
|
jobs:
|
|
trivy-scan:
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Trivy filesystem scan
|
|
run: |
|
|
docker run --rm \
|
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
-v $HOME/.cache:/root/.cache/ \
|
|
-v ${{ github.workspace }}:/root/src \
|
|
aquasec/trivy:canary fs --no-progress --severity HIGH,CRITICAL \
|
|
--format json /root/src \
|
|
> /tmp/fs-scan.json || true
|
|
|
|
- name: Build and scan Docker images
|
|
run: |
|
|
IMAGES='${{ inputs.images }}'
|
|
echo "$IMAGES" | jq -c '.[]' | while read -r item; do
|
|
NAME=$(echo "$item" | jq -r '.name')
|
|
DOCKERFILE=$(echo "$item" | jq -r '.dockerfile')
|
|
CONTEXT=$(echo "$item" | jq -r '.context')
|
|
|
|
echo "Building image: ${NAME}"
|
|
docker build -t "${NAME}:latest" -f "${DOCKERFILE}" "${CONTEXT}"
|
|
|
|
echo "Scanning image: ${NAME}"
|
|
docker run --rm \
|
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
-v $HOME/.cache:/root/.cache/ \
|
|
aquasec/trivy:canary image --no-progress --severity HIGH,CRITICAL \
|
|
--format json "${NAME}:latest" \
|
|
> "/tmp/image-scan-${NAME}.json" || true
|
|
done
|
|
|
|
- name: Generate and publish security report to wiki
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
SCAN_DATE=$(date -u '+%Y-%m-%d %H:%M:%S UTC')
|
|
GITEA_URL="${{ github.server_url }}"
|
|
REPO="${{ github.repository }}"
|
|
COMMIT_SHA="${{ github.sha }}"
|
|
COMMIT_SHORT=$(echo "$COMMIT_SHA" | cut -c1-7)
|
|
|
|
generate_report() {
|
|
local json_file="$1"
|
|
local title="$2"
|
|
|
|
if ! jq empty "$json_file" 2>/dev/null; then
|
|
echo "## ${title}"
|
|
echo ""
|
|
echo "Failed to parse scan results."
|
|
echo ""
|
|
return
|
|
fi
|
|
|
|
local total_vulns
|
|
total_vulns=$(jq '[.Results[]? | .Vulnerabilities[]?] | length' "$json_file")
|
|
local high_count
|
|
high_count=$(jq '[.Results[]? | .Vulnerabilities[]? | select(.Severity == "HIGH")] | length' "$json_file")
|
|
local critical_count
|
|
critical_count=$(jq '[.Results[]? | .Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length' "$json_file")
|
|
|
|
echo "## ${title}"
|
|
echo ""
|
|
echo "| Total | CRITICAL | HIGH |"
|
|
echo "|-------|----------|------|"
|
|
echo "| ${total_vulns} | ${critical_count} | ${high_count} |"
|
|
echo ""
|
|
|
|
if [ "$total_vulns" -eq 0 ]; then
|
|
echo "No vulnerabilities found."
|
|
echo ""
|
|
return
|
|
fi
|
|
|
|
echo "| Package | Installed | Fixed | Vulnerability | Severity | Title |"
|
|
echo "|---------|-----------|-------|---------------|----------|-------|"
|
|
jq -r '
|
|
[.Results[]? | .Vulnerabilities[]?]
|
|
| sort_by(if .Severity == "CRITICAL" then 0 else 1 end)
|
|
| .[]
|
|
| "| \(.PkgName) | \(.InstalledVersion) | \(.FixedVersion // "-") | \(.VulnerabilityID) | \(.Severity) | \(.Title // "-" | gsub("[\\n\\r]"; " ") | if length > 60 then .[:60] + "..." else . end) |"
|
|
' "$json_file"
|
|
echo ""
|
|
}
|
|
|
|
{
|
|
echo "# Security Scan Report"
|
|
echo ""
|
|
echo "| | |"
|
|
echo "|---|---|"
|
|
echo "| **Date** | ${SCAN_DATE} |"
|
|
echo "| **Commit** | ${COMMIT_SHORT} |"
|
|
echo "| **Branch** | ${{ github.ref_name }} |"
|
|
echo "| **Severity Filter** | HIGH, CRITICAL |"
|
|
echo ""
|
|
echo "---"
|
|
echo ""
|
|
generate_report /tmp/fs-scan.json "Filesystem Scan"
|
|
|
|
IMAGES='${{ inputs.images }}'
|
|
echo "$IMAGES" | jq -c '.[]' | while read -r item; do
|
|
NAME=$(echo "$item" | jq -r '.name')
|
|
echo "---"
|
|
echo ""
|
|
generate_report "/tmp/image-scan-${NAME}.json" "Docker Image - ${NAME}"
|
|
done
|
|
} > /tmp/wiki-content.md
|
|
|
|
base64 -w 0 /tmp/wiki-content.md > /tmp/content_b64.txt
|
|
|
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
-H "Authorization: token ${GITHUB_TOKEN}" \
|
|
"${GITEA_URL}/api/v1/repos/${REPO}/wiki/page/security")
|
|
|
|
if [ "$HTTP_CODE" = "200" ]; then
|
|
jq -n \
|
|
--rawfile content /tmp/content_b64.txt \
|
|
--arg message "Update security scan report - ${SCAN_DATE}" \
|
|
--arg title "security" \
|
|
'{content_base64: ($content | rtrimstr("\n")), message: $message, title: $title}' > /tmp/payload.json
|
|
|
|
curl -s -X PATCH \
|
|
-H "Authorization: token ${GITHUB_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
-d @/tmp/payload.json \
|
|
"${GITEA_URL}/api/v1/repos/${REPO}/wiki/page/security"
|
|
echo "Wiki page 'security' updated."
|
|
else
|
|
jq -n \
|
|
--rawfile content /tmp/content_b64.txt \
|
|
--arg message "Create security scan report - ${SCAN_DATE}" \
|
|
--arg title "security" \
|
|
'{content_base64: ($content | rtrimstr("\n")), message: $message, title: $title}' > /tmp/payload.json
|
|
|
|
curl -s -X POST \
|
|
-H "Authorization: token ${GITHUB_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
-d @/tmp/payload.json \
|
|
"${GITEA_URL}/api/v1/repos/${REPO}/wiki/new"
|
|
echo "Wiki page 'security' created."
|
|
fi
|