packages/constructs/L3/datalake/datalake-l3-construct/lib/datalake-bucket-l3-construct.ts (594 lines of code) (raw):
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import { MdaaRole } from '@aws-mdaa/iam-constructs';
import { MdaaRoleRef } from '@aws-mdaa/iam-role-helper';
import { ENCRYPT_ACTIONS, IMdaaKmsKey, MdaaKmsKey } from '@aws-mdaa/kms-constructs';
import { MdaaL3Construct, MdaaL3ConstructProps } from '@aws-mdaa/l3-construct';
import { MdaaLambdaFunction, MdaaLambdaRole } from '@aws-mdaa/lambda-constructs';
import { IMdaaResourceNaming } from '@aws-mdaa/naming';
import { RestrictBucketToRoles, RestrictObjectPrefixToRoles } from '@aws-mdaa/s3-bucketpolicy-helper';
import { MdaaBucket } from '@aws-mdaa/s3-constructs';
import { BucketInventory, InventoryHelper } from '@aws-mdaa/s3-inventory-helper';
import { Database } from '@aws-cdk/aws-glue-alpha';
import { CustomResource, Duration } from 'aws-cdk-lib';
import { Effect, IRole, PolicyStatement, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
import { IKey } from 'aws-cdk-lib/aws-kms';
import { CfnResource } from 'aws-cdk-lib/aws-lakeformation';
import { Code, Runtime } from 'aws-cdk-lib/aws-lambda';
import {
Bucket,
CfnBucket,
IBucket,
LifecycleRule,
NoncurrentVersionTransition,
StorageClass,
Transition,
} from 'aws-cdk-lib/aws-s3';
import { Provider } from 'aws-cdk-lib/custom-resources';
import { MdaaNagSuppressions } from '@aws-mdaa/construct'; //NOSONAR
import { Construct } from 'constructs';
export interface InventoryDefinition {
/**
* The S3 prefix which will be inventoried
*/
readonly prefix: string;
/**
* The bucket to which inventory will be written. If not specified, will be written back to the inventoried bucket under /inventory.
*/
readonly destinationBucket?: string;
/**
* The S3 prefix (on the destinationBucket) to which inventory will be written. If not specified, defaults to /inventory.
*/
readonly destinationPrefix?: string;
/**
* The destination account to which the destinationBucket should belong. Used by S3 service to validate bucket ownership before writing inventory.
*/
readonly destinationAccount?: string;
}
export interface LakeFormationLocation {
/**
* The S3 prefix of the location
*/
readonly prefix: string;
/**
* If true, LF role will be granted write access to the location in addition to read.
*/
readonly write?: boolean;
}
export interface BucketDefinition {
/**
* The zone of the bucket (IE "raw","transformed","curated",etc). Use to create the unique bucket name.
*/
readonly bucketZone: string;
/**
* List of access policies names which will be applied to the bucket
*/
readonly accessPolicies: AccessPolicyProps[];
/**
* List of S3 lifecycle configuration rules which will be applied to the bucket
*/
readonly lifecycleConfiguration?: LifecycleConfigurationRuleProps[];
/**
* List of inventory configurations to be applied to the bucket
*/
readonly inventories?: { [key: string]: InventoryDefinition };
/**
* If true, EventBridgeNotifications will be enabled on the bucket, allowing bucket data events to be matched and actioned by EventBridge rules
*/
readonly enableEventBridgeNotifications?: boolean;
/**
* Locations which will be created as LakeFormation resources using the specified role.
*/
readonly lakeFormationLocations?: { [key: string]: LakeFormationLocation };
/**
* If true (default), a "folder" object will be created on the bucket for each applied access policy.
*/
readonly createFolderSkeleton?: boolean;
/**
* If true (default), any roles not explicitely listed in the config will be blocked from reading/writing objects from this s3 bucket.
*/
readonly defaultDeny?: boolean;
}
export interface AccessPolicyProps {
/**
* Name of the access policy
*/
readonly name: string;
/**
* The S3 Prefix to which the access policy will apply
*/
readonly s3Prefix: string;
/**
* List of role ids which will be granted readonly access to the S3 prefix
*/
readonly readRoleRefs?: MdaaRoleRef[];
/**
* List of role ids which will be granted read/write access to the S3 prefix
*/
readonly readWriteRoleRefs?: MdaaRoleRef[];
/**
* List of role ids which will be granted superuser access to the S3 prefix
*/
readonly readWriteSuperRoleRefs?: MdaaRoleRef[];
}
interface AccessPolicyResolved {
readonly name: string;
readonly s3Prefix: string;
readonly readRoleIds: string[];
readonly readWriteRoleIds: string[];
readonly readWriteSuperRoleIds: string[];
readonly defaultDeny?: boolean;
}
export interface LifecycleTransitionProps {
readonly days: number;
readonly storageClass: string;
readonly newerNoncurrentVersions?: number;
}
export interface LifecycleConfigurationRuleProps {
readonly id: string;
readonly status: string;
readonly prefix?: string;
readonly objectSizeGreaterThan?: number;
readonly objectSizeLessThan?: number;
readonly abortIncompleteMultipartUploadAfter?: number;
readonly transitions?: LifecycleTransitionProps[];
readonly expirationdays?: number;
readonly expiredObjectDeleteMarker?: boolean;
readonly noncurrentVersionTransitions?: LifecycleTransitionProps[];
readonly noncurrentVersionExpirationDays?: number;
readonly noncurrentVersionsToRetain?: number;
}
export interface DataLakeL3ConstructProps extends MdaaL3ConstructProps {
/**
* List of bucket defintions for the data lake
*/
readonly buckets: BucketDefinition[];
}
export class S3DatalakeBucketL3Construct extends MdaaL3Construct {
protected readonly props: DataLakeL3ConstructProps;
private dataLakeFolderProvider?: Provider;
public readonly buckets: { [key: string]: IBucket };
public readonly kmsKey: IKey;
constructor(scope: Construct, id: string, props: DataLakeL3ConstructProps) {
super(scope, id, props);
this.props = props;
//Create a Glue Database to contain bucket utility tables such as inventory
const glueUtilDatabase = new Database(this.scope, 'util-database', {
databaseName: props.naming.resourceName('util').replace(/-/gi, '_'),
});
const dataLakeFolderFunctionRole = new MdaaLambdaRole(this.scope, 'folder-function-role', {
description: 'CR Role',
roleName: 'folder-cr',
naming: this.props.naming,
logGroupNames: [this.props.naming.resourceName('folder-cr')],
createParams: false,
createOutputs: false,
});
const lakeFormationRole = new MdaaRole(this.scope, 'lake-formation-role', {
naming: this.props.naming,
assumedBy: new ServicePrincipal('lakeformation.amazonaws.com'),
roleName: 'lake-formation',
description: 'Role for accessing the data lake via LakeFormation.',
});
this.props.buckets.sort((a, b) => a.bucketZone.localeCompare(b.bucketZone));
const allRoleIds = this.props.buckets.flatMap(bucketProps => {
bucketProps.accessPolicies.sort((a, b) => a.s3Prefix.localeCompare(b.s3Prefix));
return bucketProps.accessPolicies
.flatMap(ap => this.resolveAccessPolicy(ap))
.flatMap(ap => [...ap.readRoleIds, ...ap.readWriteRoleIds, ...ap.readWriteSuperRoleIds]);
});
this.kmsKey = this.createDataLakeKmsKey([
dataLakeFolderFunctionRole.roleId,
lakeFormationRole.roleId,
...allRoleIds,
]);
// Iterate over all the buckets we need to create
this.buckets = Object.fromEntries(
this.props.buckets.map(bucketDefinition => {
const bucket = this.createBucket(
bucketDefinition,
this.kmsKey,
props.naming,
glueUtilDatabase,
dataLakeFolderFunctionRole,
this.getDataLakeFolderCrProvider(dataLakeFolderFunctionRole),
lakeFormationRole,
);
return [bucketDefinition.bucketZone, bucket];
}),
);
}
private resolveAccessPolicy(accessPolicy: AccessPolicyProps): AccessPolicyResolved {
return {
name: accessPolicy.name,
s3Prefix: accessPolicy.s3Prefix,
readRoleIds: this.props.roleHelper
.resolveRoleRefsWithOrdinals(accessPolicy.readRoleRefs || [], `${accessPolicy.name}-r`)
.map(x => x.id()),
readWriteRoleIds: this.props.roleHelper
.resolveRoleRefsWithOrdinals(accessPolicy.readWriteRoleRefs || [], `${accessPolicy.name}-rw`)
.map(x => x.id()),
readWriteSuperRoleIds: this.props.roleHelper
.resolveRoleRefsWithOrdinals(accessPolicy.readWriteSuperRoleRefs || [], `${accessPolicy.name}-rws`)
.map(x => x.id()),
};
}
private resolveTransitions(transitionsWithName: LifecycleTransitionProps[]): Transition[] {
return Object.entries(transitionsWithName).map(transitionWithName => {
const transition = transitionWithName[1];
const lifecycleTransitionResolved: Transition = {
storageClass: new StorageClass(transition.storageClass),
transitionAfter: Duration.days(transition.days),
};
return lifecycleTransitionResolved;
});
}
private resolveNoncurrentVersionTransitions(
transitionsWithName: LifecycleTransitionProps[],
): NoncurrentVersionTransition[] {
return Object.entries(transitionsWithName).map(transitionWithName => {
const transition = transitionWithName[1];
const lifecycleTransitionResolved: NoncurrentVersionTransition = {
storageClass: new StorageClass(transition.storageClass),
transitionAfter: Duration.days(transition.days),
noncurrentVersionsToRetain: transition.newerNoncurrentVersions ? transition.newerNoncurrentVersions : undefined,
};
return lifecycleTransitionResolved;
});
}
private resolveLifecycleConfigurationRules(
lifecycleConfigurationRulesWithName: LifecycleConfigurationRuleProps[],
): LifecycleRule[] {
return Object.entries(lifecycleConfigurationRulesWithName).map(lifecycleConfigurationRuleWithName => {
const lifecycleConfigurationRule = lifecycleConfigurationRuleWithName[1];
const lifecycleConfigurationRuleResolved: LifecycleRule = {
...lifecycleConfigurationRule,
...{
enabled: lifecycleConfigurationRule.status.toLowerCase() === 'enabled',
abortIncompleteMultipartUploadAfter: lifecycleConfigurationRule.abortIncompleteMultipartUploadAfter
? Duration.days(lifecycleConfigurationRule.abortIncompleteMultipartUploadAfter)
: undefined,
transitions: lifecycleConfigurationRule.transitions
? this.resolveTransitions(lifecycleConfigurationRule.transitions)
: undefined,
expiration: lifecycleConfigurationRule.expirationdays
? Duration.days(lifecycleConfigurationRule.expirationdays)
: undefined,
noncurrentVersionTransitions: lifecycleConfigurationRule.noncurrentVersionTransitions
? this.resolveNoncurrentVersionTransitions(lifecycleConfigurationRule.noncurrentVersionTransitions)
: undefined,
noncurrentVersionExpiration: lifecycleConfigurationRule.noncurrentVersionExpirationDays
? Duration.days(lifecycleConfigurationRule.noncurrentVersionExpirationDays)
: undefined,
},
};
return lifecycleConfigurationRuleResolved;
});
}
private createBucket(
bucketDefinition: BucketDefinition,
encryptionKey: IMdaaKmsKey,
naming: IMdaaResourceNaming,
glueUtilDatabase: Database,
dataLakeFolderFunctionRole: IRole,
dataLakeFolderProvider: Provider,
lakeFormationRole: MdaaRole,
): IBucket {
const bucket = new MdaaBucket(this.scope, `bucket-${bucketDefinition.bucketZone}`, {
encryptionKey: encryptionKey,
bucketName: bucketDefinition.bucketZone,
naming: naming,
});
MdaaNagSuppressions.addCodeResourceSuppressions(
bucket,
[
{ id: 'NIST.800.53.R5-S3BucketReplicationEnabled', reason: 'MDAA Data Lake does not use bucket replication.' },
{ id: 'HIPAA.Security-S3BucketReplicationEnabled', reason: 'MDAA Data Lake does not use bucket replication.' },
{ id: 'PCI.DSS.321-S3BucketReplicationEnabled', reason: 'MDAA Data Lake does not use bucket replication.' },
],
true,
);
this.createBucketInventories(bucketDefinition, bucket, glueUtilDatabase);
this.createLakeFormationLocations(bucketDefinition, bucket, lakeFormationRole);
// Iterate over the accessPolicies and add to the bucket
const bucketAllowIds: string[] = [lakeFormationRole.roleId];
const folderCreatePrefixes: string[] = [];
bucketDefinition.accessPolicies
.map(ap => this.resolveAccessPolicy(ap))
.forEach(accessPolicy => {
const s3Prefix = accessPolicy.s3Prefix;
//Apply bucket policy restrictions for Object prefixes
const prefixRestrictPolicies = new RestrictObjectPrefixToRoles({
s3Bucket: bucket,
s3Prefix: s3Prefix,
readRoleIds: accessPolicy.readRoleIds,
readWriteRoleIds: accessPolicy.readWriteRoleIds,
readWriteSuperRoleIds: accessPolicy.readWriteSuperRoleIds,
});
prefixRestrictPolicies.statements().forEach(statement => bucket.addToResourcePolicy(statement));
// Add the ARNs from this loop to bucketAllowArns
bucketAllowIds.push(
...[...accessPolicy.readRoleIds, ...accessPolicy.readWriteRoleIds, ...accessPolicy.readWriteSuperRoleIds],
);
folderCreatePrefixes.push(
...this.createFolderPrefix(s3Prefix, bucketDefinition, accessPolicy, dataLakeFolderProvider, bucket),
);
});
this.createFolderPrefixes(folderCreatePrefixes, bucket, dataLakeFolderFunctionRole);
this.addBucketRestrictPolicy(bucketDefinition, bucket, bucketAllowIds, dataLakeFolderFunctionRole);
this.addBucketLifecyclePolicy(bucketDefinition, bucket);
this.addBucketEventBridgeNotification(bucketDefinition, bucket);
return bucket;
}
private addBucketEventBridgeNotification(bucketDefinition: BucketDefinition, bucket: Bucket) {
//Enable EventBridge notifications
if (bucketDefinition.enableEventBridgeNotifications && bucketDefinition.enableEventBridgeNotifications.valueOf()) {
const cfnBucket = bucket.node.defaultChild as CfnBucket;
cfnBucket.addPropertyOverride('NotificationConfiguration.EventBridgeConfiguration.EventBridgeEnabled', true);
}
}
private addBucketLifecyclePolicy(bucketDefinition: BucketDefinition, bucket: Bucket) {
// Add S3 Lifecycle Policy
if (bucketDefinition.lifecycleConfiguration) {
this.resolveLifecycleConfigurationRules(bucketDefinition.lifecycleConfiguration).forEach(lifecycleRule => {
bucket.addLifecycleRule(lifecycleRule);
});
}
}
private createFolderPrefixes(folderCreatePrefixes: string[], bucket: Bucket, dataLakeFolderFunctionRole: IRole) {
if (folderCreatePrefixes.length > 0) {
//Allow folder custom resource provider role to create folders in the bucket
const resources = folderCreatePrefixes.map(s3Prefix => {
let rawPrefix = s3Prefix;
// Removes trailing slashes
rawPrefix = rawPrefix.endsWith('/') ? rawPrefix.slice(0, -1) : rawPrefix;
// Removes leading slashes
rawPrefix = rawPrefix.startsWith('/') ? rawPrefix.substring(1) : rawPrefix;
return `${bucket.bucketArn}/${rawPrefix}/`;
});
const createFolderPolicyStatement = new PolicyStatement({
effect: Effect.ALLOW,
resources: resources,
actions: ['s3:PutObject'],
});
createFolderPolicyStatement.addArnPrincipal(dataLakeFolderFunctionRole.roleArn);
bucket.addToResourcePolicy(createFolderPolicyStatement);
}
}
private createFolderPrefix(
s3Prefix: string,
bucketDefinition: BucketDefinition,
accessPolicy: AccessPolicyResolved,
dataLakeFolderProvider: Provider,
bucket: Bucket,
): string[] {
if (
s3Prefix != '/' &&
(bucketDefinition.createFolderSkeleton == undefined || bucketDefinition.createFolderSkeleton.valueOf())
) {
const folderResource = new CustomResource(
this.scope,
`datalake-folder-${bucketDefinition.bucketZone}-${accessPolicy.name}`,
{
serviceToken: dataLakeFolderProvider.serviceToken,
properties: {
bucket_name: bucket.bucketName,
folder_name: s3Prefix,
},
},
);
folderResource.node.addDependency(bucket.node.findChild('Policy'));
return [s3Prefix];
}
return [];
}
private addBucketRestrictPolicy(
bucketDefinition: BucketDefinition,
bucket: MdaaBucket,
bucketAllowIds: string[],
dataLakeFolderFunctionRole: IRole,
) {
const bucketRestrictPolicy = new RestrictBucketToRoles({
s3Bucket: bucket,
// De-duplicate our list of Arns.
roleExcludeIds: [...new Set(bucketAllowIds)],
principalExcludes: [dataLakeFolderFunctionRole.roleArn],
prefixExcludes: ['inventory/'],
});
bucket.addToResourcePolicy(bucketRestrictPolicy.allowStatement);
if (!('defaultDeny' in bucketDefinition) || bucketDefinition.defaultDeny) {
bucket.addToResourcePolicy(bucketRestrictPolicy.denyStatement);
}
}
private createLakeFormationLocations(bucketDefinition: BucketDefinition, bucket: IBucket, lakeFormationRole: IRole) {
//Add Lake Formation locations
if (bucketDefinition.lakeFormationLocations) {
Object.keys(bucketDefinition.lakeFormationLocations).forEach(locationName => {
const locationProps = (bucketDefinition.lakeFormationLocations || {})[locationName];
this.createLakeFormationLocation(
locationName,
locationProps,
bucketDefinition.bucketZone,
bucket,
lakeFormationRole,
);
});
}
}
private createBucketInventories(bucketDefinition: BucketDefinition, bucket: Bucket, glueUtilDatabase: Database) {
if (bucketDefinition.inventories) {
const bucketInventories: BucketInventory[] = [];
Object.keys(bucketDefinition.inventories).forEach(invName => {
const inventoryDefinition = (bucketDefinition.inventories || {})[invName];
const inventory = this.createInventory(
invName,
inventoryDefinition,
bucketDefinition.bucketZone,
bucketInventories,
);
bucket.addInventory(inventory);
});
if (bucketInventories.length > 0) {
InventoryHelper.createGlueInvTable(
this.scope,
this.account,
bucketDefinition.bucketZone,
glueUtilDatabase,
this.props.naming.resourceName(bucketDefinition.bucketZone),
bucketInventories,
'inventory/',
);
}
const allowInventoryStatement = InventoryHelper.createInventoryBucketPolicyStatement(
bucket.bucketArn,
this.account,
bucket.bucketArn,
'inventory/',
);
bucket.addToResourcePolicy(allowInventoryStatement);
}
}
private createLakeFormationLocation(
locationName: string,
locationProps: LakeFormationLocation,
bucketZone: string,
bucket: IBucket,
lakeFormationRole: IRole,
) {
new CfnResource(this.scope, `lf-resource-${bucketZone}-${locationName}`, {
resourceArn: `${bucket.bucketArn}/${MdaaBucket.formatS3Prefix(locationProps.prefix)}`,
useServiceLinkedRole: false,
roleArn: lakeFormationRole.roleArn,
});
const permissions = locationProps.write?.valueOf
? {
readWritePrincipals: [lakeFormationRole],
}
: {
readPrincipals: [lakeFormationRole],
};
//Add Access for the LF Role to the Prefix
const lfPrefixRestrictPolicies = new RestrictObjectPrefixToRoles({
s3Bucket: bucket,
s3Prefix: locationProps.prefix,
...permissions,
});
lfPrefixRestrictPolicies.statements().forEach(statement => bucket.addToResourcePolicy(statement));
}
private createInventory(
invName: string,
inventoryDefinition: InventoryDefinition,
bucketZone: string,
bucketInventories: BucketInventory[],
) {
let destinationBucketName: string;
let destinationPrefix: string;
if (inventoryDefinition.destinationBucket) {
//Remote destination bucket
destinationBucketName = inventoryDefinition.destinationBucket;
destinationPrefix = inventoryDefinition.destinationPrefix ? inventoryDefinition.destinationPrefix : 'inventory/';
} else {
//Write inventory to this bucket
if (inventoryDefinition.destinationPrefix) {
throw new Error('destinationPrefix should be set only if destinationBucket is set');
}
destinationBucketName = this.props.naming.resourceName(bucketZone);
destinationPrefix = 'inventory/';
bucketInventories.push({ bucketName: destinationBucketName, inventoryName: invName });
}
const destinationBucket: IBucket = MdaaBucket.fromBucketName(
this,
`InvDestinationBucket${bucketZone}${invName}`,
destinationBucketName,
);
return InventoryHelper.createInvConfig(
destinationBucket,
invName,
inventoryDefinition.prefix,
destinationPrefix,
inventoryDefinition.destinationAccount,
);
}
private getDataLakeFolderCrProvider(folderCrFunctionRole: MdaaLambdaRole): Provider {
if (this.dataLakeFolderProvider) {
return this.dataLakeFolderProvider;
}
const sourceDir = `${__dirname}/../src/python/datalake_folder`;
// This Lambda is used as a Custom Resource in order to create the Data Lake Folder
const datalakeFolderLambda = new MdaaLambdaFunction(this.scope, 'folder-cr-function', {
functionName: 'folder-cr',
code: Code.fromAsset(sourceDir),
handler: 'datalake_folder.lambda_handler',
runtime: Runtime.PYTHON_3_13,
timeout: Duration.seconds(120),
role: folderCrFunctionRole,
naming: this.props.naming,
createParams: false,
createOutputs: false,
environment: {
LOG_LEVEL: 'INFO',
},
});
MdaaNagSuppressions.addCodeResourceSuppressions(
datalakeFolderLambda,
[
{
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,
);
const folderCrProviderFunctionName = this.props.naming.resourceName('folder-cr-prov', 64);
const folderCrProviderRole = new MdaaLambdaRole(this.scope, 'folder-provider-role', {
description: 'CR Role',
roleName: 'folder-provider-role',
naming: this.props.naming,
logGroupNames: [folderCrProviderFunctionName],
createParams: false,
createOutputs: false,
});
const datalakeFolderProvider = new Provider(this.scope, 'datalake-folder-cr-provider', {
providerFunctionName: folderCrProviderFunctionName,
onEventHandler: datalakeFolderLambda,
role: folderCrProviderRole,
});
MdaaNagSuppressions.addCodeResourceSuppressions(
folderCrProviderRole,
[
{
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(
datalakeFolderProvider,
[
{ 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,
);
this.dataLakeFolderProvider = datalakeFolderProvider;
return datalakeFolderProvider;
}
private createDataLakeKmsKey(keyUserRoles: string[]): MdaaKmsKey {
//This statement allows S3 to write inventory data to the encrypted data lake buckets
const S3ServiceEncryptPolicy = new PolicyStatement({
effect: Effect.ALLOW,
// Use of * mirrors what is done in the CDK methods for adding policy helpers.
resources: ['*'],
actions: ENCRYPT_ACTIONS,
});
S3ServiceEncryptPolicy.addServicePrincipal('s3.amazonaws.com');
const kmsKey = new MdaaKmsKey(this.scope, 'cmk', {
naming: this.props.naming,
keyUserRoleIds: keyUserRoles,
});
kmsKey.addToResourcePolicy(S3ServiceEncryptPolicy);
return kmsKey;
}
}