From d31a0eafb546ff392b951606a339ff87e1b64c2f Mon Sep 17 00:00:00 2001 From: Alejandro Alvarez Date: Tue, 12 May 2026 13:04:00 +0200 Subject: [PATCH 1/3] chore(scan): remove scan workflows (TECHOPS-408 / decommission PR A) Deleted trivy.yml and trivy-scan-published-images.yml as part of decommissioning liquibase/docker scan ownership (TECHOPS-408). Scanning for the community image moves to liquibase/liquibase. Scanning for liquibase-secure stays in liquibase-pro. Jira: TECHOPS-408 Co-Authored-By: Claude Sonnet 4.6 --- .../workflows/trivy-scan-published-images.yml | 274 ------------------ .github/workflows/trivy.yml | 186 ------------ 2 files changed, 460 deletions(-) delete mode 100644 .github/workflows/trivy-scan-published-images.yml delete mode 100644 .github/workflows/trivy.yml diff --git a/.github/workflows/trivy-scan-published-images.yml b/.github/workflows/trivy-scan-published-images.yml deleted file mode 100644 index a72d46c8..00000000 --- a/.github/workflows/trivy-scan-published-images.yml +++ /dev/null @@ -1,274 +0,0 @@ -# Vulnerability scanning for published Docker images -# This workflow scans published images on Docker Hub for vulnerabilities -# using the reusable vulnerability scanning workflow from build-logic. -# -# NOTE: SARIF results are NOT uploaded to GitHub Security tab to avoid stale alerts. -# Published image vulnerabilities are tracked via workflow artifacts and GitHub Actions summary. -# Only the main branch workflow (trivy.yml) populates the Security tab. - -name: Published Images Vulnerability Scanning - -on: - workflow_dispatch: - inputs: - max_tags_to_scan: - description: "Maximum number of published tags to scan" - required: false - default: "20" - schedule: - # Run Monday-Friday at 10 AM UTC (published image monitoring) - - cron: "0 10 * * 1-5" - -permissions: - contents: read - actions: read - id-token: write # Required for AWS OIDC authentication in reusable vulnerability scan - packages: read # Required: reusable vulnerability-scan pulls images from GHCR - security-events: write # Required: reusable declares this at job level unconditionally - -jobs: - generate-matrix: - name: Generate Scan Matrix - runs-on: ubuntu-22.04 - outputs: - matrix: ${{ steps.set-matrix.outputs.matrix }} - env: - MAX_TAGS: ${{ github.event.inputs.max_tags_to_scan || '10' }} - steps: - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Generate matrix for all image/tag combinations - id: set-matrix - run: | - scripts/generate-dockerhub-matrix.sh - - vulnerability-scan: - name: Scan ${{ matrix.image }}:${{ matrix.tag }} - needs: generate-matrix - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} - uses: liquibase/build-logic/.github/workflows/reusable-vulnerability-scan.yml@main - with: - mode: docker - source: "${{ matrix.image }}:${{ matrix.tag }}" - image_name: ${{ matrix.image }} - image_tag: ${{ matrix.tag }} - fail_on_vulnerabilities: false - upload_sarif: false - generate_sbom: true - vex_enabled: ${{ contains(matrix.image, 'liquibase-secure') }} - dispatch_new_cves: ${{ contains(matrix.image, 'liquibase-secure') }} - build_logic_ref: main - secrets: inherit - - persist-results: - name: Persist Scan Results - needs: [generate-matrix, vulnerability-scan] - if: always() && needs.vulnerability-scan.result != 'cancelled' - runs-on: ubuntu-22.04 - permissions: - actions: read - contents: write - id-token: write - steps: - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - - - name: Checkout build-logic - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - repository: liquibase/build-logic - path: build-logic - - - name: Download all scan artifacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - path: scan-artifacts - pattern: vulnerability-report-* - - - name: Persist to scan-results branch - env: - EXPECTED_MATRIX: ${{ needs.generate-matrix.outputs.matrix }} - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - scripts/persist-scan-results.sh scan-artifacts - - - name: Configure AWS credentials for vault access (CVE dispatch) - uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6.0.0 - with: - role-to-assume: ${{ secrets.LIQUIBASE_VAULT_OIDC_ROLE_ARN }} - aws-region: us-east-1 - continue-on-error: true - - - name: Get secrets from vault (CVE dispatch) - id: vault-secrets-cve - uses: aws-actions/aws-secretsmanager-get-secrets@a9a7eb4e2f2871d30dc5b892576fde60a2ecc802 # v2.0.10 - with: - secret-ids: | - ,/vault/liquibase - parse-json-secrets: true - continue-on-error: true - - - name: Get GitHub App token for liquibase-pro (CVE dispatch) - id: get-token-cve - uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2.2.2 - with: - app-id: ${{ env.LIQUIBASE_GITHUB_APP_ID }} - private-key: ${{ env.LIQUIBASE_GITHUB_APP_PRIVATE_KEY }} - owner: liquibase - repositories: liquibase-pro - permission-actions: write - permission-contents: read - continue-on-error: true - - - name: Dispatch new CVEs for investigation - continue-on-error: true - env: - GH_TOKEN: ${{ steps.get-token-cve.outputs.token }} - RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - EXPECTED_MATRIX: ${{ needs.generate-matrix.outputs.matrix }} - run: | - set +e # Never fail the parent workflow - - if [ -z "$GH_TOKEN" ]; then - echo "::warning::CVE dispatch skipped -- GitHub App token unavailable (vault step may have failed)" - echo "## CVE Investigation Dispatch (published images)" >> "$GITHUB_STEP_SUMMARY" - echo "Skipped -- GitHub App token unavailable." >> "$GITHUB_STEP_SUMMARY" - exit 0 - fi - - chmod +x build-logic/scripts/vulnerability-scanning/diff-new-cves.sh - - echo "## CVE Investigation Dispatch (published images)" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - - TOTAL_BATCHES=0 - TOTAL_FAILED=0 - TOTAL_DEDUPED=0 - - # Per-image dedup state. Same CVE id appearing across multiple tags of the - # same image is deduplicated (avoids flooding investigate-cve with one batch - # per tag). Different images (e.g., liquibase vs liquibase-secure) keep - # independent state so the same CVE is investigated separately for each — - # they are distinct artifacts with potentially different reachability. - DISPATCHED_CVES_DIR=$(mktemp -d) - - # Derive image:tag from the matrix JSON directly (avoids fragile artifact-name parsing) - ENTRY_COUNT=$(echo "$EXPECTED_MATRIX" | jq '.include | length') - - for (( idx=0; idx> "$GITHUB_STEP_SUMMARY" - continue - fi - - # Reconstruct artifact directory name (same convention as the reusable workflow) - SAFE_NAME=$(echo "$IMAGE_NAME" | tr '/' '-') - SAFE_TAG=$(echo "$IMAGE_TAG" | tr '/' '-') - ARTIFACT_DIR="scan-artifacts/vulnerability-report-${SAFE_NAME}-${SAFE_TAG}" - - # Per-image dedup files (one .json + one .ids per IMAGE_NAME). - DISPATCHED_CVES_FILE="${DISPATCHED_CVES_DIR}/${SAFE_NAME}.json" - - if [ ! -d "$ARTIFACT_DIR" ] || [ -z "$(ls -A "$ARTIFACT_DIR" 2>/dev/null)" ]; then - echo "::warning::No scan artifacts for ${IMAGE_OR_VERSION} -- skipping CVE dispatch" - echo "- **${IMAGE_OR_VERSION}**: no scan artifacts found" >> "$GITHUB_STEP_SUMMARY" - continue - fi - - echo "Diffing CVEs for ${IMAGE_OR_VERSION}..." - - BATCHES=$(build-logic/scripts/vulnerability-scanning/diff-new-cves.sh \ - --scan-dir "$ARTIFACT_DIR" \ - --source docker \ - --image-or-version "$IMAGE_OR_VERSION" \ - --run-url "$RUN_URL" \ - 2>/tmp/diff-err-$$.txt) || { - ERR=$(cat /tmp/diff-err-$$.txt 2>/dev/null || echo "unknown error") - echo "::warning::diff-new-cves.sh failed for ${IMAGE_OR_VERSION}: $ERR" - echo "- **${IMAGE_OR_VERSION}**: diff failed -- $ERR" >> "$GITHUB_STEP_SUMMARY" - rm -f "/tmp/diff-err-$$.txt" - continue - } - rm -f "/tmp/diff-err-$$.txt" - - if [ -z "$BATCHES" ]; then - echo "No unassessed CVEs for ${IMAGE_OR_VERSION} -- skipping" - echo "- **${IMAGE_OR_VERSION}**: no unassessed CVEs" >> "$GITHUB_STEP_SUMMARY" - continue - fi - - BATCH_NUM=0 - FAILED=0 - - while IFS= read -r batch_json; do - [ -z "$batch_json" ] && continue - - # Deduplicate: remove CVEs already dispatched from a previous tag of - # this same image. (Cross-image state is intentionally separate.) - if [ -f "$DISPATCHED_CVES_FILE" ]; then - NEW_CVES=$(echo "$batch_json" | jq -c --slurpfile seen "$DISPATCHED_CVES_FILE" ' - ($seen | if length > 0 then .[0] else [] end) as $already | - .cves |= [.[] | select(.id as $id | $already | index($id) | not)] - ' 2>/dev/null || echo "$batch_json") - else - NEW_CVES="$batch_json" - fi - - NEW_COUNT=$(echo "$NEW_CVES" | jq '.cves | length' 2>/dev/null || echo 0) - ORIG_COUNT=$(echo "$batch_json" | jq '.cves | length' 2>/dev/null || echo 0) - SKIPPED=$((ORIG_COUNT - NEW_COUNT)) - - if [ "$SKIPPED" -gt 0 ]; then - TOTAL_DEDUPED=$((TOTAL_DEDUPED + SKIPPED)) - echo " Deduped $SKIPPED CVE(s) already dispatched from a previous tag of ${IMAGE_NAME}" - fi - - if [ "$NEW_COUNT" -eq 0 ]; then - echo "- **${IMAGE_OR_VERSION}**: all CVEs already dispatched from a previous tag of ${IMAGE_NAME}" >> "$GITHUB_STEP_SUMMARY" - continue - fi - - BATCH_NUM=$((BATCH_NUM + 1)) - - if gh workflow run investigate-cve.lock.yml \ - -R liquibase/liquibase-pro \ - -f "cve_json=$NEW_CVES" 2>/tmp/dispatch-err-$$.txt; then - echo "- **${IMAGE_OR_VERSION}** batch $BATCH_NUM: dispatched $NEW_COUNT CVEs" >> "$GITHUB_STEP_SUMMARY" - # Record dispatched CVE IDs - echo "$NEW_CVES" | jq -r '.cves[].id' >> "$DISPATCHED_CVES_FILE.ids" - # Rebuild JSON array for jq --slurpfile - if [ -f "$DISPATCHED_CVES_FILE.ids" ]; then - sort -u "$DISPATCHED_CVES_FILE.ids" | jq -R . | jq -s . > "$DISPATCHED_CVES_FILE" - fi - else - ERR=$(cat /tmp/dispatch-err-$$.txt 2>/dev/null || echo "unknown error") - echo "::warning::CVE dispatch failed for ${IMAGE_OR_VERSION} batch $BATCH_NUM: $ERR" - echo "- **${IMAGE_OR_VERSION}** batch $BATCH_NUM: FAILED -- $ERR" >> "$GITHUB_STEP_SUMMARY" - FAILED=$((FAILED + 1)) - fi - rm -f "/tmp/dispatch-err-$$.txt" - done <<< "$BATCHES" - - TOTAL_BATCHES=$((TOTAL_BATCHES + BATCH_NUM)) - TOTAL_FAILED=$((TOTAL_FAILED + FAILED)) - done - - rm -rf "$DISPATCHED_CVES_DIR" - - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "**Total dispatched**: $TOTAL_BATCHES batch(es), $TOTAL_FAILED failure(s)" >> "$GITHUB_STEP_SUMMARY" - echo "CVE dispatch complete: $TOTAL_BATCHES batch(es), $TOTAL_FAILED failure(s)" - exit 0 # Never fail the parent workflow diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml deleted file mode 100644 index 1edf0d71..00000000 --- a/.github/workflows/trivy.yml +++ /dev/null @@ -1,186 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -name: Vulnerability Scanning - -on: - workflow_dispatch: - push: - branches: ["main"] - paths: - - "Dockerfile*" - - "docker-entrypoint.sh" - - ".github/workflows/trivy.yml" - - "examples/**" - - "pom.xml" - pull_request: - branches: ["main"] - paths: - - "Dockerfile*" - - "docker-entrypoint.sh" - - ".github/workflows/trivy.yml" - - "examples/**" - - "pom.xml" - schedule: - - cron: "0 7 * * 1-5" # Run every weekday at 7am UTC - -permissions: - contents: read - id-token: write # Required for AWS OIDC authentication - packages: write # Required for pushing to GHCR - security-events: write # Required for uploading SARIF results to GitHub Security tab - actions: read # Required for private repositories to get Action run status - pull-requests: write # Required for writing comments on pull requests - -jobs: - # ============================================ - # SCAN: community (Dockerfile) - # ============================================ - scan-community: - name: Scan liquibase/liquibase - uses: liquibase/build-logic/.github/workflows/reusable-docker-scan.yml@main - with: - dockerfile_path: Dockerfile - image_name: liquibase/liquibase - suffix: "" - vex_enabled: false - upload_sarif: true - dispatch_new_cves: false - scout_enabled: true - build_logic_ref: main - secrets: inherit - - # ============================================ - # SCAN: alpine (Dockerfile.alpine) - # ============================================ - scan-alpine: - name: Scan liquibase/liquibase-alpine - uses: liquibase/build-logic/.github/workflows/reusable-docker-scan.yml@main - with: - dockerfile_path: Dockerfile.alpine - image_name: liquibase/liquibase - suffix: "-alpine" - vex_enabled: false - upload_sarif: true - dispatch_new_cves: false - scout_enabled: true - build_logic_ref: main - secrets: inherit - - # ============================================ - # SCAN: secure (DockerfileSecure) — VEX enabled - # ============================================ - scan-secure: - name: Scan liquibase/liquibase-secure - uses: liquibase/build-logic/.github/workflows/reusable-docker-scan.yml@main - with: - dockerfile_path: DockerfileSecure - image_name: liquibase/liquibase-secure - suffix: "" - vex_enabled: true - upload_sarif: true - dispatch_new_cves: true - scout_enabled: true - build_logic_ref: main - secrets: inherit - - # ============================================ - # PERSIST SCAN RESULTS TO scan-results BRANCH - # ============================================ - persist-results: - name: Persist Scan Results - needs: [scan-community, scan-alpine, scan-secure] - if: always() && github.event_name != 'pull_request' && needs.scan-community.result != 'cancelled' - runs-on: ubuntu-22.04 - permissions: - actions: read - contents: write - id-token: write - steps: - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - - - name: Download vulnerability report artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 - with: - path: scan-artifacts - pattern: vulnerability-report-* - - - name: Download Scout JSON artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 - with: - path: scout-artifacts - pattern: scout-results* - continue-on-error: true - - - name: Merge Scout JSON into vulnerability report directories - run: | - # Map scout artifact suffix to matching vulnerability-report artifact name. - # The reusable-vulnerability-scan.yml uses image_tag = suffix || 'latest', - # so the alpine image_tag is "-alpine" (leading dash) producing a double dash. - # scout-results -> vulnerability-report-liquibase-liquibase-latest - # scout-results-alpine -> vulnerability-report-liquibase-liquibase--alpine - # scout-results-secure -> vulnerability-report-liquibase-liquibase-secure-latest - declare -A SCOUT_TO_VULN=( - ["scout-results"]="vulnerability-report-liquibase-liquibase-latest" - ["scout-results-alpine"]="vulnerability-report-liquibase-liquibase--alpine" - ["scout-results-secure"]="vulnerability-report-liquibase-liquibase-secure-latest" - ) - - for SCOUT_DIR in scout-artifacts/scout-results*/; do - [ -d "$SCOUT_DIR" ] || continue - SCOUT_NAME=$(basename "$SCOUT_DIR") - VULN_NAME="${SCOUT_TO_VULN[$SCOUT_NAME]:-}" - if [ -z "$VULN_NAME" ]; then - echo "WARNING: no mapping for $SCOUT_NAME — skipping" - continue - fi - VULN_DIR="scan-artifacts/$VULN_NAME" - if [ -d "$VULN_DIR" ] && [ -f "$SCOUT_DIR/scout-results.json" ]; then - cp "$SCOUT_DIR/scout-results.json" "$VULN_DIR/scout-results.json" - echo "Merged scout-results.json into $VULN_NAME" - fi - done - - - name: Persist to scan-results branch - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - scripts/persist-scan-results.sh scan-artifacts - - # ============================================ - # SLACK NOTIFICATION ON FAILURE - # ============================================ - notify-failure: - needs: [scan-community, scan-alpine, scan-secure] - if: failure() - runs-on: "ubuntu-22.04" - steps: - - name: Configure AWS credentials for vault access - uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6.0.0 - with: - role-to-assume: ${{ secrets.LIQUIBASE_VAULT_OIDC_ROLE_ARN }} - aws-region: us-east-1 - - - name: Get secrets from vault - uses: aws-actions/aws-secretsmanager-get-secrets@a9a7eb4e2f2871d30dc5b892576fde60a2ecc802 # v2.0.10 - with: - secret-ids: | - ,/vault/liquibase - parse-json-secrets: true - - - name: Notify Slack on Build Failure - uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3 - env: - SLACK_COLOR: "failure" - SLACK_MESSAGE: "View details on GitHub Actions: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}. Triggered by repository: ${{ github.repository }}" - SLACK_TITLE: "❌ ${{ github.repository }} ❌ Trivy failed on branch ${{ github.ref_name }} for commit ${{ github.sha }} in repository ${{ github.repository }}" - SLACK_USERNAME: liquibot - SLACK_WEBHOOK: ${{ env.DOCKER_SLACK_WEBHOOK_URL }} - SLACK_ICON_EMOJI: ":whale:" - SLACK_FOOTER: "${{ github.repository }}" - SLACK_LINK_NAMES: true From bff262c3357c6dc92cce5c1ddc21922c6ea1e1d2 Mon Sep 17 00:00:00 2001 From: Alejandro Alvarez Date: Tue, 12 May 2026 13:04:07 +0200 Subject: [PATCH 2/3] chore(scan): remove scan scripts Deleted generate-dockerhub-matrix.sh, persist-scan-results.sh, and scripts/README.md (which documented only these two scan scripts). The scripts/ directory is now empty and has been removed. Jira: TECHOPS-408 Co-Authored-By: Claude Sonnet 4.6 --- scripts/README.md | 54 ----- scripts/generate-dockerhub-matrix.sh | 110 --------- scripts/persist-scan-results.sh | 320 --------------------------- 3 files changed, 484 deletions(-) delete mode 100644 scripts/README.md delete mode 100755 scripts/generate-dockerhub-matrix.sh delete mode 100755 scripts/persist-scan-results.sh diff --git a/scripts/README.md b/scripts/README.md deleted file mode 100644 index ed085e3b..00000000 --- a/scripts/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# Vulnerability Scanning Scripts - -This directory contains shell scripts used by GitHub Actions workflows for vulnerability scanning of published Docker images. - -> **For Support & Sales:** See [SECURITY.md](../SECURITY.md) for a guide on understanding vulnerability reports, terminology definitions, and how to interpret scan results. - -## Scripts - -### `generate-dockerhub-matrix.sh` - -Generates a JSON matrix of Docker images and tags to scan from Docker Hub. Used by the `trivy-scan-published-images.yml` workflow to determine which published tags to scan. - -**Usage:** -```bash -./generate-dockerhub-matrix.sh [max_tags] -``` - -**Arguments:** -- `max_tags`: Maximum number of tags to scan per image (default: 10) - -**Environment Variables:** -- `MAX_TAGS`: Maximum tags per image (overrides argument) -- `GITHUB_OUTPUT`: GitHub Actions output file path (optional) - -**Outputs:** -- JSON matrix written to stdout and `$GITHUB_OUTPUT` if available -- Format: `{"include":[{"image":"...","tag":"...","published":"..."}]}` - -**Example:** -```bash -# Generate matrix for 5 most recent tags -./generate-dockerhub-matrix.sh 5 - -# Use in GitHub Actions -MAX_TAGS=10 ./generate-dockerhub-matrix.sh -``` - -**How it works:** -1. Queries Docker Hub API for active tags of `liquibase/liquibase` and `liquibase/liquibase-secure` -2. Filters to semantic version tags only (e.g., `5.0.1`, `4.28`) -3. Removes redundant minor version tags when the full version exists (e.g., skips `4.28` if `4.28.0` exists) -4. Returns the most recent N tags per image as a GitHub Actions matrix - -## Dependencies - -- **bash**: Shell interpreter (version 4.0+) -- **jq**: JSON processor -- **curl**: For Docker Hub API access - -## Related Documentation - -- [SECURITY.md](../SECURITY.md) - Understanding vulnerability scan reports -- [Trivy Documentation](https://trivy.dev/) - Official Trivy scanner documentation -- [Grype Documentation](https://github.com/anchore/grype) - Official Grype scanner documentation diff --git a/scripts/generate-dockerhub-matrix.sh b/scripts/generate-dockerhub-matrix.sh deleted file mode 100755 index ccc46c38..00000000 --- a/scripts/generate-dockerhub-matrix.sh +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env bash -# -# generate-dockerhub-matrix.sh -# -# Generates a JSON matrix of Docker images and tags to scan from Docker Hub. -# Fetches recent tags for both liquibase/liquibase and liquibase/liquibase-secure. -# -# Usage: -# generate-dockerhub-matrix.sh [max_tags] -# -# Arguments: -# max_tags: Maximum number of tags to scan per image (default: 10) -# -# Environment Variables: -# MAX_TAGS: Maximum tags per image (overrides argument) -# -# Outputs: -# - JSON matrix written to stdout and $GITHUB_OUTPUT if available -# - Format: {"include":[{"image":"...","tag":"...","published":"..."},...]} -# - published: ISO 8601 timestamp of when the tag was last updated - -set -e - -# Configuration -MAX_TAGS="${MAX_TAGS:-${1:-10}}" - -echo "Generating matrix for scanning with max $MAX_TAGS tags per image..." >&2 - -MATRIX_INCLUDE="[" -FIRST=true - -for IMAGE in "liquibase/liquibase" "liquibase/liquibase-secure"; do - echo "Getting tags for $IMAGE..." >&2 - REPO=$(basename "$IMAGE") - TAGS="" - URL="https://hub.docker.com/v2/namespaces/liquibase/repositories/${REPO}/tags?page_size=100" - - while [ -n "$URL" ]; do - RESPONSE=$(curl -s "$URL") - - # Only include semantic version tags (e.g., 5.0.1, 4.28) - # Format: tag|last_updated (pipe-separated to preserve dates through filtering) - # Match semver followed by pipe delimiter (the line continues with |last_updated) - TAG_REGEX='^[0-9]+\.[0-9]+(\.[0-9]+)?(\||$)' - NEW_TAGS=$(echo "$RESPONSE" | jq -r '.results[] | select(.tag_status == "active") | "\(.name)|\(.last_updated)"' | grep -E "$TAG_REGEX" || true) - TAGS=$(echo -e "$TAGS\n$NEW_TAGS" | sort -t'|' -k1 -Vu) - - # Filter out minor version tags if we have the full version - # e.g., if we have 4.28.0, skip 4.28 - # Preserves the |last_updated suffix through filtering - # Note: Uses GNU awk match() with capture groups (Ubuntu default, not BSD awk) - TAGS=$(echo "$TAGS" | awk -F'|' ' - { - tag = $1 - date = $2 - tags[NR] = $0 - tag_only[NR] = tag - if (match(tag, /^([0-9]+)\.([0-9]+)\.([0-9]+)$/, m)) { - full = m[1] "." m[2] "." m[3] - has_full[full] = 1 - } - } - END { - for (i = 1; i <= NR; i++) { - tag = tag_only[i] - if (match(tag, /^([0-9]+)\.([0-9]+)$/, m)) { - short = m[1] "." m[2] ".0" - if (has_full[short]) continue - } - print tags[i] - } - } - ') - - # Get next page URL - URL=$(echo "$RESPONSE" | jq -r '.next') - [ "$URL" = "null" ] && break - done - - # Get most recent tags (reverse sort and take first N) - TAGS=$(echo "$TAGS" | tac | head -n "$MAX_TAGS") - - # Build matrix JSON - # Each line is in format: tag|last_updated - while IFS='|' read -r tag published; do - if [ -n "$tag" ]; then - # Escape any special characters in the date string for JSON - published="${published:-unknown}" - if [ "$FIRST" = true ]; then - MATRIX_INCLUDE="${MATRIX_INCLUDE}{\"image\":\"$IMAGE\",\"tag\":\"$tag\",\"published\":\"$published\"}" - FIRST=false - else - MATRIX_INCLUDE="${MATRIX_INCLUDE},{\"image\":\"$IMAGE\",\"tag\":\"$tag\",\"published\":\"$published\"}" - fi - fi - done <<< "$TAGS" -done - -MATRIX_INCLUDE="${MATRIX_INCLUDE}]" -MATRIX="{\"include\":$MATRIX_INCLUDE}" - -echo "Generated matrix: $MATRIX" >&2 - -# Output to GitHub Actions if running in CI -if [ -n "${GITHUB_OUTPUT:-}" ]; then - echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" -fi - -# Always output to stdout for testing/debugging -echo "$MATRIX" diff --git a/scripts/persist-scan-results.sh b/scripts/persist-scan-results.sh deleted file mode 100755 index 419bcf4c..00000000 --- a/scripts/persist-scan-results.sh +++ /dev/null @@ -1,320 +0,0 @@ -#!/usr/bin/env bash -# -# persist-scan-results.sh -# -# Persists vulnerability scan results to the scan-results branch. -# Called after the vulnerability-scan matrix job completes. -# Downloads scan artifacts and commits them to a persistent branch -# so the Liquibase Security dashboard can read them via GitHub Contents API. -# -# Usage: -# persist-scan-results.sh -# -# Arguments: -# artifacts-dir: Directory containing downloaded scan artifacts. -# Each subdirectory is named vulnerability-report-- -# and contains trivy-surface.json, trivy-deep.json, grype-results.json. -# -# Environment Variables: -# GITHUB_REPOSITORY: owner/repo (set by GitHub Actions) -# GITHUB_SERVER_URL: GitHub server URL (set by GitHub Actions) -# GITHUB_RUN_ID: Workflow run ID (set by GitHub Actions) -# EXPECTED_MATRIX: JSON matrix from generate-matrix job (optional). -# When set, images in the matrix that have no artifact -# are recorded with status "failed" in metadata.json. -# -# Branch structure: -# scan-results/ -# manifest.json -# /// -# trivy-surface.json -# trivy-deep.json -# grype-results.json -# metadata.json - -set -euo pipefail - -ARTIFACTS_DIR="$(cd "${1:?Usage: persist-scan-results.sh }" && pwd)" -BRANCH="scan-results" -SCANNED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)" - -if [ ! -d "$ARTIFACTS_DIR" ]; then - echo "Error: artifacts directory not found: $ARTIFACTS_DIR" >&2 - exit 1 -fi - -# Count artifact directories -ARTIFACT_COUNT=$(find "$ARTIFACTS_DIR" -mindepth 1 -maxdepth 1 -type d -name "vulnerability-report-*" | wc -l | tr -d ' ') -if [ "$ARTIFACT_COUNT" -eq 0 ] && [ -z "${EXPECTED_MATRIX:-}" ]; then - echo "No scan artifacts found in $ARTIFACTS_DIR" >&2 - exit 0 -fi - -echo "Found $ARTIFACT_COUNT scan artifact(s) to persist" - -# --- Set up worktree for the scan-results branch --- - -REPO_ROOT="$(pwd)" -WORKTREE_DIR=$(mktemp -d) -trap 'cd "$REPO_ROOT" && git worktree remove --force "$WORKTREE_DIR" 2>/dev/null || rm -rf "$WORKTREE_DIR"' EXIT - -# Check if the branch exists on remote -if git ls-remote --exit-code origin "refs/heads/$BRANCH" >/dev/null 2>&1; then - git fetch origin "$BRANCH" - git worktree add "$WORKTREE_DIR" "origin/$BRANCH" - cd "$WORKTREE_DIR" - git checkout -B "$BRANCH" "origin/$BRANCH" -else - # Create orphan branch - git worktree add --detach "$WORKTREE_DIR" - cd "$WORKTREE_DIR" - git checkout --orphan "$BRANCH" - git rm -rf . 2>/dev/null || true - echo '{"lastUpdated":"","images":{},"scan_status":{}}' > manifest.json - git add manifest.json - git commit -m "Initialize scan-results branch" -fi - -# --- Load existing manifest --- - -if [ -f manifest.json ]; then - MANIFEST=$(cat manifest.json) -else - MANIFEST='{"lastUpdated":"","images":{},"scan_status":{}}' -fi - -# --- Process each artifact --- - -PERSISTED_IMAGES=() - -for ARTIFACT_PATH in "$ARTIFACTS_DIR"/vulnerability-report-*; do - [ -d "$ARTIFACT_PATH" ] || continue - ARTIFACT_NAME=$(basename "$ARTIFACT_PATH") - - # Parse image and tag from artifact name: vulnerability-report--- - # The reusable workflow sanitizes: tr '/' '-' - # So liquibase/liquibase:5.0.1 becomes vulnerability-report-liquibase-liquibase-5.0.1 - # We need to reconstruct org/image and tag - # - # Strategy (data-driven, no hardcoded image list): - # - First segment is always the org (liquibase) - # - Last segment matching a version pattern (e.g. 5.0.1, 4.30.0, latest) is the tag - # - Everything in between is the image name - SUFFIX="${ARTIFACT_NAME#vulnerability-report-}" - - # Split suffix into segments by hyphen, filtering out empty segments - # (empty segments appear when image_tag has a leading dash, e.g. "-alpine" - # produces a double-dash: vulnerability-report-liquibase-liquibase--alpine) - IFS='-' read -ra RAW_SEGMENTS <<< "$SUFFIX" - SEGMENTS=() - for seg in "${RAW_SEGMENTS[@]}"; do - [ -n "$seg" ] && SEGMENTS+=("$seg") - done - - if [ "${#SEGMENTS[@]}" -lt 3 ]; then - echo "Error: cannot parse artifact name (too few segments): $ARTIFACT_NAME" >&2 - exit 1 - fi - - ORG="${SEGMENTS[0]}" - - # Find the tag: scan from the end for the last segment that looks like a version - # A version segment starts with a digit, or is a known non-semver tag like "latest" or "alpine" - TAG_INDEX=-1 - for (( i=${#SEGMENTS[@]}-1; i>=2; i-- )); do - if [[ "${SEGMENTS[$i]}" =~ ^[0-9] ]] || [[ "${SEGMENTS[$i]}" == "latest" ]] || [[ "${SEGMENTS[$i]}" =~ ^(alpine|slim|jammy|focal|bullseye|noble)$ ]]; then - TAG_INDEX=$i - break - fi - done - - if [ "$TAG_INDEX" -lt 0 ]; then - echo "Error: cannot identify version tag in artifact name: $ARTIFACT_NAME" >&2 - exit 1 - fi - - # Tag may contain hyphens (e.g. 5.0.1-beta) — rejoin from TAG_INDEX to end - TAG=$(IFS='-'; echo "${SEGMENTS[*]:$TAG_INDEX}") - - # Image name is everything between org and tag - IMAGE_NAME=$(IFS='-'; echo "${SEGMENTS[*]:1:$((TAG_INDEX-1))}") - - if [ -z "$IMAGE_NAME" ] || [ -z "$TAG" ]; then - echo "Error: could not parse artifact name: $ARTIFACT_NAME (org=$ORG, image=$IMAGE_NAME, tag=$TAG)" >&2 - exit 1 - fi - - # Reconstruct image path: org/image-name - IMAGE_PATH="$ORG/$IMAGE_NAME" - - DEST_DIR="$IMAGE_PATH/$TAG" - mkdir -p "$DEST_DIR" - - echo "Persisting $IMAGE_PATH:$TAG" - - # Copy scan result files and track any missing ones - MISSING_FILES=() - REQUIRED_SCAN_FILES=(trivy-surface.json trivy-deep.json grype-results.json) - for FILE in "${REQUIRED_SCAN_FILES[@]}"; do - if [ -f "$ARTIFACT_PATH/$FILE" ]; then - cp "$ARTIFACT_PATH/$FILE" "$DEST_DIR/$FILE" - else - MISSING_FILES+=("$FILE") - fi - done - - if [ "${#MISSING_FILES[@]}" -gt 0 ]; then - echo "WARNING: $IMAGE_PATH:$TAG is missing scan files: ${MISSING_FILES[*]}" - fi - - # Copy optional Scout JSON if present (not all images get a Scout scan). - # When absent, remove any stale copy from a previous run (the branch is persistent). - OPTIONAL_SCAN_FILES=(scout-results.json) - for FILE in "${OPTIONAL_SCAN_FILES[@]}"; do - if [ -f "$ARTIFACT_PATH/$FILE" ]; then - cp "$ARTIFACT_PATH/$FILE" "$DEST_DIR/$FILE" - echo " Included optional file: $FILE" - else - rm -f "$DEST_DIR/$FILE" - fi - done - - # Also check for grype-results.json variants (some workflows output grype-results.sarif too) - # We only need the JSON - - # Build missing_files JSON array - MISSING_JSON="[]" - if [ "${#MISSING_FILES[@]}" -gt 0 ]; then - MISSING_JSON=$(printf '%s\n' "${MISSING_FILES[@]}" | jq -R . | jq -s .) - fi - - # Create metadata.json - cat > "$DEST_DIR/metadata.json" < "$DEST_DIR/metadata.json" < manifest.json -git add manifest.json - -# --- Commit and push --- - -if git diff --cached --quiet; then - echo "No changes to commit" - exit 0 -fi - -CHANGED_FILES=$(git diff --cached --name-only) -CHANGED_COUNT=0 -if [ -n "$CHANGED_FILES" ]; then - CHANGED_COUNT=$(echo "$CHANGED_FILES" | grep -c "metadata.json" || true) -fi -git commit -m "Update scan results ($CHANGED_COUNT version(s)) — $SCANNED_AT" -git push origin "$BRANCH" - -echo "Persisted scan results to $BRANCH branch ($CHANGED_COUNT version(s))" From 4d6b48d5a78a4ae29e179f79ac064f35d0694fda Mon Sep 17 00:00:00 2001 From: Alejandro Alvarez Date: Tue, 12 May 2026 13:04:30 +0200 Subject: [PATCH 3/3] docs: replace Vulnerability Scanning section with redirect The section previously described scan workflows and scripts that have been deleted in this PR. Replace with a single pointer paragraph that directs readers to where scanning now lives: liquibase/liquibase for the community image and liquibase-pro for the secure image. Jira: TECHOPS-408 Co-Authored-By: Claude Sonnet 4.6 --- README.md | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/README.md b/README.md index 550ddba1..04328944 100644 --- a/README.md +++ b/README.md @@ -278,31 +278,7 @@ docker buildx imagetools inspect liquibase/liquibase-secure:latest --format '{{ ## 🛡️ Vulnerability Scanning -Published Liquibase Docker images (Community and Secure) are automatically scanned for known vulnerabilities using multiple security scanners. Scans run Monday through Friday at 10 AM UTC and cover the most recent tags of each image (up to 10 per repository by default). - -### What Gets Scanned - -| Scan | Scanner | Coverage | -|------|---------|----------| -| OS & Application Libraries | Trivy | Operating system packages and top-level Java libraries | -| Nested JAR Dependencies | Trivy | Libraries bundled inside Liquibase JARs | -| SBOM-based Scan | Grype | Full Software Bill of Materials analysis | - -### Viewing Scan Results - -**Security Dashboard** — [Liquibase Security](https://security.liquibase.com/docker) provides an interactive interface to explore vulnerability scan results across all image versions: - -- **Image Overview** — Browse all scanned versions with severity breakdowns, CVSS trends, and total counts -- **Version Detail** — View every CVE in a specific image version, filterable by severity and component type (OS, JRE, JAR, Driver), with upgrade recommendations -- **Version Compare** — Compare two versions side by side to see which CVEs were fixed, which are new, and which are shared -- **Export** — Download vulnerability data as CSV or print reports as PDF - -**GitHub Actions** — Raw scan results are also available directly from this repository: -1. Go to the **Actions** tab -2. Select **Published Images Vulnerability Scanning** -3. Choose a workflow run to view the summary or download artifacts - -For a detailed guide on reading vulnerability reports, see [SECURITY.md](SECURITY.md). +This repository no longer owns published-image vulnerability scanning. The community Liquibase image (`liquibase/liquibase`) is scanned by [liquibase/liquibase](https://github.com/liquibase/liquibase/blob/main/.github/workflows/trivy-scan-published-images.yml) and results are published to its `scan-results` branch — consumed by [security.liquibase.com](https://security.liquibase.com). Secure-distribution images (`liquibase/liquibase-secure`) are scanned in `liquibase-pro` and consumed by the internal security portal. ---