Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/actions/setup-caches/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ runs:
CACHE_TYPE: ${{ inputs.cache-type }}
run: |
case "$CACHE_TYPE" in
application-server-quality|application-server-verification|application-server-integration|server-contracts|webapp-e2e) ;;
application-server-quality|application-server-verification|application-server-integration|server-contracts) ;;
*) echo "::error::Unknown cache type '$CACHE_TYPE'."; exit 1 ;;
esac

- name: Compute generated-client cache identity
id: generated-clients-identity
if: startsWith(inputs.cache-type, 'application-server-') || contains(fromJSON('["webapp-e2e", "server-contracts"]'), inputs.cache-type)
if: startsWith(inputs.cache-type, 'application-server-') || inputs.cache-type == 'server-contracts'
shell: bash
env:
DEPENDENCY_HASH: ${{ hashFiles('server/pom.xml', 'server/generated-clients/pom.xml', 'server/.mvn/**', 'server/generated-clients/src/**') }}
Expand All @@ -37,7 +37,7 @@ runs:

- name: Set up JDK 21
id: java
if: startsWith(inputs.cache-type, 'application-server-') || contains(fromJSON('["webapp-e2e", "server-contracts"]'), inputs.cache-type)
if: startsWith(inputs.cache-type, 'application-server-') || inputs.cache-type == 'server-contracts'
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: "temurin"
Expand All @@ -51,14 +51,14 @@ runs:
cache-jdk: false

- name: Restore generated clients
if: github.event_name == 'pull_request' && (startsWith(inputs.cache-type, 'application-server-') || contains(fromJSON('["webapp-e2e", "server-contracts"]'), inputs.cache-type))
if: github.event_name == 'pull_request' && (startsWith(inputs.cache-type, 'application-server-') || inputs.cache-type == 'server-contracts')
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.m2/build-cache
key: ${{ inputs.os }}-${{ steps.java.outputs.distribution }}-${{ steps.java.outputs.version }}-generated-clients-${{ steps.generated-clients-identity.outputs.hash }}

- name: Cache generated clients
if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && contains(fromJSON('["push", "schedule", "workflow_dispatch"]'), github.event_name) && (startsWith(inputs.cache-type, 'application-server-') || contains(fromJSON('["webapp-e2e", "server-contracts"]'), inputs.cache-type))
if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && contains(fromJSON('["push", "schedule", "workflow_dispatch"]'), github.event_name) && (startsWith(inputs.cache-type, 'application-server-') || inputs.cache-type == 'server-contracts')
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.m2/build-cache
Expand Down
23 changes: 18 additions & 5 deletions .github/workflows/ci-security-scan.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
name: Security

# CodeQL SAST scanning is handled by GitHub's Default Setup (enabled in repo settings).
# This provides automatic scanning on push/PR with incremental analysis and zero maintenance.
# This workflow handles dependency scanning (Trivy), secret detection (TruffleHog), and
# a freshness check on the RFC 9116 security.txt Expires field.

on:
workflow_call:
inputs:
Expand All @@ -15,6 +10,24 @@ on:
default: "false"

jobs:
dependency-review:
name: "New dependency risk"
runs-on: ubuntu-latest
if: inputs.should_skip != 'true' && github.event_name == 'pull_request'
timeout-minutes: 5
permissions:
contents: read
steps:
- name: Review dependency changes
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
with:
fail-on-severity: high
# CI tooling is part of the release trust boundary.
fail-on-scopes: runtime, development, unknown
license-check: true
show-patched-versions: true
vulnerability-check: true

