diff --git a/.github/workflows/lambda.yml b/.github/workflows/lambda.yml index 41d509116a..e8a4a661c2 100644 --- a/.github/workflows/lambda.yml +++ b/.github/workflows/lambda.yml @@ -50,3 +50,37 @@ jobs: name: coverage-reports path: ./**/coverage retention-days: 5 + + microvm-lifecycle-hooks: + name: Build MicroVM lifecycle hook + runs-on: ubuntu-latest + container: + image: node:24@sha256:aa648b387728c25f81ff811799bbf8de39df66d7e2d9b3ab55cc6300cb9175d9 + defaults: + run: + working-directory: ./lambdas + + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Run prettier + run: yarn prettier --check "services/microvm-lifecycle-hooks/**/*.{ts,json,md}" + + - name: Run linter + run: yarn eslint services/microvm-lifecycle-hooks/src + + - name: Run tests + run: yarn nx test @aws-github-runner/microvm-lifecycle-hooks + + - name: Build distribution + run: yarn workspace @aws-github-runner/microvm-lifecycle-hooks build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index db012803b3..1aed0aac27 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,6 +38,14 @@ jobs: - name: Build dist working-directory: lambdas run: yarn install --frozen-lockfile && yarn run test && yarn dist + + - name: Build MicroVM lifecycle hook + working-directory: lambdas + run: yarn workspace @aws-github-runner/microvm-lifecycle-hooks build + + - name: Package MicroVM lifecycle hook + working-directory: lambdas/services/microvm-lifecycle-hooks + run: (cd dist && zip -r ../microvm-lifecycle-hooks.zip .) - name: Get installation token uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 id: token @@ -61,24 +69,34 @@ jobs: uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 with: subject-path: '${{ github.workspace }}/lambdas/functions/**/*.zip' + + - name: Attest MicroVM lifecycle hook + if: ${{ steps.release.outputs.releases_created == 'true' }} + id: lifecycle-hook-attest + uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 + with: + subject-path: '${{ github.workspace }}/lambdas/services/microvm-lifecycle-hooks/microvm-lifecycle-hooks.zip' + - name: Update release notes with attestation if: ${{ steps.release.outputs.releases_created == 'true' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VERSION: ${{ github.event.inputs.version }} TAG_NAME: ${{ steps.release.outputs.tag_name }} ATTESTATION_URL: ${{ steps.attest.outputs.attestation-url }} + LIFECYCLE_HOOK_ATTESTATION_URL: ${{ steps.lifecycle-hook-attest.outputs.attestation-url }} REPOSITORY: ${{ github.repository }} run: | - version="${VERSION}" tag_name="${TAG_NAME}" attestation_url="${ATTESTATION_URL}" + lifecycle_hook_attestation_url="${LIFECYCLE_HOOK_ATTESTATION_URL}" repository="${REPOSITORY}" - gh release view $version --json body -q '.body' > new-release-notes.md + gh release view "$tag_name" --json body -q '.body' > new-release-notes.md echo "## Attestation" >> new-release-notes.md - echo "Attestation url: $attestation_url" >> new-release-notes.md + echo "Lambda attestation url: $attestation_url" >> new-release-notes.md + echo "MicroVM lifecycle hook attestation url: $lifecycle_hook_attestation_url" >> new-release-notes.md echo "Verify the artifacts by running \`gh attestation verify --repo ${repository}\`" >> new-release-notes.md - gh release edit $tag_name -F new-release-notes.md -t $tag_name + gh release edit "$tag_name" -F new-release-notes.md -t "$tag_name" + - name: Upload release assets if: ${{ steps.release.outputs.releases_created == 'true' }} env: @@ -86,10 +104,13 @@ jobs: TAG_NAME: ${{ steps.release.outputs.tag_name }} run: | tag_name="${TAG_NAME}" - for f in $(find . -name '*.zip'); do - gh release upload $tag_name $f - done - - name: Attach attestation + while IFS= read -r -d '' f; do + gh release upload "$tag_name" "$f" + done < <(find lambdas/functions -name '*.zip' -print0) + gh release upload "$tag_name" \ + "lambdas/services/microvm-lifecycle-hooks/microvm-lifecycle-hooks.zip" + + - name: Attach Lambda attestation if: ${{ steps.release.outputs.releases_created == 'true' }} env: ATTESTATION_BUNDLE: ${{ steps.attest.outputs.bundle-path }} @@ -99,13 +120,26 @@ jobs: run: | # rename attest bundle to github-aws-runners-terraform-aws-github-runner-attestation-$attestation-id.sigstore # OpenSSF expects the attestation bundle to be named in this format (*.sigstore) - SIGSTORE_BUNDLE=$RUNNER_TEMP/github-aws-runners-terraform-aws-github-runner-attestation-${ATTESTATION_ID}.sigstore - INTOTO_BUNDLE=$RUNNER_TEMP/github-aws-runners-terraform-aws-github-runner-attestation-${ATTESTATION_ID}.intoto.jsonl - mv ${ATTESTATION_BUNDLE} $SIGSTORE_BUNDLE - if [ -z "$SIGSTORE_BUNDLE" ]; then - echo "No attestation bundle found, skipping attachment." - exit 0 - fi - gh release upload $TAG_NAME "$SIGSTORE_BUNDLE" - cat ${SIGSTORE_BUNDLE} | jq -r '.dsseEnvelope | select(.payloadType == "application/vnd.in-toto+json").payload' | base64 -d | jq .> ${INTOTO_BUNDLE} - gh release upload $TAG_NAME "${INTOTO_BUNDLE}" + sigstore_bundle="$RUNNER_TEMP/github-aws-runners-terraform-aws-github-runner-attestation-${ATTESTATION_ID}.sigstore" + intoto_bundle="$RUNNER_TEMP/github-aws-runners-terraform-aws-github-runner-attestation-${ATTESTATION_ID}.intoto.jsonl" + cp "$ATTESTATION_BUNDLE" "$sigstore_bundle" + gh release upload "$TAG_NAME" "$sigstore_bundle" + jq -r '.dsseEnvelope | select(.payloadType == "application/vnd.in-toto+json").payload' "$sigstore_bundle" \ + | base64 --decode > "$intoto_bundle" + gh release upload "$TAG_NAME" "$intoto_bundle" + + - name: Attach MicroVM lifecycle hook attestation + if: ${{ steps.release.outputs.releases_created == 'true' }} + env: + ATTESTATION_BUNDLE: ${{ steps.lifecycle-hook-attest.outputs.bundle-path }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG_NAME: ${{ steps.release.outputs.tag_name }} + ATTESTATION_ID: ${{ steps.lifecycle-hook-attest.outputs.attestation-id }} + run: | + sigstore_bundle="$RUNNER_TEMP/github-aws-runners-microvm-lifecycle-hooks-attestation-${ATTESTATION_ID}.sigstore" + intoto_bundle="$RUNNER_TEMP/github-aws-runners-microvm-lifecycle-hooks-attestation-${ATTESTATION_ID}.intoto.jsonl" + cp "$ATTESTATION_BUNDLE" "$sigstore_bundle" + gh release upload "$TAG_NAME" "$sigstore_bundle" + jq -r '.dsseEnvelope | select(.payloadType == "application/vnd.in-toto+json").payload' "$sigstore_bundle" \ + | base64 --decode > "$intoto_bundle" + gh release upload "$TAG_NAME" "$intoto_bundle" diff --git a/lambdas/package.json b/lambdas/package.json index c6fa5d72c3..0a0b0b088a 100644 --- a/lambdas/package.json +++ b/lambdas/package.json @@ -3,7 +3,8 @@ "private": true, "workspaces": [ "functions/*", - "libs/*" + "libs/*", + "services/*" ], "scripts": { "build": "nx run-many --target=build --all", diff --git a/lambdas/services/microvm-lifecycle-hooks/src/storage.test.ts b/lambdas/services/microvm-lifecycle-hooks/src/storage.test.ts index b174be6e50..bbbda87015 100644 --- a/lambdas/services/microvm-lifecycle-hooks/src/storage.test.ts +++ b/lambdas/services/microvm-lifecycle-hooks/src/storage.test.ts @@ -20,9 +20,9 @@ describe('StorageJitConfigSource', () => { return 'encoded-jit'; }), }; - const exportEnvironment = vi.fn((context: RunnerConfigStorageContext, target: NodeJS.ProcessEnv) => { + const exportEnvironment = vi.fn((context: RunnerConfigStorageContext) => { events.push('export'); - Object.assign(target, context); + return context; }); const createConsumer = vi.fn((target: NodeJS.ProcessEnv) => { events.push('create'); @@ -61,9 +61,7 @@ describe('StorageJitConfigSource', () => { it('rejects storage context changes after the one-time environment export', async () => { const environment: NodeJS.ProcessEnv = {}; const consumer: RunnerConfigConsumer = { consume: vi.fn().mockResolvedValue('encoded-jit') }; - const exportEnvironment = vi.fn((context: RunnerConfigStorageContext, target: NodeJS.ProcessEnv) => { - Object.assign(target, context); - }); + const exportEnvironment = vi.fn((context: RunnerConfigStorageContext) => context); const createConsumer = vi.fn().mockReturnValue(consumer); const source = new StorageJitConfigSource({ createConsumer, environment, exportEnvironment }); const options = { deadlineMs: 123_456, signal: new AbortController().signal }; diff --git a/lambdas/services/microvm-lifecycle-hooks/src/storage.ts b/lambdas/services/microvm-lifecycle-hooks/src/storage.ts index 4e50a4c9b0..528683f59e 100644 --- a/lambdas/services/microvm-lifecycle-hooks/src/storage.ts +++ b/lambdas/services/microvm-lifecycle-hooks/src/storage.ts @@ -36,7 +36,7 @@ export class StorageJitConfigSource implements JitConfigSource { public async consume(context: RunContext, options: ConsumeOptions): Promise { const fingerprint = storageContextFingerprint(context.storage); if (this.exportedStorageFingerprint === undefined) { - this.exportEnvironment(context.storage, this.environment); + Object.assign(this.environment, this.exportEnvironment(context.storage)); this.exportedStorageFingerprint = fingerprint; } else if (this.exportedStorageFingerprint !== fingerprint) { throw new Error('runner configuration storage context cannot change after initialization'); diff --git a/lambdas/yarn.lock b/lambdas/yarn.lock index 4f313f5413..73daf5509e 100644 --- a/lambdas/yarn.lock +++ b/lambdas/yarn.lock @@ -202,6 +202,16 @@ __metadata: languageName: unknown linkType: soft +"@aws-github-runner/microvm-lifecycle-hooks@workspace:services/microvm-lifecycle-hooks": + version: 0.0.0-use.local + resolution: "@aws-github-runner/microvm-lifecycle-hooks@workspace:services/microvm-lifecycle-hooks" + dependencies: + "@aws-github-runner/storage-providers": "npm:*" + "@types/node": "npm:^22.19.3" + "@vercel/ncc": "npm:^0.38.4" + languageName: unknown + linkType: soft + "@aws-github-runner/storage-providers@npm:*, @aws-github-runner/storage-providers@workspace:libs/storage-providers": version: 0.0.0-use.local resolution: "@aws-github-runner/storage-providers@workspace:libs/storage-providers"