Skip to content

feat: opt-in EBS encryption for runner root volume (Phase 6.b)#27

Merged
kurok merged 1 commit into
feat/al2023-supportfrom
feat/12b-ebs-encryption
Apr 21, 2026
Merged

feat: opt-in EBS encryption for runner root volume (Phase 6.b)#27
kurok merged 1 commit into
feat/al2023-supportfrom
feat/12b-ebs-encryption

Conversation

@kurok
Copy link
Copy Markdown

@kurok kurok commented Apr 21, 2026

Completes Phase 6 (IMDSv2 landed in #24; EBS encryption was deferred until we could do per-AMI root-device lookup safely).

Change

New encrypt-ebs input, default 'false'. When set to 'true':

  1. DescribeImages returns the AMI metadata (same call already used to resolve AMI IDs when ec2-image-filters is used; widened to include BlockDeviceMappings).
  2. The action finds the mapping matching RootDeviceName, clones its EBS sub-object, drops SnapshotId, and sets Encrypted: true.
  3. Passes the cloned mapping to RunInstances.BlockDeviceMappings.

Volume size, type, IOPS, DeleteOnTermination — all preserved from the AMI. The only new bit is encryption.

Why opt-in default

Enabling EBS encryption requires the launch account to have either default EBS encryption on, or permission to use the alias/aws/ebs KMS key. If the AMI is shared from another account and encrypted with a customer-managed key, the launch account needs a cross-account grant. Defaulting to 'true' would regress every consumer whose policy isn't set up for that. Defaulting to 'false' makes the switch visible — maintainers see a new input + opt in after verifying.

Refactor

resolveImageIdresolveImage. Returns { id, image } instead of just the ID. The EBS-encryption path uses .image.BlockDeviceMappings; existing callers just use .id.

Tests

tests/ebs.test.js — 6 new cases for the pure buildEncryptedRootMapping function:

  • Happy path: clones full EBS config (size / type / IOPS / DeleteOnTermination), drops SnapshotId, sets Encrypted: true.
  • Non-EBS sibling mapping (e.g., ephemeral0) is ignored correctly.
  • Returns null for: no RootDeviceName, no BlockDeviceMappings, non-EBS root mapping, orphan RootDeviceName.

tests/config.test.js — 2 new cases for the encrypt-ebs input (default + override).

Total: 44 → 52 tests.

Dogfood plan

Separate PR on terraform-provider-namecheap rotates the pin AND adds encrypt-ebs: 'true' to the Start EC2 runner step. If the dogfood launch fails with a KMS / IAM error, the action-side code is still fine — just means the launch account needs policy work before opting in. I'll diagnose via the aws ec2 get-console-output recipe (from the Phase 4.b work in my local notes).

Out of scope for this PR

  • Custom KMS key support (kms-key-id input). AWS's default AWS-managed key is almost always the right choice; supporting customer-managed keys is a future feature if anyone asks.
  • Non-root volume encryption. Consumers who need it can pass an explicit BlockDeviceMappings via... nowhere right now. Today the action only lets you encrypt the root via this toggle; everything else falls back to AMI defaults.

Rounds out Phase 6 (IMDSv2 landed in #24, EBS encryption deferred
until a per-AMI root-device lookup could be done safely).

## Change

New 'encrypt-ebs' input on action.yml, default 'false' (opt-in).
When 'true', the action:

1. Fetches the AMI's DescribeImages result (already needed to
   resolve image IDs when 'ec2-image-filters' is set).
2. Finds the BlockDeviceMapping matching the AMI's RootDeviceName.
3. Clones that mapping, drops SnapshotId (AWS uses the AMI's
   snapshot automatically), sets 'Encrypted: true'.
4. Passes the cloned mapping as RunInstances.BlockDeviceMappings.

Result: root volume launches with SSE-EBS, key 'alias/aws/ebs' in
the launch account. VolumeSize / VolumeType / IOPS / DeleteOnTermination
preserved from the AMI — only the encryption bit is new.

## Why opt-in

The launch account (not necessarily the AMI owner account) must
have either default EBS encryption enabled, or at minimum permission
to use the default AWS-managed KMS key. If the AMI's snapshot is
encrypted with a customer-managed key that doesn't have a cross-
account grant, RunInstances fails. Defaulting to 'true' would
regress every consumer whose IAM / KMS policy isn't set up for
this. Default 'false' lets each consumer opt in after verifying
their account can handle it.

## Why not account-level default encryption

AWS supports 'aws ec2 enable-ebs-encryption-by-default' at the
account level — and that's the preferred belt-and-suspenders. But
not every consumer runs in an AWS account they control (e.g.,
Namecheap's CI runs in a shared org account). Action-side opt-in
is the only portable control.

## Refactor alongside

resolveImageId -> resolveImage: now returns both the ID and the
full Image metadata. Callers that only need the ID use .id; the
EBS-encryption code path uses .image.BlockDeviceMappings to build
the encrypted clone.

## Tests

tests/ebs.test.js — 6 new cases for buildEncryptedRootMapping:
happy path with full EBS config + non-EBS sibling mapping, volume
type / size / iops preservation, and five null-return paths for
exotic AMI shapes (no RootDeviceName, no mappings, non-EBS root,
orphan RootDeviceName).

tests/config.test.js — 2 new cases for the encrypt-ebs input
(default fallback + override).

Total: 44 -> 52 tests.

## Consumer dogfood

Separate PR on terraform-provider-namecheap rotates the pin and
enables 'encrypt-ebs: true' on the CI job. If the dogfood fails
with a KMS / IAM permissions error, we know the account needs
policy work before enabling; action-side code is fine either way.

Signed-off-by: yuriyryabikov <22548029+kurok@users.noreply.github.com>
@kurok kurok merged commit 7c6a9a7 into feat/al2023-support Apr 21, 2026
4 checks passed
@kurok kurok deleted the feat/12b-ebs-encryption branch April 21, 2026 14:46
kurok added a commit to namecheap/terraform-provider-namecheap that referenced this pull request Apr 21, 2026
#192)

namecheap/ec2-github-runner#27 merged. Rotates both pins from
0fdd401 (Phase 4 retry) to 7c6a9a7 (Phase 6.b) and flips the new
encrypt-ebs input to 'true' so the acceptance-test runner's root
volume launches with SSE-EBS.

Risk: if the CI AWS account can't use the default aws/ebs KMS key
or the shared AMI's snapshot is encrypted with a customer-managed
key lacking a cross-account grant, start-runner will fail with a
KMS / IAM error. The action-side code handles that gracefully
(throws early rather than timing out on registration), so
diagnostics are easy via aws ec2 get-console-output.

If dogfood fails, revert just the encrypt-ebs line; SHA rotation
is orthogonal and can stay green.

Signed-off-by: yuriyryabikov <22548029+kurok@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant