Rename AwsCredentialsProvider.getCredentials -> AwsCredentialsManager.getCredentialProvider

Signed-off-by: Clare Liguori <liguori@amazon.com>
This commit is contained in:
Clare Liguori
2022-12-01 12:06:32 -08:00
parent e40790d0c2
commit f248b75bde
8 changed files with 211 additions and 184 deletions
@@ -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/,
);
});
@@ -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<string, AwsCredentials>();
const accountCredProviders = new Map<string, AwsCredentialProvider>();
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<string, AwsCredentials>,
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<AwsCredentials> {
async getCredentialProvider(
opts?: AwsCredentialProviderOptions,
): Promise<AwsCredentialProvider> {
// 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
+4 -4
View File
@@ -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';
+16 -5
View File
@@ -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<AwsCredentials>;
getCredentialProvider(
opts?: AwsCredentialProviderOptions,
): Promise<AwsCredentialProvider>;
}