From f132394e11659036d72ad343fbc9f83b36e579aa Mon Sep 17 00:00:00 2001 From: Louis Parkin Date: Wed, 5 Aug 2026 16:57:39 +0200 Subject: [PATCH 1/3] STAC-25500: port the DEB signing and pre-release publishing lane to GitHub Closes the last two GitLab jobs with no GitHub counterpart: sign_deb and pre_release_deb (.gitlab-ci-agent.yml lines 604 and 635). Security prerequisite, not cleanup ---------------------------------- sign_debian_package.sh called printenv unconditionally. On GitLab that dumped the GPG private key and its passphrase into the job log; stackstate-agent is a PUBLIC repo, so on GitHub that log is world-readable. It also left the exported private key in the checkout as gpg_private.key, where any later artifact upload would collect it. Both are removed before any signing secret is wired in. Key setup now happens in an ephemeral GNUPGHOME created with mktemp and mode 700, removed by an EXIT trap that also kills the gpg-agent so a preset passphrase cannot outlive the job on a reused runner. The passphrase reaches gpg through a mode-600 file inside that directory rather than argv. Fixes a latent signing bug -------------------------- The old preset step interpolated an unquoted command substitution into a single gpg-preset-passphrase call. A key exposes one keygrip per primary and subkey, so with a signing subkey the second keygrip was passed as a stray argument and never presetted, leaving signing able to block on a pinentry prompt no CI runner can answer. Each keygrip is now presetted individually. Verified against a throwaway key: two keygrips, both presetted. Shared setup ------------ sign_debian_package.sh and publish_package.sh need the same key in the same state but are separate processes, and on GitHub may be separate steps, so neither can rely on a keyring the other left behind. The setup moves into gpg_signing_setup.sh, sourced by both. gpg-preset-passphrase is auto-detected across /usr/lib/gnupg2, /usr/lib/gnupg, /usr/libexec and PATH, and the script fails loudly rather than silently skipping the preset when it is absent. Both scripts now fall back from CI_PROJECT_DIR to GITHUB_WORKSPACE, and publish_package.sh falls back from CI_COMMIT_REF_NAME to GITHUB_REF_NAME, so the apt codename stays the release branch exactly as it was on GitLab. Workflow -------- sign-and-publish-deb downloads both architecture artifacts and runs a single deb-s3 upload. GitLab fanned this out per architecture, so two jobs rewrote the same apt index concurrently; collecting both first removes that race. The install script is split in two. generate-install-script runs inv release.generate-install -t inside the build container, because the task collection imports python-gitlab and the GitHub and Datadog API helpers and only loads in the conda environment, and asserts the rendered script contains no None.s3.amazonaws.com from an unset bucket variable. publish-install-script then uploads it, so the container job never holds a credential. Both publishing jobs are gated on push and bound to the agent-pre-release environment, which carries the deployment branch rule, the signing secrets and the AWS role from STAC-25545. Pull requests cannot reach them. Validated: shellcheck -x clean on all three scripts; gpg_signing_setup exercised against a generated throwaway key covering explicit override, PATH auto-detection and the missing-binary failure path; actionlint clean apart from the pre-existing self-hosted runner-label notices; zizmor reports no findings. Blocked until the agent-pre-release environment, its four SIGNING_* secrets and AGENT_PRERELEASE_ROLE_ARN exist. Refs STAC-25546, STAC-25545 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-deb.yml | 135 +++++++++++++++++- omnibus/package-scripts/gpg_signing_setup.sh | 67 +++++++++ omnibus/package-scripts/publish_package.sh | 40 +++--- .../package-scripts/sign_debian_package.sh | 36 ++--- 4 files changed, 236 insertions(+), 42 deletions(-) create mode 100755 omnibus/package-scripts/gpg_signing_setup.sh diff --git a/.github/workflows/build-deb.yml b/.github/workflows/build-deb.yml index ced8a5452144..11d0ea6e5341 100644 --- a/.github/workflows/build-deb.yml +++ b/.github/workflows/build-deb.yml @@ -236,7 +236,7 @@ jobs: timeout-minutes: 60 env: ARCH: ${{ matrix.arch }} - LOCAL_IMAGE: quay.io/stackstate/stackstate-k8s-agent:ci-${{ matrix.arch }} + LOCAL_IMAGE: stackstate-agent:ci-${{ matrix.arch }} steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -283,7 +283,6 @@ jobs: mode: gate severity: UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL with-grype: true - exceptions-path: exceptions upload-sarif: false sarif-category: stackstate-k8s-agent-${{ matrix.arch }} @@ -399,6 +398,135 @@ jobs: target-registry-user: ${{ vars.QUAY_USER }} target-registry-password: ${{ secrets.QUAY_PASSWORD }} + sign-and-publish-deb: + name: Sign DEB packages (GPG) and publish to the pre-release apt repository + needs: + - build-deb + - test-deb-renaming + if: github.event_name == 'push' + runs-on: ubuntu-24.04 + environment: agent-pre-release + timeout-minutes: 30 + permissions: + contents: read + id-token: write + steps: + - name: Check out repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Download amd64 DEB package + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: deb-package-amd64 + + - name: Download arm64 DEB package + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: deb-package-arm64 + + - name: Install debsigs, GnuPG and deb-s3 + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y --no-install-recommends debsigs gnupg gpg-agent + sudo gem install --no-document deb-s3 + + - name: Assume the pre-release publishing role + uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3 + with: + role-to-assume: ${{ vars.AGENT_PRERELEASE_ROLE_ARN }} + aws-region: eu-west-1 + + - name: Sign the DEB packages with debsigs + env: + SIGNING_PUBLIC_KEY: ${{ secrets.SIGNING_PUBLIC_KEY }} + SIGNING_PRIVATE_KEY: ${{ secrets.SIGNING_PRIVATE_KEY }} + SIGNING_PRIVATE_PASSPHRASE: ${{ secrets.SIGNING_PRIVATE_PASSPHRASE }} + SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} + run: ./omnibus/package-scripts/sign_debian_package.sh + + - name: Publish the DEB packages to the pre-release apt repository + env: + SIGNING_PUBLIC_KEY: ${{ secrets.SIGNING_PUBLIC_KEY }} + SIGNING_PRIVATE_KEY: ${{ secrets.SIGNING_PRIVATE_KEY }} + SIGNING_PRIVATE_PASSPHRASE: ${{ secrets.SIGNING_PRIVATE_PASSPHRASE }} + SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} + run: ./omnibus/package-scripts/publish_package.sh stackstate-agent-3-test + + generate-install-script: + name: Generate the pre-release agent install script + needs: godeps-cache-amd64 + if: github.event_name == 'push' + runs-on: xlarge-public + timeout-minutes: 30 + permissions: + contents: read + container: + image: ${{ needs.godeps-cache-amd64.outputs.ci_image }} # zizmor: ignore[unpinned-images] + credentials: + username: ${{ vars.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + steps: + - name: Check out repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Render install.sh against the pre-release repositories + env: + STS_AWS_TEST_BUCKET: stackstate-agent-3-test + STS_AWS_TEST_BUCKET_YUM: stackstate-agent-3-rpm-test + STS_AWS_TEST_BUCKET_WIN: stackstate-agent-3-test + run: | + set -eo pipefail + . /root/miniforge3/etc/profile.d/conda.sh + conda activate "${CONDA_ENV}" + git config --global --add safe.directory '*' + inv release.generate-install -t + grep -q 's3.amazonaws.com' ./cmd/agent/install.sh + if grep -q 'None.s3.amazonaws.com' ./cmd/agent/install.sh; then + echo "install.sh references an unset bucket variable" >&2 + exit 1 + fi + + - name: Upload the rendered install script + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: agent-install-script + path: cmd/agent/install.sh + retention-days: 5 + if-no-files-found: error + + publish-install-script: + name: Publish the pre-release agent install script to S3 + needs: generate-install-script + if: github.event_name == 'push' + runs-on: ubuntu-24.04 + environment: agent-pre-release + timeout-minutes: 15 + permissions: + contents: read + id-token: write + steps: + - name: Download the rendered install script + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: agent-install-script + + - name: Assume the pre-release publishing role + uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3 + with: + role-to-assume: ${{ vars.AGENT_PRERELEASE_ROLE_ARN }} + aws-region: eu-west-1 + + - name: Upload install.sh + run: | + set -euo pipefail + aws s3 cp ./install.sh s3://stackstate-agent-3-test/install.sh --acl public-read + aws s3 ls s3://stackstate-agent-3-test/ + cerberus-notify: name: Report failure to Slack (Cerberus) needs: @@ -409,6 +537,9 @@ jobs: - build-agent-image - publish-agent-image - merge-agent-manifest + - sign-and-publish-deb + - generate-install-script + - publish-install-script if: >- always() && github.event_name == 'push' diff --git a/omnibus/package-scripts/gpg_signing_setup.sh b/omnibus/package-scripts/gpg_signing_setup.sh new file mode 100755 index 000000000000..973f7b4160eb --- /dev/null +++ b/omnibus/package-scripts/gpg_signing_setup.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# Sourced by sign_debian_package.sh and publish_package.sh. +# +# Both scripts need the same signing key in the same state, but they are +# separate processes and on GitHub Actions they may run as separate steps, so +# neither can rely on a keyring the other left behind. Sourcing this keeps the +# setup identical in both without duplicating it. + +gpg_signing_setup() { + : "${SIGNING_PUBLIC_KEY:?SIGNING_PUBLIC_KEY is not set}" + : "${SIGNING_PRIVATE_KEY:?SIGNING_PRIVATE_KEY is not set}" + : "${SIGNING_PRIVATE_PASSPHRASE:?SIGNING_PRIVATE_PASSPHRASE is not set}" + : "${SIGNING_KEY_ID:?SIGNING_KEY_ID is not set}" + + # Debian ships this under /usr/lib/gnupg2 on the old signing image and under + # /usr/lib/gnupg on current releases, and it is not on PATH in either. + local preset="${GPG_PRESET_PASSPHRASE:-}" + if [ -z "${preset}" ]; then + for candidate in \ + /usr/lib/gnupg2/gpg-preset-passphrase \ + /usr/lib/gnupg/gpg-preset-passphrase \ + /usr/libexec/gpg-preset-passphrase \ + "$(command -v gpg-preset-passphrase 2>/dev/null || true)"; do + if [ -n "${candidate}" ] && [ -x "${candidate}" ]; then + preset="${candidate}" + break + fi + done + fi + if [ ! -x "${preset:-}" ]; then + echo "gpg-preset-passphrase not found; set GPG_PRESET_PASSPHRASE" >&2 + return 1 + fi + + # Keep the keyring and the private key off the build workspace: this + # repository is public, and anything left in the checkout can be swept up by + # an artifact upload. The trap also stops a gpg-agent holding a preset + # passphrase from outliving the job on a reused runner. + GNUPGHOME="$(mktemp -d)" + export GNUPGHOME + chmod 700 "${GNUPGHOME}" + trap 'gpgconf --kill gpg-agent >/dev/null 2>&1 || true; rm -rf "${GNUPGHOME}"' EXIT + + cat <<-CONF >"${GNUPGHOME}/gpg-agent.conf" + default-cache-ttl 46000 + allow-preset-passphrase + CONF + + local passphrase_file="${GNUPGHOME}/passphrase" + (umask 077; printf '%s' "${SIGNING_PRIVATE_PASSPHRASE}" >"${passphrase_file}") + + printf '%s\n' "${SIGNING_PUBLIC_KEY}" | gpg --batch --quiet --import + printf '%s\n' "${SIGNING_PRIVATE_KEY}" \ + | gpg --batch --yes --quiet --pinentry-mode loopback \ + --passphrase-file "${passphrase_file}" --import + + gpg-connect-agent RELOADAGENT /bye + + # A key can expose more than one keygrip (primary plus subkeys); preset each + # so signing never blocks on a pinentry prompt a CI runner cannot answer. + gpg --list-secret-keys --with-fingerprint --with-colons \ + | awk -F: '$1 == "grp" { print $10 }' \ + | while read -r keygrip; do + "${preset}" --preset "${keygrip}" <"${passphrase_file}" + done +} diff --git a/omnibus/package-scripts/publish_package.sh b/omnibus/package-scripts/publish_package.sh index 828fabf40c71..3af581dbf98f 100755 --- a/omnibus/package-scripts/publish_package.sh +++ b/omnibus/package-scripts/publish_package.sh @@ -1,29 +1,31 @@ #!/bin/bash -TARGET_BUCKET=$1 +set -euo pipefail -CODENAME=${2:-$CI_COMMIT_REF_NAME} -TARGET_CODENAME=${CODENAME:-dirty} +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=omnibus/package-scripts/gpg_signing_setup.sh +source "${script_dir}/gpg_signing_setup.sh" - -if [ -z ${TARGET_BUCKET+x} ]; then - echo "Missing S3 bucket parameter" - exit 1; +TARGET_BUCKET="${1:-}" +if [ -z "${TARGET_BUCKET}" ]; then + echo "Missing S3 bucket parameter" >&2 + exit 1 fi -if [ -z ${STACKSTATE_AGENT_VERSION+x} ]; then - STACKSTATE_AGENT_VERSION=$(cat $CI_PROJECT_DIR/version.txt) -fi -echo $STACKSTATE_AGENT_VERSION +# CI_PROJECT_DIR is GitLab's; GITHUB_WORKSPACE is the GitHub Actions equivalent. +PROJECT_DIR="${CI_PROJECT_DIR:-${GITHUB_WORKSPACE:-$(pwd)}}" +PKG_DIR="${PKG_DIR:-${PROJECT_DIR}/outcomes/pkg}" -ls $CI_PROJECT_DIR/outcomes/pkg/*.* +CODENAME="${2:-${CI_COMMIT_REF_NAME:-${GITHUB_REF_NAME:-}}}" +TARGET_CODENAME="${CODENAME:-dirty}" + +if [ -z "${STACKSTATE_AGENT_VERSION:-}" ]; then + STACKSTATE_AGENT_VERSION=$(cat "${PROJECT_DIR}/version.txt") +fi -cat <~/.gnupg/gpg-agent.conf -default-cache-ttl 46000 -allow-preset-passphrase -EOF +echo "Publishing stackstate-agent ${STACKSTATE_AGENT_VERSION} to ${TARGET_BUCKET} (${TARGET_CODENAME})" +ls "${PKG_DIR}"/*.* -gpg-connect-agent RELOADAGENT /bye -echo $SIGNING_PRIVATE_PASSPHRASE | /usr/lib/gnupg2/gpg-preset-passphrase -v -c $(gpg --list-secret-keys --with-fingerprint --with-colons | awk -F: '$1 == "grp" { print $10 }') +gpg_signing_setup -deb-s3 upload --sign=${SIGNING_KEY_ID} --codename ${TARGET_CODENAME} --bucket ${TARGET_BUCKET} $CI_PROJECT_DIR/outcomes/pkg/*.deb +deb-s3 upload --sign="${SIGNING_KEY_ID}" --codename "${TARGET_CODENAME}" --bucket "${TARGET_BUCKET}" "${PKG_DIR}"/*.deb diff --git a/omnibus/package-scripts/sign_debian_package.sh b/omnibus/package-scripts/sign_debian_package.sh index 7ed0f3c8aa94..a1915bdec7ad 100755 --- a/omnibus/package-scripts/sign_debian_package.sh +++ b/omnibus/package-scripts/sign_debian_package.sh @@ -1,30 +1,24 @@ #!/bin/bash -set -e +set -euo pipefail -if [ -z ${STACKSTATE_AGENT_VERSION+x} ]; then +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=omnibus/package-scripts/gpg_signing_setup.sh +source "${script_dir}/gpg_signing_setup.sh" + +# CI_PROJECT_DIR is GitLab's; GITHUB_WORKSPACE is the GitHub Actions equivalent. +PROJECT_DIR="${CI_PROJECT_DIR:-${GITHUB_WORKSPACE:-$(pwd)}}" +PKG_DIR="${PKG_DIR:-${PROJECT_DIR}/outcomes/pkg}" + +if [ -z "${STACKSTATE_AGENT_VERSION:-}" ]; then # Pick the latest tag by default for our version. - STACKSTATE_AGENT_VERSION=$(cat $CI_PROJECT_DIR/version.txt) + STACKSTATE_AGENT_VERSION=$(cat "${PROJECT_DIR}/version.txt") # But we will be building from the master branch in this case. fi -echo $STACKSTATE_AGENT_VERSION - -printenv - -echo "$SIGNING_PUBLIC_KEY" | gpg --import -echo "$SIGNING_PRIVATE_KEY" > gpg_private.key -echo "$SIGNING_PRIVATE_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 --import gpg_private.key -echo "$SIGNING_KEY_ID" - -ls $CI_PROJECT_DIR/outcomes/pkg/*.* - -cat <~/.gnupg/gpg-agent.conf -default-cache-ttl 46000 -allow-preset-passphrase -EOF +echo "Signing stackstate-agent ${STACKSTATE_AGENT_VERSION}" +ls "${PKG_DIR}"/*.* -gpg-connect-agent RELOADAGENT /bye -echo $SIGNING_PRIVATE_PASSPHRASE | /usr/lib/gnupg2/gpg-preset-passphrase -v -c $(gpg --list-secret-keys --with-fingerprint --with-colons | awk -F: '$1 == "grp" { print $10 }') +gpg_signing_setup -debsigs --sign=origin -k ${SIGNING_KEY_ID} $CI_PROJECT_DIR/outcomes/pkg/*.deb +debsigs --sign=origin -k "${SIGNING_KEY_ID}" "${PKG_DIR}"/*.deb From e3a578b59b9a2614ef4a474e3f89e711f927540d Mon Sep 17 00:00:00 2001 From: Louis Parkin Date: Thu, 6 Aug 2026 15:52:16 +0200 Subject: [PATCH 2/3] STAC-25500: pin deb-s3 and gate install.sh publication on DEB publication Addresses review feedback on PR #455. [P1] The signing job installed the publisher with `gem install deb-s3`, resolving the latest code at run time into a job that then executes it with the package signing key and the pre-release AWS credentials in scope. GitLab never did this: `sign_deb` ran in a pinned image with deb-s3 already baked in, so the runtime resolve was a regression introduced by the port. deb-s3 and its full runtime dependency tree are now pinned in `.github/deb-s3-gems.sha256` and installed by `.github/scripts/install-deb-s3.sh`, which fetches each gem at its exact version and verifies it against the SHA256 RubyGems publishes for that release before anything is installed or executed. The manifest covers nine gems. base64, bigdecimal and logger are deliberately excluded: aws-sdk-core requires them at ">= 0" and they are Ruby default gems supplied by the distribution's own ruby package, so pinning them would force a native build for no supply-chain gain. The script also links the canonical executable when RubyGems installs a versioned binstub, since publish_package.sh invokes deb-s3 by bare name, and ends with a `deb-s3 help` smoke check that activates the whole pinned set so a missing or incompatible gem fails at install time rather than midway through publishing. [P2] publish-install-script depended only on generate-install-script, so it could overwrite the public install.sh even when the build, signing or apt upload had failed. GitLab's pre_release_deb required sign_deb. It now requires both generate-install-script and sign-and-publish-deb, restoring that release boundary. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/deb-s3-gems.sha256 | 9 +++++ .github/scripts/install-deb-s3.sh | 65 +++++++++++++++++++++++++++++++ .github/workflows/build-deb.yml | 12 ++++-- 3 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 .github/deb-s3-gems.sha256 create mode 100755 .github/scripts/install-deb-s3.sh diff --git a/.github/deb-s3-gems.sha256 b/.github/deb-s3-gems.sha256 new file mode 100644 index 000000000000..75afacad61c1 --- /dev/null +++ b/.github/deb-s3-gems.sha256 @@ -0,0 +1,9 @@ +116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b aws-eventstream-1.4.0.gem +40bda996876a45a60c43fbf489b04b46216e98c1814c1ac6453b942e0df6501e aws-partitions-1.1277.0.gem +ee3e3220b8468a3c9e59daba18e6ec897bf5c7ce8adcc0670cfa2f1f092112fe aws-sdk-core-3.254.0.gem +a2e83662ca31b77a2a19c9aa2f40a98165a67270c18c718fe1c70d0cbd7cd749 aws-sdk-kms-1.130.0.gem +1217b878b554b45f2152115c5d2623e3497222f46a738e5a96e9767cbf41468b aws-sdk-s3-1.228.2.gem +6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00 aws-sigv4-1.12.1.gem +8beb36bd7d5f524644f2e4b947e9212bcb47cab0b50cd8ad459ce527f938b956 deb-s3-26.1.0.gem +238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1 jmespath-1.6.2.gem +e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73 thor-1.5.0.gem diff --git a/.github/scripts/install-deb-s3.sh b/.github/scripts/install-deb-s3.sh new file mode 100755 index 000000000000..9f975b9e82a2 --- /dev/null +++ b/.github/scripts/install-deb-s3.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# +# Install deb-s3 and its full runtime dependency tree from a pinned, checksum +# verified manifest. +# +# deb-s3 runs with the package signing key and the pre-release AWS credentials +# in scope, so it must not be resolved at run time. Every gem is fetched at the +# exact version recorded in the manifest and verified against the SHA256 that +# RubyGems publishes for that release before anything is installed or executed. +# +# Usage: install-deb-s3.sh [manifest] +# +# Regenerating the manifest: fetch each gem and record +# " -.gem", matching the checksum published at +# https://rubygems.org/api/v1/versions/.json for that version. + +set -euo pipefail + +MANIFEST="${1:-.github/deb-s3-gems.sha256}" + +if [[ ! -f "${MANIFEST}" ]]; then + echo "gem manifest not found: ${MANIFEST}" >&2 + exit 1 +fi + +MANIFEST_ABS="$(cd "$(dirname "${MANIFEST}")" && pwd)/$(basename "${MANIFEST}")" + +WORKDIR="$(mktemp -d)" +trap 'rm -rf "${WORKDIR}"' EXIT + +cp "${MANIFEST_ABS}" "${WORKDIR}/gems.sha256" +cd "${WORKDIR}" + +while read -r _sha file; do + [[ -n "${file:-}" ]] || continue + name="${file%-*}" + version="${file##*-}" + version="${version%.gem}" + echo "fetching ${name} ${version}" + gem fetch "${name}" --version "${version}" --platform ruby +done < gems.sha256 + +echo "verifying checksums" +sha256sum --check --strict gems.sha256 + +echo "installing" +${GEM_INSTALL_SUDO-sudo} gem install --local --no-document --ignore-dependencies ./*.gem + +# RubyGems installs versioned binstubs on some distributions (deb-s3.ruby3.2, +# deb-s33.2), so a plain "deb-s3" on PATH is not guaranteed. publish_package.sh +# invokes it by bare name, so link the canonical executable when it is missing. +if ! command -v deb-s3 >/dev/null 2>&1; then + canonical="$(gem contents deb-s3 | grep -E '/bin/deb-s3$' | head -n 1)" + if [[ -z "${canonical}" ]]; then + echo "deb-s3 was installed but its executable could not be located" >&2 + exit 1 + fi + ${GEM_INSTALL_SUDO-sudo} ln -sf "${canonical}" "${LINK_DIR:-/usr/local/bin}/deb-s3" +fi + +# Smoke check: this activates the whole pinned dependency set, so a missing or +# incompatible gem fails here rather than midway through publishing. +echo "verifying deb-s3" +deb-s3 help >/dev/null +echo "deb-s3 ready: $(command -v deb-s3)" diff --git a/.github/workflows/build-deb.yml b/.github/workflows/build-deb.yml index 11d0ea6e5341..591e6e548230 100644 --- a/.github/workflows/build-deb.yml +++ b/.github/workflows/build-deb.yml @@ -426,12 +426,14 @@ jobs: with: name: deb-package-arm64 - - name: Install debsigs, GnuPG and deb-s3 + - name: Install debsigs and GnuPG run: | set -euo pipefail sudo apt-get update - sudo apt-get install -y --no-install-recommends debsigs gnupg gpg-agent - sudo gem install --no-document deb-s3 + sudo apt-get install -y --no-install-recommends debsigs gnupg gpg-agent ruby + + - name: Install deb-s3 from the pinned, checksum-verified manifest + run: ./.github/scripts/install-deb-s3.sh .github/deb-s3-gems.sha256 - name: Assume the pre-release publishing role uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3 @@ -501,7 +503,9 @@ jobs: publish-install-script: name: Publish the pre-release agent install script to S3 - needs: generate-install-script + needs: + - generate-install-script + - sign-and-publish-deb if: github.event_name == 'push' runs-on: ubuntu-24.04 environment: agent-pre-release From 1a2d643ef3deeacd450dcbff175cc4c472fb29e5 Mon Sep 17 00:00:00 2001 From: Louis Parkin Date: Fri, 7 Aug 2026 13:53:37 +0200 Subject: [PATCH 3/3] STAC-25500 Restore the image scan exception wiring lost in the rebase Resolving the rebase conflict against the rewritten base took this branch's whole copy of build-deb.yml, which predated the exception work on STAC-25457. That silently reverted two lines, so the gate ran here with no exceptions loaded and reported all 24 findings as unmanaged. Restores the quay LOCAL_IMAGE name the evaluator matches exceptions on, and the exceptions-path input. The workflow diff against the base branch is now additions only. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-deb.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-deb.yml b/.github/workflows/build-deb.yml index 591e6e548230..4b5503ce2f36 100644 --- a/.github/workflows/build-deb.yml +++ b/.github/workflows/build-deb.yml @@ -236,7 +236,7 @@ jobs: timeout-minutes: 60 env: ARCH: ${{ matrix.arch }} - LOCAL_IMAGE: stackstate-agent:ci-${{ matrix.arch }} + LOCAL_IMAGE: quay.io/stackstate/stackstate-k8s-agent:ci-${{ matrix.arch }} steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -283,6 +283,7 @@ jobs: mode: gate severity: UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL with-grype: true + exceptions-path: exceptions upload-sarif: false sarif-category: stackstate-k8s-agent-${{ matrix.arch }}