1
0
Fork 0
mirror of synced 2026-06-05 17:08:20 +00:00

Compare commits

...

2 commits

Author SHA1 Message Date
Tom Keller
15f02495fa feat: add github_action to ua string 2026-05-12 16:06:13 -07:00
Tom Keller
f8d102ad7d feat: expose run id in STS client user-agent
Closes #483.
This commit modifies the user-agent string so that it includes the
GITHUB_RUN_ID and the GITHUB_RUN_ATTEMPT, in the format typically used
by the SDK. User agent strings are logged to CloudTrail, allowing users
to correlate CloudTrail events with GHA runs. We took this approach
instead of logging the ACCESS_KEY_ID as suggested in the issue to avoid
logging sensitive information.
2026-05-12 12:05:07 -07:00
5 changed files with 8556 additions and 13622 deletions

View file

@ -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
These packages each contain the following license:
This package contains the following license:
Apache License
Version 2.0, January 2004
@ -644,7 +643,7 @@ Apache License
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-node@3.973.24
- @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-user-agent@3.972.38
- @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/middleware-serde@4.2.20
- @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-endpoint@4.4.32
- @smithy/middleware-stack@4.2.14
- @smithy/node-http-handler@4.7.1
- @smithy/property-provider@4.3.1
- @smithy/node-http-handler@4.6.1
- @smithy/property-provider@4.2.14
- @smithy/shared-ini-file-loader@4.4.9
- @smithy/signature-v4@5.3.14
- @smithy/util-base64@4.3.2

16684
dist/index.js generated vendored

File diff suppressed because it is too large Load diff

View file

@ -3,10 +3,12 @@ import { STSClient } from '@aws-sdk/client-sts';
import type { AwsCredentialIdentity } from '@aws-sdk/types';
import { NodeHttpHandler } from '@smithy/node-http-handler';
import { ProxyAgent } from 'proxy-agent';
import { errorMessage, getCallerIdentity } from './helpers';
import { buildCustomUserAgent, errorMessage, getCallerIdentity } from './helpers';
import { ProxyResolver } from './ProxyResolver';
const USER_AGENT = 'configure-aws-credentials-for-github-actions';
if (!process.env.AWS_EXECUTION_ENV) {
process.env.AWS_EXECUTION_ENV = 'GitHubActions';
}
export interface CredentialsClientProps {
region?: string;
@ -51,16 +53,12 @@ export class CredentialsClient {
public get stsClient(): STSClient {
if (!this._stsClient || this.roleChaining) {
const config = { customUserAgent: USER_AGENT } as {
customUserAgent: string;
region?: string;
endpoint?: string;
requestHandler?: NodeHttpHandler;
};
if (this.region !== undefined) config.region = this.region;
if (this.stsEndpoint !== undefined) config.endpoint = this.stsEndpoint;
if (this.requestHandler !== undefined) config.requestHandler = this.requestHandler;
this._stsClient = new STSClient(config);
this._stsClient = new STSClient({
customUserAgent: buildCustomUserAgent(),
...(this.region !== undefined && { region: this.region }),
...(this.stsEndpoint !== undefined && { endpoint: this.stsEndpoint }),
...(this.requestHandler !== undefined && { requestHandler: this.requestHandler }),
});
}
return this._stsClient;
}

View file

@ -1,11 +1,32 @@
import * as core from '@actions/core';
import type { Credentials, STSClient } from '@aws-sdk/client-sts';
import { GetCallerIdentityCommand } from '@aws-sdk/client-sts';
import type { UserAgent } from '@smithy/types';
import type { CredentialsClient } from './CredentialsClient';
const MAX_TAG_VALUE_LENGTH = 256;
const SANITIZATION_CHARACTER = '_';
const SPECIAL_CHARS_REGEX = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]+/;
const USER_AGENT_PREFIX = 'configure-aws-credentials-for-github-actions';
const UA_FIELDS: ReadonlyArray<{ env: string; label: string; pattern: RegExp }> = [
{ env: 'GITHUB_ACTION', label: 'action', pattern: /^[A-Za-z0-9_-]{1,128}$/ },
{ env: 'GITHUB_RUN_ID', label: 'run_id', pattern: /^[0-9]{1,20}$/ },
{ env: 'GITHUB_RUN_ATTEMPT', label: 'attempt', pattern: /^[0-9]{1,10}$/ },
];
export function buildCustomUserAgent(): UserAgent {
const tokens: UserAgent = [[USER_AGENT_PREFIX]];
for (const { env, label, pattern } of UA_FIELDS) {
const value = process.env[env];
if (value === undefined) continue;
if (pattern.test(value)) {
tokens.push(['md', `${label}#${value}`]);
} else {
core.warning(`${env} has unexpected format; omitting from User-Agent`);
}
}
return tokens;
}
export function translateEnvVariables() {
const envVars = [

View file

@ -1218,9 +1218,7 @@ describe('Configure AWS Credentials', {}, () => {
}),
);
// biome-ignore lint/suspicious/noExplicitAny: any required to mock private method
vi.spyOn(CredentialsClient.prototype as any, 'loadCredentials').mockRejectedValue(
new Error('network glitch'),
);
vi.spyOn(CredentialsClient.prototype as any, 'loadCredentials').mockRejectedValue(new Error('network glitch'));
await run();
expect(core.setFailed).toHaveBeenCalled();
expect(core.info).not.toHaveBeenCalledWith(expect.stringContaining('Retry'));
@ -1258,4 +1256,80 @@ describe('Configure AWS Credentials', {}, () => {
expect(core.setFailed).not.toHaveBeenCalled();
});
});
describe('User-Agent enrichment', {}, () => {
async function getCustomUserAgent(): Promise<unknown> {
const { CredentialsClient: FreshClient } = await import('../src/CredentialsClient');
const client = new FreshClient({ region: 'fake-region-1', roleChaining: false });
// biome-ignore lint/suspicious/noExplicitAny: SDK config readout
return (client.stsClient.config as any).customUserAgent;
}
it('includes action, run_id and attempt tokens when env vars are valid', async () => {
vi.resetModules();
process.env.GITHUB_ACTION = '__run_2';
process.env.GITHUB_RUN_ID = '16412345678';
process.env.GITHUB_RUN_ATTEMPT = '1';
const ua = await getCustomUserAgent();
expect(ua).toEqual([
['configure-aws-credentials-for-github-actions'],
['md', 'action#__run_2'],
['md', 'run_id#16412345678'],
['md', 'attempt#1'],
]);
expect(core.warning).not.toHaveBeenCalled();
});
it('omits tokens when env vars are unset, with no warning', async () => {
vi.resetModules();
delete process.env.GITHUB_ACTION;
const ua = await getCustomUserAgent();
expect(ua).toEqual([['configure-aws-credentials-for-github-actions']]);
expect(core.warning).not.toHaveBeenCalled();
});
it('warns and skips when env vars are malformed', async () => {
vi.resetModules();
process.env.GITHUB_ACTION = '$(curl evil)';
process.env.GITHUB_RUN_ID = '$(curl evil)';
process.env.GITHUB_RUN_ATTEMPT = '1; rm -rf /';
const ua = await getCustomUserAgent();
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_RUN_ID has unexpected format; omitting from User-Agent');
expect(core.warning).toHaveBeenCalledWith('GITHUB_RUN_ATTEMPT has unexpected format; omitting from User-Agent');
expect(core.warning).toHaveBeenCalledTimes(3);
});
it('warns and skips when env vars exceed the length bound', async () => {
vi.resetModules();
process.env.GITHUB_ACTION = 'a'.repeat(200);
process.env.GITHUB_RUN_ID = '1'.repeat(50);
process.env.GITHUB_RUN_ATTEMPT = '1'.repeat(50);
const ua = await getCustomUserAgent();
expect(ua).toEqual([['configure-aws-credentials-for-github-actions']]);
expect(core.warning).toHaveBeenCalledTimes(3);
});
it('rejects GITHUB_ACTION containing whitespace or other characters', async () => {
vi.resetModules();
process.env.GITHUB_ACTION = 'has space';
const ua = await getCustomUserAgent();
expect(ua).toEqual([['configure-aws-credentials-for-github-actions']]);
expect(core.warning).toHaveBeenCalledWith('GITHUB_ACTION has unexpected format; omitting from User-Agent');
});
it('sets AWS_EXECUTION_ENV to GitHubActions when unset', async () => {
vi.resetModules();
await import('../src/CredentialsClient');
expect(process.env.AWS_EXECUTION_ENV).toBe('GitHubActions');
});
it('preserves a pre-existing AWS_EXECUTION_ENV value', async () => {
vi.resetModules();
process.env.AWS_EXECUTION_ENV = 'CustomRunner';
await import('../src/CredentialsClient');
expect(process.env.AWS_EXECUTION_ENV).toBe('CustomRunner');
});
});
});