From 2ef5bc7ea1034fe6c74d42e7e0e78fe14b786d30 Mon Sep 17 00:00:00 2001 From: Joel Low Date: Tue, 9 Mar 2021 13:20:24 +0800 Subject: [PATCH] Implement proper AWS Credentials precedence This properly configures the precedence for explicit assume-role ARNs, explicit AWS credentials (via access keys), and the default fallback for the AWS SDK. The general precedence of using: 1. explicitly provided credentials 2. AWS SDK config (AWS.config.credentials) 3. AWS SDK credentials provider chain (AWS.config.credentialProviders) has been respected. This removes the need to explicitly configure `AWS.config.credentials` in Backstage installations also. Signed-off-by: Joel Low --- .changeset/little-kids-pull.md | 6 ++ packages/techdocs-common/__mocks__/aws-sdk.ts | 2 + .../src/stages/publish/awsS3.ts | 58 ++++++++++--------- .../AwsOrganizationCloudAccountProcessor.ts | 32 +++++----- 4 files changed, 59 insertions(+), 39 deletions(-) create mode 100644 .changeset/little-kids-pull.md diff --git a/.changeset/little-kids-pull.md b/.changeset/little-kids-pull.md new file mode 100644 index 0000000000..9a971db58f --- /dev/null +++ b/.changeset/little-kids-pull.md @@ -0,0 +1,6 @@ +--- +'@backstage/techdocs-common': patch +'@backstage/plugin-catalog-backend': patch +--- + +Implement proper AWS Credentials precedence with assume-role and explicit credentials diff --git a/packages/techdocs-common/__mocks__/aws-sdk.ts b/packages/techdocs-common/__mocks__/aws-sdk.ts index 4717feda9b..363662c63c 100644 --- a/packages/techdocs-common/__mocks__/aws-sdk.ts +++ b/packages/techdocs-common/__mocks__/aws-sdk.ts @@ -19,6 +19,8 @@ import fs from 'fs-extra'; import os from 'os'; import path from 'path'; +export { Credentials } from 'aws-sdk'; + const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; /** diff --git a/packages/techdocs-common/src/stages/publish/awsS3.ts b/packages/techdocs-common/src/stages/publish/awsS3.ts index cf950da72f..c4c39898f4 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.ts @@ -63,32 +63,7 @@ export class AwsS3Publish implements PublisherBase { const credentialsConfig = config.getOptionalConfig( 'techdocs.publisher.awsS3.credentials', ); - let accessKeyId = undefined; - let secretAccessKey = undefined; - let credentials: Credentials | CredentialsOptions | undefined = undefined; - if (credentialsConfig) { - const roleArn = credentialsConfig.getOptionalString('roleArn'); - if (roleArn && aws.config.credentials instanceof Credentials) { - credentials = new aws.ChainableTemporaryCredentials({ - masterCredentials: aws.config.credentials as Credentials, - params: { - RoleSessionName: 'backstage-aws-techdocs-s3-publisher', - RoleArn: roleArn, - }, - }); - } else { - accessKeyId = credentialsConfig.getOptionalString('accessKeyId'); - secretAccessKey = credentialsConfig.getOptionalString( - 'secretAccessKey', - ); - if (accessKeyId && secretAccessKey) { - credentials = { - accessKeyId, - secretAccessKey, - }; - } - } - } + const credentials = AwsS3Publish.buildCredentials(credentialsConfig); // AWS Region is an optional config. If missing, default AWS env variable AWS_REGION // or AWS shared credentials file at ~/.aws/credentials will be used. @@ -133,6 +108,37 @@ export class AwsS3Publish implements PublisherBase { return new AwsS3Publish(storageClient, bucketName, logger); } + private static buildCredentials( + config?: Config, + ): Credentials | CredentialsOptions | undefined { + if (!config) { + return undefined; + } + + const accessKeyId = config.getOptionalString('accessKeyId'); + const secretAccessKey = config.getOptionalString('secretAccessKey'); + let explicitCredentials: Credentials | undefined; + if (accessKeyId && secretAccessKey) { + explicitCredentials = new Credentials({ + accessKeyId, + secretAccessKey, + }); + } + + const roleArn = config.getOptionalString('roleArn'); + if (roleArn) { + return new aws.ChainableTemporaryCredentials({ + masterCredentials: explicitCredentials, + params: { + RoleSessionName: 'backstage-aws-techdocs-s3-publisher', + RoleArn: roleArn, + }, + }); + } + + return explicitCredentials; + } + constructor( private readonly storageClient: aws.S3, private readonly bucketName: string, diff --git a/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.ts index 7964541840..146de1d1f5 100644 --- a/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.ts @@ -51,25 +51,31 @@ export class AwsOrganizationCloudAccountProcessor implements CatalogProcessor { }); } + private static buildCredentials( + config: AwsOrganizationProviderConfig, + ): Credentials | undefined { + const roleArn = config.roleArn; + if (!roleArn) { + return undefined; + } + + return new AWS.ChainableTemporaryCredentials({ + params: { + RoleSessionName: 'backstage-aws-organization-processor', + RoleArn: roleArn, + }, + }); + } + constructor(options: { provider: AwsOrganizationProviderConfig; logger: Logger; }) { this.provider = options.provider; this.logger = options.logger; - let credentials = undefined; - if ( - this.provider.roleArn !== undefined && - AWS.config.credentials instanceof Credentials - ) { - credentials = new AWS.ChainableTemporaryCredentials({ - masterCredentials: AWS.config.credentials as Credentials, - params: { - RoleSessionName: 'backstage-aws-organization-processor', - RoleArn: this.provider.roleArn, - }, - }); - } + const credentials = AwsOrganizationCloudAccountProcessor.buildCredentials( + this.provider, + ); this.organizations = new AWS.Organizations({ credentials, region: AWS_ORGANIZATION_REGION,