packages/constructs/L3/analytics/quicksight-namespace-l3-construct/lib/quicksight-namespace-l3-construct.ts (661 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 { MdaaL3Construct, MdaaL3ConstructProps } from '@aws-mdaa/l3-construct';
import { MdaaLambdaFunction, MdaaLambdaRole } from '@aws-mdaa/lambda-constructs';
import { aws_events_targets, CustomResource, Duration } from 'aws-cdk-lib';
import { Rule } from 'aws-cdk-lib/aws-events';
import { Effect, FederatedPrincipal, IRole, ManagedPolicy, PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { Code, Function as LambdaFunction, 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';
export type QSUserType = 'READER' | 'AUTHOR';
export interface FederationRoleProps {
/**
* QS Groups info for creating Creating QS Groups
*/
readonly qsGroups: string[];
/**
* QS Role(Reader|Author) info for creating IAM Roles
*/
readonly qsUserType: QSUserType;
}
export interface FederationProps {
/**
* URL used by the connecting driver
*/
readonly url: string;
/**
* Arn or SSM Import (prefix with ssm:) of the federation provider
*/
readonly providerArn: string;
/**
* QS Groups and QS Role(Reader|Author) info for creating IAM Roles, Creating QS Groups, Registering Users with a QS Role
*/
readonly roles: { [key: string]: FederationRoleProps };
}
export interface NameAndFederationProps extends FederationProps {
/**
* Name of the Federation
*/
readonly federationName: string;
}
export interface QuickSightNamespaceL3ConstructProps extends MdaaL3ConstructProps {
/**
* Map of federation names to federation definitions
*/
readonly federations: NameAndFederationProps[];
/**
* List of glue resources to which the namespace roles will be granted access.
*/
readonly glueResourceAccess?: string[];
}
//This stack creates QuickSight namespaces
export class QuickSightNamespaceL3Construct extends MdaaL3Construct {
protected readonly props: QuickSightNamespaceL3ConstructProps;
private readonly namespaceName: string;
constructor(scope: Construct, id: string, props: QuickSightNamespaceL3ConstructProps) {
super(scope, id, props);
this.props = props;
this.namespaceName = props.naming.resourceName();
const readerManagedPolicy = this.createReaderManagedPolicy();
const glueManagedPolicy =
this.props.glueResourceAccess && this.props.glueResourceAccess.length > 0
? this.createGlueManagedPolicy()
: undefined;
const authorManagedPolicy = this.createAuthorManagedPolicy();
const namespaceProvider = this.createNamespaceProvider();
this.createNamespace(this.namespaceName, namespaceProvider);
const namespaceLambdaUserRole = this.createNamespaceUserLambdaRole();
//Create federation roles for each federation config
props.federations.forEach(federation => {
Object.entries(federation.roles).forEach(roleProps => {
const roleProp: FederationRoleProps = roleProps[1];
const roleName: string = roleProps[0];
const role: IRole = this.createFederationRoles(federation, roleName);
if (roleProp.qsUserType == 'AUTHOR') {
authorManagedPolicy.attachToRole(role);
if (glueManagedPolicy) {
glueManagedPolicy.attachToRole(role);
}
} else if (roleProp.qsUserType == 'READER') {
readerManagedPolicy.attachToRole(role);
}
//role.roleName is IAM Role that was created by createFederationRoles
//roleName=sampleReaders|sampleAuthors
//qSUserType=READER|AUTHOR -> Not needed in logical names
this.createNamespaceUserMonitor(
this.namespaceName,
[role.roleName],
roleProp.qsUserType,
roleProp.qsGroups,
roleName,
namespaceLambdaUserRole,
);
});
});
}
private createNamespaceUserMonitor(
namespace: string,
iamRoleName: string[],
qsUserType: string,
qsGroupNames: string[],
roleName: string,
namespaceLambdaUserRole: MdaaLambdaRole,
): void {
//Create the Lambda Function for this namespace and role
const namespaceUserFunction = this.createNamespaceUserFunction(
this.namespaceName,
qsUserType,
qsGroupNames,
roleName,
namespaceLambdaUserRole,
);
//Create an EventBridge Rule to trigger this lambda function each time this role is used to create a QuickSight user
const eventRule = new Rule(this, `event-rule-${roleName}`, {
enabled: true,
description: `Events to map QuickSight users for ${roleName} to namespace ${namespace}`,
ruleName: this.props.naming.resourceName(`event-rule-${roleName}`, 64),
eventPattern: {
source: ['aws.quicksight'],
detail: {
eventName: ['CreateUser'],
userIdentity: {
sessionContext: {
sessionIssuer: {
userName: iamRoleName,
},
},
},
},
},
});
eventRule.addTarget(
new aws_events_targets.LambdaFunction(namespaceUserFunction, {
retryAttempts: 5, // Optional: set the max number of retry attempts
}),
);
}
private createNamespaceUserLambdaRole(): MdaaLambdaRole {
const namespaceUserRole: MdaaLambdaRole = new MdaaLambdaRole(this, 'user-cr-role', {
description: 'CR Role',
roleName: 'user-cr',
naming: this.props.naming,
logGroupNames: [this.props.naming.resourceName('user-READER'), this.props.naming.resourceName('user-AUTHOR')],
createParams: false,
createOutputs: false,
});
const managedPolicy: ManagedPolicy = new ManagedPolicy(this, 'ns-user-lambda', {
managedPolicyName: this.props.naming.resourceName('ns-user-lambda'),
roles: [namespaceUserRole],
});
// Allow describing default namespace users by the lambda
const describeUserStatement = new PolicyStatement({
effect: Effect.ALLOW,
resources: [`arn:${this.partition}:quicksight:${this.region}:${this.account}:user/default/*`],
actions: ['quicksight:DescribeUser'],
});
managedPolicy.addStatements(describeUserStatement);
// Allow registering users by the lambda
const registerUserStatement: PolicyStatement = new PolicyStatement({
effect: Effect.ALLOW,
resources: [`arn:${this.partition}:quicksight:${this.region}:${this.account}:user/${this.namespaceName}/*`],
actions: ['quicksight:RegisterUser'],
});
managedPolicy.addStatements(registerUserStatement);
// Allow registering users by the lambda
const groupRelatedStatement: PolicyStatement = new PolicyStatement({
effect: Effect.ALLOW,
resources: [`arn:${this.partition}:quicksight:${this.region}:${this.account}:group/${this.namespaceName}/*`],
actions: [
'quicksight:ListGroups',
'quicksight:CreateGroup',
'quicksight:CreateGroupMembership',
'quicksight:ListGroupMemberships',
],
});
managedPolicy.addStatements(groupRelatedStatement);
// Allow deleting users by the Default federated user principals need to be deleted before registration in a namespace.
const deleteUserStatement: PolicyStatement = new PolicyStatement({
effect: Effect.ALLOW,
resources: [`arn:${this.partition}:quicksight:${this.region}:${this.account}:user/default/*`],
actions: ['quicksight:DeleteUser'],
});
managedPolicy.addStatements(deleteUserStatement);
MdaaNagSuppressions.addCodeResourceSuppressions(
managedPolicy,
[
{
id: 'AwsSolutions-IAM5',
reason: 'User Name is not known at deployment time.',
},
],
true,
);
return namespaceUserRole;
}
private createNamespaceUserFunction(
namespace: string,
qsUserType: string,
qsGroupNames: string[],
roleName: string,
namespaceLambdaUserRole: MdaaLambdaRole,
): LambdaFunction {
// This Lambda is used as a Custom Resource in order to create the QuickSight Namespace
const quicksightNamespaceUserLambda: MdaaLambdaFunction = new MdaaLambdaFunction(this, `user-${roleName}`, {
functionName: `user-${roleName}`,
naming: this.props.naming,
code: Code.fromAsset(`${__dirname}/../src/python/quicksight_namespace_user`),
handler: 'quicksight_namespace_user.lambda_handler',
runtime: Runtime.PYTHON_3_13,
timeout: Duration.seconds(600),
environment: {
ACCOUNT_ID: this.account,
NAMESPACE: namespace,
QUICKSIGHT_ROLE: qsUserType,
QUICKSIGHT_GROUPS: qsGroupNames
.map(qsGroupName => {
return `${namespace}-${qsGroupName}`;
})
.toString(),
LOG_LEVEL: 'INFO',
},
role: namespaceLambdaUserRole,
});
MdaaNagSuppressions.addCodeResourceSuppressions(
quicksightNamespaceUserLambda,
[
{ id: 'NIST.800.53.R5-LambdaInsideVPC', reason: 'Function will interact only with QuickSight APIs.' },
{ id: 'NIST.800.53.R5-LambdaDLQ', reason: 'No DLQ required. Failures to be manually remediated by Admin.' },
{ id: 'NIST.800.53.R5-LambdaConcurrency', reason: 'Concurrency limits not required.' },
{ id: 'HIPAA.Security-LambdaInsideVPC', reason: 'Function will interact only with QuickSight APIs.' },
{ id: 'PCI.DSS.321-LambdaInsideVPC', reason: 'Function will interact only with QuickSight APIs.' },
{ id: 'HIPAA.Security-LambdaDLQ', reason: 'No DLQ required. Failures to be manually remediated by Admin.' },
{ id: 'PCI.DSS.321-LambdaDLQ', reason: 'No DLQ required. Failures to be manually remediated by Admin.' },
{ id: 'HIPAA.Security-LambdaConcurrency', reason: 'Concurrency limits not required.' },
{ id: 'PCI.DSS.321-LambdaConcurrency', reason: 'Concurrency limits not required.' },
],
true,
);
return quicksightNamespaceUserLambda;
}
private createNamespace(name: string, namespaceProvider: Provider): CustomResource {
return new CustomResource(this, `namespace-cr`, {
serviceToken: namespaceProvider.serviceToken,
properties: {
name: name,
},
});
}
private createNamespaceProvider(): Provider {
//Create a role which will be used by the Namespace Custom Resource Function
const namespaceCrRole: MdaaLambdaRole = new MdaaLambdaRole(this, 'namespace-cr-role', {
description: 'CR Role',
roleName: 'namespace-cr',
naming: this.props.naming,
logGroupNames: [this.props.naming.resourceName('ns-cr-func')],
createParams: false,
createOutputs: false,
});
const namespaceCrManagedPolicy: ManagedPolicy = new ManagedPolicy(this, 'ns-cr-lambda', {
managedPolicyName: this.props.naming.resourceName('ns-cr-lambda'),
roles: [namespaceCrRole],
});
const qsNamespacePolicyStatement: PolicyStatement = new PolicyStatement({
effect: Effect.ALLOW,
resources: [`arn:${this.partition}:quicksight:${this.region}:${this.account}:namespace/${this.namespaceName}`],
actions: [
'quicksight:CreateNamespace',
'quicksight:DescribeNamespace',
'quicksight:DeleteNamespace',
'quicksight:TagResource',
],
});
namespaceCrManagedPolicy.addStatements(qsNamespacePolicyStatement);
//QuickSight uses Directory Service to manage users
const dsPolicyStatement: PolicyStatement = new PolicyStatement({
effect: Effect.ALLOW,
resources: ['*'],
actions: [
'ds:CreateIdentityPoolDirectory', //Takes no resource
'ds:DescribeDirectories', //Takes no resource
],
});
namespaceCrManagedPolicy.addStatements(dsPolicyStatement);
const dsPolicyStatement2: PolicyStatement = new PolicyStatement({
effect: Effect.ALLOW,
resources: [`arn:${this.partition}:ds:${this.region}:${this.account}:directory/*`],
actions: ['ds:AuthorizeApplication', 'ds:UnauthorizeApplication'],
});
namespaceCrManagedPolicy.addStatements(dsPolicyStatement2);
MdaaNagSuppressions.addCodeResourceSuppressions(
namespaceCrManagedPolicy,
[
{
id: 'AwsSolutions-IAM5',
reason: 'ds:CreateIdentityPoolDirectory,ds:DescribeDirectories - Takes no resource.',
appliesTo: ['Resource::*'],
},
{
id: 'AwsSolutions-IAM5',
reason: 'ds:AuthorizeApplication,ds:UnauthorizeApplication - Directory name randomly generated.',
appliesTo: [`Resource::arn:${this.partition}:ds:${this.region}:${this.account}:directory/*`],
},
],
true,
);
const srcDir = `${__dirname}/../src/python/quicksight_namespace`;
// This Lambda is used as a Custom Resource in order to create the QuickSight Namespace
const quicksightNamespaceCrLambda: MdaaLambdaFunction = new MdaaLambdaFunction(this, 'ns-cr-func', {
functionName: 'namespace-cr',
naming: this.props.naming,
code: Code.fromAsset(srcDir),
handler: 'quicksight_namespace.lambda_handler',
runtime: Runtime.PYTHON_3_13,
timeout: Duration.seconds(120),
environment: {
ACCOUNT_ID: this.account,
IDENTITY_STORE: 'QUICKSIGHT',
LOG_LEVEL: 'INFO',
},
role: namespaceCrRole,
});
MdaaNagSuppressions.addCodeResourceSuppressions(
quicksightNamespaceCrLambda,
[
{
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: '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 QuickSight APIs.',
},
{
id: 'PCI.DSS.321-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-LambdaConcurrency',
reason:
'Function is for custom resource and will only execute during stack deployement. Reserved concurrency not appropriate.',
},
],
true,
);
const namespaceCrProviderFunctionName: string = this.props.naming.resourceName('ns-cr-prov', 64);
const namespaceCrProviderRole: MdaaLambdaRole = new MdaaLambdaRole(this, 'namespace-cr-prov-role', {
description: 'CR Role',
roleName: 'namespace-cr-prov',
naming: this.props.naming,
logGroupNames: [namespaceCrProviderFunctionName],
createParams: false,
createOutputs: false,
});
const namespaceCrProvider: Provider = new Provider(this, 'ns-cr-provider', {
providerFunctionName: namespaceCrProviderFunctionName,
onEventHandler: quicksightNamespaceCrLambda,
role: namespaceCrProviderRole,
});
MdaaNagSuppressions.addCodeResourceSuppressions(
namespaceCrProviderRole,
[
{
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(
namespaceCrProvider,
[
{ 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: '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 QuickSight APIs.',
},
{
id: 'PCI.DSS.321-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-LambdaConcurrency',
reason:
'Function is for custom resource and will only execute during stack deployement. Reserved concurrency not appropriate.',
},
],
true,
);
return namespaceCrProvider;
}
//Creates Federation roles for each federation config
private createFederationRoles(federation: NameAndFederationProps, roleName: string): IRole {
//Create a Role which will be provided the accesses required to access Athena via Lake Formation
return new MdaaRole(this, `role-${roleName}-${federation.federationName}`, {
naming: this.props.naming,
assumedBy: new FederatedPrincipal(federation.providerArn, {}, 'sts:AssumeRoleWithSAML'),
description: `QuickSight Federation Role for ${roleName}`,
roleName: `${roleName}-${federation.federationName}`,
});
}
private createReaderManagedPolicy(): ManagedPolicy {
const managedPolicy: ManagedPolicy = new ManagedPolicy(this, 'reader-policy', {
managedPolicyName: this.props.naming.resourceName('reader-policy'),
});
const accessQuickSightCreateReaderStatement: PolicyStatement = new PolicyStatement({
sid: 'CreateReader',
effect: Effect.ALLOW,
actions: ['quicksight:CreateReader'],
resources: [`arn:${this.partition}:quicksight::${this.account}:user/` + '${aws:userid}'],
});
managedPolicy.addStatements(accessQuickSightCreateReaderStatement);
MdaaNagSuppressions.addCodeResourceSuppressions(
managedPolicy,
[
{
id: 'AwsSolutions-IAM5',
reason: 'quicksight:CreateReader - Username not known at deployment time.',
},
],
true,
);
const accessQuickSightDescribeStatement = new PolicyStatement({
sid: 'Describe',
effect: Effect.ALLOW,
actions: [
'quicksight:DescribeAnalysis',
'quicksight:DescribeDashboard',
'quicksight:DescribeDataset',
'quicksight:DescribeDataSource',
'quicksight:DescribeFolder',
'quicksight:DescribeGroup',
'quicksight:DescribeIngestion',
'quicksight:DescribeTemplate',
'quicksight:DescribeTheme',
'quicksight:DescribeUser',
],
resources: ['*'],
});
managedPolicy.addStatements(accessQuickSightDescribeStatement);
const accessQuickSightListStatement: PolicyStatement = new PolicyStatement({
sid: 'List',
effect: Effect.ALLOW,
actions: [
'quicksight:ListAnalyses',
'quicksight:ListCustomPermissions',
'quicksight:ListDashboards',
'quicksight:ListDashboardVersions',
'quicksight:ListDataSets',
'quicksight:ListDataSources',
'quicksight:ListFolders',
'quicksight:ListFolderMembers',
'quicksight:ListGroups',
'quicksight:ListGroupMemberships',
'quicksight:ListIngestions',
'quicksight:ListTagsForResource',
'quicksight:ListTemplates',
'quicksight:ListTemplateAliases',
'quicksight:ListTemplateVersions',
'quicksight:ListThemes',
'quicksight:ListThemeAliases',
'quicksight:ListThemeVersions',
'quicksight:ListUsers',
'quicksight:ListUserGroups',
],
resources: ['*'],
});
managedPolicy.addStatements(accessQuickSightListStatement);
const accessQuickSightSearchStatement: PolicyStatement = new PolicyStatement({
sid: 'Search',
effect: Effect.ALLOW,
actions: ['quicksight:SearchAnalyses', 'quicksight:SearchDashboards', 'quicksight:SearchFolders'],
resources: ['*'],
});
managedPolicy.addStatements(accessQuickSightSearchStatement);
const accessLakeFormationStatement: PolicyStatement = new PolicyStatement({
sid: 'LakeFormationAccess',
effect: Effect.ALLOW,
actions: ['lakeformation:GetDataAccess'],
resources: ['*'],
});
managedPolicy.addStatements(accessLakeFormationStatement);
MdaaNagSuppressions.addCodeResourceSuppressions(
managedPolicy,
[
{
id: 'AwsSolutions-IAM5',
reason:
'lakeformation:GetDataAccess does not take resource. QuickSight resource permissions managed in QuickSight.',
appliesTo: [`Resource::*`],
},
],
true,
);
return managedPolicy;
}
private createAuthorManagedPolicy(): ManagedPolicy {
const managedPolicy: ManagedPolicy = new ManagedPolicy(this, 'author-policy', {
managedPolicyName: this.props.naming.resourceName('author-policy'),
});
const accessRedShiftDescribeStatement: PolicyStatement = new PolicyStatement({
sid: 'RedShiftDescribe',
effect: Effect.ALLOW,
actions: ['redshift:DescribeClusters'],
resources: ['*'],
});
managedPolicy.addStatements(accessRedShiftDescribeStatement);
const accessQuickSightCancelIngestionStatement: PolicyStatement = new PolicyStatement({
sid: 'CancelIngestion',
effect: Effect.ALLOW,
actions: ['quicksight:CancelIngestion'],
resources: ['*'],
});
managedPolicy.addStatements(accessQuickSightCancelIngestionStatement);
const accessQuickSightCreateStatement: PolicyStatement = new PolicyStatement({
sid: 'Create',
effect: Effect.ALLOW,
actions: [
'quicksight:CreateDashboard',
'quicksight:CreateFolder',
'quicksight:CreateFolderMembership',
'quicksight:CreateIngestion',
],
resources: ['*'],
});
managedPolicy.addStatements(accessQuickSightCreateStatement);
const accessQuickSightDeleteStatement: PolicyStatement = new PolicyStatement({
sid: 'Delete',
effect: Effect.ALLOW,
actions: [
'quicksight:DeleteAnalysis',
'quicksight:DeleteDashboard',
'quicksight:DeleteFolder',
'quicksight:DeleteFolderMembership',
],
resources: ['*'],
});
managedPolicy.addStatements(accessQuickSightDeleteStatement);
const accessQuickSightEmbedUrlStatement: PolicyStatement = new PolicyStatement({
sid: 'GenerateEmbedUrl',
effect: Effect.ALLOW,
actions: ['quicksight:GenerateEmbedUrlForRegisteredUser', 'quicksight:GetDashboardEmbedUrl'],
resources: ['*'],
});
managedPolicy.addStatements(accessQuickSightEmbedUrlStatement);
const accessQuickSightPassDataStatement: PolicyStatement = new PolicyStatement({
sid: 'PassData',
effect: Effect.ALLOW,
actions: ['quicksight:PassDataSet', 'quicksight:PassDataSource'],
resources: ['*'],
});
managedPolicy.addStatements(accessQuickSightPassDataStatement);
const accessQuickSightRestoreAnalysisStatement: PolicyStatement = new PolicyStatement({
sid: 'RestoreAnalysis',
effect: Effect.ALLOW,
actions: ['quicksight:RestoreAnalysis'],
resources: ['*'],
});
managedPolicy.addStatements(accessQuickSightRestoreAnalysisStatement);
const accessQuickSightTagsStatement: PolicyStatement = new PolicyStatement({
sid: 'Tags',
effect: Effect.ALLOW,
actions: ['quicksight:TagResource', 'quicksight:UnTagResource'],
resources: ['*'],
});
managedPolicy.addStatements(accessQuickSightTagsStatement);
const accessQuickSightUpdateStatement: PolicyStatement = new PolicyStatement({
sid: 'Update',
effect: Effect.ALLOW,
actions: [
'quicksight:UpdateAnalysis',
'quicksight:UpdateAnalysisPermissions',
'quicksight:UpdateDashboard',
'quicksight:UpdateDashboardPermissions',
'quicksight:UpdateDashboardPublishedVersion',
'quicksight:UpdateFolder',
'quicksight:UpdateFolderPermissions',
],
resources: ['*'],
});
managedPolicy.addStatements(accessQuickSightUpdateStatement);
const accessQuickSightCreateUserStatement: PolicyStatement = new PolicyStatement({
sid: 'CreateUser',
effect: Effect.ALLOW,
actions: ['quicksight:CreateUser'],
resources: [`arn:${this.partition}:quicksight::${this.account}:user/` + '${aws:userid}'],
});
managedPolicy.addStatements(accessQuickSightCreateUserStatement);
MdaaNagSuppressions.addCodeResourceSuppressions(
managedPolicy,
[
{
id: 'AwsSolutions-IAM5',
reason: 'Quicksight usernames not known at deployment time.',
},
{
id: 'AwsSolutions-IAM5',
reason:
'redshift:DescribeClusters does not take resource. QuickSight resource permissions managed in QuickSight.',
appliesTo: [`Resource::*`],
},
],
true,
);
return managedPolicy;
}
private createGlueManagedPolicy(): ManagedPolicy | void {
const glueResourceArns: string[] | undefined = this.props.glueResourceAccess?.map(resource => {
if (resource.includes('*')) {
console.warn(
`Glue resource access '${resource}' contains wildcard (*). Consider revising to specific resources.`,
);
}
return `arn:${this.partition}:glue:${this.region}:${this.account}:${resource}`;
});
glueResourceArns?.push(`arn:${this.partition}:glue:${this.region}:${this.account}:catalog`);
glueResourceArns?.push(`arn:${this.partition}:glue:${this.region}:${this.account}:database/default`);
//Allow to access the glue catalog resources
const gluePolicy: ManagedPolicy = new ManagedPolicy(this, 'glue-access-policy', {
managedPolicyName: this.props.naming.resourceName('glue-access-policy'),
});
const accessGlueStatement: PolicyStatement = new PolicyStatement({
effect: Effect.ALLOW,
actions: [
'glue:GetDatabase',
'glue:GetDatabases',
'glue:GetTable',
'glue:GetTables',
'glue:GetPartition',
'glue:GetPartitions',
'glue:SearchTables',
],
resources: glueResourceArns,
});
gluePolicy.addStatements(accessGlueStatement);
MdaaNagSuppressions.addCodeResourceSuppressions(
gluePolicy,
[
{
id: 'AwsSolutions-IAM5',
reason: 'Resource wildcards may originate from app config. Warnings logged.',
},
],
true,
);
return gluePolicy;
}
}