packages/constructs/L3/analytics/quicksight-project-l3-construct/lib/quicksight-project-l3-construct.ts (407 lines of code) (raw):
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import { MdaaL3Construct, MdaaL3ConstructProps } from '@aws-mdaa/l3-construct';
import { MdaaLambdaFunction, MdaaLambdaRole } from '@aws-mdaa/lambda-constructs';
import { Effect, ManagedPolicy, PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { Code, Runtime } from 'aws-cdk-lib/aws-lambda';
import { CustomResource, Duration } from 'aws-cdk-lib';
import { Provider } from 'aws-cdk-lib/custom-resources';
import { MdaaNagSuppressions } from '@aws-mdaa/construct'; //NOSONAR
import { Construct } from 'constructs';
import { MdaaQuickSightDataSource } from '@aws-mdaa/quicksight-constructs';
import { ConfigurationElement } from '@aws-mdaa/config';
//Interfaces for Shared Folders
export type FolderActions = 'READER_FOLDER' | 'AUTHOR_FOLDER';
export interface SharedFoldersPermissionsProps {
/**
* List of Principals
*/
readonly principal: string;
/**
* Either READER_FOLDER or AUTHOR_FOLDER
*/
readonly actions: FolderActions;
}
export interface SharedFoldersProps {
/**
* Permissions to be tied to the folder
*/
readonly permissions: SharedFoldersPermissionsProps[];
/**
* Sub-folders if any
*/
readonly folders?: { [key: string]: SharedFoldersProps };
}
interface FolderDetailPermissionsProps {
readonly Principal?: string;
readonly Actions?: string[];
}
interface FolderDetailProps {
readonly folderName: string;
readonly folderPermissions: FolderDetailPermissionsProps[];
readonly folderNameWithParentName: string;
readonly parentFolderArn?: string;
}
//Interfaces for DataSource
export type DataSourceActions = 'READER_DATA_SOURCE' | 'AUTHOR_DATA_SOURCE';
export interface DataSourcePermissionsProps {
/**
* Either "READER_DATA_SOURCE" or "AUTHOR_DATA_SOURCE"
*/
readonly actions: DataSourceActions;
/**
* The Amazon Resource Name (ARN) of the principal.
*/
readonly principal: string;
}
export interface DataSourcePermissions2Props {
/**
* API Actions for "READER_DATA_SOURCE" or "AUTHOR_DATA_SOURCE"
*/
readonly actions: string[];
/**
* The Amazon Resource Name (ARN) of the principal.
*/
readonly principal: string;
}
export interface DataSourceErrorInfoProps {
/**
* Error message(Optional)
*/
readonly message?: string;
/**
* Error type.(Optional)
* Valid Values are: ACCESS_DENIED | CONFLICT | COPY_SOURCE_NOT_FOUND | ENGINE_VERSION_NOT_SUPPORTED | GENERIC_SQL_FAILURE | TIMEOUT | UNKNOWN | UNKNOWN_HOST
*/
readonly type?: string;
}
export interface DataSourceCredentialPairProps {
/**
* Password
*/
readonly password: string;
/**
* Username
*/
readonly username: string;
/**
* A set of alternate data source parameters that you want to share for these credentials.
*/
/** @jsii ignore */
readonly alternateDataSourceParameters?: [
{
/** @jsii ignore */
[key: string]: unknown;
},
];
}
export interface DataSourceCredentialsProps {
/**
* The Amazon Resource Name (ARN) of a data source that has the credential pair that you want to use.
*/
readonly copySourceArn?: string;
/**
* Credential pair. For more information, see [CredentialPair](https://docs.aws.amazon.com/quicksight/latest/APIReference/API_CredentialPair.html) .
*/
readonly credentialPair?: DataSourceCredentialPairProps;
/**
* CfnDataSource.DataSourceCredentialsProperty.SecretArn.
*/
readonly secretArn?: string;
}
export type DataSourceTypeProps =
| 'ADOBE_ANALYTICS'
| 'AMAZON_ELASTICSEARCH'
| 'AMAZON_OPENSEARCH'
| 'ATHENA'
| 'AURORA'
| 'AURORA_POSTGRESQL'
| 'AWS_IOT_ANALYTICS'
| 'DATABRICKS'
| 'EXASOL'
| 'GITHUB'
| 'JIRA'
| 'MARIADB'
| 'MYSQL'
| 'ORACLE'
| 'POSTGRESQL'
| 'PRESTO'
| 'REDSHIFT'
| 'S3'
| 'SALESFORCE'
| 'SERVICENOW'
| 'SNOWFLAKE'
| 'SPARK'
| 'SQLSERVER'
| 'TERADATA'
| 'TIMESTREAM'
| 'TWITTER';
export interface DataSourceSSLProps {
/**
* Enable to Disable SSL: Default value is false(SSL is enabled)
*/
readonly disableSsl: boolean;
}
export interface DataSourceVPCProps {
/**
* QuickSight VPC(created in QS) ARN
*/
readonly vpcConnectionArn: string;
}
export interface DataSourceProps {
readonly dataSourceSpecificParameters: ConfigurationElement;
/**
* The AWS account ID.
*
* @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-quicksight-datasource.html#cfn-quicksight-datasource-awsaccountid
*/
readonly awsAccountId?: string;
/**
* The credentials Amazon QuickSight that uses to connect to your underlying source. Currently, only credentials based on user name and password are supported.
*
* @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-quicksight-datasource.html#cfn-quicksight-datasource-credentials
*/
readonly credentials?: DataSourceCredentialsProps;
/**
* Error information from the last update or the creation of the data source.
*
* @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-quicksight-datasource.html#cfn-quicksight-datasource-errorinfo
*/
readonly errorInfo?: DataSourceErrorInfoProps;
/**
* A display name for the data source.
*
* @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-quicksight-datasource.html#cfn-quicksight-datasource-name
*/
readonly displayName: string;
/**
* A list of resource permissions on the data source.
*
* @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-quicksight-datasource.html#cfn-quicksight-datasource-permissions
*/
readonly permissions: DataSourcePermissionsProps[];
/**
* Secure Socket Layer (SSL) properties that apply when Amazon QuickSight connects to your underlying source.
*
* @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-quicksight-datasource.html#cfn-quicksight-datasource-sslproperties
*/
readonly sslProperties?: DataSourceSSLProps;
/**
* Use this parameter only when you want Amazon QuickSight to use a VPC connection when connecting to your underlying source.
*
* @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-quicksight-datasource.html#cfn-quicksight-datasource-vpcconnectionproperties
*/
readonly vpcConnectionProperties?: DataSourceVPCProps;
}
export interface DataSourceWithIdAndTypeProps extends DataSourceProps {
/**
* Type of Data Source. ADOBE_ANALYTICS | AMAZON_ELASTICSEARCH | AMAZON_OPENSEARCH | ATHENA | AURORA | AURORA_POSTGRESQL | AWS_IOT_ANALYTICS | DATABRICKS | EXASOL | GITHUB | JIRA | MARIADB | MYSQL | ORACLE | POSTGRESQL | PRESTO | REDSHIFT | S3 | SALESFORCE | SERVICENOW | SNOWFLAKE | SPARK | SQLSERVER | TERADATA | TIMESTREAM | TWITTER
*
* @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-quicksight-datasource.html#cfn-quicksight-datasource-sslproperties
*/
readonly type: Record<DataSourceTypeProps, string>[DataSourceTypeProps];
/**
* An ID for the data source. This ID is unique per AWS Region for each AWS account.
*
* @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-quicksight-datasource.html#cfn-quicksight-datasource-datasourceid
*/
readonly dataSourceId: string;
}
export interface QuickSightProjectL3ConstructProps extends MdaaL3ConstructProps {
/**
*(Required) Details about the Data Sources to be created
*/
readonly dataSources?: DataSourceWithIdAndTypeProps[];
/**
* (Required) Objects representing various QS Principals
*/
readonly principals: { [key: string]: string };
/**
* (Required) QS Shared Folders
*/
readonly sharedFolders?: { [key: string]: SharedFoldersProps };
}
export class QuickSightProjectL3Construct extends MdaaL3Construct {
protected readonly props: QuickSightProjectL3ConstructProps;
public static sharedFoldersActions: { [key: string]: string[] } = {
READER_FOLDER: ['quicksight:DescribeFolder'],
AUTHOR_FOLDER: [
'quicksight:CreateFolder',
'quicksight:DescribeFolder',
'quicksight:UpdateFolder',
'quicksight:DeleteFolder',
'quicksight:CreateFolder',
'quicksight:CreateFolderMembership',
'quicksight:DeleteFolderMembership',
'quicksight:DescribeFolderPermissions',
'quicksight:UpdateFolderPermissions',
],
};
public static dataSourceActions: { [key: string]: string[] } = {
READER_DATA_SOURCE: [
'quicksight:DescribeDataSource',
'quicksight:DescribeDataSourcePermissions',
'quicksight:PassDataSource',
],
AUTHOR_DATA_SOURCE: [
'quicksight:DescribeDataSource',
'quicksight:DescribeDataSourcePermissions',
'quicksight:PassDataSource',
'quicksight:UpdateDataSource',
'quicksight:DeleteDataSource',
'quicksight:UpdateDataSourcePermissions',
],
};
constructor(scope: Construct, id: string, props: QuickSightProjectL3ConstructProps) {
super(scope, id, props);
this.props = props;
//Create QS Data Sources
if (this.props.dataSources) {
const DataSourceWithIdAndTypeProps: DataSourceWithIdAndTypeProps[] = this.props.dataSources;
this.createQSDataSource(DataSourceWithIdAndTypeProps);
}
//Create QS Shared Folders
if (this.props.sharedFolders) {
const arraySharedFolders: { [key: string]: SharedFoldersProps } = this.props.sharedFolders;
const qsFolderProvider: Provider = this.createQSFoldersProvider();
this.createQSFolders(qsFolderProvider, arraySharedFolders);
}
}
// Creates Custom Resource per Shared Folder - Handles OnCreate, OnUpdate, OnDelete Stack Events
private createQSFoldersCr(qsFolderProvider: Provider, folderDetail: FolderDetailProps): CustomResource {
return new CustomResource(this, `qsFolders-${folderDetail.folderNameWithParentName}`, {
serviceToken: qsFolderProvider.serviceToken,
properties: {
folderDetails: folderDetail,
},
});
}
private createQSFoldersProvider(): Provider {
//Create a role which will be used by the QSFolders Custom Resource Lambda Function
const qsFoldersCrRole = new MdaaLambdaRole(this, 'qsFolders-cr-role', {
description: 'CR Lambda Role',
roleName: 'qsFolders-cr',
naming: this.props.naming,
logGroupNames: [this.props.naming.resourceName('qsFolders-cr-func')],
createParams: false,
createOutputs: false,
});
const qsFoldersCrManagedPolicy = new ManagedPolicy(this, 'qsFolders-cr-lambda', {
managedPolicyName: this.props.naming.resourceName('qsFolders-cr-lambda'),
roles: [qsFoldersCrRole],
});
const qsFoldersPolicyStatement = new PolicyStatement({
effect: Effect.ALLOW,
resources: [`arn:${this.partition}:quicksight:${this.region}:${this.account}:folder/*`],
actions: [
'quicksight:CreateFolder',
'quicksight:DeleteFolder',
'quicksight:DescribeFolder',
'quicksight:DescribeFolderPermissions',
'quicksight:DescribeFolderResolvedPermissions',
'quicksight:ListFolderMembers',
'quicksight:ListFolders',
'quicksight:UpdateFolder',
'quicksight:UpdateFolderPermissions',
],
});
qsFoldersCrManagedPolicy.addStatements(qsFoldersPolicyStatement);
const qsFoldersPolicyStatement2 = new PolicyStatement({
effect: Effect.ALLOW,
resources: [`arn:${this.partition}:quicksight:${this.region}:${this.account}:folder/*`],
actions: ['quicksight:CreateFolderMembership', 'quicksight:DeleteFolderMembership'],
});
qsFoldersCrManagedPolicy.addStatements(qsFoldersPolicyStatement2);
MdaaNagSuppressions.addCodeResourceSuppressions(
qsFoldersCrManagedPolicy,
[
{
id: 'AwsSolutions-IAM5',
reason: 'ds:CreateIdentityPoolDirectory,ds:DescribeDirectories - Takes no resource.',
},
],
true,
);
const srcDir = `${__dirname}/../src/python/quicksight_folders`;
// This Lambda is used as a Custom Resource in order to create the QuickSight Folders
const quicksightFoldersCrLambda = new MdaaLambdaFunction(this, 'qsFolders-cr-func', {
functionName: 'qsFolders-cr-func',
naming: this.props.naming,
code: Code.fromAsset(srcDir),
handler: 'quicksight_folders.lambda_handler',
runtime: Runtime.PYTHON_3_13,
timeout: Duration.seconds(120),
environment: {
ACCOUNT_ID: this.account,
LOG_LEVEL: 'INFO',
},
role: qsFoldersCrRole,
});
MdaaNagSuppressions.addCodeResourceSuppressions(
quicksightFoldersCrLambda,
[
{
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 QuickSight APIs.',
},
{
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: 'HIPAA.Security-LambdaInsideVPC',
reason: 'Function is for custom resource and will interact only with QuickSight APIs.',
},
{
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-LambdaDLQ',
reason: 'Function is for custom resource and error handling will be handled by CloudFormation.',
},
{
id: 'PCI.DSS.321-LambdaInsideVPC',
reason: 'Function is for custom resource and will interact only with QuickSight APIs.',
},
{
id: 'PCI.DSS.321-LambdaConcurrency',
reason:
'Function is for custom resource and will only execute during stack deployement. Reserved concurrency not appropriate.',
},
],
true,
);
const qsFoldersCrProviderFunctionName = this.props.naming.resourceName('qsFolders-cr-prov', 64);
const qsFoldersCrProviderRole = new MdaaLambdaRole(this, 'qsFolders-cr-prov-role', {
description: 'CR Role',
roleName: 'qsFolders-cr-prov',
naming: this.props.naming,
logGroupNames: [qsFoldersCrProviderFunctionName],
createParams: false,
createOutputs: false,
});
const qsFoldersCrProvider = new Provider(this, 'qsFolders-cr-provider', {
providerFunctionName: qsFoldersCrProviderFunctionName,
onEventHandler: quicksightFoldersCrLambda,
role: qsFoldersCrProviderRole,
});
MdaaNagSuppressions.addCodeResourceSuppressions(
qsFoldersCrProviderRole,
[
{
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(
qsFoldersCrProvider,
[
{
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 QuickSight APIs.',
},
{
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: 'HIPAA.Security-LambdaInsideVPC',
reason: 'Function is for custom resource and will interact only with QuickSight APIs.',
},
{
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-LambdaDLQ',
reason: 'Function is for custom resource and error handling will be handled by CloudFormation.',
},
{
id: 'PCI.DSS.321-LambdaInsideVPC',
reason: 'Function is for custom resource and will interact only with QuickSight APIs.',
},
{
id: 'PCI.DSS.321-LambdaConcurrency',
reason:
'Function is for custom resource and will only execute during stack deployement. Reserved concurrency not appropriate.',
},
],
true,
);
return qsFoldersCrProvider;
}
//Parses Config to prepare inputs to create_folder api and recursively creates Custom Resources
private createQSFolders(
qsFolderProvider: Provider,
arrSharedFolders: { [key: string]: SharedFoldersProps },
parentFolderName?: string,
parentFolderArn?: string,
): void {
Object.keys(arrSharedFolders).forEach(folderName => {
const folderDetails: SharedFoldersProps = arrSharedFolders[folderName];
const fullName = parentFolderName + '/' + folderName;
const folderNameWithParentName = fullName.replace(/^\/+/, '').replace(/\//g, '-').replace('undefined-', '');
const returnPermissions: FolderDetailPermissionsProps[] = folderDetails.permissions.map(element => {
const folderPermissions: FolderDetailPermissionsProps = {
Principal: this.props.principals[element.principal],
Actions: QuickSightProjectL3Construct.sharedFoldersActions[element.actions],
};
return folderPermissions;
});
const folderDetail: FolderDetailProps = {
folderName: folderName,
folderPermissions: returnPermissions,
folderNameWithParentName: folderNameWithParentName,
parentFolderArn: parentFolderArn,
};
const folderArn: string = this.createQSFoldersCr(qsFolderProvider, folderDetail).getAttString('FolderArn');
if (folderDetails.folders) {
//Recursion to Check if there are any sub-folders
this.createQSFolders(qsFolderProvider, folderDetails.folders, fullName, folderArn);
}
});
}
// Creates Quicksight Data Sources
private createQSDataSource(dataSourcesProps: DataSourceWithIdAndTypeProps[]): void {
dataSourcesProps.forEach(dataSourceWithIdAndTypeProps => {
const qsDataSourcePermissions: DataSourcePermissions2Props[] = dataSourceWithIdAndTypeProps.permissions.map(
permissionDetail => {
const qsDataSourcePermission: DataSourcePermissions2Props = {
actions: QuickSightProjectL3Construct.dataSourceActions[permissionDetail.actions],
principal: this.props.principals[permissionDetail.principal],
};
return qsDataSourcePermission;
},
);
return new MdaaQuickSightDataSource(
this,
this.props.naming.resourceName(dataSourceWithIdAndTypeProps.dataSourceId),
{
naming: this.props.naming,
alternateDataSourceParameters: [dataSourceWithIdAndTypeProps.dataSourceSpecificParameters],
awsAccountId: this.account,
credentials: dataSourceWithIdAndTypeProps.credentials,
dataSourceId: this.props.naming.resourceName(dataSourceWithIdAndTypeProps.dataSourceId),
dataSourceParameters: dataSourceWithIdAndTypeProps.dataSourceSpecificParameters,
errorInfo: dataSourceWithIdAndTypeProps.errorInfo,
name: dataSourceWithIdAndTypeProps.displayName,
permissions: qsDataSourcePermissions,
type: dataSourceWithIdAndTypeProps.type,
vpcConnectionProperties: dataSourceWithIdAndTypeProps.vpcConnectionProperties,
},
);
});
}
}