Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ inputs:
IAM Role Name to attach to the created EC2 instance.
This requires additional permissions on the AWS role used to launch instances.
required: false
http-tokens:
description: >-
Instance Metadata Service (IMDS) token mode. Accepted values:
- 'required' (default): IMDSv2-only. Any request to the IMDS
endpoint (169.254.169.254) must present a session token.
Mitigates SSRF-style credential theft.
- 'optional': IMDSv1 and IMDSv2 both work. Only set this if
a consumer workflow explicitly needs IMDSv1 compatibility.
Passed through to RunInstances MetadataOptions.HttpTokens.
required: false
default: 'required'
debug:
description: >-
When 'true', the action emits extra diagnostic output to the
Expand Down
10 changes: 10 additions & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -87942,6 +87942,15 @@ async function startEc2Instance(label, githubRegistrationToken) {
SecurityGroupIds: [config.input.securityGroupId],
IamInstanceProfile: { Name: config.input.iamRoleName },
TagSpecifications: config.tagSpecifications,
// IMDSv2 required by default. Mitigates SSRF-style IAM credential
// theft from the runner — any metadata request must present a
// session token. HttpPutResponseHopLimit: 1 prevents the token
// from reaching containerized workloads one hop deeper.
MetadataOptions: {
HttpTokens: config.input.httpTokens,
HttpPutResponseHopLimit: 1,
HttpEndpoint: 'enabled',
},
};

let ec2InstanceId;
Expand Down Expand Up @@ -88033,6 +88042,7 @@ class Config {
label: core.getInput('label'),
ec2InstanceId: core.getInput('ec2-instance-id'),
iamRoleName: core.getInput('iam-role-name'),
httpTokens: core.getInput('http-tokens') || 'required',
debug: core.getInput('debug') || 'false',
};

Expand Down
9 changes: 9 additions & 0 deletions src/aws.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ async function startEc2Instance(label, githubRegistrationToken) {
SecurityGroupIds: [config.input.securityGroupId],
IamInstanceProfile: { Name: config.input.iamRoleName },
TagSpecifications: config.tagSpecifications,
// IMDSv2 required by default. Mitigates SSRF-style IAM credential
// theft from the runner — any metadata request must present a
// session token. HttpPutResponseHopLimit: 1 prevents the token
// from reaching containerized workloads one hop deeper.
MetadataOptions: {
HttpTokens: config.input.httpTokens,
HttpPutResponseHopLimit: 1,
HttpEndpoint: 'enabled',
},
};

let ec2InstanceId;
Expand Down
1 change: 1 addition & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class Config {
label: core.getInput('label'),
ec2InstanceId: core.getInput('ec2-instance-id'),
iamRoleName: core.getInput('iam-role-name'),
httpTokens: core.getInput('http-tokens') || 'required',
debug: core.getInput('debug') || 'false',
};

Expand Down
12 changes: 12 additions & 0 deletions tests/config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,18 @@ describe('Config — mode validation', () => {
});
});

describe('Config — http-tokens input', () => {
test('defaults to "required" when unset', () => {
const config = loadConfig(startModeInputs);
expect(config.input.httpTokens).toBe('required');
});

test('honors an "optional" override', () => {
const config = loadConfig({ ...startModeInputs, 'http-tokens': 'optional' });
expect(config.input.httpTokens).toBe('optional');
});
});

describe('Config — debug input', () => {
test('defaults to "false" when unset', () => {
const config = loadConfig(startModeInputs);
Expand Down
Loading