diff --git a/src/__tests__/credentials.test.ts b/src/__tests__/credentials.test.ts new file mode 100644 index 00000000..c294b8e6 --- /dev/null +++ b/src/__tests__/credentials.test.ts @@ -0,0 +1,59 @@ +import * as given from './given'; + +jest.mock('@aws-sdk/credential-providers', () => ({ + fromNodeProviderChain: () => async () => ({ + accessKeyId: 'DEFAULT_CHAIN', + secretAccessKey: 'default-chain', + }), +})); + +type CredentialProvider = () => Promise>; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const resolveCredentials = (plugin: any): Promise> => + (plugin.clientFactory.credentials as CredentialProvider)(); + +describe('AWS credentials', () => { + // osls 4 removed provider.getCredentials(); calling it throws + // AWS_SDK_V2_SURFACE_REMOVED. Falling back to the default chain would ignore + // provider.profile and --aws-profile, so the osls-resolved config must win. + it('uses the credentials osls 4 resolves via getAwsSdkV3Config()', async () => { + const plugin = given.plugin(); + const identity = { accessKeyId: 'OSLS', secretAccessKey: 'osls' }; + Object.assign(plugin['provider'], { + getAwsSdkV3Config: jest.fn().mockResolvedValue({ + region: 'eu-west-1', + credentials: async () => identity, + }), + getCredentials: () => { + throw new Error('AWS_SDK_V2_SURFACE_REMOVED'); + }, + }); + + await expect(resolveCredentials(plugin)).resolves.toEqual(identity); + }); + + it('accepts static credentials from getAwsSdkV3Config()', async () => { + const plugin = given.plugin(); + const identity = { accessKeyId: 'STATIC', secretAccessKey: 'static' }; + Object.assign(plugin['provider'], { + getAwsSdkV3Config: jest.fn().mockResolvedValue({ credentials: identity }), + }); + + await expect(resolveCredentials(plugin)).resolves.toEqual(identity); + }); + + it('uses getCredentials() on Serverless 3 / osls 3', async () => { + const plugin = given.plugin(); + Object.assign(plugin['provider'], { + getCredentials: () => ({ + credentials: { accessKeyId: 'SLS3', secretAccessKey: 'sls3' }, + }), + }); + + await expect(resolveCredentials(plugin)).resolves.toMatchObject({ + accessKeyId: 'SLS3', + secretAccessKey: 'sls3', + }); + }); +}); diff --git a/src/index.ts b/src/index.ts index e32fbe6d..ed244f89 100644 --- a/src/index.ts +++ b/src/index.ts @@ -70,6 +70,17 @@ const CONSOLE_BASE_URL = 'https://console.aws.amazon.com'; */ const resolveCredentials = (provider: Provider): AwsCredentials => { return async () => { + // osls 4 removed `getCredentials()` (it throws AWS_SDK_V2_SURFACE_REMOVED) + // and exposes its resolved SDK v3 client config instead. + if (typeof provider.getAwsSdkV3Config === 'function') { + const { credentials: v3Credentials } = await provider.getAwsSdkV3Config(); + if (!v3Credentials) { + return fromNodeProviderChain()(); + } + return typeof v3Credentials === 'function' + ? v3Credentials() + : v3Credentials; + } let credentials; try { ({ credentials } = provider.getCredentials()); diff --git a/src/types/serverless.d.ts b/src/types/serverless.d.ts index f6ffd362..a9e271ad 100644 --- a/src/types/serverless.d.ts +++ b/src/types/serverless.d.ts @@ -125,6 +125,14 @@ declare module 'serverless/lib/plugins/aws/provider.js' { region?: string; signatureVersion?: string; }; + // osls 4 only: client config (region, credentials, retries, proxy) for + // plugin-built SDK v3 clients. Replaces request() and getCredentials(). + getAwsSdkV3Config?: () => Promise<{ + region?: string; + credentials?: + | import('@aws-sdk/types').AwsCredentialIdentity + | import('@aws-sdk/types').AwsCredentialIdentityProvider; + }>; } export default Provider;