You've already forked devops-pipeline-template
Add Trivy Security Scan workflow template for Docker images
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
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
|
||||
Reference in New Issue
Block a user