security-scan:
name: "Dependencies, secrets, and policy"
runs-on: ubuntu-latest
Expand Down
10 changes: 4 additions & 6 deletions .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/setup-node-pnpm
with:
install: "none"
- uses: ./.github/actions/setup-caches
with:
cache-type: application-server-verification
Expand Down Expand Up @@ -276,10 +273,11 @@ jobs:
- uses: ./.github/actions/setup-node-pnpm
with:
install: "frozen"
- uses: ./.github/actions/setup-caches
- uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
cache-type: webapp-e2e
os: ${{ runner.os }}
distribution: "temurin"
java-version: "21"
cache-jdk: false
- uses: ./.github/actions/setup-browsers
- name: Download built reactor
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
Expand Down
39 changes: 29 additions & 10 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ jobs:
postgres-image: ${{ steps.filter.outputs.postgres-image }}
docs: ${{ steps.filter.outputs.docs }}
ci-config: ${{ steps.filter.outputs.ci-config }}
quality-config: ${{ steps.filter.outputs.quality-config }}
test-config: ${{ steps.filter.outputs.test-config }}
security-config: ${{ steps.filter.outputs.security-config }}
docker-config: ${{ steps.filter.outputs.docker-config }}
pmd-canary: ${{ steps.filter.outputs.pmd-canary }}
version-bump: ${{ steps.version_bump.outputs.changed }}
Expand Down Expand Up @@ -168,6 +171,20 @@ jobs:
- '.github/workflows/**'
- '.github/actions/**'
- '.github/actionlint.yaml'
quality-config:
- '.github/workflows/cicd.yml'
- '.github/workflows/ci-quality-gates.yml'
- '.github/actions/setup-node-pnpm/**'
- '.github/actions/setup-caches/**'
test-config:
- '.github/workflows/cicd.yml'
- '.github/workflows/ci-tests.yml'
- '.github/actions/setup-node-pnpm/**'
- '.github/actions/setup-caches/**'
- '.github/actions/setup-browsers/**'
security-config:
- '.github/workflows/cicd.yml'
- '.github/workflows/ci-security-scan.yml'
docker-config:
- '.github/workflows/ci-docker-build.yml'
- '.github/workflows/reusable-docker-build.yml'
Expand Down Expand Up @@ -220,7 +237,9 @@ jobs:
timeout-minutes: 10
runs-on: ubuntu-latest
needs: [detect-changes]
if: github.event_name != 'push' || needs.detect-changes.outputs.version-bump == 'true'
if: |
(github.event_name != 'push' || needs.detect-changes.outputs.version-bump == 'true') &&
(github.event_name != 'pull_request' || needs.detect-changes.outputs.ci-config == 'true')
permissions:
contents: read
security-events: write
Expand All @@ -247,19 +266,19 @@ jobs:
(github.event_name != 'push' || needs.detect-changes.outputs.version-bump == 'true') &&
needs.detect-changes.outputs.should_skip != 'true' && (
needs.detect-changes.outputs.any-code == 'true' ||
needs.detect-changes.outputs.ci-config == 'true' ||
needs.detect-changes.outputs.quality-config == 'true' ||
github.event_name != 'pull_request'
)
permissions:
checks: write
contents: read
with:
should_skip: ${{ needs.detect-changes.outputs.should_skip }}
webapp_changed: ${{ (needs.detect-changes.outputs.webapp == 'true' || needs.detect-changes.outputs.ci-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}
application_server_changed: ${{ (needs.detect-changes.outputs.application-server == 'true' || needs.detect-changes.outputs.ci-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}
tooling_changed: ${{ (needs.detect-changes.outputs.tooling == 'true' || needs.detect-changes.outputs.ci-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}
webapp_changed: ${{ (needs.detect-changes.outputs.webapp == 'true' || needs.detect-changes.outputs.quality-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}
application_server_changed: ${{ (needs.detect-changes.outputs.application-server == 'true' || needs.detect-changes.outputs.quality-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}
tooling_changed: ${{ (needs.detect-changes.outputs.tooling == 'true' || needs.detect-changes.outputs.quality-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}
postgres_image_changed: ${{ (needs.detect-changes.outputs.postgres-image == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}
contracts_changed: ${{ (needs.detect-changes.outputs.application-server == 'true' || needs.detect-changes.outputs.postgres-image == 'true' || needs.detect-changes.outputs.ci-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}
contracts_changed: ${{ (needs.detect-changes.outputs.application-server == 'true' || needs.detect-changes.outputs.postgres-image == 'true' || needs.detect-changes.outputs.quality-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}
pmd_canary: ${{ (needs.detect-changes.outputs.pmd-canary == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}

Security:
Expand All @@ -269,7 +288,7 @@ jobs:
(github.event_name != 'push' || needs.detect-changes.outputs.version-bump == 'true') &&
needs.detect-changes.outputs.should_skip != 'true' && (
needs.detect-changes.outputs.any-code == 'true' ||
needs.detect-changes.outputs.ci-config == 'true' ||
needs.detect-changes.outputs.security-config == 'true' ||
github.event_name != 'pull_request'
)
permissions:
Expand All @@ -285,7 +304,7 @@ jobs:
(github.event_name != 'push' || needs.detect-changes.outputs.version-bump == 'true') &&
needs.detect-changes.outputs.should_skip != 'true' && (
needs.detect-changes.outputs.any-code == 'true' ||
needs.detect-changes.outputs.ci-config == 'true' ||
needs.detect-changes.outputs.test-config == 'true' ||
github.event_name != 'pull_request'
)
secrets:
Expand All @@ -298,8 +317,8 @@ jobs:
statuses: write
with:
should_skip: ${{ needs.detect-changes.outputs.should_skip }}
webapp_changed: ${{ (needs.detect-changes.outputs.webapp == 'true' || needs.detect-changes.outputs.ci-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}
application_server_changed: ${{ (needs.detect-changes.outputs.application-server == 'true' || needs.detect-changes.outputs.ci-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}
webapp_changed: ${{ (needs.detect-changes.outputs.webapp == 'true' || needs.detect-changes.outputs.test-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}
application_server_changed: ${{ (needs.detect-changes.outputs.application-server == 'true' || needs.detect-changes.outputs.test-config == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}
e2e_changed: ${{ (needs.detect-changes.outputs.e2e == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}
postgres_image_changed: ${{ (needs.detect-changes.outputs.postgres-image == 'true' || github.event_name != 'pull_request') && 'true' || 'false' }}

Expand Down
17 changes: 0 additions & 17 deletions .github/workflows/rescan-main-images.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,7 @@
# Rescans the images built from main's HEAD against the release vulnerability policy.
#
# The gate in reusable-docker-build.yml runs where an image is built, so it catches a change that
# introduces a finding and cannot catch a finding that appears with no change at all. That is the
# common case: v0.75.0 was blocked by CVEs published after the code merged clean, and nothing
# noticed until the release failed. rescan-release-images.yml already covers published releases on
# this cadence; this covers what main would release today.
#
# Non-blocking by design. Findings go to a tracking issue, updated in place, because a red status on
# a schedule nobody triggered — for a regression no commit caused and no revert fixes — is how a team
# learns to ignore the gate.
name: Rescan main images

on:
schedule:
# Weekly, twenty minutes after the supported-release rescan, so the two never contend for the
# Trivy database. Monday also puts findings in front of the same review Renovate's PRs land for.
- cron: "37 4 * * 1"
workflow_dispatch:

Expand Down Expand Up @@ -57,15 +44,11 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# A day old at most: an older mirror would report last week's drift as this week's.
- uses: ./.github/actions/download-trivy-db
with:
max-age-hours: "24"
metadata-path: reports/trivy-db.json

# Resolves each image's `:main` tag to its linux/amd64 digest, scans it, and evaluates the one
# security/vulnerability-policy.json through the one scripts/check-release-vulnerabilities.ts.
# A finding does not fail this step; an unreachable registry or a broken scanner does.
- name: Scan the images built from main
env:
TRIVY_USERNAME: ${{ github.actor }}
Expand Down
12 changes: 1 addition & 11 deletions .github/workflows/reusable-docker-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -443,14 +443,7 @@ jobs:
> /dev/null
gh attestation verify "oci://$IMAGE_REF" --owner "${{ github.repository_owner }}"

# The release vulnerability gate, run where the image is built. Until this job existed the
# policy's first execution was at `tag-images`, so a release was the first thing it could
# fail — a tripwire, not a safety net (issue #1703). It calls the same script and the same
# security/vulnerability-policy.json the release and the scheduled rescan call; a second
# copy of the policy is precisely the failure this prevents.
#
# linux/amd64 only: Alpine and Debian ship the same package versions across architectures,
# and the release still scans both, where the evidence bundle has to be complete.
# Scan one platform before merge; release evidence scans every published platform.
scan:
name: Scan linux/amd64 Image
needs: [build, merge]
Expand Down Expand Up @@ -489,13 +482,10 @@ jobs:
- name: Resolve the linux/amd64 subject
id: subject
env:
# The run tag both build paths already publish; it names this run's image whether
# `build` pushed it directly or `merge` assembled an index from the per-arch pushes.
IMAGE_REF: ${{ inputs.registry }}/${{ inputs.image-name }}:run-${{ github.run_id }}-${{ github.run_attempt }}
INPUT_IMAGE_NAME: ${{ inputs.image-name }}
run: |
set -euo pipefail
# First, so the artifact below is still named after the image if the lookup fails.
echo "image=${INPUT_IMAGE_NAME##*/}" >> "$GITHUB_OUTPUT"
raw=$(docker buildx imagetools inspect "$IMAGE_REF" --raw)
if jq -e 'has("manifests")' <<< "$raw" > /dev/null; then
Expand Down
51 changes: 51 additions & 0 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: OpenSSF Scorecard

on:
branch_protection_rule:
schedule:
- cron: "23 4 * * 1"
push:
branches: ["main"]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions: read-all

jobs:
analysis:
name: Supply-chain posture
runs-on: ubuntu-latest
# OpenSSF publishing is limited to the default branch.
if: github.event.repository.default_branch == github.ref_name
timeout-minutes: 10
permissions:
contents: read
id-token: write
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Run OpenSSF Scorecard
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
with:
results_file: results.sarif
results_format: sarif
publish_results: true

- name: Upload findings to code scanning
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
with:
sarif_file: results.sarif

- name: Preserve the assessment
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: openssf-scorecard-${{ github.sha }}
path: results.sarif
if-no-files-found: error
retention-days: 14
Loading
Loading