feat: enforce IMDSv2 by default (Phase 6.a)#24
Merged
Conversation
Part of #12. Phase 6 split: this PR ships IMDSv2 enforcement only. EBS encryption (also scoped in #12) becomes a Phase 6.b follow-up that needs per-AMI root-device lookup to set BlockDeviceMappings safely (wrong DeviceName creates an unused extra volume or clobbers the root). ## Change RunInstances params now include: MetadataOptions: { HttpTokens: config.input.httpTokens, HttpPutResponseHopLimit: 1, HttpEndpoint: 'enabled', } - HttpTokens default 'required' — IMDSv2 session token mandatory on every metadata request. Mitigates SSRF to IAM-credential theft. - HttpPutResponseHopLimit: 1 — token can't cross into a nested container, cap the blast radius of a containerized workload that tries to walk metadata. - HttpEndpoint: 'enabled' — explicit; default but good to pin. ## New input 'http-tokens' in action.yml, default 'required', accepted values 'required' and 'optional'. Consumers who must keep IMDSv1 compatibility set 'optional' explicitly. ## Tests tests/config.test.js — 2 new cases: default fallback + override. Total 34 -> 36. ## Consumer impact Transparent for code running on the runner that uses IMDS via a modern SDK (aws-sdk v2/v3, SSM agent, cloud-init) — all support IMDSv2 without config. Risk: an old tool or hand-rolled script that hits 169.254.169.254/latest/meta-data without first PUTting for a token. None expected on the hardened AL2023 AMI. ## Dogfood Small single-knob change — should pass the provider acctest cleanly once rotated. Phase 6.b (EBS encryption) will be scoped separately. Signed-off-by: yuriyryabikov <22548029+kurok@users.noreply.github.com>
kurok
added a commit
to namecheap/terraform-provider-namecheap
that referenced
this pull request
Apr 21, 2026
…187) namecheap/ec2-github-runner#24 merged. RunInstances now sets MetadataOptions.HttpTokens to 'required' by default (opt-out via new http-tokens input). Mitigates SSRF-to-IAM-credential theft from workloads running on the runner. Transparent for aws-sdk, SSM agent, cloud-init, and any go/python workflow — all use IMDSv2 natively. Rotation: 46cf1d0 (Phase 5) -> 6bb148b (Phase 6.a). Signed-off-by: yuriyryabikov <22548029+kurok@users.noreply.github.com>
This was referenced Apr 21, 2026
kurok
added a commit
that referenced
this pull request
Apr 21, 2026
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #12. Phase 6 split into two — this PR lands IMDSv2 enforcement only.
EBS encryption (also scoped in #12) is pushed to a Phase 6.b follow-up: setting
BlockDeviceMappings[*].Ebs.Encrypted: truerequires knowing the AMI's root device name (/dev/xvdavs/dev/sda1etc.) to avoid creating an unused extra volume or clobbering the root. Safer to land the one-line-per-API-call IMDSv2 change independently.Change
RunInstancesparams now include:Rationale per line:
HttpTokens: 'required'(default via new input) — IMDSv2 session token mandatory. Mitigates the SSRF-to-IAM-credential-theft class of bug.HttpPutResponseHopLimit: 1— the token cannot cross into a nested container hop. Bounds the blast radius if a containerized workload on the runner tries to walk metadata.HttpEndpoint: 'enabled'— explicit; matches default but pins the value so a future AWS default-flip doesn't silently disable metadata.New input
http-tokensinaction.yml, default'required', accepted values'required'/'optional'. Consumers who must keep IMDSv1 compatibility for a specific workload set'optional'explicitly.Tests
tests/config.test.js— 2 new cases: default fallback + explicit override. Total 34 → 36.Compatibility
Transparent for code using IMDS via any modern SDK (aws-sdk v2/v3, SSM agent, cloud-init) — all support IMDSv2 natively. Risk: a hand-rolled script or an old tool that hits
169.254.169.254/latest/meta-datawithout first PUTting for a token.None of that exists in the provider's acctest path —
make testaccis plaingo testand doesn't touch IMDS.Follow-up