Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
754e093
feat(scale-set): add service controller
edersonbrilhante Aug 26, 2026
e5f9768
refactor(scale-set): split EC2 provider contexts
edersonbrilhante Aug 26, 2026
b0f1d41
fix(scale-set): avoid unsafe URL regular expressions
edersonbrilhante Aug 26, 2026
924ab11
test(scale-set): split provider coverage by context
edersonbrilhante Aug 26, 2026
b8b5101
test(scale-set): clarify invalid region fixture
edersonbrilhante Aug 26, 2026
fb95b1a
refactor(scale-set): remove control-plane retry policy
edersonbrilhante Aug 27, 2026
3089992
refactor(scale-set): remove orchestration tag plumbing
edersonbrilhante Aug 28, 2026
de69e1d
fix(scale-set): expose configuration error details
edersonbrilhante Aug 28, 2026
9f8a74e
feat(scale-set): resolve GitHub IDs from names
edersonbrilhante Aug 28, 2026
2627ce0
feat(scale-set): discover GitHub app installation
edersonbrilhante Aug 28, 2026
7a8edb4
feat(scale-set): cache resolved IDs in SSM
edersonbrilhante Aug 28, 2026
7220eb2
fix(scale-set): parse GitHub installation list
edersonbrilhante Aug 28, 2026
b13b746
feat(scale-set): register missing GitHub scale sets
edersonbrilhante Aug 28, 2026
fdb5b76
feat(scale-set): log compute provider lifecycle
edersonbrilhante Aug 28, 2026
d684e5e
fix(scale-set): include endpoint in HTTP failure logs
edersonbrilhante Aug 28, 2026
7f05bf3
fix(scale-set): recover from inventory lookup failures
edersonbrilhante Aug 28, 2026
fae929e
feat(scale-set): add independent recovery janitor
edersonbrilhante Aug 28, 2026
91f880e
fix(scale-set): add runner inventory diagnostics
edersonbrilhante Aug 28, 2026
fc51166
fix(scale-set): align provider reconciliation with Actions service
edersonbrilhante Aug 28, 2026
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
16 changes: 16 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,22 @@ updates:
- dependency-name: "mcr.microsoft.com/vscode/devcontainers/typescript-node"
update-types: ["version-update:semver-major"]

- package-ecosystem: "docker"
directory: "/lambdas/services/scale-set"
schedule:
interval: "weekly"
cooldown:
default-days: 7
labels:
- "dependencies"
- "docker"
commit-message:
prefix: "chore(scale-set)"
# Keep the service runtime aligned with the supported Node.js major.
ignore:
- dependency-name: "node"
update-types: ["version-update:semver-major"]

- package-ecosystem: "pip"
directory: "/.github/workflows/mkdocs"
schedule:
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/lambda.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,29 @@ jobs:
name: coverage-reports
path: ./**/coverage
retention-days: 5

scale-set-container:
name: Build scale-set service container
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit

- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up QEMU
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
- name: Build scale-set service image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
with:
context: .
file: ./lambdas/services/scale-set/Dockerfile
platforms: linux/amd64,linux/arm64
push: false
cache-from: type=gha,scope=scale-set-service
cache-to: type=gha,mode=max,scope=scale-set-service
60 changes: 56 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
name: Release build
env:
SCALE_SET_IMAGE: ghcr.io/${{ github.repository_owner }}/terraform-aws-github-runner-scale-set-service

on:
push:
branches:
Expand All @@ -21,6 +24,8 @@ jobs:
actions: write # for release-please-action to trigger other workflows
id-token: write # for actions/attest-build-provenance to generate attestations
attestations: write # for actions/attest-build-provenance to write attestations
artifact-metadata: write # for publishing linked container attestations
packages: write # for publishing the scale-set service image to GHCR
environment: release
steps:
- name: Harden the runner (Audit all outbound calls)
Expand Down Expand Up @@ -55,6 +60,47 @@ jobs:
target-branch: ${{ steps.branch.outputs.name }}
release-type: terraform-module
token: ${{ steps.token.outputs.token }}
- name: Set up QEMU
if: ${{ steps.release.outputs.releases_created == 'true' }}
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4
- name: Set up Docker Buildx
if: ${{ steps.release.outputs.releases_created == 'true' }}
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
- name: Log in to the GitHub Container Registry
if: ${{ steps.release.outputs.releases_created == 'true' }}
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and publish scale-set service image
if: ${{ steps.release.outputs.releases_created == 'true' }}
id: scale-set-image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
with:
context: .
file: ./lambdas/services/scale-set/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ env.SCALE_SET_IMAGE }}:${{ steps.release.outputs.tag_name }}
${{ env.SCALE_SET_IMAGE }}:latest
labels: |
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.version=${{ steps.release.outputs.tag_name }}
sbom: true
provenance: mode=max
cache-from: type=gha,scope=scale-set-service
cache-to: type=gha,mode=max,scope=scale-set-service
- name: Attest scale-set service image
if: ${{ steps.release.outputs.releases_created == 'true' }}
id: scale-set-image-attest
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4
with:
subject-name: ${{ env.SCALE_SET_IMAGE }}
subject-digest: ${{ steps.scale-set-image.outputs.digest }}
push-to-registry: true
- name: Attest
if: ${{ steps.release.outputs.releases_created == 'true' }}
id: attest
Expand All @@ -65,20 +111,26 @@ jobs:
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 }}
CONTAINER_ATTESTATION_URL: ${{ steps.scale-set-image-attest.outputs.attestation-url }}
CONTAINER_IMAGE: ${{ env.SCALE_SET_IMAGE }}
CONTAINER_DIGEST: ${{ steps.scale-set-image.outputs.digest }}
REPOSITORY: ${{ github.repository }}
run: |
version="${VERSION}"
tag_name="${TAG_NAME}"
attestation_url="${ATTESTATION_URL}"
container_attestation_url="${CONTAINER_ATTESTATION_URL}"
container_image="${CONTAINER_IMAGE}"
container_digest="${CONTAINER_DIGEST}"
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 "Verify the artifacts by running \`gh attestation verify <name_of_artifact> --repo ${repository}\`" >> new-release-notes.md
gh release edit $tag_name -F new-release-notes.md -t $tag_name
echo "Scale-set service image: \`${container_image}@${container_digest}\`" >> new-release-notes.md
echo "Container attestation url: $container_attestation_url" >> new-release-notes.md
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:
Expand Down
10 changes: 9 additions & 1 deletion docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ The examples are using standard AMI's for different operating systems. Instances

## Attestation

The module is released using GitHub actions and the lambda artifacts are attached to the release as attachment. During the release attestations are created. The attestations are created by the release pipeline. You find a link to the attestation in the GitHub release. The attestation only provides provenance information about the release. The attestations are not a security guarantee. We recommend you to verify the attestation after downloading the lambda artifacts.
The module is released using GitHub Actions and the Lambda artifacts are attached to the release. The release pipeline creates provenance attestations for those artifacts. You can find a link to the attestation in the GitHub release. The attestation only provides provenance information about the release; it is not a security guarantee. We recommend verifying the attestation after downloading the Lambda artifacts.

Releases also publish the multi-architecture scale-set service image to the GitHub Container Registry with an SBOM, build provenance, and a registry attestation. The convenience image default follows the latest module release. Production deployments should override it with the immutable image digest printed in the release notes, then verify that image with:

```bash
gh attestation verify \
oci://ghcr.io/github-aws-runners/terraform-aws-github-runner-scale-set-service@sha256:<digest> \
--repo github-aws-runners/terraform-aws-github-runner
```

--8<-- "SECURITY.md:mkdocsrunners"
32 changes: 32 additions & 0 deletions lambdas/libs/compute-providers/aws/ec2/scale-set.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { ScaleSetComputeProviderModule, ScaleSetComputeProviderPlugin } from '../../scale-set';

import { createEc2ScaleSetProvider, type Ec2ScaleSetProviderDependencies } from './src/scale-set/provider';

export type { Ec2ScaleSetProviderConfig, Ec2ScaleSetProviderDependencies } from './src/scale-set/provider';
export { createEc2ScaleSetProvider, parseEc2ScaleSetProviderConfig } from './src/scale-set/provider';

export function createEc2ScaleSetPlugin(
dependencies: Ec2ScaleSetProviderDependencies = {},
): ScaleSetComputeProviderPlugin<'ec2'> {
return {
type: 'ec2',
capabilities: {
environmentVariables: {},
create: ({ runnerConfigName, scaleSetId, githubScope, configuration }) =>
createEc2ScaleSetProvider(
{
runnerConfigName,
scaleSetId,
githubScope,
configuration: configuration as Parameters<typeof createEc2ScaleSetProvider>[0]['configuration'],
},
dependencies,
),
},
};
}

export const provider = {
type: 'ec2',
createPlugin: createEc2ScaleSetPlugin,
} satisfies ScaleSetComputeProviderModule<'ec2'>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { describe, expect, it } from 'vitest';

import { parseEc2ScaleSetProviderConfig } from './configuration';
import { config } from './test/fixtures';

describe('EC2 scale-set provider configuration', () => {
it('strictly parses the supported provider-owned configuration', () => {
expect(parseEc2ScaleSetProviderConfig(config)).toMatchObject(config);
expect(parseEc2ScaleSetProviderConfig({ ...config, runnerNamePrefix: '' })).toMatchObject({
runnerNamePrefix: '',
});
expect(parseEc2ScaleSetProviderConfig({ ...config, runnerNamePrefix: 'r'.repeat(45) })).toMatchObject({
runnerNamePrefix: 'r'.repeat(45),
});
});

it.each([
[{ ...config, region: 'eu-west-one' }],
[{ ...config, subnets: ['subnet-12345678', 'subnet-12345678'] }],
[{ ...config, ec2instanceCriteria: { ...config.ec2instanceCriteria, instanceAllocationStrategy: 'diversified' } }],
[{ ...config, ec2OverrideConfig: { UserData: 'untrusted' } }],
[{ ...config, scaleErrors: ['ThrottlingException'] }],
[{ ...config, ssmParameterTags: [{ Key: 'aws:owner', Value: 'untrusted' }] }],
[{ ...config, runnerNamePrefix: 'r'.repeat(46) }],
[{ ...config, bootTimeoutMinutes: 10 }],
])('rejects invalid or unsupported values instead of forwarding them to AWS', (invalid) => {
expect(() => parseEc2ScaleSetProviderConfig(invalid)).toThrow();
});
});
Loading