Skip to content

Commit

Permalink
Allow explicit temporary credentials construction for AWS entity prov…
Browse files Browse the repository at this point in the history
…iders to enable custom role assumption functionality.
  • Loading branch information
Xantier committed Jun 20, 2024
1 parent 87c8999 commit be22bad
Show file tree
Hide file tree
Showing 12 changed files with 148 additions and 30 deletions.
5 changes: 5 additions & 0 deletions .changeset/kind-bottles-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@roadiehq/catalog-backend-module-aws': minor
---

Allow explicit temporary credentials construction for AWS entity providers to enable custom role assumption functionality.
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,17 @@ export class AWSDynamoDbTableProvider extends AWSEntityProvider {
catalogApi?: CatalogApi;
providerId?: string;
ownerTag?: string;
useTemporaryCredentials?: boolean;
},
) {
const accountId = config.getString('accountId');
const roleArn = config.getOptionalString('roleArn');
const roleName = config.getString('roleName');
const externalId = config.getOptionalString('externalId');
const region = config.getString('region');

return new AWSDynamoDbTableProvider(
{ accountId, roleName, externalId, region },
{ accountId, roleName, roleArn, externalId, region },
options,
);
}
Expand All @@ -56,16 +58,23 @@ export class AWSDynamoDbTableProvider extends AWSEntityProvider {
return `aws-dynamo-db-table-${this.accountId}-${this.providerId ?? 0}`;
}

private async getDdb() {
const credentials = this.useTemporaryCredentials
? this.getCredentials()
: await this.getCredentialsProvider();
return this.useTemporaryCredentials
? new DynamoDB({ credentials })
: new DynamoDB(credentials);
}

