diff --git a/packages/integration-aws-node/README.md b/packages/integration-aws-node/README.md index 6132f4240a..62df26756f 100644 --- a/packages/integration-aws-node/README.md +++ b/packages/integration-aws-node/README.md @@ -11,7 +11,7 @@ Backstage app config. Users can configure IAM user credentials, IAM roles, and profile names for their AWS accounts in their Backstage config. -If the AWS integration configuration is missing, the credentials provider +If the AWS integration configuration is missing, the credentials manager from this package will fall back to the AWS SDK default credentials chain for resources in the main AWS account. The default credentials chain for Node resolves credentials in the @@ -79,32 +79,35 @@ aws: ## Integrate new plugins Backend plugins can provide an AWS ARN or account ID to this library in order to -retrieve a credentials provider for the relevant account that can be fed directly +retrieve a credential provider for the relevant account that can be fed directly to an AWS SDK client. The AWS SDK for Javascript V3 must be used. ```typescript -const awsCredentialsProvider = DefaultAwsCredentialsProvider.fromConfig(config); +const awsCredentialsManager = DefaultAwsCredentialsManager.fromConfig(config); // provide the account ID explicitly -const creds = await awsCredentialsProvider.getCredentials({ accountId }); +const credProvider = await awsCredentialsManager.getCredentialProvider({ + accountId, +}); // OR extract the account ID from the ARN -const creds = await awsCredentialsProvider.getCredentials({ arn }); +const credProvider = await awsCredentialsManager.getCredentialProvider({ arn }); // OR provide neither to get main account's credentials -const creds = await awsCredentialsProvider.getCredentials({}); +const credProvider = await awsCredentialsManager.getCredentialProvider({}); -// Example constructing an AWS Proton client with the returned credentials provider +// Example constructing an AWS Proton client with the returned credential provider const client = new ProtonClient({ region, - credentialDefaultProvider: () => creds.provider, + credentialDefaultProvider: () => credProvider.sdkCredentialProvider, }); ``` -Depending on the nature of your plguin, you may either have the user specify the +Depending on the nature of your plugin, you may either have the user specify the relevant ARN or account ID in a catalog entity annotation or in the static Backstage app configuration for your plugin. -For example, you can create a new catalog entity annotation for your plugin: +For example, you can create a new catalog entity annotation for your plugin containing +either an AWS account ID or ARN: ```yaml apiVersion: backstage.io/v1alpha1 @@ -117,7 +120,7 @@ metadata: my-other-plugin.io/aws-dynamodb-table: 'arn:aws:dynamodb:us-east-2:123456789012:table/example-table' ``` -In your plugin, read the annotation value so that you can retrieve the credentials provider: +In your plugin, read the annotation value so that you can retrieve the credential provider: ```typescript const MY_AWS_ACCOUNT_ID_ANNOTATION = 'my-plugin.io/aws-account-id'; @@ -126,7 +129,7 @@ const getAwsAccountId = (entity: Entity) => entity.metadata.annotations?.[MY_AWS_ACCOUNT_ID_ANNOTATION]); ``` -Alternatively, you can create a new configuration field for your plugin: +Alternatively, you can create a new Backstage app configuration field for your plugin: ```yaml # app-config.yaml @@ -138,18 +141,20 @@ my-other-plugin: awsDynamoDbTable: 'arn:aws:dynamodb:us-east-2:123456789012:table/example-table' ``` -In your plugin, read the configuration value so that you can retrieve the credentials provider: +In your plugin, read the configuration value so that you can retrieve the credential provider: ```typescript // Read an account ID from your plugin's configuration -const awsCredentialsProvider = DefaultAwsCredentialsProvider.fromConfig(config); -const accountId = config.getString('my-plugin.awsAccountId'); -const creds = await awsCredentialsProvider.getCredentials({ accountId }); +const awsCredentialsManager = DefaultAwsCredentialsManager.fromConfig(config); +const accountId = config.getOptionalString('my-plugin.awsAccountId'); +const credProvider = await awsCredentialsManager.getCredentialProvider({ + accountId, +}); // Or, read an AWS ARN from your plugin's configuration -const awsCredentialsProvider = DefaultAwsCredentialsProvider.fromConfig(config); +const awsCredentialsManager = DefaultAwsCredentialsManager.fromConfig(config); const arn = config.getString('my-other-plugin.awsDynamoDbTable'); -const creds = await awsCredentialsProvider.getCredentials({ arn }); +const credProvider = await awsCredentialsManager.getCredentialProvider({ arn }); ``` ## Links diff --git a/packages/integration-aws-node/api-report.md b/packages/integration-aws-node/api-report.md index bac43c67d4..58bdde4090 100644 --- a/packages/integration-aws-node/api-report.md +++ b/packages/integration-aws-node/api-report.md @@ -7,23 +7,25 @@ import { AwsCredentialIdentityProvider } from '@aws-sdk/types'; import { Config } from '@backstage/config'; // @public -export type AwsCredentials = { +export type AwsCredentialProvider = { accountId?: string; stsRegion?: string; - provider: AwsCredentialIdentityProvider; + sdkCredentialProvider: AwsCredentialIdentityProvider; }; // @public -export interface AwsCredentialsProvider { - getCredentials(opts?: AwsCredentialsProviderOptions): Promise; -} - -// @public -export type AwsCredentialsProviderOptions = { +export type AwsCredentialProviderOptions = { accountId?: string; arn?: string; }; +// @public +export interface AwsCredentialsManager { + getCredentialProvider( + opts?: AwsCredentialProviderOptions, + ): Promise; +} + // @public export type AwsIntegrationAccountConfig = { accountId: string; @@ -60,10 +62,12 @@ export type AwsIntegrationMainAccountConfig = { }; // @public -export class DefaultAwsCredentialsProvider implements AwsCredentialsProvider { +export class DefaultAwsCredentialsManager implements AwsCredentialsManager { // (undocumented) - static fromConfig(config: Config): DefaultAwsCredentialsProvider; - getCredentials(opts?: AwsCredentialsProviderOptions): Promise; + static fromConfig(config: Config): DefaultAwsCredentialsManager; + getCredentialProvider( + opts?: AwsCredentialProviderOptions, + ): Promise; } // @public diff --git a/packages/integration-aws-node/src/DefaultAwsCredentialsProvider.test.ts b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts similarity index 66% rename from packages/integration-aws-node/src/DefaultAwsCredentialsProvider.test.ts rename to packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts index 60f2f558b8..1b21d43338 100644 --- a/packages/integration-aws-node/src/DefaultAwsCredentialsProvider.test.ts +++ b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { DefaultAwsCredentialsProvider } from './DefaultAwsCredentialsProvider'; +import { DefaultAwsCredentialsManager } from './DefaultAwsCredentialsManager'; import { mockClient, AwsClientStub } from 'aws-sdk-client-mock'; import 'aws-sdk-client-mock-jest'; import { @@ -31,7 +31,7 @@ let config: Config; jest.mock('fs', () => ({ promises: { readFile: jest.fn() } })); -describe('DefaultAwsCredentialsProvider', () => { +describe('DefaultAwsCredentialsManager', () => { beforeEach(() => { process.env = { ...env }; jest.resetAllMocks(); @@ -145,16 +145,16 @@ describe('DefaultAwsCredentialsProvider', () => { process.env = env; }); - describe('#getCredentials', () => { + describe('#getCredentialProvider', () => { it('retrieves assume-role creds for the given account ID and caches the provider', async () => { - const provider = DefaultAwsCredentialsProvider.fromConfig(config); - const awsCredentials = await provider.getCredentials({ + const provider = DefaultAwsCredentialsManager.fromConfig(config); + const awsCredentialProvider = await provider.getCredentialProvider({ accountId: '111111111111', }); - expect(awsCredentials.accountId).toEqual('111111111111'); + expect(awsCredentialProvider.accountId).toEqual('111111111111'); - const creds = await awsCredentials.provider(); + const creds = await awsCredentialProvider.sdkCredentialProvider(); expect(creds).toEqual({ accessKeyId: 'ACCESS_KEY_ID_1', secretAccessKey: 'SECRET_ACCESS_KEY_1', @@ -162,23 +162,23 @@ describe('DefaultAwsCredentialsProvider', () => { expiration: new Date('2022-01-01'), }); - const awsCredentials2 = await provider.getCredentials({ + const awsCredentialProvider2 = await provider.getCredentialProvider({ accountId: '111111111111', }); - expect(awsCredentials).toBe(awsCredentials2); + expect(awsCredentialProvider).toBe(awsCredentialProvider2); expect(stsMock).toHaveReceivedCommandTimes(AssumeRoleCommand, 1); }); it('retrieves assume-role creds in another partition for the given account ID', async () => { - const provider = DefaultAwsCredentialsProvider.fromConfig(config); - const awsCredentials = await provider.getCredentials({ + const provider = DefaultAwsCredentialsManager.fromConfig(config); + const awsCredentialProvider = await provider.getCredentialProvider({ accountId: '222222222222', }); - expect(awsCredentials.accountId).toEqual('222222222222'); + expect(awsCredentialProvider.accountId).toEqual('222222222222'); - const creds = await awsCredentials.provider(); + const creds = await awsCredentialProvider.sdkCredentialProvider(); expect(creds).toEqual({ accessKeyId: 'ACCESS_KEY_ID_2', secretAccessKey: 'SECRET_ACCESS_KEY_2', @@ -188,14 +188,14 @@ describe('DefaultAwsCredentialsProvider', () => { }); it('retrieves assume-role creds for an account using the account defaults', async () => { - const provider = DefaultAwsCredentialsProvider.fromConfig(config); - const awsCredentials = await provider.getCredentials({ + const provider = DefaultAwsCredentialsManager.fromConfig(config); + const awsCredentialProvider = await provider.getCredentialProvider({ accountId: '999999999999', }); - expect(awsCredentials.accountId).toEqual('999999999999'); + expect(awsCredentialProvider.accountId).toEqual('999999999999'); - const creds = await awsCredentials.provider(); + const creds = await awsCredentialProvider.sdkCredentialProvider(); expect(creds).toEqual({ accessKeyId: 'ACCESS_KEY_ID_9', secretAccessKey: 'SECRET_ACCESS_KEY_9', @@ -205,14 +205,14 @@ describe('DefaultAwsCredentialsProvider', () => { }); it('retrieves static creds for the given account ID', async () => { - const provider = DefaultAwsCredentialsProvider.fromConfig(config); - const awsCredentials = await provider.getCredentials({ + const provider = DefaultAwsCredentialsManager.fromConfig(config); + const awsCredentialProvider = await provider.getCredentialProvider({ accountId: '333333333333', }); - expect(awsCredentials.accountId).toEqual('333333333333'); + expect(awsCredentialProvider.accountId).toEqual('333333333333'); - const creds = await awsCredentials.provider(); + const creds = await awsCredentialProvider.sdkCredentialProvider(); expect(creds).toEqual({ accessKeyId: 'my-access-key', secretAccessKey: 'my-secret-access-key', @@ -228,14 +228,14 @@ describe('DefaultAwsCredentialsProvider', () => { }, }, }); - const provider = DefaultAwsCredentialsProvider.fromConfig(minConfig); - const awsCredentials = await provider.getCredentials({ + const provider = DefaultAwsCredentialsManager.fromConfig(minConfig); + const awsCredentialProvider = await provider.getCredentialProvider({ accountId: '123456789012', }); - expect(awsCredentials.accountId).toEqual('123456789012'); + expect(awsCredentialProvider.accountId).toEqual('123456789012'); - const creds = await awsCredentials.provider(); + const creds = await awsCredentialProvider.sdkCredentialProvider(); expect(creds).toEqual({ accessKeyId: 'GHI', secretAccessKey: 'JKL', @@ -251,23 +251,23 @@ describe('DefaultAwsCredentialsProvider', () => { }, }, }); - const provider = DefaultAwsCredentialsProvider.fromConfig(minConfig); - const awsCredentials1 = await provider.getCredentials({}); - const awsCredentials2 = await provider.getCredentials({}); + const provider = DefaultAwsCredentialsManager.fromConfig(minConfig); + const awsCredentialProvider1 = await provider.getCredentialProvider({}); + const awsCredentialProvider2 = await provider.getCredentialProvider({}); - expect(awsCredentials1).toBe(awsCredentials2); + expect(awsCredentialProvider1).toBe(awsCredentialProvider2); expect(stsMock).toHaveReceivedCommandTimes(GetCallerIdentityCommand, 1); }); it('retrieves the ini provider chain for the given account ID', async () => { - const provider = DefaultAwsCredentialsProvider.fromConfig(config); - const awsCredentials = await provider.getCredentials({ + const provider = DefaultAwsCredentialsManager.fromConfig(config); + const awsCredentialProvider = await provider.getCredentialProvider({ accountId: '555555555555', }); - expect(awsCredentials.accountId).toEqual('555555555555'); + expect(awsCredentialProvider.accountId).toEqual('555555555555'); - const creds = await awsCredentials.provider(); + const creds = await awsCredentialProvider.sdkCredentialProvider(); expect(creds).toEqual({ accessKeyId: 'ACCESS_KEY_ID_9', secretAccessKey: 'SECRET_ACCESS_KEY_9', @@ -275,14 +275,14 @@ describe('DefaultAwsCredentialsProvider', () => { }); it('retrieves the default cred provider chain for the given account ID', async () => { - const provider = DefaultAwsCredentialsProvider.fromConfig(config); - const awsCredentials = await provider.getCredentials({ + const provider = DefaultAwsCredentialsManager.fromConfig(config); + const awsCredentialProvider = await provider.getCredentialProvider({ accountId: '444444444444', }); - expect(awsCredentials.accountId).toEqual('444444444444'); + expect(awsCredentialProvider.accountId).toEqual('444444444444'); - const creds = await awsCredentials.provider(); + const creds = await awsCredentialProvider.sdkCredentialProvider(); expect(creds).toEqual({ accessKeyId: 'ACCESS_KEY_ID_10', secretAccessKey: 'SECRET_ACCESS_KEY_10', @@ -299,14 +299,14 @@ describe('DefaultAwsCredentialsProvider', () => { }, }, }); - const provider = DefaultAwsCredentialsProvider.fromConfig(minConfig); - const awsCredentials = await provider.getCredentials({ + const provider = DefaultAwsCredentialsManager.fromConfig(minConfig); + const awsCredentialProvider = await provider.getCredentialProvider({ accountId: '123456789012', }); - expect(awsCredentials.accountId).toEqual('123456789012'); + expect(awsCredentialProvider.accountId).toEqual('123456789012'); - const creds = await awsCredentials.provider(); + const creds = await awsCredentialProvider.sdkCredentialProvider(); expect(creds).toEqual({ accessKeyId: 'ACCESS_KEY_ID_9', secretAccessKey: 'SECRET_ACCESS_KEY_9', @@ -317,14 +317,14 @@ describe('DefaultAwsCredentialsProvider', () => { const minConfig = new ConfigReader({ aws: {}, }); - const provider = DefaultAwsCredentialsProvider.fromConfig(minConfig); - const awsCredentials = await provider.getCredentials({ + const provider = DefaultAwsCredentialsManager.fromConfig(minConfig); + const awsCredentialProvider = await provider.getCredentialProvider({ accountId: '123456789012', }); - expect(awsCredentials.accountId).toEqual('123456789012'); + expect(awsCredentialProvider.accountId).toEqual('123456789012'); - const creds = await awsCredentials.provider(); + const creds = await awsCredentialProvider.sdkCredentialProvider(); expect(creds).toEqual({ accessKeyId: 'ACCESS_KEY_ID_10', secretAccessKey: 'SECRET_ACCESS_KEY_10', @@ -335,14 +335,14 @@ describe('DefaultAwsCredentialsProvider', () => { it('retrieves default cred provider chain from the main account when there is no AWS integration config', async () => { const minConfig = new ConfigReader({}); - const provider = DefaultAwsCredentialsProvider.fromConfig(minConfig); - const awsCredentials = await provider.getCredentials({ + const provider = DefaultAwsCredentialsManager.fromConfig(minConfig); + const awsCredentialProvider = await provider.getCredentialProvider({ accountId: '123456789012', }); - expect(awsCredentials.accountId).toEqual('123456789012'); + expect(awsCredentialProvider.accountId).toEqual('123456789012'); - const creds = await awsCredentials.provider(); + const creds = await awsCredentialProvider.sdkCredentialProvider(); expect(creds).toEqual({ accessKeyId: 'ACCESS_KEY_ID_10', secretAccessKey: 'SECRET_ACCESS_KEY_10', @@ -352,14 +352,14 @@ describe('DefaultAwsCredentialsProvider', () => { }); it('extracts the account ID from an ARN', async () => { - const provider = DefaultAwsCredentialsProvider.fromConfig(config); - const awsCredentials = await provider.getCredentials({ + const provider = DefaultAwsCredentialsManager.fromConfig(config); + const awsCredentialProvider = await provider.getCredentialProvider({ arn: 'arn:aws:ecs:region:111111111111:service/cluster-name/service-name', }); - expect(awsCredentials.accountId).toEqual('111111111111'); + expect(awsCredentialProvider.accountId).toEqual('111111111111'); - const creds = await awsCredentials.provider(); + const creds = await awsCredentialProvider.sdkCredentialProvider(); expect(creds).toEqual({ accessKeyId: 'ACCESS_KEY_ID_1', secretAccessKey: 'SECRET_ACCESS_KEY_1', @@ -369,14 +369,14 @@ describe('DefaultAwsCredentialsProvider', () => { }); it('falls back to main account credentials when account ID cannot be extracted from the ARN', async () => { - const provider = DefaultAwsCredentialsProvider.fromConfig(config); - const awsCredentials = await provider.getCredentials({ + const provider = DefaultAwsCredentialsManager.fromConfig(config); + const awsCredentialProvider = await provider.getCredentialProvider({ arn: 'arn:aws:s3:::bucket_name', }); - expect(awsCredentials.accountId).toEqual('123456789012'); + expect(awsCredentialProvider.accountId).toEqual('123456789012'); - const creds = await awsCredentials.provider(); + const creds = await awsCredentialProvider.sdkCredentialProvider(); expect(creds).toEqual({ accessKeyId: 'GHI', secretAccessKey: 'JKL', @@ -384,12 +384,12 @@ describe('DefaultAwsCredentialsProvider', () => { }); it('falls back to main account credentials when neither account ID nor ARN are provided', async () => { - const provider = DefaultAwsCredentialsProvider.fromConfig(config); - const awsCredentials = await provider.getCredentials({}); + const provider = DefaultAwsCredentialsManager.fromConfig(config); + const awsCredentialProvider = await provider.getCredentialProvider({}); - expect(awsCredentials.accountId).toEqual('123456789012'); + expect(awsCredentialProvider.accountId).toEqual('123456789012'); - const creds = await awsCredentials.provider(); + const creds = await awsCredentialProvider.sdkCredentialProvider(); expect(creds).toEqual({ accessKeyId: 'GHI', secretAccessKey: 'JKL', @@ -397,12 +397,12 @@ describe('DefaultAwsCredentialsProvider', () => { }); it('falls back to main account credentials when no options are provided', async () => { - const provider = DefaultAwsCredentialsProvider.fromConfig(config); - const awsCredentials = await provider.getCredentials(); + const provider = DefaultAwsCredentialsManager.fromConfig(config); + const awsCredentialProvider = await provider.getCredentialProvider(); - expect(awsCredentials.accountId).toEqual('123456789012'); + expect(awsCredentialProvider.accountId).toEqual('123456789012'); - const creds = await awsCredentials.provider(); + const creds = await awsCredentialProvider.sdkCredentialProvider(); expect(creds).toEqual({ accessKeyId: 'GHI', secretAccessKey: 'JKL', @@ -413,16 +413,16 @@ describe('DefaultAwsCredentialsProvider', () => { const minConfig = new ConfigReader({ aws: {}, }); - const provider = DefaultAwsCredentialsProvider.fromConfig(minConfig); + const provider = DefaultAwsCredentialsManager.fromConfig(minConfig); await expect( - provider.getCredentials({ accountId: '111222333444' }), + provider.getCredentialProvider({ accountId: '111222333444' }), ).rejects.toThrow(/no AWS integration that matches 111222333444/); }); it('rejects main account that has invalid credentials', async () => { stsMock.on(GetCallerIdentityCommand).rejects('No credentials found'); - const provider = DefaultAwsCredentialsProvider.fromConfig(config); - await expect(provider.getCredentials({})).rejects.toThrow( + const provider = DefaultAwsCredentialsManager.fromConfig(config); + await expect(provider.getCredentialProvider({})).rejects.toThrow( /No credentials found/, ); }); diff --git a/packages/integration-aws-node/src/DefaultAwsCredentialsProvider.ts b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.ts similarity index 66% rename from packages/integration-aws-node/src/DefaultAwsCredentialsProvider.ts rename to packages/integration-aws-node/src/DefaultAwsCredentialsManager.ts index 8b26b5fe84..7799f08b4d 100644 --- a/packages/integration-aws-node/src/DefaultAwsCredentialsProvider.ts +++ b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.ts @@ -21,9 +21,9 @@ import { AwsIntegrationMainAccountConfig, } from './config'; import { - AwsCredentials, - AwsCredentialsProvider, - AwsCredentialsProviderOptions, + AwsCredentialsManager, + AwsCredentialProvider, + AwsCredentialProviderOptions, } from './types'; import { GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts'; import { @@ -36,20 +36,20 @@ import { parse } from '@aws-sdk/util-arn-parser'; import { Config } from '@backstage/config'; /** - * Retrieves the account ID for the given credentials provider from STS. + * Retrieves the account ID for the given credential provider from STS. */ -async function fillInAccountId(creds: AwsCredentials) { - if (creds.accountId) { +async function fillInAccountId(credProvider: AwsCredentialProvider) { + if (credProvider.accountId) { return; } const client = new STSClient({ - region: creds.stsRegion, - customUserAgent: 'backstage-aws-credentials-provider', - credentialDefaultProvider: () => creds.provider, + region: credProvider.stsRegion, + customUserAgent: 'backstage-aws-credentials-manager', + credentialDefaultProvider: () => credProvider.sdkCredentialProvider, }); const resp = await client.send(new GetCallerIdentityCommand({})); - creds.accountId = resp.Account!; + credProvider.accountId = resp.Account!; } function getStaticCredentials( @@ -72,7 +72,7 @@ function getProfileCredentials( profile, clientConfig: { region, - customUserAgent: 'backstage-aws-credentials-provider', + customUserAgent: 'backstage-aws-credentials-manager', }, }); } @@ -91,9 +91,9 @@ function getDefaultCredentialsChain(): AwsCredentialIdentityProvider { * 4. Profile creds * 5. Default AWS SDK creds chain */ -function getAccountCredentialsProvider( +function getSdkCredentialProvider( config: AwsIntegrationAccountConfig, - mainAccountCreds: AwsCredentialIdentityProvider, + mainAccountCredProvider: AwsCredentialIdentityProvider, ): AwsCredentialIdentityProvider { if (config.roleName) { const region = config.region ?? 'us-east-1'; @@ -102,7 +102,7 @@ function getAccountCredentialsProvider( return fromTemporaryCredentials({ masterCredentials: config.accessKeyId ? getStaticCredentials(config.accessKeyId!, config.secretAccessKey!) - : mainAccountCreds, + : mainAccountCredProvider, params: { RoleArn: `arn:${partition}:iam::${config.accountId}:role/${config.roleName}`, RoleSessionName: 'backstage', @@ -110,7 +110,7 @@ function getAccountCredentialsProvider( }, clientConfig: { region, - customUserAgent: 'backstage-aws-credentials-provider', + customUserAgent: 'backstage-aws-credentials-manager', }, }); } @@ -134,7 +134,7 @@ function getAccountCredentialsProvider( * 2. Profile creds * 3. Default AWS SDK creds chain */ -function getMainAccountCredentialsProvider( +function getMainAccountSdkCredentialProvider( config: AwsIntegrationMainAccountConfig, ): AwsCredentialIdentityProvider { if (config.accessKeyId) { @@ -153,8 +153,8 @@ function getMainAccountCredentialsProvider( * * @public */ -export class DefaultAwsCredentialsProvider implements AwsCredentialsProvider { - static fromConfig(config: Config): DefaultAwsCredentialsProvider { +export class DefaultAwsCredentialsManager implements AwsCredentialsManager { + static fromConfig(config: Config): DefaultAwsCredentialsManager { const awsConfig = config.has('aws') ? readAwsIntegrationConfig(config.getConfig('aws')) : { @@ -163,63 +163,66 @@ export class DefaultAwsCredentialsProvider implements AwsCredentialsProvider { accountDefaults: {}, }; - const mainAccountProvider = getMainAccountCredentialsProvider( + const mainAccountSdkCredProvider = getMainAccountSdkCredentialProvider( awsConfig.mainAccount, ); - const mainAccountCreds: AwsCredentials = { - provider: mainAccountProvider, + const mainAccountCredProvider: AwsCredentialProvider = { + sdkCredentialProvider: mainAccountSdkCredProvider, }; - const accountCreds = new Map(); + const accountCredProviders = new Map(); for (const accountConfig of awsConfig.accounts) { - const provider = getAccountCredentialsProvider( + const sdkCredentialProvider = getSdkCredentialProvider( accountConfig, - mainAccountCreds.provider, + mainAccountSdkCredProvider, ); - accountCreds.set(accountConfig.accountId, { + accountCredProviders.set(accountConfig.accountId, { accountId: accountConfig.accountId, stsRegion: accountConfig.region, - provider, + sdkCredentialProvider, }); } - return new DefaultAwsCredentialsProvider( - accountCreds, + return new DefaultAwsCredentialsManager( + accountCredProviders, awsConfig.accountDefaults, - mainAccountCreds, + mainAccountCredProvider, ); } private constructor( - private readonly accountCredentials: Map, + private readonly accountCredentialProviders: Map< + string, + AwsCredentialProvider + >, private readonly accountDefaults: AwsIntegrationDefaultAccountConfig, - private readonly mainAccountCredentials: AwsCredentials, + private readonly mainAccountCredentialProvider: AwsCredentialProvider, ) {} /** - * Returns {@link AwsCredentials} for a given AWS account. + * Returns an {@link AwsCredentialProvider} for a given AWS account. * * @example * ```ts - * const { provider } = await getCredentials({ + * const { provider } = await getCredentialProvider({ * accountId: '0123456789012', * }) * - * const { provider } = await getCredentials({ + * const { provider } = await getCredentialProvider({ * arn: 'arn:aws:ecs:us-west-2:123456789012:service/my-http-service' * }) * ``` * * @param opts - the AWS account ID or AWS resource ARN - * @returns A promise of {@link AwsCredentials}. + * @returns A promise of {@link AwsCredentialProvider}. */ - async getCredentials( - opts?: AwsCredentialsProviderOptions, - ): Promise { + async getCredentialProvider( + opts?: AwsCredentialProviderOptions, + ): Promise { // If no options provided, fall back to the main account if (!opts) { - await fillInAccountId(this.mainAccountCredentials); - return this.mainAccountCredentials; + await fillInAccountId(this.mainAccountCredentialProvider); + return this.mainAccountCredentialProvider; } // Determine the account ID: either explicitly provided or extracted from the provided ARN @@ -232,13 +235,13 @@ export class DefaultAwsCredentialsProvider implements AwsCredentialsProvider { // If the account ID was not provided (explicitly or in the ARN), // fall back to the main account if (!accountId) { - await fillInAccountId(this.mainAccountCredentials); - return this.mainAccountCredentials; + await fillInAccountId(this.mainAccountCredentialProvider); + return this.mainAccountCredentialProvider; } // Return a cached provider if available - if (this.accountCredentials.has(accountId)) { - return this.accountCredentials.get(accountId)!; + if (this.accountCredentialProviders.has(accountId)) { + return this.accountCredentialProviders.get(accountId)!; } // First, fall back to using the account defaults @@ -250,20 +253,23 @@ export class DefaultAwsCredentialsProvider implements AwsCredentialsProvider { region: this.accountDefaults.region, externalId: this.accountDefaults.externalId, }; - const provider = getAccountCredentialsProvider( + const sdkCredentialProvider = getSdkCredentialProvider( config, - this.mainAccountCredentials.provider, + this.mainAccountCredentialProvider.sdkCredentialProvider, ); - const creds: AwsCredentials = { accountId, provider }; - this.accountCredentials.set(accountId, creds); - return creds; + const credProvider: AwsCredentialProvider = { + accountId, + sdkCredentialProvider, + }; + this.accountCredentialProviders.set(accountId, credProvider); + return credProvider; } // Then, fall back to using the main account, but only // if the account requested matches the main account ID - await fillInAccountId(this.mainAccountCredentials); - if (accountId === this.mainAccountCredentials.accountId) { - return this.mainAccountCredentials; + await fillInAccountId(this.mainAccountCredentialProvider); + if (accountId === this.mainAccountCredentialProvider.accountId) { + return this.mainAccountCredentialProvider; } // Otherwise, the account needs to be explicitly configured in Backstage diff --git a/packages/integration-aws-node/src/index.ts b/packages/integration-aws-node/src/index.ts index d0a86acb47..0b6dc630db 100644 --- a/packages/integration-aws-node/src/index.ts +++ b/packages/integration-aws-node/src/index.ts @@ -21,9 +21,9 @@ export type { AwsIntegrationDefaultAccountConfig, AwsIntegrationMainAccountConfig, } from './config'; -export { DefaultAwsCredentialsProvider } from './DefaultAwsCredentialsProvider'; +export { DefaultAwsCredentialsManager } from './DefaultAwsCredentialsManager'; export type { - AwsCredentials, - AwsCredentialsProvider, - AwsCredentialsProviderOptions, + AwsCredentialsManager, + AwsCredentialProvider, + AwsCredentialProviderOptions, } from './types'; diff --git a/packages/integration-aws-node/src/types.ts b/packages/integration-aws-node/src/types.ts index 44f7850cf8..3ff069b20b 100644 --- a/packages/integration-aws-node/src/types.ts +++ b/packages/integration-aws-node/src/types.ts @@ -20,10 +20,19 @@ import { AwsCredentialIdentityProvider } from '@aws-sdk/types'; * * @public */ -export type AwsCredentials = { +export type AwsCredentialProvider = { + /** + * The AWS account ID of these credentials + */ accountId?: string; + /** + * The STS region used with these credentials + */ stsRegion?: string; - provider: AwsCredentialIdentityProvider; + /** + * The credential identity provider to use when creating AWS SDK for Javascript V3 clients + */ + sdkCredentialProvider: AwsCredentialIdentityProvider; }; /** @@ -31,7 +40,7 @@ export type AwsCredentials = { * * @public */ -export type AwsCredentialsProviderOptions = { +export type AwsCredentialProviderOptions = { /** * The AWS account ID, e.g. '0123456789012' */ @@ -49,9 +58,11 @@ export type AwsCredentialsProviderOptions = { * * @public */ -export interface AwsCredentialsProvider { +export interface AwsCredentialsManager { /** * Get credentials for an AWS account. */ - getCredentials(opts?: AwsCredentialsProviderOptions): Promise; + getCredentialProvider( + opts?: AwsCredentialProviderOptions, + ): Promise; } diff --git a/plugins/techdocs-node/src/stages/publish/awsS3.test.ts b/plugins/techdocs-node/src/stages/publish/awsS3.test.ts index ff61ac58a2..f3b2162a6b 100644 --- a/plugins/techdocs-node/src/stages/publish/awsS3.test.ts +++ b/plugins/techdocs-node/src/stages/publish/awsS3.test.ts @@ -28,9 +28,9 @@ import { getVoidLogger } from '@backstage/backend-common'; import { Entity, DEFAULT_NAMESPACE } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { - AwsCredentials, - AwsCredentialsProviderOptions, - DefaultAwsCredentialsProvider, + AwsCredentialProvider, + AwsCredentialProviderOptions, + DefaultAwsCredentialsManager, } from '@backstage/integration-aws-node'; import { mockClient, AwsClientStub } from 'aws-sdk-client-mock'; import express from 'express'; @@ -45,9 +45,9 @@ import { Readable } from 'stream'; const env = process.env; let s3Mock: AwsClientStub; -function getMockCredentials(): Promise { +function getMockCredentialProvider(): Promise { return Promise.resolve({ - provider: async () => { + sdkCredentialProvider: async () => { return Promise.resolve({ accessKeyId: 'MY_ACCESS_KEY_ID', secretAccessKey: 'MY_SECRET_ACCESS_KEY', @@ -55,9 +55,9 @@ function getMockCredentials(): Promise { }, }); } -const credsProviderMock = jest.spyOn( - DefaultAwsCredentialsProvider.prototype, - 'getCredentials', +const getCredProviderMock = jest.spyOn( + DefaultAwsCredentialsManager.prototype, + 'getCredentialProvider', ); const getEntityRootDir = (entity: Entity) => { @@ -178,8 +178,8 @@ describe('AwsS3Publish', () => { process.env.AWS_REGION = 'us-west-2'; jest.resetAllMocks(); - credsProviderMock.mockImplementation((_?: AwsCredentialsProviderOptions) => - getMockCredentials(), + getCredProviderMock.mockImplementation((_?: AwsCredentialProviderOptions) => + getMockCredentialProvider(), ); mockFs({ @@ -249,10 +249,10 @@ describe('AwsS3Publish', () => { describe('buildCredentials', () => { it('should retrieve credentials for a specific account ID', async () => { await createPublisherFromConfig(); - expect(credsProviderMock).toHaveBeenCalledWith({ + expect(getCredProviderMock).toHaveBeenCalledWith({ accountId: '111111111111', }); - expect(credsProviderMock).toHaveBeenCalledTimes(1); + expect(getCredProviderMock).toHaveBeenCalledTimes(1); }); it('should retrieve default credentials when no config is present', async () => { @@ -268,8 +268,8 @@ describe('AwsS3Publish', () => { }); await AwsS3Publish.fromConfig(mockConfig, logger); - expect(credsProviderMock).toHaveBeenCalledWith(); - expect(credsProviderMock).toHaveBeenCalledTimes(1); + expect(getCredProviderMock).toHaveBeenCalledWith(); + expect(getCredProviderMock).toHaveBeenCalledTimes(1); }); it('should fall back to deprecated method of retrieving credentials', async () => { @@ -290,7 +290,7 @@ describe('AwsS3Publish', () => { }); await AwsS3Publish.fromConfig(mockConfig, logger); - expect(credsProviderMock).toHaveBeenCalledTimes(0); + expect(getCredProviderMock).toHaveBeenCalledTimes(0); }); }); diff --git a/plugins/techdocs-node/src/stages/publish/awsS3.ts b/plugins/techdocs-node/src/stages/publish/awsS3.ts index c71a25cfe4..a13baef926 100644 --- a/plugins/techdocs-node/src/stages/publish/awsS3.ts +++ b/plugins/techdocs-node/src/stages/publish/awsS3.ts @@ -17,8 +17,8 @@ import { Entity, CompoundEntityRef } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { assertError, ForwardedError } from '@backstage/errors'; import { - AwsCredentialsProvider, - DefaultAwsCredentialsProvider, + AwsCredentialsManager, + DefaultAwsCredentialsManager, } from '@backstage/integration-aws-node'; import { GetObjectCommand, @@ -136,9 +136,9 @@ export class AwsS3Publish implements PublisherBase { const credentialsConfig = config.getOptionalConfig( 'techdocs.publisher.awsS3.credentials', ); - const credsProvider = DefaultAwsCredentialsProvider.fromConfig(config); - const credentials = await AwsS3Publish.buildCredentials( - credsProvider, + const credsManager = DefaultAwsCredentialsManager.fromConfig(config); + const sdkCredentialProvider = await AwsS3Publish.buildCredentials( + credsManager, accountId, credentialsConfig, region, @@ -158,7 +158,7 @@ export class AwsS3Publish implements PublisherBase { const storageClient = new S3Client({ customUserAgent: 'backstage-aws-techdocs-s3-publisher', - credentialDefaultProvider: () => credentials, + credentialDefaultProvider: () => sdkCredentialProvider, ...(region && { region }), ...(endpoint && { endpoint }), ...(s3ForcePathStyle && { s3ForcePathStyle }), @@ -192,20 +192,21 @@ export class AwsS3Publish implements PublisherBase { } private static async buildCredentials( - credsProvider: AwsCredentialsProvider, + credsManager: AwsCredentialsManager, accountId?: string, config?: Config, region?: string, ): Promise { // Pull credentials for the specified account ID from the 'aws' config section if (accountId) { - return (await credsProvider.getCredentials({ accountId })).provider; + return (await credsManager.getCredentialProvider({ accountId })) + .sdkCredentialProvider; } // Fall back to the default credential chain if neither account ID // nor explicit credentials are provided if (!config) { - return (await credsProvider.getCredentials()).provider; + return (await credsManager.getCredentialProvider()).sdkCredentialProvider; } // Pull credentials from the techdocs config section (deprecated) @@ -214,7 +215,7 @@ export class AwsS3Publish implements PublisherBase { const explicitCredentials: AwsCredentialIdentityProvider = accessKeyId && secretAccessKey ? AwsS3Publish.buildStaticCredentials(accessKeyId, secretAccessKey) - : (await credsProvider.getCredentials()).provider; + : (await credsManager.getCredentialProvider()).sdkCredentialProvider; const roleArn = config.getOptionalString('roleArn'); if (roleArn) {