Compare commits
1 commit
main
...
kellertk/3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ca54121eb |
6 changed files with 8773 additions and 13670 deletions
33
README.md
33
README.md
|
|
@ -350,6 +350,9 @@ definitions][gh-env-vars])
|
||||||
[gh-env-vars]:
|
[gh-env-vars]:
|
||||||
https://docs.github.com/en/actions/reference/workflows-and-actions/variables#default-environment-variables
|
https://docs.github.com/en/actions/reference/workflows-and-actions/variables#default-environment-variables
|
||||||
|
|
||||||
|
**Protected tags** are always emitted when session tags are used, and cannot be
|
||||||
|
overridden via `custom-tags`:
|
||||||
|
|
||||||
| Key | Value |
|
| Key | Value |
|
||||||
| ---------- | ----------------- |
|
| ---------- | ----------------- |
|
||||||
| GitHub | "Actions" |
|
| GitHub | "Actions" |
|
||||||
|
|
@ -357,14 +360,32 @@ definitions][gh-env-vars])
|
||||||
| Workflow | GITHUB_WORKFLOW |
|
| Workflow | GITHUB_WORKFLOW |
|
||||||
| Action | GITHUB_ACTION |
|
| Action | GITHUB_ACTION |
|
||||||
| Actor | GITHUB_ACTOR |
|
| Actor | GITHUB_ACTOR |
|
||||||
| Branch | GITHUB_REF |
|
|
||||||
| Commit | GITHUB_SHA |
|
| Commit | GITHUB_SHA |
|
||||||
|
| Branch | GITHUB_REF |
|
||||||
|
|
||||||
|
**Overrideable tags** are automatically added to the set of default session
|
||||||
|
tags but may be overridden via `custom-tags`. AWS has a maximum limit of 50
|
||||||
|
session tags; tags from this list are dropped in reverse priority order if
|
||||||
|
your `custom-tags` set plus the protected set exceeds this limit.
|
||||||
|
|
||||||
|
| Key | Value | Priority |
|
||||||
|
| --------------- | ----------------------- | -------- |
|
||||||
|
| EventName | GITHUB_EVENT_NAME | 1 |
|
||||||
|
| BaseRef | GITHUB_BASE_REF | 2 |
|
||||||
|
| HeadRef | GITHUB_HEAD_REF | 3 |
|
||||||
|
| RefName | GITHUB_REF_NAME | 4 |
|
||||||
|
| RunId | GITHUB_RUN_ID | 5 |
|
||||||
|
| RefType | GITHUB_REF_TYPE | 6 |
|
||||||
|
| Job | GITHUB_JOB | 7 |
|
||||||
|
| TriggeringActor | GITHUB_TRIGGERING_ACTOR | 8 |
|
||||||
|
|
||||||
|
Tags whose source environment variable is unset are omitted (e.g., `BaseRef`
|
||||||
|
and `HeadRef` are only set on `pull_request` events).
|
||||||
|
|
||||||
_Note: all tag values must conform to
|
_Note: all tag values must conform to
|
||||||
[the tag requirements](https://docs.aws.amazon.com/STS/latest/APIReference/API_Tag.html).
|
[the tag requirements](https://docs.aws.amazon.com/STS/latest/APIReference/API_Tag.html).
|
||||||
Particularly, `GITHUB_WORKFLOW` will be truncated if it's too long. If
|
Values longer than 256 characters will be truncated, and characters outside the
|
||||||
`GITHUB_ACTOR` or `GITHUB_WORKFLOW` contain invalid characters, the characters
|
allowed set will be replaced with an underscore (`_`)._
|
||||||
will be replaced with an '\*'._
|
|
||||||
|
|
||||||
The action will use session tagging by default unless you are using OIDC.
|
The action will use session tagging by default unless you are using OIDC.
|
||||||
|
|
||||||
|
|
@ -391,7 +412,9 @@ with:
|
||||||
### Custom session tags
|
### Custom session tags
|
||||||
|
|
||||||
You can add custom session tags using the `custom-tags` input, which accepts a
|
You can add custom session tags using the `custom-tags` input, which accepts a
|
||||||
JSON object. Custom tags cannot override the default tags listed above.
|
JSON object. Custom tags cannot override protected tags, but they can override
|
||||||
|
overrideable tags (in which case the overrideable tag's slot is freed for the
|
||||||
|
next overrideable tag in the priority list, if any).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
uses: aws-actions/configure-aws-credentials@v6
|
uses: aws-actions/configure-aws-credentials@v6
|
||||||
|
|
|
||||||
13
THIRD-PARTY
13
THIRD-PARTY
|
|
@ -431,12 +431,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
The following npm packages may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- @aws-crypto/crc32@5.2.0
|
|
||||||
- @aws-crypto/util@5.2.0
|
- @aws-crypto/util@5.2.0
|
||||||
|
|
||||||
These packages each contain the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
Apache License
|
Apache License
|
||||||
Version 2.0, January 2004
|
Version 2.0, January 2004
|
||||||
|
|
@ -644,7 +643,7 @@ Apache License
|
||||||
|
|
||||||
The following npm packages may be included in this product:
|
The following npm packages may be included in this product:
|
||||||
|
|
||||||
- @aws-sdk/client-sts@3.1045.0
|
- @aws-sdk/client-sts@3.1044.0
|
||||||
- @aws-sdk/util-user-agent-browser@3.972.10
|
- @aws-sdk/util-user-agent-browser@3.972.10
|
||||||
- @aws-sdk/util-user-agent-node@3.973.24
|
- @aws-sdk/util-user-agent-node@3.973.24
|
||||||
- @smithy/middleware-retry@4.5.7
|
- @smithy/middleware-retry@4.5.7
|
||||||
|
|
@ -868,7 +867,7 @@ The following npm packages may be included in this product:
|
||||||
- @aws-sdk/middleware-sdk-s3@3.972.37
|
- @aws-sdk/middleware-sdk-s3@3.972.37
|
||||||
- @aws-sdk/middleware-user-agent@3.972.38
|
- @aws-sdk/middleware-user-agent@3.972.38
|
||||||
- @aws-sdk/signature-v4-multi-region@3.996.25
|
- @aws-sdk/signature-v4-multi-region@3.996.25
|
||||||
- @smithy/core@3.24.1
|
- @smithy/core@3.23.17
|
||||||
- @smithy/invalid-dependency@4.2.14
|
- @smithy/invalid-dependency@4.2.14
|
||||||
- @smithy/middleware-serde@4.2.20
|
- @smithy/middleware-serde@4.2.20
|
||||||
- @smithy/protocol-http@5.3.14
|
- @smithy/protocol-http@5.3.14
|
||||||
|
|
@ -1710,8 +1709,8 @@ The following npm packages may be included in this product:
|
||||||
- @smithy/middleware-content-length@4.2.14
|
- @smithy/middleware-content-length@4.2.14
|
||||||
- @smithy/middleware-endpoint@4.4.32
|
- @smithy/middleware-endpoint@4.4.32
|
||||||
- @smithy/middleware-stack@4.2.14
|
- @smithy/middleware-stack@4.2.14
|
||||||
- @smithy/node-http-handler@4.7.1
|
- @smithy/node-http-handler@4.6.1
|
||||||
- @smithy/property-provider@4.3.1
|
- @smithy/property-provider@4.2.14
|
||||||
- @smithy/shared-ini-file-loader@4.4.9
|
- @smithy/shared-ini-file-loader@4.4.9
|
||||||
- @smithy/signature-v4@5.3.14
|
- @smithy/signature-v4@5.3.14
|
||||||
- @smithy/util-base64@4.3.2
|
- @smithy/util-base64@4.3.2
|
||||||
|
|
|
||||||
16702
dist/index.js
generated
vendored
16702
dist/index.js
generated
vendored
File diff suppressed because it is too large
Load diff
|
|
@ -87,6 +87,32 @@ const MAX_TAG_KEY_LENGTH = 128;
|
||||||
const MAX_TAG_VALUE_LENGTH = 256;
|
const MAX_TAG_VALUE_LENGTH = 256;
|
||||||
const MAX_SESSION_TAGS = 50;
|
const MAX_SESSION_TAGS = 50;
|
||||||
|
|
||||||
|
// Identity/audit primitives. Always emitted and cannot be overridden by custom-tags.
|
||||||
|
const PROTECTED_TAG_SOURCES: ReadonlyArray<{ key: string; envVar: string }> = [
|
||||||
|
{ key: 'Repository', envVar: 'GITHUB_REPOSITORY' },
|
||||||
|
{ key: 'Workflow', envVar: 'GITHUB_WORKFLOW' },
|
||||||
|
{ key: 'Action', envVar: 'GITHUB_ACTION' },
|
||||||
|
{ key: 'Actor', envVar: 'GITHUB_ACTOR' },
|
||||||
|
{ key: 'Commit', envVar: 'GITHUB_SHA' },
|
||||||
|
{ key: 'Branch', envVar: 'GITHUB_REF' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Convenience metadata. Custom-tags may override (suppresses the default for that key).
|
||||||
|
// Listed in priority order; lower-priority entries are dropped first if the user's custom-tags
|
||||||
|
// would push the total above MAX_SESSION_TAGS.
|
||||||
|
const OVERRIDEABLE_TAG_SOURCES_BY_PRIORITY: ReadonlyArray<{ key: string; envVar: string }> = [
|
||||||
|
{ key: 'EventName', envVar: 'GITHUB_EVENT_NAME' },
|
||||||
|
{ key: 'BaseRef', envVar: 'GITHUB_BASE_REF' },
|
||||||
|
{ key: 'HeadRef', envVar: 'GITHUB_HEAD_REF' },
|
||||||
|
{ key: 'RefName', envVar: 'GITHUB_REF_NAME' },
|
||||||
|
{ key: 'RunId', envVar: 'GITHUB_RUN_ID' },
|
||||||
|
{ key: 'RefType', envVar: 'GITHUB_REF_TYPE' },
|
||||||
|
{ key: 'Job', envVar: 'GITHUB_JOB' },
|
||||||
|
{ key: 'TriggeringActor', envVar: 'GITHUB_TRIGGERING_ACTOR' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const PROTECTED_TAG_KEYS = new Set<string>(['GitHub', ...PROTECTED_TAG_SOURCES.map((s) => s.key)]);
|
||||||
|
|
||||||
export function parseAndValidateCustomTags(customTags: string, existingTags: Tag[]): Tag[] {
|
export function parseAndValidateCustomTags(customTags: string, existingTags: Tag[]): Tag[] {
|
||||||
let parsed: unknown;
|
let parsed: unknown;
|
||||||
try {
|
try {
|
||||||
|
|
@ -99,7 +125,6 @@ export function parseAndValidateCustomTags(customTags: string, existingTags: Tag
|
||||||
throw new Error('custom-tags: input must be a JSON object (not an array or primitive)');
|
throw new Error('custom-tags: input must be a JSON object (not an array or primitive)');
|
||||||
}
|
}
|
||||||
|
|
||||||
const reservedKeys = new Set(existingTags.map((tag) => tag.Key));
|
|
||||||
const newTags: Tag[] = [];
|
const newTags: Tag[] = [];
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(parsed)) {
|
for (const [key, value] of Object.entries(parsed)) {
|
||||||
|
|
@ -129,9 +154,9 @@ export function parseAndValidateCustomTags(customTags: string, existingTags: Tag
|
||||||
`custom-tags: value for key '${key}' contains invalid characters. Allowed: unicode letters, digits, spaces, and _.:/=+-@`,
|
`custom-tags: value for key '${key}' contains invalid characters. Allowed: unicode letters, digits, spaces, and _.:/=+-@`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (reservedKeys.has(key)) {
|
if (PROTECTED_TAG_KEYS.has(key)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`custom-tags: key '${key}' conflicts with a default session tag set by this action and cannot be overridden`,
|
`custom-tags: key '${key}' conflicts with a protected session tag set by this action and cannot be overridden`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -170,28 +195,32 @@ export async function assumeRole(params: assumeRoleParams) {
|
||||||
throw new Error('Missing required environment variables. Are you running in GitHub Actions?');
|
throw new Error('Missing required environment variables. Are you running in GitHub Actions?');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load role session tags
|
// Build session tags. Values are sanitized because the AWS tag value spec is more
|
||||||
const tagArray: Tag[] = [
|
// restrictive than permissible characters in environment variables.
|
||||||
{ Key: 'GitHub', Value: 'Actions' },
|
const protectedTags: Tag[] = [{ Key: 'GitHub', Value: 'Actions' }];
|
||||||
{ Key: 'Repository', Value: GITHUB_REPOSITORY },
|
for (const { key, envVar } of PROTECTED_TAG_SOURCES) {
|
||||||
{ Key: 'Workflow', Value: sanitizeGitHubVariables(GITHUB_WORKFLOW) },
|
const value = process.env[envVar];
|
||||||
{ Key: 'Action', Value: GITHUB_ACTION },
|
if (value) {
|
||||||
{ Key: 'Actor', Value: sanitizeGitHubVariables(GITHUB_ACTOR) },
|
protectedTags.push({ Key: key, Value: sanitizeGitHubVariables(value) });
|
||||||
{ Key: 'Commit', Value: GITHUB_SHA },
|
}
|
||||||
];
|
|
||||||
|
|
||||||
if (process.env.GITHUB_REF) {
|
|
||||||
tagArray.push({
|
|
||||||
Key: 'Branch',
|
|
||||||
Value: sanitizeGitHubVariables(process.env.GITHUB_REF),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (customTags) {
|
const parsedCustomTags: Tag[] = customTags ? parseAndValidateCustomTags(customTags, protectedTags) : [];
|
||||||
const parsed = parseAndValidateCustomTags(customTags, tagArray);
|
const customTagKeys = new Set(parsedCustomTags.map((t) => t.Key));
|
||||||
tagArray.push(...parsed);
|
|
||||||
|
const availableOverrideableSlots = MAX_SESSION_TAGS - protectedTags.length - parsedCustomTags.length;
|
||||||
|
const overrideableTags: Tag[] = [];
|
||||||
|
for (const { key, envVar } of OVERRIDEABLE_TAG_SOURCES_BY_PRIORITY) {
|
||||||
|
if (overrideableTags.length >= availableOverrideableSlots) break;
|
||||||
|
if (customTagKeys.has(key)) continue;
|
||||||
|
const value = process.env[envVar];
|
||||||
|
if (value) {
|
||||||
|
overrideableTags.push({ Key: key, Value: sanitizeGitHubVariables(value) });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tagArray: Tag[] = [...protectedTags, ...overrideableTags, ...parsedCustomTags];
|
||||||
|
|
||||||
const tags = roleSkipSessionTagging ? undefined : tagArray;
|
const tags = roleSkipSessionTagging ? undefined : tagArray;
|
||||||
if (!tags) {
|
if (!tags) {
|
||||||
core.debug('Role session tagging has been skipped.');
|
core.debug('Role session tagging has been skipped.');
|
||||||
|
|
|
||||||
|
|
@ -245,6 +245,74 @@ describe('Configure AWS Credentials', {}, () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Default session tags', {}, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockedSTSClient.on(AssumeRoleCommand).resolvesOnce(mocks.outputs.STS_CREDENTIALS);
|
||||||
|
mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY });
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: any required to mock private method
|
||||||
|
vi.spyOn(CredentialsClient.prototype as any, 'loadCredentials')
|
||||||
|
.mockResolvedValueOnce({ accessKeyId: 'MYAWSACCESSKEYID' })
|
||||||
|
.mockResolvedValueOnce({ accessKeyId: 'STSAWSACCESSKEYID' });
|
||||||
|
});
|
||||||
|
it('emits exactly the expected default tag set with no custom-tags', {}, async () => {
|
||||||
|
vi.mocked(core.getInput).mockImplementation(mocks.getInput(mocks.IAM_ASSUMEROLE_INPUTS));
|
||||||
|
await run();
|
||||||
|
const tags = mockedSTSClient.commandCalls(AssumeRoleCommand)[0].args[0].input.Tags ?? [];
|
||||||
|
// 7 protected (GitHub + Repository, Workflow, Action, Actor, Commit, Branch)
|
||||||
|
// + 8 overrideable (EventName, BaseRef, HeadRef, RefName, RunId, RefType, Job, TriggeringActor).
|
||||||
|
// No custom-tags, all env vars set in mocks.envs → all 15 should be present, nothing else.
|
||||||
|
expect(tags).toHaveLength(15);
|
||||||
|
const tagsByKey = Object.fromEntries(tags.map((t) => [t.Key, t.Value]));
|
||||||
|
expect(tagsByKey).toEqual({
|
||||||
|
GitHub: 'Actions',
|
||||||
|
Repository: 'MY-REPOSITORY-NAME',
|
||||||
|
Workflow: 'MY-WORKFLOW-ID',
|
||||||
|
Action: 'MY-ACTION-NAME',
|
||||||
|
Actor: 'MY-USERNAME_bot_',
|
||||||
|
Commit: 'MY-COMMIT-ID',
|
||||||
|
Branch: 'refs/pull/42/merge',
|
||||||
|
EventName: 'pull_request',
|
||||||
|
BaseRef: 'main',
|
||||||
|
HeadRef: 'feature-branch',
|
||||||
|
RefName: 'feature-branch',
|
||||||
|
RunId: '16412345678',
|
||||||
|
RefType: 'branch',
|
||||||
|
Job: 'build',
|
||||||
|
TriggeringActor: 'MY-USERNAME_bot_',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('omits overrideable tags whose env vars are unset', {}, async () => {
|
||||||
|
vi.mocked(core.getInput).mockImplementation(mocks.getInput(mocks.IAM_ASSUMEROLE_INPUTS));
|
||||||
|
delete process.env.GITHUB_BASE_REF;
|
||||||
|
delete process.env.GITHUB_HEAD_REF;
|
||||||
|
delete process.env.GITHUB_TRIGGERING_ACTOR;
|
||||||
|
await run();
|
||||||
|
const tags = mockedSTSClient.commandCalls(AssumeRoleCommand)[0].args[0].input.Tags ?? [];
|
||||||
|
const tagKeys = tags.map((t) => t.Key);
|
||||||
|
expect(tagKeys).not.toContain('BaseRef');
|
||||||
|
expect(tagKeys).not.toContain('HeadRef');
|
||||||
|
expect(tagKeys).not.toContain('TriggeringActor');
|
||||||
|
expect(tagKeys).toContain('EventName');
|
||||||
|
expect(tagKeys).toContain('RunId');
|
||||||
|
});
|
||||||
|
it('sanitizes invalid characters in env-derived tag values', {}, async () => {
|
||||||
|
vi.mocked(core.getInput).mockImplementation(mocks.getInput(mocks.IAM_ASSUMEROLE_INPUTS));
|
||||||
|
process.env.GITHUB_HEAD_REF = 'feature/has spaces&bad?chars';
|
||||||
|
await run();
|
||||||
|
expect(mockedSTSClient.commandCalls(AssumeRoleCommand)[0].args[0].input).toMatchObject({
|
||||||
|
Tags: expect.arrayContaining([{ Key: 'HeadRef', Value: 'feature/has spaces_bad_chars' }]),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('truncates env-derived tag values longer than 256 characters', {}, async () => {
|
||||||
|
vi.mocked(core.getInput).mockImplementation(mocks.getInput(mocks.IAM_ASSUMEROLE_INPUTS));
|
||||||
|
process.env.GITHUB_HEAD_REF = 'a'.repeat(300);
|
||||||
|
await run();
|
||||||
|
const tags = mockedSTSClient.commandCalls(AssumeRoleCommand)[0].args[0].input.Tags ?? [];
|
||||||
|
const headRef = tags.find((t) => t.Key === 'HeadRef');
|
||||||
|
expect(headRef?.Value).toHaveLength(256);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Custom Tags', {}, () => {
|
describe('Custom Tags', {}, () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockedSTSClient.on(AssumeRoleCommand).resolvesOnce(mocks.outputs.STS_CREDENTIALS);
|
mockedSTSClient.on(AssumeRoleCommand).resolvesOnce(mocks.outputs.STS_CREDENTIALS);
|
||||||
|
|
@ -273,6 +341,15 @@ describe('Configure AWS Credentials', {}, () => {
|
||||||
{ Key: 'Action', Value: 'MY-ACTION-NAME' },
|
{ Key: 'Action', Value: 'MY-ACTION-NAME' },
|
||||||
{ Key: 'Actor', Value: 'MY-USERNAME_bot_' },
|
{ Key: 'Actor', Value: 'MY-USERNAME_bot_' },
|
||||||
{ Key: 'Commit', Value: 'MY-COMMIT-ID' },
|
{ Key: 'Commit', Value: 'MY-COMMIT-ID' },
|
||||||
|
{ Key: 'Branch', Value: 'refs/pull/42/merge' },
|
||||||
|
{ Key: 'BaseRef', Value: 'main' },
|
||||||
|
{ Key: 'HeadRef', Value: 'feature-branch' },
|
||||||
|
{ Key: 'EventName', Value: 'pull_request' },
|
||||||
|
{ Key: 'RunId', Value: '16412345678' },
|
||||||
|
{ Key: 'Job', Value: 'build' },
|
||||||
|
{ Key: 'RefName', Value: 'feature-branch' },
|
||||||
|
{ Key: 'RefType', Value: 'branch' },
|
||||||
|
{ Key: 'TriggeringActor', Value: 'MY-USERNAME_bot_' },
|
||||||
{ Key: 'Environment', Value: 'Production' },
|
{ Key: 'Environment', Value: 'Production' },
|
||||||
{ Key: 'Team', Value: 'DevOps' },
|
{ Key: 'Team', Value: 'DevOps' },
|
||||||
]),
|
]),
|
||||||
|
|
@ -286,11 +363,11 @@ describe('Configure AWS Credentials', {}, () => {
|
||||||
);
|
);
|
||||||
expect(mockedSTSClient.commandCalls(AssumeRoleCommand)).toHaveLength(0);
|
expect(mockedSTSClient.commandCalls(AssumeRoleCommand)).toHaveLength(0);
|
||||||
});
|
});
|
||||||
it('rejects custom tags that conflict with default session tags', {}, async () => {
|
it('rejects custom tags that conflict with protected session tags', {}, async () => {
|
||||||
vi.mocked(core.getInput).mockImplementation(mocks.getInput(mocks.CUSTOM_TAGS_RESERVED_KEY_INPUTS));
|
vi.mocked(core.getInput).mockImplementation(mocks.getInput(mocks.CUSTOM_TAGS_RESERVED_KEY_INPUTS));
|
||||||
await run();
|
await run();
|
||||||
expect(core.setFailed).toHaveBeenCalledWith(
|
expect(core.setFailed).toHaveBeenCalledWith(
|
||||||
"custom-tags: key 'Repository' conflicts with a default session tag set by this action and cannot be overridden",
|
"custom-tags: key 'Repository' conflicts with a protected session tag set by this action and cannot be overridden",
|
||||||
);
|
);
|
||||||
expect(mockedSTSClient.commandCalls(AssumeRoleCommand)).toHaveLength(0);
|
expect(mockedSTSClient.commandCalls(AssumeRoleCommand)).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
@ -320,6 +397,128 @@ describe('Configure AWS Credentials', {}, () => {
|
||||||
await run();
|
await run();
|
||||||
expect(core.warning).toHaveBeenCalledWith(expect.stringContaining("'custom-tags' is set but will be ignored"));
|
expect(core.warning).toHaveBeenCalledWith(expect.stringContaining("'custom-tags' is set but will be ignored"));
|
||||||
});
|
});
|
||||||
|
it('lets custom tags override overrideable default tag keys', {}, async () => {
|
||||||
|
vi.mocked(core.getInput).mockImplementation(
|
||||||
|
mocks.getInput({
|
||||||
|
...mocks.IAM_ASSUMEROLE_INPUTS,
|
||||||
|
'custom-tags': JSON.stringify({ EventName: 'workflow_dispatch', BaseRef: 'release/2026' }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await run();
|
||||||
|
const tags = mockedSTSClient.commandCalls(AssumeRoleCommand)[0].args[0].input.Tags ?? [];
|
||||||
|
const eventNameTags = tags.filter((t) => t.Key === 'EventName');
|
||||||
|
const baseRefTags = tags.filter((t) => t.Key === 'BaseRef');
|
||||||
|
expect(eventNameTags).toHaveLength(1);
|
||||||
|
expect(eventNameTags[0]?.Value).toBe('workflow_dispatch');
|
||||||
|
expect(baseRefTags).toHaveLength(1);
|
||||||
|
expect(baseRefTags[0]?.Value).toBe('release/2026');
|
||||||
|
});
|
||||||
|
it('rejects custom tags that conflict with the protected Branch tag', {}, async () => {
|
||||||
|
// Regression guard: Branch was a default before v6.2 and must remain unoverridable.
|
||||||
|
vi.mocked(core.getInput).mockImplementation(
|
||||||
|
mocks.getInput({
|
||||||
|
...mocks.IAM_ASSUMEROLE_INPUTS,
|
||||||
|
'custom-tags': JSON.stringify({ Branch: 'evil-branch' }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await run();
|
||||||
|
expect(core.setFailed).toHaveBeenCalledWith(
|
||||||
|
"custom-tags: key 'Branch' conflicts with a protected session tag set by this action and cannot be overridden",
|
||||||
|
);
|
||||||
|
expect(mockedSTSClient.commandCalls(AssumeRoleCommand)).toHaveLength(0);
|
||||||
|
});
|
||||||
|
it('drops lower-priority overrideable tags when custom-tags would exceed the session-tag limit', {}, async () => {
|
||||||
|
// 7 protected (GitHub + 6 from PROTECTED_TAG_SOURCES) + 40 custom = 47 used → 3 overrideable slots.
|
||||||
|
// The first 3 overrideable tags by priority are EventName, BaseRef, HeadRef (RefName, RunId, RefType,
|
||||||
|
// Job, TriggeringActor must be dropped).
|
||||||
|
const customTagsObj: Record<string, string> = {};
|
||||||
|
for (let i = 0; i < 40; i++) {
|
||||||
|
customTagsObj[`Custom${i}`] = `value${i}`;
|
||||||
|
}
|
||||||
|
vi.mocked(core.getInput).mockImplementation(
|
||||||
|
mocks.getInput({
|
||||||
|
...mocks.IAM_ASSUMEROLE_INPUTS,
|
||||||
|
'custom-tags': JSON.stringify(customTagsObj),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await run();
|
||||||
|
const tags = mockedSTSClient.commandCalls(AssumeRoleCommand)[0].args[0].input.Tags ?? [];
|
||||||
|
const tagKeys = tags.map((t) => t.Key);
|
||||||
|
expect(tags).toHaveLength(50);
|
||||||
|
expect(tagKeys).toContain('Branch');
|
||||||
|
expect(tagKeys).toContain('EventName');
|
||||||
|
expect(tagKeys).toContain('BaseRef');
|
||||||
|
expect(tagKeys).toContain('HeadRef');
|
||||||
|
expect(tagKeys).not.toContain('RefName');
|
||||||
|
expect(tagKeys).not.toContain('RunId');
|
||||||
|
expect(tagKeys).not.toContain('RefType');
|
||||||
|
expect(tagKeys).not.toContain('Job');
|
||||||
|
expect(tagKeys).not.toContain('TriggeringActor');
|
||||||
|
});
|
||||||
|
it('overridden overrideable tags free a slot for a lower-priority overrideable tag', {}, async () => {
|
||||||
|
// Same 40-custom-tag scenario as above, but one of the customs overrides BaseRef.
|
||||||
|
// BaseRef no longer competes for the overrideable budget, so the next-priority overrideable (RefName) gets in.
|
||||||
|
const customTagsObj: Record<string, string> = { BaseRef: 'release/2026' };
|
||||||
|
for (let i = 0; i < 39; i++) {
|
||||||
|
customTagsObj[`Custom${i}`] = `value${i}`;
|
||||||
|
}
|
||||||
|
vi.mocked(core.getInput).mockImplementation(
|
||||||
|
mocks.getInput({
|
||||||
|
...mocks.IAM_ASSUMEROLE_INPUTS,
|
||||||
|
'custom-tags': JSON.stringify(customTagsObj),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await run();
|
||||||
|
const tags = mockedSTSClient.commandCalls(AssumeRoleCommand)[0].args[0].input.Tags ?? [];
|
||||||
|
const tagKeys = tags.map((t) => t.Key);
|
||||||
|
expect(tags).toHaveLength(50);
|
||||||
|
expect(tagKeys).toContain('Branch');
|
||||||
|
expect(tagKeys).toContain('EventName');
|
||||||
|
expect(tagKeys).toContain('BaseRef');
|
||||||
|
expect(tagKeys).toContain('HeadRef');
|
||||||
|
expect(tagKeys).toContain('RefName');
|
||||||
|
expect(tagKeys).not.toContain('RunId');
|
||||||
|
});
|
||||||
|
it('rejects custom-tags that would exceed the session-tag limit on their own', {}, async () => {
|
||||||
|
// 7 protected + 44 custom = 51, which is over 50 even with zero overrideable tags.
|
||||||
|
const customTagsObj: Record<string, string> = {};
|
||||||
|
for (let i = 0; i < 44; i++) {
|
||||||
|
customTagsObj[`Custom${i}`] = `value${i}`;
|
||||||
|
}
|
||||||
|
vi.mocked(core.getInput).mockImplementation(
|
||||||
|
mocks.getInput({
|
||||||
|
...mocks.IAM_ASSUMEROLE_INPUTS,
|
||||||
|
'custom-tags': JSON.stringify(customTagsObj),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await run();
|
||||||
|
expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('would exceed the AWS limit of 50'));
|
||||||
|
expect(mockedSTSClient.commandCalls(AssumeRoleCommand)).toHaveLength(0);
|
||||||
|
});
|
||||||
|
it('drops transitive-tag-keys entries that refer to evicted overrideable tags', {}, async () => {
|
||||||
|
// Force eviction of all overrideable tags below EventName/BaseRef/HeadRef. The user transitive-tags
|
||||||
|
// RunId (which gets evicted) and Repository (which is protected and stays). The TransitiveTagKeys
|
||||||
|
// payload must include only the keys that actually appear in Tags.
|
||||||
|
const customTagsObj: Record<string, string> = {};
|
||||||
|
for (let i = 0; i < 40; i++) {
|
||||||
|
customTagsObj[`Custom${i}`] = `value${i}`;
|
||||||
|
}
|
||||||
|
vi.mocked(core.getInput).mockImplementation(
|
||||||
|
mocks.getInput({
|
||||||
|
...mocks.IAM_ASSUMEROLE_INPUTS,
|
||||||
|
'custom-tags': JSON.stringify(customTagsObj),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
vi.mocked(core.getMultilineInput).mockImplementation((name: string) => {
|
||||||
|
if (name === 'transitive-tag-keys') return ['Repository', 'RunId'];
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
await run();
|
||||||
|
const callInput = mockedSTSClient.commandCalls(AssumeRoleCommand)[0].args[0].input;
|
||||||
|
const tagKeys = (callInput.Tags ?? []).map((t) => t.Key);
|
||||||
|
expect(tagKeys).not.toContain('RunId');
|
||||||
|
expect(callInput.TransitiveTagKeys).toEqual(['Repository']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Odd inputs', {}, () => {
|
describe('Odd inputs', {}, () => {
|
||||||
|
|
@ -1283,6 +1482,8 @@ describe('Configure AWS Credentials', {}, () => {
|
||||||
it('omits tokens when env vars are unset, with no warning', async () => {
|
it('omits tokens when env vars are unset, with no warning', async () => {
|
||||||
vi.resetModules();
|
vi.resetModules();
|
||||||
delete process.env.GITHUB_ACTION;
|
delete process.env.GITHUB_ACTION;
|
||||||
|
delete process.env.GITHUB_RUN_ID;
|
||||||
|
delete process.env.GITHUB_RUN_ATTEMPT;
|
||||||
const ua = await getCustomUserAgent();
|
const ua = await getCustomUserAgent();
|
||||||
expect(ua).toEqual([['configure-aws-credentials-for-github-actions']]);
|
expect(ua).toEqual([['configure-aws-credentials-for-github-actions']]);
|
||||||
expect(core.warning).not.toHaveBeenCalled();
|
expect(core.warning).not.toHaveBeenCalled();
|
||||||
|
|
@ -1314,6 +1515,8 @@ describe('Configure AWS Credentials', {}, () => {
|
||||||
it('rejects GITHUB_ACTION containing whitespace or other characters', async () => {
|
it('rejects GITHUB_ACTION containing whitespace or other characters', async () => {
|
||||||
vi.resetModules();
|
vi.resetModules();
|
||||||
process.env.GITHUB_ACTION = 'has space';
|
process.env.GITHUB_ACTION = 'has space';
|
||||||
|
delete process.env.GITHUB_RUN_ID;
|
||||||
|
delete process.env.GITHUB_RUN_ATTEMPT;
|
||||||
const ua = await getCustomUserAgent();
|
const ua = await getCustomUserAgent();
|
||||||
expect(ua).toEqual([['configure-aws-credentials-for-github-actions']]);
|
expect(ua).toEqual([['configure-aws-credentials-for-github-actions']]);
|
||||||
expect(core.warning).toHaveBeenCalledWith('GITHUB_ACTION has unexpected format; omitting from User-Agent');
|
expect(core.warning).toHaveBeenCalledWith('GITHUB_ACTION has unexpected format; omitting from User-Agent');
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,15 @@ const envs = {
|
||||||
GITHUB_SHA: 'MY-COMMIT-ID',
|
GITHUB_SHA: 'MY-COMMIT-ID',
|
||||||
GITHUB_WORKSPACE: '/home/github',
|
GITHUB_WORKSPACE: '/home/github',
|
||||||
GITHUB_ACTIONS: 'true',
|
GITHUB_ACTIONS: 'true',
|
||||||
|
GITHUB_REF: 'refs/pull/42/merge',
|
||||||
|
GITHUB_EVENT_NAME: 'pull_request',
|
||||||
|
GITHUB_RUN_ID: '16412345678',
|
||||||
|
GITHUB_JOB: 'build',
|
||||||
|
GITHUB_REF_NAME: 'feature-branch',
|
||||||
|
GITHUB_REF_TYPE: 'branch',
|
||||||
|
GITHUB_BASE_REF: 'main',
|
||||||
|
GITHUB_HEAD_REF: 'feature-branch',
|
||||||
|
GITHUB_TRIGGERING_ACTOR: 'MY-USERNAME[bot]',
|
||||||
};
|
};
|
||||||
|
|
||||||
const outputs = {
|
const outputs = {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue