Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
9 changes: 9 additions & 0 deletions .github/deb-s3-gems.sha256
Original file line number Diff line number Diff line change
@@ -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
65 changes: 65 additions & 0 deletions .github/scripts/install-deb-s3.sh
Original file line number Diff line number Diff line change
@@ -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
# "<sha256> <name>-<version>.gem", matching the checksum published at
# https://rubygems.org/api/v1/versions/<name>.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)"
136 changes: 136 additions & 0 deletions .github/workflows/build-deb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,139 @@ 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 and GnuPG
run: |
set -euo pipefail
sudo apt-get update
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
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
- sign-and-publish-deb
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:
Expand All @@ -409,6 +542,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'
Expand Down
67 changes: 67 additions & 0 deletions omnibus/package-scripts/gpg_signing_setup.sh
Original file line number Diff line number Diff line change
@@ -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
}
40 changes: 21 additions & 19 deletions omnibus/package-scripts/publish_package.sh
Original file line number Diff line number Diff line change
@@ -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 <<EOF >~/.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
36 changes: 15 additions & 21 deletions omnibus/package-scripts/sign_debian_package.sh
Original file line number Diff line number Diff line change
@@ -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 <<EOF >~/.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
Loading