packages/utilities/iam-role-helper/lib/rolehelper.ts (248 lines of code) (raw):
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import { MdaaLambdaRole, MdaaLambdaFunction } from '@aws-mdaa/lambda-constructs';
import { IMdaaResourceNaming } from '@aws-mdaa/naming';
import { Duration } from 'aws-cdk-lib';
import { PolicyDocument, PolicyStatement, ManagedPolicy } from 'aws-cdk-lib/aws-iam';
import { Code, Runtime } from 'aws-cdk-lib/aws-lambda';
import { Provider } from 'aws-cdk-lib/custom-resources';
import { MdaaNagSuppressions } from '@aws-mdaa/construct'; //NOSONAR
import { Construct } from 'constructs';
import { MdaaResolvableRole } from './resolvablerole';
import { MdaaRoleRef, MdaaResolvableRoleRef } from './roleref';
/**
* A Helper class which can be used to resolve MdaaRoleRefs using CustomResources.
*/
export class MdaaRoleHelper {
private readonly scope: Construct;
private providerServiceToken?: string;
private readonly naming: IMdaaResourceNaming;
private readonly resolveRefCache: { [key: string]: MdaaResolvableRole } = {};
private readonly resolveIdCache: { [key: string]: MdaaResolvableRole } = {};
private readonly resolveArnCache: { [key: string]: MdaaResolvableRole } = {};
private readonly resolveNameCache: { [key: string]: MdaaResolvableRole } = {};
/**
*
* @param scope The scope in which role resolution CR Provider will be created.
* @param naming The MDAA naming implementation which will be used to name resources
* from the perspective of the calling module.
*/
constructor(scope: Construct, naming: IMdaaResourceNaming, providerServiceToken?: string) {
this.scope = scope;
this.naming = naming;
this.providerServiceToken = providerServiceToken;
}
/**
* Can be used to resolve MdaaRoleRefs. Each MdaaRoleRef is first converted
* to a MdaaResolvableRoleRef by auto generating a role ref unique id using
* refPrefix and a generated ordinal.
* @param roleRefs The role references to be resolved
* @param refPrefix The prefix which will be used with ordinal to create a unique ID for use as a resource ID within scopes
* @returns Resolvable roles.
*/
public resolveRoleRefsWithOrdinals(roleRefs: MdaaRoleRef[], refPrefix: string): MdaaResolvableRole[] {
let i = 0;
const resolvableRoleRefs = roleRefs.map(roleRef => {
return {
refId: roleRef.refId || `${refPrefix}-${i++}`,
...roleRef,
};
});
return this.resolveRoleRefs(resolvableRoleRefs);
}
/**
*
* @param roleRefs The role references to be resolved
* @returns Resolvable roles.
*/
public resolveRoleRefs(roleRefs: MdaaResolvableRoleRef[]): MdaaResolvableRole[] {
return roleRefs.map(roleRef => {
return this.resolveRoleRef(roleRef);
});
}
/**
*
* @param roleRef The role references to be resolved
* @param refId The id of the reference to be used in creating the custom resource
* @returns Resolvable roles.
*/
public resolveRoleRefWithRefId(roleRef: MdaaRoleRef, refId: string): MdaaResolvableRole {
const resolvableRoleRef = {
refId: refId,
...roleRef,
};
return this.resolveRoleRef(resolvableRoleRef);
}
/**
*
* @param roleRef The role reference to be resolved
* @returns Resolvable roles.
*/
public resolveRoleRef(roleRef: MdaaResolvableRoleRef): MdaaResolvableRole {
if (!roleRef.id && !roleRef.arn && !roleRef.name) {
throw new Error('Role References must have at least one of arn, id, or name specified.');
}
if (roleRef.id && this.resolveIdCache[roleRef.id]) {
return this.resolveIdCache[roleRef.id];
} else if (roleRef.arn && this.resolveArnCache[roleRef.arn]) {
return this.resolveArnCache[roleRef.arn];
} else if (roleRef.name && this.resolveNameCache[roleRef.name]) {
return this.resolveNameCache[roleRef.name];
} else {
return this.createAndReturnResolvableRole(roleRef);
}
}
private createAndReturnResolvableRole(roleRef: MdaaResolvableRoleRef) {
const resolvableRole = new MdaaResolvableRole(this.scope, this, roleRef);
this.resolveRefCache[roleRef.refId] = resolvableRole;
if (roleRef.id) {
this.resolveIdCache[roleRef.id] = resolvableRole;
}
if (roleRef.arn) {
this.resolveArnCache[roleRef.arn] = resolvableRole;
}
if (roleRef.name) {
this.resolveNameCache[roleRef.name] = resolvableRole;
}
return resolvableRole;
}
/**
*
* @returns A Custom Resource Provider Service Token which can be used to create role resolver custom resources.
*/
public createProviderServiceToken(): string {
if (!this.providerServiceToken) {
console.log('Role resolution required by config. Creating CR Provider.');
this.providerServiceToken = this.createResolveRoleProvider().serviceToken;
}
return this.providerServiceToken;
}
private createResolveRoleProvider(): Provider {
const crLambdaRole = new MdaaLambdaRole(this.scope, 'role-res-cr', {
description: 'CR Role',
roleName: 'role-res-cr',
naming: this.naming,
logGroupNames: [this.naming.resourceName('role-res-cr')],
createParams: false,
createOutputs: false,
});
const listRolesPolicyDoc = new PolicyDocument({
statements: [
new PolicyStatement({
resources: ['*'],
actions: ['iam:ListRoles'],
}),
],
});
const iamPolicy = new ManagedPolicy(crLambdaRole, `role-res-pol`, {
managedPolicyName: this.naming.resourceName(`role-res-pol`),
document: listRolesPolicyDoc,
roles: [crLambdaRole],
});
MdaaNagSuppressions.addCodeResourceSuppressions(
iamPolicy,
[{ id: 'AwsSolutions-IAM5', reason: 'iam:ListRoles does not take a resource.' }],
true,
);
// This Lambda is used as a Custom Resource in order to create the Data Lake Folder
const resolveRoleLambda = new MdaaLambdaFunction(this.scope, 'resolve-role-res-cr-function', {
functionName: 'role-res-cr',
code: Code.fromAsset(`${__dirname}/../src/python/resolve_role/`),
handler: 'resolve_role.lambda_handler',
runtime: Runtime.PYTHON_3_13,
timeout: Duration.seconds(120),
role: crLambdaRole,
naming: this.naming,
createParams: false,
createOutputs: false,
environment: {
LOG_LEVEL: 'INFO',
},
});
resolveRoleLambda.node.addDependency(iamPolicy);
MdaaNagSuppressions.addCodeResourceSuppressions(
resolveRoleLambda,
[
{
id: 'NIST.800.53.R5-LambdaDLQ',
reason: 'Function is for custom resource and error handling will be handled by CloudFormation.',
},
{
id: 'NIST.800.53.R5-LambdaInsideVPC',
reason: 'Function is for custom resource and will interact only with IAM.',
},
{
id: 'NIST.800.53.R5-LambdaConcurrency',
reason:
'Function is for custom resource and will only execute during stack deployement. Reserved concurrency not appropriate.',
},
{
id: 'HIPAA.Security-LambdaDLQ',
reason: 'Function is for custom resource and error handling will be handled by CloudFormation.',
},
{
id: 'PCI.DSS.321-LambdaDLQ',
reason: 'Function is for custom resource and error handling will be handled by CloudFormation.',
},
{
id: 'HIPAA.Security-LambdaInsideVPC',
reason: 'Function is for custom resource and will interact only with IAM.',
},
{
id: 'PCI.DSS.321-LambdaInsideVPC',
reason: 'Function is for custom resource and will interact only with IAM.',
},
{
id: 'HIPAA.Security-LambdaConcurrency',
reason:
'Function is for custom resource and will only execute during stack deployement. Reserved concurrency not appropriate.',
},
{
id: 'PCI.DSS.321-LambdaConcurrency',
reason:
'Function is for custom resource and will only execute during stack deployement. Reserved concurrency not appropriate.',
},
],
true,
);
const resolveRoleProviderFunctionName = this.naming.resourceName('role-res-cr-prov', 64);
const resolveRoleCrProviderRole = new MdaaLambdaRole(this.scope, 'role-res-cr-prov', {
description: 'CR Role Resolver Provider',
roleName: 'role-res-cr-prov',
naming: this.naming,
logGroupNames: [resolveRoleProviderFunctionName],
createParams: false,
createOutputs: false,
});
const resolveRoleProvider = new Provider(this.scope, 'resolve-role-res-cr-provider', {
providerFunctionName: resolveRoleProviderFunctionName,
onEventHandler: resolveRoleLambda,
role: resolveRoleCrProviderRole,
});
MdaaNagSuppressions.addCodeResourceSuppressions(
resolveRoleCrProviderRole,
[
{
id: 'NIST.800.53.R5-IAMNoInlinePolicy',
reason: 'Role is for Custom Resource Provider. Inline policy automatically added.',
},
{
id: 'HIPAA.Security-IAMNoInlinePolicy',
reason: 'Role is for Custom Resource Provider. Inline policy automatically added.',
},
{
id: 'PCI.DSS.321-IAMNoInlinePolicy',
reason: 'Role is for Custom Resource Provider. Inline policy automatically added.',
},
],
true,
);
MdaaNagSuppressions.addCodeResourceSuppressions(
resolveRoleProvider,
[
{ id: 'AwsSolutions-L1', reason: 'Lambda function Runtime set by CDK Provider Framework' },
{
id: 'NIST.800.53.R5-LambdaDLQ',
reason: 'Function is for custom resource and error handling will be handled by CloudFormation.',
},
{
id: 'NIST.800.53.R5-LambdaInsideVPC',
reason: 'Function is for custom resource and will interact only with S3.',
},
{
id: 'NIST.800.53.R5-LambdaConcurrency',
reason:
'Function is for custom resource and will only execute during stack deployement. Reserved concurrency not appropriate.',
},
{
id: 'HIPAA.Security-LambdaDLQ',
reason: 'Function is for custom resource and error handling will be handled by CloudFormation.',
},
{
id: 'PCI.DSS.321-LambdaDLQ',
reason: 'Function is for custom resource and error handling will be handled by CloudFormation.',
},
{
id: 'HIPAA.Security-LambdaInsideVPC',
reason: 'Function is for custom resource and will interact only with S3.',
},
{
id: 'PCI.DSS.321-LambdaInsideVPC',
reason: 'Function is for custom resource and will interact only with S3.',
},
{
id: 'HIPAA.Security-LambdaConcurrency',
reason:
'Function is for custom resource and will only execute during stack deployement. Reserved concurrency not appropriate.',
},
{
id: 'PCI.DSS.321-LambdaConcurrency',
reason:
'Function is for custom resource and will only execute during stack deployement. Reserved concurrency not appropriate.',
},
],
true,
);
return resolveRoleProvider;
}
}