async run(): Promise<void> {
if (!this.connection) {
throw new Error('Not initialized');
}
const groups = await this.getGroups();

const credentials = await this.getCredentialsProvider();
const ddb = new DynamoDB(credentials);
const defaultAnnotations = await this.buildDefaultAnnotations();

const ddb = await this.getDdb();
this.logger.info(
`Retrieving all DynamoDB tables for account ${this.accountId}`,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,17 @@ export class AWSEC2Provider extends AWSEntityProvider {
catalogApi?: CatalogApi;
providerId?: string;
ownerTag?: string;
useTemporaryCredentials?: boolean;
},
) {
const accountId = config.getString('accountId');
const roleName = config.getString('roleName');
const roleArn = config.getOptionalString('roleArn');
const externalId = config.getOptionalString('externalId');
const region = config.getString('region');

return new AWSEC2Provider(
{ accountId, roleName, externalId, region },
{ accountId, roleName, roleArn, externalId, region },
options,
);
}
Expand All @@ -56,6 +58,15 @@ export class AWSEC2Provider extends AWSEntityProvider {
return `aws-ec2-provider-${this.accountId}-${this.providerId ?? 0}`;
}

private async getEc2() {
const credentials = this.useTemporaryCredentials
? this.getCredentials()
: await this.getCredentialsProvider();
return this.useTemporaryCredentials
? new EC2({ credentials, region: this.region })
: new EC2(credentials);
}

async run(): Promise<void> {
if (!this.connection) {
throw new Error('Not initialized');
Expand All @@ -65,8 +76,7 @@ export class AWSEC2Provider extends AWSEntityProvider {
this.logger.info(`Providing ec2 resources from aws: ${this.accountId}`);
const ec2Resources: ResourceEntity[] = [];

const credentials = await this.getCredentialsProvider();
const ec2 = new EC2(credentials);
const ec2 = await this.getEc2();

const defaultAnnotations = this.buildDefaultAnnotations();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,17 @@ export class AWSEKSClusterProvider extends AWSEntityProvider {
catalogApi?: CatalogApi;
providerId?: string;
ownerTag?: string;
useTemporaryCredentials?: boolean;
},
) {
const accountId = config.getString('accountId');
const roleName = config.getString('roleName');
const roleArn = config.getOptionalString('roleArn');
const externalId = config.getOptionalString('externalId');
const region = config.getString('region');

return new AWSEKSClusterProvider(
{ accountId, roleName, externalId, region },
{ accountId, roleName, roleArn, externalId, region },
options,
);
}
Expand All @@ -59,6 +61,15 @@ export class AWSEKSClusterProvider extends AWSEntityProvider {
return `aws-eks-cluster-${this.accountId}-${this.providerId ?? 0}`;
}

private async getEks() {
const credentials = this.useTemporaryCredentials
? this.getCredentials()
: await this.getCredentialsProvider();
return this.useTemporaryCredentials
? new EKS({ credentials, region: this.region })
: new EKS(credentials);
}

async run(): Promise<void> {
if (!this.connection) {
throw new Error('Not initialized');
Expand All @@ -70,8 +81,7 @@ export class AWSEKSClusterProvider extends AWSEntityProvider {
);
const eksResources: ResourceEntity[] = [];

const credentials = await this.getCredentialsProvider();
const eks = new EKS(credentials);
const eks = await this.getEks();

const defaultAnnotations = this.buildDefaultAnnotations();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ import { ANNOTATION_ACCOUNT_ID } from '../annotations';
import { CatalogApi } from '@backstage/catalog-client';
import { DefaultAwsCredentialsManager } from '@backstage/integration-aws-node';
import { ConfigReader } from '@backstage/config';
import { fromTemporaryCredentials } from '@aws-sdk/credential-providers';
import { parse as parseArn } from '@aws-sdk/util-arn-parser';

export abstract class AWSEntityProvider implements EntityProvider {
protected readonly useTemporaryCredentials: boolean;
protected readonly providerId?: string;
protected readonly logger: winston.Logger;
protected connection?: EntityProviderConnection;
Expand All @@ -48,13 +51,15 @@ export abstract class AWSEntityProvider implements EntityProvider {
catalogApi?: CatalogApi;
providerId?: string;
ownerTag?: string;
useTemporaryCredentials?: boolean;
},
) {
this.logger = options.logger;
this.providerId = options.providerId;
this.ownerTag = options.ownerTag;
this.catalogApi = options.catalogApi;
this.account = account;
this.useTemporaryCredentials = !!options.useTemporaryCredentials;
this.credentialsManager = DefaultAwsCredentialsManager.fromConfig(
new ConfigReader({ aws: { accounts: [account] } }),
);
Expand All @@ -71,6 +76,19 @@ export abstract class AWSEntityProvider implements EntityProvider {
return this.ownerTag ?? 'owner';
}

protected getCredentials() {
const region = parseArn(
this.account.roleArn ?? this.account.roleName,
).region;
return fromTemporaryCredentials({
params: {
RoleArn: this.account.roleArn,
ExternalId: this.account.externalId,
},
clientConfig: { region: region },
});
}

protected async getCredentialsProvider() {
const awsCredentialProvider =
await this.credentialsManager.getCredentialProvider({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,17 @@ export class AWSIAMRoleProvider extends AWSEntityProvider {
catalogApi?: CatalogApi;
providerId?: string;
ownerTag?: string;
useTemporaryCredentials?: boolean;
},
) {
const accountId = config.getString('accountId');
const roleName = config.getString('roleName');
const roleArn = config.getOptionalString('roleArn');
const externalId = config.getOptionalString('externalId');
const region = config.getString('region');

return new AWSIAMRoleProvider(
{ accountId, roleName, externalId, region },
{ accountId, roleName, roleArn, externalId, region },
options,
);
}
Expand All @@ -57,6 +59,15 @@ export class AWSIAMRoleProvider extends AWSEntityProvider {
return `aws-iam-role-${this.accountId}-${this.providerId ?? 0}`;
}

private async getIam() {
const credentials = this.useTemporaryCredentials
? this.getCredentials()
: await this.getCredentialsProvider();
return this.useTemporaryCredentials
? new IAM({ credentials, region: this.region })
: new IAM(credentials);
}

async run(): Promise<void> {
if (!this.connection) {
throw new Error('Not initialized');
Expand All @@ -68,11 +79,9 @@ export class AWSIAMRoleProvider extends AWSEntityProvider {
);
const roleResources: ResourceEntity[] = [];

const credentials = await this.getCredentialsProvider();

const defaultAnnotations = this.buildDefaultAnnotations();

const iam = new IAM(credentials);
const iam = await this.getIam();

const paginatorConfig = {
client: iam,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,17 @@ export class AWSIAMUserProvider extends AWSEntityProvider {
catalogApi?: CatalogApi;
providerId?: string;
ownerTag?: string;
useTemporaryCredentials?: boolean;
},
) {
const accountId = config.getString('accountId');
const roleName = config.getString('roleName');
const roleArn = config.getOptionalString('roleArn');
const externalId = config.getOptionalString('externalId');
const region = config.getString('region');

return new AWSIAMUserProvider(
{ accountId, roleName, externalId, region },
{ accountId, roleName, roleArn, externalId, region },
options,
);
}
Expand All @@ -53,6 +55,15 @@ export class AWSIAMUserProvider extends AWSEntityProvider {
return `aws-iam-user-${this.accountId}-${this.providerId ?? 0}`;
}

private async getIam() {
const credentials = this.useTemporaryCredentials
? this.getCredentials()
: await this.getCredentialsProvider();
return this.useTemporaryCredentials
? new IAM({ credentials, region: this.region })
: new IAM(credentials);
}

async run(): Promise<void> {
if (!this.connection) {
throw new Error('Not initialized');
Expand All @@ -63,11 +74,9 @@ export class AWSIAMUserProvider extends AWSEntityProvider {
);
const userResources: UserEntity[] = [];

const credentials = await this.getCredentialsProvider();

const defaultAnnotations = this.buildDefaultAnnotations();

const iam = new IAM(credentials);
const iam = await this.getIam();

const paginatorConfig = {
client: iam,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,17 @@ export class AWSLambdaFunctionProvider extends AWSEntityProvider {
catalogApi?: CatalogApi;
providerId?: string;
ownerTag?: string;
useTemporaryCredentials?: boolean;
},
) {
const accountId = config.getString('accountId');
const roleName = config.getString('roleName');
const roleArn = config.getOptionalString('roleArn');
const externalId = config.getOptionalString('externalId');
const region = config.getString('region');

return new AWSLambdaFunctionProvider(
{ accountId, roleName, externalId, region },
{ accountId, roleName, roleArn, externalId, region },
options,
);
}
Expand All @@ -60,6 +62,15 @@ export class AWSLambdaFunctionProvider extends AWSEntityProvider {
return `aws-lambda-function-${this.accountId}-${this.providerId ?? 0}`;
}

private async getLambda() {
const credentials = this.useTemporaryCredentials
? this.getCredentials()
: await this.getCredentialsProvider();
return this.useTemporaryCredentials
? new Lambda({ credentials, region: this.region })
: new Lambda(credentials);
}

async run(): Promise<void> {
if (!this.connection) {
throw new Error('Not initialized');
Expand All @@ -72,8 +83,7 @@ export class AWSLambdaFunctionProvider extends AWSEntityProvider {

const lambdaComponents: ResourceEntity[] = [];

const credentials = await this.getCredentialsProvider();
const lambda = new Lambda(credentials);
const lambda = await this.getLambda();

const defaultAnnotations = this.buildDefaultAnnotations();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,17 @@ export class AWSOrganizationAccountsProvider extends AWSEntityProvider {
catalogApi?: CatalogApi;
providerId?: string;
ownerTag?: string;
useTemporaryCredentials?: boolean;
},
) {
const accountId = config.getString('accountId');
const roleName = config.getString('roleName');
const roleArn = config.getOptionalString('roleArn');
const externalId = config.getOptionalString('externalId');
const region = config.getString('region');

return new AWSOrganizationAccountsProvider(
{ accountId, roleName, externalId, region },
{ accountId, roleName, roleArn, externalId, region },
options,
);
}
Expand All @@ -67,6 +69,18 @@ export class AWSOrganizationAccountsProvider extends AWSEntityProvider {
}`;
}

private async getOrganizationsClient() {
const credentials = this.useTemporaryCredentials
? this.getCredentials()
: await this.getCredentialsProvider();
return this.useTemporaryCredentials
? new OrganizationsClient({
credentials,
region: this.region,
})
: new OrganizationsClient(credentials);
}

async run(): Promise<void> {
if (!this.connection) {
throw new Error('Not initialized');
Expand All @@ -78,8 +92,7 @@ export class AWSOrganizationAccountsProvider extends AWSEntityProvider {
);
const accountResources: ResourceEntity[] = [];

const credentials = await this.getCredentialsProvider();
const organizationsClient = new OrganizationsClient(credentials);
const organizationsClient = await this.getOrganizationsClient();

const defaultAnnotations = this.buildDefaultAnnotations();

Expand Down
Loading

0 comments on commit be22bad

Please sign in to comment.