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
+23 -18
View File
@@ -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
+15 -11
View File
@@ -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<AwsCredentials>;
}
// @public
export type AwsCredentialsProviderOptions = {
export type AwsCredentialProviderOptions = {
accountId?: string;
arn?: string;
};
// @public
export interface AwsCredentialsManager {
getCredentialProvider(
opts?: AwsCredentialProviderOptions,
): Promise<AwsCredentialProvider>;
}
// @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<AwsCredentials>;
static fromConfig(config: Config): DefaultAwsCredentialsManager;
getCredentialProvider(
opts?: AwsCredentialProviderOptions,
): Promise<AwsCredentialProvider>;
}
// @public
@@ -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>;
}
@@ -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<S3Client>;
function getMockCredentials(): Promise<AwsCredentials> {
function getMockCredentialProvider(): Promise<AwsCredentialProvider> {
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<AwsCredentials> {
},
});
}
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);
});
});
@@ -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<AwsCredentialIdentityProvider> {
// 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) {