Files
michaelschiemer/deployment/legacy/gitea-workflows/security-scan.yml
2025-11-24 21:28:25 +01:00

305 lines
12 KiB
YAML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: Security Vulnerability Scan
on:
push:
branches: [ main, staging, develop ]
pull_request:
branches: [ main, staging, develop ]
schedule:
# Daily security scan at 2 AM UTC
- cron: '0 2 * * *'
workflow_dispatch:
jobs:
check-changes:
name: Check for Dependency Changes
runs-on: ubuntu-latest
outputs:
dependencies_changed: ${{ steps.filter.outputs.dependencies_changed }}
steps:
- name: Download CI helpers
shell: bash
env:
CI_TOKEN: ${{ secrets.CI_TOKEN }}
run: |
set -euo pipefail
REF="${{ github.sha }}"
if [ -z "$REF" ]; then
REF="${{ github.ref_name }}"
fi
if [ -z "$REF" ]; then
REF="${{ github.head_ref }}"
fi
if [ -z "$REF" ]; then
REF="main"
fi
URL="https://git.michaelschiemer.de/${{ github.repository }}/raw/${REF}/scripts/ci/clone_repo.sh"
mkdir -p /tmp/ci-tools
if [ -n "$CI_TOKEN" ]; then
curl -sfL -u "$CI_TOKEN:x-oauth-basic" "$URL" -o /tmp/ci-tools/clone_repo.sh
else
curl -sfL "$URL" -o /tmp/ci-tools/clone_repo.sh
fi
chmod +x /tmp/ci-tools/clone_repo.sh
- name: Analyse changed files
id: filter
shell: bash
run: |
set -euo pipefail
REF_NAME="${{ github.ref_name }}"
if [ -z "$REF_NAME" ]; then
REF_NAME="${{ github.head_ref }}"
fi
if [ -z "$REF_NAME" ]; then
REF_NAME="main"
fi
REPO="${{ github.repository }}"
WORKDIR="/workspace/repo"
export CI_REPOSITORY="$REPO"
export CI_TOKEN="${{ secrets.CI_TOKEN }}"
export CI_REF_NAME="$REF_NAME"
export CI_DEFAULT_BRANCH="main"
export CI_TARGET_DIR="$WORKDIR"
export CI_FETCH_DEPTH="2"
/tmp/ci-tools/clone_repo.sh
cd "$WORKDIR"
# For scheduled or manual runs, always run the scan
if [ "${{ github.event_name }}" = "schedule" ] || [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "dependencies_changed=true" >> "$GITHUB_OUTPUT"
echo " Scheduled/manual run - will scan dependencies"
exit 0
fi
CHANGED_FILES=""
EVENT_BEFORE="${{ github.event.before }}"
if [ "${{ github.event_name }}" = "push" ] && [ -n "$EVENT_BEFORE" ]; then
if git rev-parse "$EVENT_BEFORE" >/dev/null 2>&1; then
CHANGED_FILES="$(git diff --name-only "$EVENT_BEFORE" HEAD || true)"
else
git fetch origin "$EVENT_BEFORE" --depth 1 || true
if git rev-parse "$EVENT_BEFORE" >/dev/null 2>&1; then
CHANGED_FILES="$(git diff --name-only "$EVENT_BEFORE" HEAD || true)"
fi
fi
fi
if [ -z "$CHANGED_FILES" ]; then
if git rev-parse HEAD^ >/dev/null 2>&1; then
CHANGED_FILES="$(git diff --name-only HEAD^ HEAD || true)"
else
git fetch origin "$REF_NAME" --depth 50 || true
if git rev-parse HEAD^ >/dev/null 2>&1; then
CHANGED_FILES="$(git diff --name-only HEAD^ HEAD || true)"
fi
fi
fi
DEPENDENCIES_CHANGED=false
if [ -n "$CHANGED_FILES" ]; then
while IFS= read -r FILE; do
[ -z "$FILE" ] && continue
if echo "$FILE" | grep -Eq "^(composer\.json|composer\.lock)$"; then
DEPENDENCIES_CHANGED=true
break
fi
done <<< "$CHANGED_FILES"
fi
echo "dependencies_changed=$DEPENDENCIES_CHANGED" >> "$GITHUB_OUTPUT"
if [ "$DEPENDENCIES_CHANGED" = "true" ]; then
echo " Dependencies changed - security scan will run"
else
echo " No dependency changes detected - skipping security scan"
fi
security-audit:
needs: check-changes
if: needs.check-changes.outputs.dependencies_changed == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
name: Composer Security Audit
runs-on: php-ci # Uses pre-built PHP 8.5 CI image with Composer pre-installed
steps:
- name: Checkout code
run: |
# For pull_request events, use the head ref (source branch)
if [ "${{ github.event_name }}" = "pull_request" ]; then
REF_NAME="${{ github.head_ref || github.event.pull_request.head.ref }}"
elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
REF_NAME="${{ inputs.branch || github.ref_name }}"
else
REF_NAME="${{ github.ref_name }}"
fi
# Fallback to main if REF_NAME is still empty
if [ -z "$REF_NAME" ] || [ "$REF_NAME" = "" ]; then
REF_NAME="main"
fi
REPO="${{ github.repository }}"
echo "📋 Cloning branch: $REF_NAME"
echo "📦 Repository: $REPO"
# Use CI token if available, otherwise try public access
if [ -n "${{ secrets.CI_TOKEN }}" ]; then
git clone --depth 1 --branch "$REF_NAME" \
"https://${{ secrets.CI_TOKEN }}@git.michaelschiemer.de/${REPO}.git" \
/workspace/repo
else
# Try public HTTPS (works if repository is public)
git clone --depth 1 --branch "$REF_NAME" \
"https://git.michaelschiemer.de/${REPO}.git" \
/workspace/repo || \
# Fallback: Try to use Gitea's internal runner access
git clone --depth 1 \
"https://git.michaelschiemer.de/${REPO}.git" \
/workspace/repo
fi
cd /workspace/repo
- name: Get Composer cache directory
id: composer-cache
shell: bash
run: |
echo "dir=$(composer global config cache-dir 2>/dev/null | cut -d' ' -f3 || echo "$HOME/.composer/cache")" >> $GITHUB_OUTPUT
- name: Cache Composer dependencies
uses: actions/cache@v4
with:
path: |
${{ steps.composer-cache.outputs.dir }}
vendor/
key: ${{ runner.os }}-composer-security-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-security-
- name: Validate composer.json and composer.lock
run: |
cd /workspace/repo
# Validate composer.json (less strict - lock file might be updated during install)
composer validate --no-check-lock || echo "⚠️ composer.lock might need update, but continuing..."
# Try to update lock file if needed
composer update --lock --no-interaction || echo "⚠️ Could not update lock file, but continuing..."
- name: Install dependencies
run: |
cd /workspace/repo
# TEMPORARY WORKAROUND: Ignore PHP 8.5 platform requirement until dependencies officially support PHP 8.5
# TODO: Remove --ignore-platform-req=php when dependencies are updated (estimated: 1 month)
composer install --prefer-dist --no-progress --no-dev --ignore-platform-req=php
- name: Run Composer Security Audit
id: security-audit
run: |
cd /workspace/repo
composer audit --format=json > audit-result.json || true
cat audit-result.json
- name: Parse audit results
id: parse-audit
run: |
cd /workspace/repo
if [ -f audit-result.json ]; then
# jq is pre-installed in php-ci image
jq --version
ADVISORIES=$(jq -r '.advisories | length' audit-result.json 2>/dev/null || echo "0")
ABANDONED=$(jq -r '.abandoned | length' audit-result.json 2>/dev/null || echo "0")
echo "advisories_count=$ADVISORIES" >> $GITHUB_OUTPUT
echo "abandoned_count=$ABANDONED" >> $GITHUB_OUTPUT
echo "## Security Audit Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Vulnerabilities Found:** $ADVISORIES" >> $GITHUB_STEP_SUMMARY
echo "- **Abandoned Packages:** $ABANDONED" >> $GITHUB_STEP_SUMMARY
if [ "$ADVISORIES" -gt 0 ]; then
echo "has_vulnerabilities=true" >> $GITHUB_OUTPUT
echo "- **Status:** ❌ Security vulnerabilities detected - review required" >> $GITHUB_STEP_SUMMARY
# Display vulnerability details
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Vulnerability Details" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '```json' >> $GITHUB_STEP_SUMMARY
jq '.advisories' audit-result.json >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "has_vulnerabilities=false" >> $GITHUB_OUTPUT
echo "- **Status:** ✅ No security vulnerabilities detected" >> $GITHUB_STEP_SUMMARY
fi
if [ "$ABANDONED" -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Abandoned Packages" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
jq -r '.abandoned | to_entries[] | "- **\(.key)**: \(.value // "No replacement suggested")"' audit-result.json >> $GITHUB_STEP_SUMMARY
fi
fi
- name: Save audit results
if: always()
run: |
cd /workspace/repo
if [ -f audit-result.json ]; then
mkdir -p /tmp/artifacts
cp audit-result.json /tmp/artifacts/security-audit-results-${{ github.run_number }}.json || true
echo "✅ Audit results saved"
fi
- name: Create Gitea issue on vulnerability (scheduled runs only)
if: failure() && github.event_name == 'schedule'
run: |
# Prepare issue body
ISSUE_TITLE="🚨 Security Vulnerabilities Detected in Dependencies"
ISSUE_BODY="## Security Audit Report\n\n"
ISSUE_BODY="${ISSUE_BODY}**Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC")\n"
ISSUE_BODY="${ISSUE_BODY}**Workflow Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\n\n"
if [ -f audit-result.json ]; then
# Add vulnerability details
ISSUE_BODY="${ISSUE_BODY}### Vulnerabilities Found\n\n"
ISSUE_BODY="${ISSUE_BODY}\`\`\`json\n"
ISSUE_BODY="${ISSUE_BODY}$(cat audit-result.json)\n"
ISSUE_BODY="${ISSUE_BODY}\`\`\`\n\n"
fi
ISSUE_BODY="${ISSUE_BODY}### Action Required\n\n"
ISSUE_BODY="${ISSUE_BODY}1. Review the vulnerability details above\n"
ISSUE_BODY="${ISSUE_BODY}2. Update affected packages: \`composer update <package>\`\n"
ISSUE_BODY="${ISSUE_BODY}3. Run tests: \`make test\`\n"
ISSUE_BODY="${ISSUE_BODY}4. Verify with: \`make security-check\`\n"
# Create issue using Gitea API
# Note: Requires CI_TOKEN secret to be configured
if [ -n "${{ secrets.CI_TOKEN }}" ]; then
curl -X POST \
-H "Authorization: token ${{ secrets.CI_TOKEN }}" \
-H "Content-Type: application/json" \
-d "{\"title\":\"${ISSUE_TITLE}\",\"body\":\"${ISSUE_BODY}\",\"labels\":[\"security\",\"dependencies\",\"automated\"]}" \
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/issues"
else
echo "⚠️ CI_TOKEN not configured - skipping issue creation"
echo "Please add CI_TOKEN as repository secret for automated issue creation"
fi
- name: Notify on failure
if: failure()
run: |
echo "::error::Security vulnerabilities detected! Check the audit results for details."
echo "Run 'make security-check' locally to review vulnerabilities."