From 278d5ef4a21d18adb53964256ea4a888213ee0a9 Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Fri, 22 May 2026 08:05:47 +0200 Subject: [PATCH 1/4] ci(lint): delegate hadolint to lint-container reusable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract the Dockerfile-lint and bake-graph-validate steps from build.yml's inline `lint` job into a new top-level workflow .github/workflows/lint.yml. The hadolint piece now delegates to netresearch/.github's reusable `lint-container.yml@main`, which pins hadolint to v2.14.0 (handles Docker 25's HEALTHCHECK --start-interval that crashes hadolint-action's bundled v2.12.0). The `docker buildx bake --print` graph validation stays inline in the new lint.yml because the reusable model is single-target build-push-action, not bake. build.yml's `build` job stays inline — its bake multi-target shape (minimal + full variants from docker-bake.hcl) doesn't map onto the build-container reusable's single-target shape, flagged as upstream follow-up. No shell-scandirs passed: phpbu-docker does not ship its own shell helper directories — docker-entrypoint.sh lives in the build context and is exercised via the structure tests, not separately shellchecked. Signed-off-by: Sebastian Mendel --- .github/workflows/build.yml | 30 ++++++++------------- .github/workflows/lint.yml | 54 +++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 46fe999..a9c5a67 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,26 +16,18 @@ env: IMAGE_NAME: ${{ github.repository }} jobs: - lint: - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Lint Dockerfile - uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0 - with: - dockerfile: Dockerfile - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - - name: Validate bake file - run: docker buildx bake --print - build: - needs: [lint] + # Dockerfile lint (hadolint) and bake-graph validation moved to + # .github/workflows/lint.yml — the hadolint piece now delegates to + # netresearch/.github's reusable lint-container.yml. The bake-graph + # check stays inline there because the reusable model is single-target + # docker/build-push-action, not bake. This `build` job likewise stays + # inline: the bake multi-target shape (minimal + full variants from + # docker-bake.hcl with shared cache and combined tag fan-out) doesn't + # map onto build-container.yml's single-target shape, and a + # bake-to-matrix refactor would either duplicate the tag fan-out into + # `metadata-tags` for two parallel reusable calls or abandon + # docker-bake.hcl entirely. Both invasive; flagged as upstream follow-up. runs-on: ubuntu-latest permissions: contents: read diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..d448269 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 Netresearch DTT GmbH +# +# Static checks for the phpbu-docker repo. +# +# Splits into: +# - container-lint → delegates Dockerfile (hadolint) to netresearch/.github's +# reusable lint-container.yml on @main. Reusable pins +# hadolint to v2.14.0, which handles Docker 25's +# HEALTHCHECK --start-interval correctly. No +# shell-scandirs because phpbu-docker does not ship its +# own shell helpers (docker-entrypoint.sh is the only +# shipped script and lives under the build context, not +# a separate scandir). +# - bake-validate → stays inline. Validates the docker-bake.hcl multi- +# target build graph via `docker buildx bake --print`. +# Caller-specific — the reusable model is single-target +# build-push-action, not bake. + +name: lint + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +jobs: + container-lint: + # hadolint only — no shellcheck (shell-scandirs left empty). + uses: netresearch/.github/.github/workflows/lint-container.yml@main + permissions: + contents: read + + bake-validate: + name: docker buildx bake --print + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + + - name: Validate bake file + run: docker buildx bake --print From 335ec173fae210453fbb1bb4e21291f6a5208a9e Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Fri, 22 May 2026 08:05:59 +0200 Subject: [PATCH 2/4] ci(scorecard): delegate to scorecard reusable Replace inline OpenSSF Scorecard workflow with a caller stub that delegates to netresearch/.github's reusable `scorecard.yml@main`. The reusable already runs harden-runner, pins all third-party action SHAs, sets persist-credentials: false, and uploads SARIF (skipping on merge_group where the gh-readonly-queue ref races with the upload). Caller passes the full permissions set the reusable's job requires (security-events: write, id-token: write, contents: read, actions: read) plus the workflow-level `contents: read` minimum that SonarCloud rule githubactions:S8234 expects. Schedule slot kept at Monday 06:00 UTC to match the build.yml weekly security rebuild cadence; workflow_dispatch added for on-demand runs. Signed-off-by: Sebastian Mendel --- .github/workflows/scorecard.yml | 46 +++++++++++++++++---------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 55ad93d..d9c60fd 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -1,36 +1,38 @@ -name: OpenSSF Scorecard +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 Netresearch DTT GmbH +# +# OpenSSF Scorecard — supply-chain security health check. +# Delegates to the org-wide reusable in netresearch/.github. +# Runs weekly against main and uploads SARIF to GitHub code-scanning. +# Results also surface on the OpenSSF Scorecard public dashboard +# (https://securityscorecards.dev/) once enabled. + +name: scorecard on: branch_protection_rule: schedule: - - cron: '0 6 * * 1' # Weekly on Monday + - cron: '0 6 * * 1' # weekly, Monday 06:00 UTC push: branches: [main] + workflow_dispatch: -permissions: read-all +# Top-level permissions explicitly enumerated (SonarCloud rule +# githubactions:S8234). The reusable's job requests its own +# additional permissions via its own permissions block; this minimum +# lets supporting tooling read metadata without granting writes. +permissions: + contents: read jobs: analysis: name: Scorecard analysis - runs-on: ubuntu-latest permissions: + # required by scorecard-action for publishing results security-events: write + # needed to publish results and get a badge id-token: write - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Run Scorecard - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 - with: - results_file: results.sarif - results_format: sarif - publish_results: true - - - name: Upload results - uses: github/codeql-action/upload-sarif@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 - with: - sarif_file: results.sarif + contents: read + # needed for nested API calls (Branch-Protection, Webhooks checks) + actions: read + uses: netresearch/.github/.github/workflows/scorecard.yml@main From 077f7146006249ca3b7f6055070f140062b80fcd Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Fri, 22 May 2026 08:06:33 +0200 Subject: [PATCH 3/4] ci(test): delegate container-structure-test to smoke-test-container reusable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the inline structure-test job (bake build + curl-installed CST binary + run) with a caller stub that delegates to netresearch/.github's reusable smoke-test-container.yml@main. The reusable builds via docker/build-push-action with explicit target/image-tag inputs — equivalent to the bake `ci` target which is just `target = "minimal"` with a local phpbu:ci tag. Reusable benefits: harden-runner egress audit, SHA256-pinned CST v1.22.1, persist-credentials: false, cache-scope isolation, single source of truth for the CST install procedure across all Netresearch container repos. The phpbu-specific smoke-test matrix job (matrix over ci/ci-full covering TZ env-var injection sanitisation, read-only filesystem compatibility, simulate-mode with a fixture config, full-variant extras) stays inline — those tests target the entrypoint contract and runtime shape of phpbu itself, which is outside the reusable's structure-test scope. Signed-off-by: Sebastian Mendel --- .github/workflows/test.yml | 40 +++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 26a6696..d73ff3e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -106,26 +106,22 @@ jobs: docker run --rm --entrypoint which phpbu:ci-full gpg docker run --rm --entrypoint which phpbu:ci-full ssh + # The previous `structure-test` job (inline bake + container-structure-test + # download + run) now delegates to netresearch/.github's reusable + # smoke-test-container.yml. The reusable builds via docker/build-push-action + # rather than bake — equivalent here because the bake `ci` target is just + # `target = "minimal"` with `platforms = ["linux/amd64"]` and a local + # `phpbu:ci` tag, which the reusable replicates verbatim via `target:` + + # `image-tag:`. The `smoke-test` matrix job above stays inline: it covers + # phpbu-specific surface (TZ env-var sanitisation, read-only fs, simulate + # mode with a fixture config) that's outside the reusable's structure-test + # scope. structure-test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - - name: Build test image - uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0 - with: - targets: ci - load: true - - - name: Install container-structure-test - run: | - curl -LO https://github.com/GoogleContainerTools/container-structure-test/releases/download/v1.19.3/container-structure-test-linux-amd64 - echo "fa0fa333bb6ba5c14065e7468d2904f5c82d021d7e1c763c9a45c5f2fbe9ff5f container-structure-test-linux-amd64" | sha256sum -c - - chmod +x container-structure-test-linux-amd64 - sudo mv container-structure-test-linux-amd64 /usr/local/bin/container-structure-test - - - name: Run structure tests - run: container-structure-test test --image phpbu:ci --config tests/container-structure-test.yaml + uses: netresearch/.github/.github/workflows/smoke-test-container.yml@main + permissions: + contents: read + with: + image-tag: phpbu:ci + target: minimal + cst-config-path: tests/container-structure-test.yaml + cache-scope: structure-test From 0b062120058b7ace21518d0be01d3e4d00b33950 Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Fri, 22 May 2026 08:06:56 +0200 Subject: [PATCH 4/4] ci(security): delegate gitleaks to reusable, leave trivy inline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the inline gitleaks job with a caller stub that delegates to netresearch/.github's reusable gitleaks.yml@main. The reusable runs betterleaks (gitleaks fork, OSS, no license needed — the deprecated GITLEAKS_LICENSE secret is no longer required), uploads SARIF to GitHub code-scanning, and bakes in the dependabot[bot] + merge_group skip semantics that previously lived in the caller's `if:`. The trivy job above stays inline. Its current shape is local-build-via- bake then scan, which doesn't fit security-container.yml's already-published-image model. Migrating it requires the build.yml bake-to-build-container refactor to land first; flagged as upstream follow-up. Signed-off-by: Sebastian Mendel --- .github/workflows/security.yml | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 1b2560d..2b7fff8 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -52,19 +52,16 @@ jobs: sarif_file: 'trivy-results.sarif' category: 'trivy-${{ matrix.name }}' + # Secret-scanning now delegates to netresearch/.github's reusable, which + # runs betterleaks (gitleaks fork, OSS, no license required) against the + # full git history, uploads SARIF to GitHub code-scanning, and bakes in + # the dependabot[bot]+merge_group skip semantics. The trivy job above + # stays inline because it builds locally via bake before scanning — the + # security-container.yml reusable is designed for already-published + # images (matrix over tags) and can't be wired in until the build job + # itself migrates off bake. gitleaks: - runs-on: ubuntu-latest - # Skip for dependabot PRs - org secrets not available to external PRs - if: github.actor != 'dependabot[bot]' + uses: netresearch/.github/.github/workflows/gitleaks.yml@main permissions: contents: read - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - - - name: Run Gitleaks - uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} + security-events: write