From b297b7a5ae1e6ce14ae5609c7221de92d16d25cd Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Wed, 2 Sep 2026 11:00:59 -0300 Subject: [PATCH 1/3] ci: replicate source and release artifacts --- .github/workflows/release.yml | 100 +++++++++++++++++++ .github/workflows/replicate-source.yml | 128 +++++++++++++++++++++++++ 2 files changed, 228 insertions(+) create mode 100644 .github/workflows/replicate-source.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b2a9b1c..095df0a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -180,3 +180,103 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: ci/integ/run-oci-smoke.sh "${{ matrix.arch }}" "${{ github.ref_name }}" + + replicate: + needs: smoke-test + runs-on: ubuntu-latest + timeout-minutes: 10 + environment: replication + permissions: + id-token: write + contents: read + concurrency: + group: repository-replication + cancel-in-progress: false + + steps: + - name: Download release artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + path: artifacts + merge-multiple: true + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3 + with: + role-to-assume: ${{ secrets.REPLICATION_ROLE_ARN }} + aws-region: us-west-2 + role-session-name: release-replication-${{ github.run_id }} + + - name: Upload immutable release artifacts + env: + REPLICATION_BUCKET_NAME: ${{ secrets.REPLICATION_BUCKET_NAME }} + run: | + set -euo pipefail + : > /tmp/replication-manifest.tsv + + for artifact in artifacts/*; do + if [[ ! -f "${artifact}" ]]; then + continue + fi + + artifact_key="releases/${GITHUB_REF_NAME}/$(basename "${artifact}")" + artifact_sha256="$(sha256sum "${artifact}" | awk '{print $1}')" + version_id="$(aws s3api put-object \ + --bucket "${REPLICATION_BUCKET_NAME}" \ + --key "${artifact_key}" \ + --body "${artifact}" \ + --checksum-algorithm SHA256 \ + --metadata "source-commit=${GITHUB_SHA},sha256=${artifact_sha256}" \ + --query VersionId \ + --output text)" + + if [[ -z "${version_id}" || "${version_id}" == "None" || "${version_id}" == "null" ]]; then + echo "The upload did not return an S3 object version." >&2 + exit 1 + fi + + printf '%s\t%s\n' "${artifact_key}" "${version_id}" >> /tmp/replication-manifest.tsv + done + + if [[ ! -s /tmp/replication-manifest.tsv ]]; then + echo "No release artifacts were uploaded." >&2 + exit 1 + fi + + - name: Replicate release artifacts + env: + REPLICATION_FUNCTION_ARN: ${{ secrets.REPLICATION_FUNCTION_ARN }} + REPLICATION_REQUEST_CONFIG: ${{ secrets.REPLICATION_REQUEST_CONFIG }} + run: | + set -euo pipefail + + while IFS=$'\t' read -r artifact_key version_id; do + jq -n \ + --argjson config "${REPLICATION_REQUEST_CONFIG}" \ + --arg source_path "${artifact_key}" \ + --arg source_version "${version_id}" \ + --arg repository "${GITHUB_REPOSITORY}" \ + --arg commit "${GITHUB_SHA}" \ + '$config + { + s3FilePath: $source_path, + s3ObjectVersion: $source_version, + gitHubRepo: $repository, + gitHubCommit: $commit, + extractArchive: false + }' > /tmp/replication-request.json + + function_error="$(aws lambda invoke \ + --function-name "${REPLICATION_FUNCTION_ARN}" \ + --cli-binary-format raw-in-base64-out \ + --payload file:///tmp/replication-request.json \ + --query FunctionError \ + --output text \ + /tmp/replication-response.json)" + + if [[ "${function_error}" != "None" ]]; then + echo "Replication failed; inspect the function logs for details." >&2 + exit 1 + fi + + printf 'Replicated %s successfully.\n' "${artifact_key}" + done < /tmp/replication-manifest.tsv diff --git a/.github/workflows/replicate-source.yml b/.github/workflows/replicate-source.yml new file mode 100644 index 0000000..026c632 --- /dev/null +++ b/.github/workflows/replicate-source.yml @@ -0,0 +1,128 @@ +name: Replicate source + +on: + workflow_dispatch: + schedule: + # Avoid the start of the hour, when scheduled Actions experience higher load. + - cron: '17 * * * *' + +permissions: + contents: read + id-token: write + +concurrency: + group: repository-replication + cancel-in-progress: false + +env: + AWS_REGION: us-west-2 + +jobs: + replicate: + name: Replicate source snapshot + runs-on: ubuntu-latest + timeout-minutes: 15 + environment: replication + + steps: + - name: Check out master + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: master + fetch-depth: 1 + persist-credentials: false + + - name: Create immutable source snapshot + id: snapshot + run: | + set -euo pipefail + + commit="$(git rev-parse HEAD)" + archive="/tmp/aws-lambda-cpp-${commit}.tgz" + object_key="source/${commit}.tgz" + + git archive \ + --format=tar.gz \ + --output="${archive}" \ + "${commit}" + + gzip -t "${archive}" + tar -tzf "${archive}" >/dev/null + archive_sha256="$(sha256sum "${archive}" | awk '{print $1}')" + + echo "archive=${archive}" >> "${GITHUB_OUTPUT}" + echo "commit=${commit}" >> "${GITHUB_OUTPUT}" + echo "object_key=${object_key}" >> "${GITHUB_OUTPUT}" + echo "sha256=${archive_sha256}" >> "${GITHUB_OUTPUT}" + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3 + with: + role-to-assume: ${{ secrets.REPLICATION_ROLE_ARN }} + aws-region: ${{ env.AWS_REGION }} + role-session-name: repository-replication-${{ github.run_id }} + + - name: Upload source snapshot + id: upload + env: + REPLICATION_BUCKET_NAME: ${{ secrets.REPLICATION_BUCKET_NAME }} + SOURCE_ARCHIVE: ${{ steps.snapshot.outputs.archive }} + SOURCE_COMMIT: ${{ steps.snapshot.outputs.commit }} + SOURCE_KEY: ${{ steps.snapshot.outputs.object_key }} + SOURCE_SHA256: ${{ steps.snapshot.outputs.sha256 }} + run: | + set -euo pipefail + + version_id="$(aws s3api put-object \ + --bucket "${REPLICATION_BUCKET_NAME}" \ + --key "${SOURCE_KEY}" \ + --body "${SOURCE_ARCHIVE}" \ + --checksum-algorithm SHA256 \ + --metadata "source-commit=${SOURCE_COMMIT},sha256=${SOURCE_SHA256}" \ + --query VersionId \ + --output text)" + + if [[ -z "${version_id}" || "${version_id}" == "None" || "${version_id}" == "null" ]]; then + echo "The upload did not return an S3 object version." >&2 + exit 1 + fi + + echo "version_id=${version_id}" >> "${GITHUB_OUTPUT}" + + - name: Replicate source snapshot + env: + REPLICATION_FUNCTION_ARN: ${{ secrets.REPLICATION_FUNCTION_ARN }} + REPLICATION_REQUEST_CONFIG: ${{ secrets.REPLICATION_REQUEST_CONFIG }} + SOURCE_COMMIT: ${{ steps.snapshot.outputs.commit }} + SOURCE_KEY: ${{ steps.snapshot.outputs.object_key }} + SOURCE_VERSION_ID: ${{ steps.upload.outputs.version_id }} + run: | + set -euo pipefail + + jq -e \ + --arg source_path "${SOURCE_KEY}" \ + --arg source_version "${SOURCE_VERSION_ID}" \ + --arg repository "${GITHUB_REPOSITORY}" \ + --arg commit "${SOURCE_COMMIT}" \ + '. + { + s3FilePath: $source_path, + s3ObjectVersion: $source_version, + gitHubRepo: $repository, + gitHubCommit: $commit, + extractArchive: true + }' <<< "${REPLICATION_REQUEST_CONFIG}" > /tmp/replication-request.json + + function_error="$(aws lambda invoke \ + --function-name "${REPLICATION_FUNCTION_ARN}" \ + --cli-binary-format raw-in-base64-out \ + --payload file:///tmp/replication-request.json \ + --query FunctionError \ + --output text \ + /tmp/replication-response.json)" + + if [[ "${function_error}" != "None" ]]; then + echo "Replication failed; inspect the function logs for details." >&2 + exit 1 + fi + + echo "Source snapshot replicated successfully." From 77aafc3ca9de300b8cc1d8afc945865793a3a392 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Wed, 2 Sep 2026 11:06:19 -0300 Subject: [PATCH 2/3] ci: harden replication integrity checks --- .github/workflows/release.yml | 71 ++++++++++++++++++++++++-- .github/workflows/replicate-source.yml | 44 ++++++++++++++-- 2 files changed, 107 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 095df0a..f7f2074 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -183,7 +183,7 @@ jobs: replicate: needs: smoke-test - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 10 environment: replication permissions: @@ -194,11 +194,71 @@ jobs: cancel-in-progress: false steps: - - name: Download release artifacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + - name: Check out release signing key + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - path: artifacts - merge-multiple: true + ref: ${{ github.sha }} + fetch-depth: 1 + persist-credentials: false + sparse-checkout: signing-public-key.asc + sparse-checkout-cone-mode: false + + - name: Download published release artifacts + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.ref_name }} + run: | + set -euo pipefail + mkdir artifacts + gh release download "${TAG}" \ + --repo "${GITHUB_REPOSITORY}" \ + --dir artifacts + + - name: Verify release artifact integrity + run: | + set -euo pipefail + + expected_artifacts=( + libaws-lambda-runtime-x86_64.a + libaws-lambda-runtime-aarch64.a + SHA256SUMS + SHA256SUMS.asc + ) + for artifact in "${expected_artifacts[@]}"; do + if [[ ! -f "artifacts/${artifact}" ]]; then + echo "Missing release artifact: ${artifact}" >&2 + exit 1 + fi + done + + GNUPGHOME="$(mktemp -d)" + chmod 700 "${GNUPGHOME}" + export GNUPGHOME + trap 'gpgconf --kill all >/dev/null 2>&1 || true; rm -rf "${GNUPGHOME}"' EXIT + + expected_fingerprint="4A251DDC40B28DF1529507B9B24A651ACB132CBA" + actual_fingerprint="$(gpg --batch --with-colons --show-keys signing-public-key.asc \ + | awk -F: '$1 == "fpr" { print $10; exit }')" + if [[ "${actual_fingerprint}" != "${expected_fingerprint}" ]]; then + echo "The release signing key fingerprint does not match the trusted fingerprint." >&2 + exit 1 + fi + + gpg --batch --import signing-public-key.asc + gpg --batch --verify artifacts/SHA256SUMS.asc artifacts/SHA256SUMS + + ( + cd artifacts + sha256sum --check --strict SHA256SUMS + ) + + for artifact in artifacts/*.a; do + if [[ ! -f "${artifact}.asc" ]]; then + echo "Missing detached signature for $(basename "${artifact}")." >&2 + exit 1 + fi + gpg --batch --verify "${artifact}.asc" "${artifact}" + done - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3 @@ -206,6 +266,7 @@ jobs: role-to-assume: ${{ secrets.REPLICATION_ROLE_ARN }} aws-region: us-west-2 role-session-name: release-replication-${{ github.run_id }} + mask-aws-account-id: true - name: Upload immutable release artifacts env: diff --git a/.github/workflows/replicate-source.yml b/.github/workflows/replicate-source.yml index 026c632..61e7e8c 100644 --- a/.github/workflows/replicate-source.yml +++ b/.github/workflows/replicate-source.yml @@ -20,15 +20,16 @@ env: jobs: replicate: name: Replicate source snapshot - runs-on: ubuntu-latest + if: github.ref == 'refs/heads/master' + runs-on: ubuntu-24.04 timeout-minutes: 15 environment: replication steps: - - name: Check out master + - name: Check out triggering commit uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - ref: master + ref: ${{ github.sha }} fetch-depth: 1 persist-credentials: false @@ -55,12 +56,49 @@ jobs: echo "object_key=${object_key}" >> "${GITHUB_OUTPUT}" echo "sha256=${archive_sha256}" >> "${GITHUB_OUTPUT}" + - name: Verify source snapshot integrity + env: + SOURCE_ARCHIVE: ${{ steps.snapshot.outputs.archive }} + SOURCE_COMMIT: ${{ steps.snapshot.outputs.commit }} + SOURCE_SHA256: ${{ steps.snapshot.outputs.sha256 }} + TRIGGER_COMMIT: ${{ github.sha }} + run: | + set -euo pipefail + + if [[ "${SOURCE_COMMIT}" != "${TRIGGER_COMMIT}" ]]; then + echo "The snapshot commit does not match the triggering commit." >&2 + exit 1 + fi + + if [[ "${SOURCE_COMMIT}" != "$(git rev-parse HEAD)" ]]; then + echo "The snapshot commit does not match the checked-out commit." >&2 + exit 1 + fi + + verification_archive="/tmp/source-verification.tgz" + git archive \ + --format=tar.gz \ + --output="${verification_archive}" \ + "${SOURCE_COMMIT}" + + cmp "${SOURCE_ARCHIVE}" "${verification_archive}" + echo "${SOURCE_SHA256} ${SOURCE_ARCHIVE}" | sha256sum --check --strict + gzip -t "${SOURCE_ARCHIVE}" + + while IFS= read -r archive_path; do + if [[ "/${archive_path}/" == *"/.git/"* ]]; then + echo "The snapshot unexpectedly contains Git metadata." >&2 + exit 1 + fi + done < <(tar -tzf "${SOURCE_ARCHIVE}") + - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3 with: role-to-assume: ${{ secrets.REPLICATION_ROLE_ARN }} aws-region: ${{ env.AWS_REGION }} role-session-name: repository-replication-${{ github.run_id }} + mask-aws-account-id: true - name: Upload source snapshot id: upload From d81f2a522a550a4e1f61798ff9c39e1c8d4b1f3f Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Wed, 2 Sep 2026 11:11:00 -0300 Subject: [PATCH 3/3] ci: limit replication to source snapshots --- .github/workflows/release.yml | 161 ---------------------------------- 1 file changed, 161 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f7f2074..b2a9b1c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -180,164 +180,3 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: ci/integ/run-oci-smoke.sh "${{ matrix.arch }}" "${{ github.ref_name }}" - - replicate: - needs: smoke-test - runs-on: ubuntu-24.04 - timeout-minutes: 10 - environment: replication - permissions: - id-token: write - contents: read - concurrency: - group: repository-replication - cancel-in-progress: false - - steps: - - name: Check out release signing key - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ github.sha }} - fetch-depth: 1 - persist-credentials: false - sparse-checkout: signing-public-key.asc - sparse-checkout-cone-mode: false - - - name: Download published release artifacts - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG: ${{ github.ref_name }} - run: | - set -euo pipefail - mkdir artifacts - gh release download "${TAG}" \ - --repo "${GITHUB_REPOSITORY}" \ - --dir artifacts - - - name: Verify release artifact integrity - run: | - set -euo pipefail - - expected_artifacts=( - libaws-lambda-runtime-x86_64.a - libaws-lambda-runtime-aarch64.a - SHA256SUMS - SHA256SUMS.asc - ) - for artifact in "${expected_artifacts[@]}"; do - if [[ ! -f "artifacts/${artifact}" ]]; then - echo "Missing release artifact: ${artifact}" >&2 - exit 1 - fi - done - - GNUPGHOME="$(mktemp -d)" - chmod 700 "${GNUPGHOME}" - export GNUPGHOME - trap 'gpgconf --kill all >/dev/null 2>&1 || true; rm -rf "${GNUPGHOME}"' EXIT - - expected_fingerprint="4A251DDC40B28DF1529507B9B24A651ACB132CBA" - actual_fingerprint="$(gpg --batch --with-colons --show-keys signing-public-key.asc \ - | awk -F: '$1 == "fpr" { print $10; exit }')" - if [[ "${actual_fingerprint}" != "${expected_fingerprint}" ]]; then - echo "The release signing key fingerprint does not match the trusted fingerprint." >&2 - exit 1 - fi - - gpg --batch --import signing-public-key.asc - gpg --batch --verify artifacts/SHA256SUMS.asc artifacts/SHA256SUMS - - ( - cd artifacts - sha256sum --check --strict SHA256SUMS - ) - - for artifact in artifacts/*.a; do - if [[ ! -f "${artifact}.asc" ]]; then - echo "Missing detached signature for $(basename "${artifact}")." >&2 - exit 1 - fi - gpg --batch --verify "${artifact}.asc" "${artifact}" - done - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3 - with: - role-to-assume: ${{ secrets.REPLICATION_ROLE_ARN }} - aws-region: us-west-2 - role-session-name: release-replication-${{ github.run_id }} - mask-aws-account-id: true - - - name: Upload immutable release artifacts - env: - REPLICATION_BUCKET_NAME: ${{ secrets.REPLICATION_BUCKET_NAME }} - run: | - set -euo pipefail - : > /tmp/replication-manifest.tsv - - for artifact in artifacts/*; do - if [[ ! -f "${artifact}" ]]; then - continue - fi - - artifact_key="releases/${GITHUB_REF_NAME}/$(basename "${artifact}")" - artifact_sha256="$(sha256sum "${artifact}" | awk '{print $1}')" - version_id="$(aws s3api put-object \ - --bucket "${REPLICATION_BUCKET_NAME}" \ - --key "${artifact_key}" \ - --body "${artifact}" \ - --checksum-algorithm SHA256 \ - --metadata "source-commit=${GITHUB_SHA},sha256=${artifact_sha256}" \ - --query VersionId \ - --output text)" - - if [[ -z "${version_id}" || "${version_id}" == "None" || "${version_id}" == "null" ]]; then - echo "The upload did not return an S3 object version." >&2 - exit 1 - fi - - printf '%s\t%s\n' "${artifact_key}" "${version_id}" >> /tmp/replication-manifest.tsv - done - - if [[ ! -s /tmp/replication-manifest.tsv ]]; then - echo "No release artifacts were uploaded." >&2 - exit 1 - fi - - - name: Replicate release artifacts - env: - REPLICATION_FUNCTION_ARN: ${{ secrets.REPLICATION_FUNCTION_ARN }} - REPLICATION_REQUEST_CONFIG: ${{ secrets.REPLICATION_REQUEST_CONFIG }} - run: | - set -euo pipefail - - while IFS=$'\t' read -r artifact_key version_id; do - jq -n \ - --argjson config "${REPLICATION_REQUEST_CONFIG}" \ - --arg source_path "${artifact_key}" \ - --arg source_version "${version_id}" \ - --arg repository "${GITHUB_REPOSITORY}" \ - --arg commit "${GITHUB_SHA}" \ - '$config + { - s3FilePath: $source_path, - s3ObjectVersion: $source_version, - gitHubRepo: $repository, - gitHubCommit: $commit, - extractArchive: false - }' > /tmp/replication-request.json - - function_error="$(aws lambda invoke \ - --function-name "${REPLICATION_FUNCTION_ARN}" \ - --cli-binary-format raw-in-base64-out \ - --payload file:///tmp/replication-request.json \ - --query FunctionError \ - --output text \ - /tmp/replication-response.json)" - - if [[ "${function_error}" != "None" ]]; then - echo "Replication failed; inspect the function logs for details." >&2 - exit 1 - fi - - printf 'Replicated %s successfully.\n' "${artifact_key}" - done < /tmp/replication-manifest.tsv