diff --git a/README.md b/README.md index aaf5991..a05eb6f 100644 --- a/README.md +++ b/README.md @@ -467,12 +467,6 @@ with: -### Custom STS endpoint - -Use the `sts-endpoint` input to override the AWS STS endpoint URL. Most users -should not set this option and instead let the SDK derive the correct endpoint -from the specified region. - ## OIDC Configuration Details We recommend using diff --git a/action.yml b/action.yml index 318c99d..1228533 100644 --- a/action.yml +++ b/action.yml @@ -103,9 +103,6 @@ inputs: custom-tags: description: Additional tags to apply to the assumed role session. Must be a JSON object provided as a string. required: false - sts-endpoint: - description: Custom STS endpoint URL. Use this to point to an STS-compatible API (e.g. MinIO, LocalStack) instead of the default AWS STS endpoint for the region. - required: false outputs: aws-account-id: diff --git a/src/CredentialsClient.ts b/src/CredentialsClient.ts index e065292..2d78b49 100644 --- a/src/CredentialsClient.ts +++ b/src/CredentialsClient.ts @@ -12,7 +12,6 @@ export interface CredentialsClientProps { region?: string; proxyServer?: string; noProxy?: string; - stsEndpoint?: string; roleChaining: boolean; } @@ -20,7 +19,6 @@ export class CredentialsClient { public region?: string; private _stsClient?: STSClient; private readonly requestHandler?: NodeHttpHandler; - private readonly stsEndpoint?: string; private roleChaining?: boolean; constructor(props: CredentialsClientProps) { @@ -43,9 +41,6 @@ export class CredentialsClient { httpAgent: handler, }); } - if (props.stsEndpoint) { - this.stsEndpoint = props.stsEndpoint; - } this.roleChaining = props.roleChaining; } @@ -54,11 +49,9 @@ export class CredentialsClient { 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); } diff --git a/src/index.ts b/src/index.ts index b87ef84..b7e5c9d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -64,7 +64,6 @@ export async function run() { .map((s) => s.trim()); const forceSkipOidc = getBooleanInput('force-skip-oidc', { required: false }); const noProxy = core.getInput('no-proxy', { required: false }); - const stsEndpoint = core.getInput('sts-endpoint', { required: false }); const globalTimeout = Number.parseInt(core.getInput('action-timeout-s', { required: false })) || 0; let timeoutId: NodeJS.Timeout | undefined; @@ -129,19 +128,12 @@ export async function run() { exportRegion(region, outputEnvCredentials); // Instantiate credentials client - const clientProps: { - region: string; - proxyServer?: string; - noProxy?: string; - stsEndpoint?: string; - roleChaining: boolean; - } = { + const clientProps: { region: string; proxyServer?: string; noProxy?: string; roleChaining: boolean } = { region, roleChaining, }; if (proxyServer) clientProps.proxyServer = proxyServer; if (noProxy) clientProps.noProxy = noProxy; - if (stsEndpoint) clientProps.stsEndpoint = stsEndpoint; const credentialsClient = new CredentialsClient(clientProps); let sourceAccountId: string; let webIdentityToken: string; diff --git a/test/index.test.ts b/test/index.test.ts index 0ae9193..84e25a1 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -311,7 +311,9 @@ describe('Configure AWS Credentials', {}, () => { }); process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'fake-token'; 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"), + ); }); }); @@ -799,54 +801,6 @@ describe('Configure AWS Credentials', {}, () => { }); }); - describe('Custom STS Endpoint', {}, () => { - it('passes sts-endpoint to the STS client', async () => { - const client = new CredentialsClient({ - region: 'us-east-1', - stsEndpoint: 'https://sts.custom.example.com', - roleChaining: false, - }); - const endpoint = await client.stsClient.config.endpoint(); - expect(endpoint).toMatchObject({ hostname: 'sts.custom.example.com', protocol: 'https:' }); - }); - - it('does not override endpoint when sts-endpoint is not provided', () => { - const client = new CredentialsClient({ - region: 'us-east-1', - roleChaining: false, - }); - expect(client.stsClient.config.endpoint).toBeUndefined(); - }); - - it('works with http endpoints for local services', async () => { - const client = new CredentialsClient({ - region: 'us-east-1', - stsEndpoint: 'http://localhost:9000', - roleChaining: false, - }); - const endpoint = await client.stsClient.config.endpoint(); - expect(endpoint).toMatchObject({ hostname: 'localhost', protocol: 'http:', port: 9000 }); - }); - - it('succeeds in a full action run with sts-endpoint input', async () => { - vi.mocked(core.getInput).mockImplementation( - mocks.getInput({ - ...mocks.GH_OIDC_INPUTS, - 'sts-endpoint': 'https://sts.custom.example.com', - }), - ); - vi.mocked(core.getIDToken).mockResolvedValue('testoidctoken'); - mockedSTSClient.on(GetCallerIdentityCommand).resolvesOnce({ ...mocks.outputs.GET_CALLER_IDENTITY }); - mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolvesOnce(mocks.outputs.STS_CREDENTIALS); - process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'fake-token'; - - await run(); - - expect(core.setFailed).not.toHaveBeenCalled(); - expect(core.info).toHaveBeenCalledWith('Authenticated as assumedRoleId AROAFAKEASSUMEDROLEID'); - }); - }); - describe('HTTP Proxy Configuration', {}, () => { beforeEach(() => { vi.mocked(core.getInput).mockImplementation(mocks.getInput(mocks.GH_OIDC_INPUTS)); diff --git a/test/profileManager.test.ts b/test/profileManager.test.ts index 5702684..5bf665d 100644 --- a/test/profileManager.test.ts +++ b/test/profileManager.test.ts @@ -95,10 +95,7 @@ describe('Profile Manager', {}, () => { }); it('round-trips through parseIni', {}, () => { - const data = { - dev: { aws_access_key_id: 'AKIA', aws_secret_access_key: 'secret' }, - 'profile prod': { region: 'us-west-2' }, - }; + const data = { dev: { aws_access_key_id: 'AKIA', aws_secret_access_key: 'secret' }, 'profile prod': { region: 'us-west-2' } }; const roundTripped = parseIni(stringifyIni(data)); expect(roundTripped).toEqual(data); }); @@ -199,15 +196,10 @@ describe('Profile Manager', {}, () => { const filePath = '/home/runner/.aws/credentials'; fs.mkdirSync('/home/runner/.aws', { recursive: true }); - mergeProfileSection( - filePath, - 'dev', - { - aws_access_key_id: 'AKIAIOSFODNN7EXAMPLE', - aws_secret_access_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', - }, - false, - ); + mergeProfileSection(filePath, 'dev', { + aws_access_key_id: 'AKIAIOSFODNN7EXAMPLE', + aws_secret_access_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + }, false); const content = fs.readFileSync(filePath, 'utf-8'); const parsed = parseIni(content); @@ -222,26 +214,16 @@ describe('Profile Manager', {}, () => { fs.mkdirSync('/home/runner/.aws', { recursive: true }); // Create initial profile - mergeProfileSection( - filePath, - 'dev', - { - aws_access_key_id: 'AKIAIOSFODNN7EXAMPLE', - aws_secret_access_key: 'devSecretKey', - }, - false, - ); + mergeProfileSection(filePath, 'dev', { + aws_access_key_id: 'AKIAIOSFODNN7EXAMPLE', + aws_secret_access_key: 'devSecretKey', + }, false); // Add second profile - mergeProfileSection( - filePath, - 'prod', - { - aws_access_key_id: 'AKIAPRODEXAMPLE', - aws_secret_access_key: 'prodSecretKey', - }, - false, - ); + mergeProfileSection(filePath, 'prod', { + aws_access_key_id: 'AKIAPRODEXAMPLE', + aws_secret_access_key: 'prodSecretKey', + }, false); const content = fs.readFileSync(filePath, 'utf-8'); const parsed = parseIni(content); @@ -257,28 +239,18 @@ describe('Profile Manager', {}, () => { fs.mkdirSync('/home/runner/.aws', { recursive: true }); // Create initial profile - mergeProfileSection( - filePath, - 'dev', - { - aws_access_key_id: 'OLD_KEY', - aws_secret_access_key: 'oldSecretKey', - aws_session_token: 'oldSessionToken', - }, - false, - ); + mergeProfileSection(filePath, 'dev', { + aws_access_key_id: 'OLD_KEY', + aws_secret_access_key: 'oldSecretKey', + aws_session_token: 'oldSessionToken' + }, false); // Overwrite with new credentials - mergeProfileSection( - filePath, - 'dev', - { - aws_access_key_id: 'NEW_KEY', - aws_secret_access_key: 'newSecretKey', - aws_session_token: 'newSessionToken', - }, - true, - ); + mergeProfileSection(filePath, 'dev', { + aws_access_key_id: 'NEW_KEY', + aws_secret_access_key: 'newSecretKey', + aws_session_token: 'newSessionToken', + }, true); const content = fs.readFileSync(filePath, 'utf-8'); const parsed = parseIni(content); @@ -293,27 +265,17 @@ describe('Profile Manager', {}, () => { fs.mkdirSync('/home/runner/.aws', { recursive: true }); // Create profile with session token - mergeProfileSection( - filePath, - 'dev', - { - aws_access_key_id: 'AKIA', - aws_secret_access_key: 'secret', - aws_session_token: 'old-token', - }, - false, - ); + mergeProfileSection(filePath, 'dev', { + aws_access_key_id: 'AKIA', + aws_secret_access_key: 'secret', + aws_session_token: 'old-token', + }, false); // Overwrite without session token - mergeProfileSection( - filePath, - 'dev', - { - aws_access_key_id: 'AKIA2', - aws_secret_access_key: 'secret2', - }, - true, - ); + mergeProfileSection(filePath, 'dev', { + aws_access_key_id: 'AKIA2', + aws_secret_access_key: 'secret2', + }, true); const content = fs.readFileSync(filePath, 'utf-8'); const parsed = parseIni(content); @@ -328,15 +290,10 @@ describe('Profile Manager', {}, () => { fs.mkdirSync('/home/runner/.aws', { recursive: true }); fs.writeFileSync(filePath, '', { mode: 0o600 }); - mergeProfileSection( - filePath, - 'dev', - { - aws_access_key_id: 'AKIA', - aws_secret_access_key: 'secret', - }, - false, - ); + mergeProfileSection(filePath, 'dev', { + aws_access_key_id: 'AKIA', + aws_secret_access_key: 'secret', + }, false); const content = fs.readFileSync(filePath, 'utf-8'); const parsed = parseIni(content); @@ -350,31 +307,17 @@ describe('Profile Manager', {}, () => { fs.mkdirSync('/home/runner/.aws', { recursive: true }); // Create initial profile - mergeProfileSection( - filePath, - 'dev', - { - aws_access_key_id: 'OLD_KEY', - aws_secret_access_key: 'oldSecretKey', - }, - false, - ); + mergeProfileSection(filePath, 'dev', { + aws_access_key_id: 'OLD_KEY', + aws_secret_access_key: 'oldSecretKey', + }, false); // Overwrite with new credentials - expect(() => - mergeProfileSection( - filePath, - 'dev', - { - aws_access_key_id: 'NEW_KEY', - aws_secret_access_key: 'newSecretKey', - aws_session_token: 'sessionToken', - }, - false, - ), - ).toThrow( - `Profile with name "dev" already exists. Please use the overwrite-aws-profile input if you want to overwrite existing profiles.`, - ); + expect(() => mergeProfileSection(filePath, 'dev', { + aws_access_key_id: 'NEW_KEY', + aws_secret_access_key: 'newSecretKey', + aws_session_token: 'sessionToken', + }, false)).toThrow(`Profile with name "dev" already exists. Please use the overwrite-aws-profile input if you want to overwrite existing profiles.`); const content = fs.readFileSync(filePath, 'utf-8'); const parsed = parseIni(content); @@ -525,7 +468,7 @@ describe('Profile Manager', {}, () => { SecretAccessKey: 'secret', }, 'us-east-1', - false, + false ), ).toThrow('whitespace'); }); @@ -543,7 +486,7 @@ describe('Profile Manager', {}, () => { SecretAccessKey: 'secret', }, 'us-east-1', - false, + false ); expect(fs.existsSync('/custom/credentials')).toBe(true); @@ -558,7 +501,7 @@ describe('Profile Manager', {}, () => { SecretAccessKey: 'secret', }, 'us-east-1', - false, + false ); expect(core.info).toHaveBeenCalledWith('Writing credentials to profile: dev'); @@ -574,7 +517,12 @@ describe('Profile Manager', {}, () => { '[personal]\naws_access_key_id=AKIAPERSONAL\naws_secret_access_key=personalSecret\naws_session_token=personalToken\n', ); - writeProfileFiles('dev', { AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' }, 'us-east-1', false); + writeProfileFiles( + 'dev', + { AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' }, + 'us-east-1', + false, + ); const content = fs.readFileSync(credsPath, 'utf-8'); const parsed = parseIni(content); @@ -592,9 +540,17 @@ describe('Profile Manager', {}, () => { it('preserves pre-existing config with extra keys', {}, () => { const configPath = getProfileFilePaths().config; fs.mkdirSync(require('node:path').dirname(configPath), { recursive: true }); - fs.writeFileSync(configPath, '[profile personal]\nregion=eu-west-1\noutput=json\ncli_pager=\n'); + fs.writeFileSync( + configPath, + '[profile personal]\nregion=eu-west-1\noutput=json\ncli_pager=\n', + ); - writeProfileFiles('dev', { AccessKeyId: 'AKIA', SecretAccessKey: 'secret' }, 'us-east-1', false); + writeProfileFiles( + 'dev', + { AccessKeyId: 'AKIA', SecretAccessKey: 'secret' }, + 'us-east-1', + false + ); const content = fs.readFileSync(configPath, 'utf-8'); const parsed = parseIni(content); @@ -610,9 +566,17 @@ describe('Profile Manager', {}, () => { it('preserves pre-existing default profile when writing a named profile', {}, () => { const credsPath = getProfileFilePaths().credentials; fs.mkdirSync(require('node:path').dirname(credsPath), { recursive: true }); - fs.writeFileSync(credsPath, '[default]\naws_access_key_id=AKIADEFAULT\naws_secret_access_key=defaultSecret\n'); + fs.writeFileSync( + credsPath, + '[default]\naws_access_key_id=AKIADEFAULT\naws_secret_access_key=defaultSecret\n', + ); - writeProfileFiles('dev', { AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' }, 'us-west-2', false); + writeProfileFiles( + 'dev', + { AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' }, + 'us-west-2', + false + ); const content = fs.readFileSync(credsPath, 'utf-8'); const parsed = parseIni(content); @@ -632,7 +596,12 @@ describe('Profile Manager', {}, () => { '# My important comment\n[personal]\naws_access_key_id=AKIA\naws_secret_access_key=secret\n', ); - writeProfileFiles('dev', { AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' }, 'us-east-1', false); + writeProfileFiles( + 'dev', + { AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' }, + 'us-east-1', + false + ); const content = fs.readFileSync(credsPath, 'utf-8') as string; @@ -666,7 +635,12 @@ describe('Profile Manager', {}, () => { fs.mkdirSync('/custom-creds', { recursive: true }); - writeProfileFiles('dev', { AccessKeyId: 'AKIA', SecretAccessKey: 'secret' }, 'us-east-1', false); + writeProfileFiles( + 'dev', + { AccessKeyId: 'AKIA', SecretAccessKey: 'secret' }, + 'us-east-1', + false + ); expect(fs.existsSync('/custom-creds/credentials')).toBe(true); // Config file should be at the default path (under homedir) @@ -683,7 +657,7 @@ describe('Profile Manager', {}, () => { SessionToken: 'FwoGZXIvYXdzEBYaDEXAMPLE', }, 'us-east-1', - false, + false ); const credsPath = getProfileFilePaths().credentials; @@ -698,20 +672,28 @@ describe('Profile Manager', {}, () => { // - LF line endings, trailing newline expect(credContent).toBe( '[dev]\n' + - 'aws_access_key_id = AKIAIOSFODNN7EXAMPLE\n' + - 'aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n' + - 'aws_session_token = FwoGZXIvYXdzEBYaDEXAMPLE\n', + 'aws_access_key_id = AKIAIOSFODNN7EXAMPLE\n' + + 'aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n' + + 'aws_session_token = FwoGZXIvYXdzEBYaDEXAMPLE\n', + ); + expect(configContent).toBe( + '[profile dev]\n' + + 'region = us-east-1\n', ); - expect(configContent).toBe('[profile dev]\n' + 'region = us-east-1\n'); }); it('golden file for multi-profile output', {}, () => { - writeProfileFiles('dev', { AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' }, 'us-east-1', false); + writeProfileFiles( + 'dev', + { AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' }, + 'us-east-1', + false + ); writeProfileFiles( 'prod', { AccessKeyId: 'AKIAPROD', SecretAccessKey: 'prodSecret', SessionToken: 'prodToken' }, 'us-west-2', - false, + false ); const credsPath = getProfileFilePaths().credentials; @@ -722,16 +704,20 @@ describe('Profile Manager', {}, () => { expect(credContent).toBe( '[dev]\n' + - 'aws_access_key_id = AKIADEV\n' + - 'aws_secret_access_key = devSecret\n' + - '\n' + - '[prod]\n' + - 'aws_access_key_id = AKIAPROD\n' + - 'aws_secret_access_key = prodSecret\n' + - 'aws_session_token = prodToken\n', + 'aws_access_key_id = AKIADEV\n' + + 'aws_secret_access_key = devSecret\n' + + '\n' + + '[prod]\n' + + 'aws_access_key_id = AKIAPROD\n' + + 'aws_secret_access_key = prodSecret\n' + + 'aws_session_token = prodToken\n', ); expect(configContent).toBe( - '[profile dev]\n' + 'region = us-east-1\n' + '\n' + '[profile prod]\n' + 'region = us-west-2\n', + '[profile dev]\n' + + 'region = us-east-1\n' + + '\n' + + '[profile prod]\n' + + 'region = us-west-2\n', ); }); });