feat: expose run id in STS client user-agent (#1774)
* 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. * feat: add github_action to ua string
This commit is contained in:
parent
ef734cca81
commit
29d1be3027
5 changed files with 8556 additions and 13622 deletions
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
|
||||||
|
|
|
||||||
16684
dist/index.js
generated
vendored
16684
dist/index.js
generated
vendored
File diff suppressed because it is too large
Load diff
|
|
@ -3,10 +3,12 @@ import { STSClient } from '@aws-sdk/client-sts';
|
||||||
import type { AwsCredentialIdentity } from '@aws-sdk/types';
|
import type { AwsCredentialIdentity } from '@aws-sdk/types';
|
||||||
import { NodeHttpHandler } from '@smithy/node-http-handler';
|
import { NodeHttpHandler } from '@smithy/node-http-handler';
|
||||||
import { ProxyAgent } from 'proxy-agent';
|
import { ProxyAgent } from 'proxy-agent';
|
||||||
import { errorMessage, getCallerIdentity } from './helpers';
|
import { buildCustomUserAgent, errorMessage, getCallerIdentity } from './helpers';
|
||||||
import { ProxyResolver } from './ProxyResolver';
|
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 {
|
export interface CredentialsClientProps {
|
||||||
region?: string;
|
region?: string;
|
||||||
|
|
@ -51,16 +53,12 @@ export class CredentialsClient {
|
||||||
|
|
||||||
public get stsClient(): STSClient {
|
public get stsClient(): STSClient {
|
||||||
if (!this._stsClient || this.roleChaining) {
|
if (!this._stsClient || this.roleChaining) {
|
||||||
const config = { customUserAgent: USER_AGENT } as {
|
this._stsClient = new STSClient({
|
||||||
customUserAgent: string;
|
customUserAgent: buildCustomUserAgent(),
|
||||||
region?: string;
|
...(this.region !== undefined && { region: this.region }),
|
||||||
endpoint?: string;
|
...(this.stsEndpoint !== undefined && { endpoint: this.stsEndpoint }),
|
||||||
requestHandler?: NodeHttpHandler;
|
...(this.requestHandler !== undefined && { requestHandler: this.requestHandler }),
|
||||||
};
|
});
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
return this._stsClient;
|
return this._stsClient;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,32 @@
|
||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import type { Credentials, STSClient } from '@aws-sdk/client-sts';
|
import type { Credentials, STSClient } from '@aws-sdk/client-sts';
|
||||||
import { GetCallerIdentityCommand } from '@aws-sdk/client-sts';
|
import { GetCallerIdentityCommand } from '@aws-sdk/client-sts';
|
||||||
|
import type { UserAgent } from '@smithy/types';
|
||||||
import type { CredentialsClient } from './CredentialsClient';
|
import type { CredentialsClient } from './CredentialsClient';
|
||||||
|
|
||||||
const MAX_TAG_VALUE_LENGTH = 256;
|
const MAX_TAG_VALUE_LENGTH = 256;
|
||||||
const SANITIZATION_CHARACTER = '_';
|
const SANITIZATION_CHARACTER = '_';
|
||||||
const SPECIAL_CHARS_REGEX = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]+/;
|
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() {
|
export function translateEnvVariables() {
|
||||||
const envVars = [
|
const envVars = [
|
||||||
|
|
|
||||||
|
|
@ -1218,9 +1218,7 @@ describe('Configure AWS Credentials', {}, () => {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: any required to mock private method
|
// biome-ignore lint/suspicious/noExplicitAny: any required to mock private method
|
||||||
vi.spyOn(CredentialsClient.prototype as any, 'loadCredentials').mockRejectedValue(
|
vi.spyOn(CredentialsClient.prototype as any, 'loadCredentials').mockRejectedValue(new Error('network glitch'));
|
||||||
new Error('network glitch'),
|
|
||||||
);
|
|
||||||
await run();
|
await run();
|
||||||
expect(core.setFailed).toHaveBeenCalled();
|
expect(core.setFailed).toHaveBeenCalled();
|
||||||
expect(core.info).not.toHaveBeenCalledWith(expect.stringContaining('Retry'));
|
expect(core.info).not.toHaveBeenCalledWith(expect.stringContaining('Retry'));
|
||||||
|
|
@ -1258,4 +1256,80 @@ describe('Configure AWS Credentials', {}, () => {
|
||||||
expect(core.setFailed).not.toHaveBeenCalled();
|
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');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue