示例#1
0
    def __init__(self, scope: Construct, construct_id: str, eks_cluster, env, **kwargs) -> None:
        super().__init__(scope, construct_id, env=env, **kwargs)

        statement = EksWorkerRoleStatements(env.account)
        oidc_url = eks_cluster.cluster_open_id_connect_issuer_url
        iam_oic = iam.OpenIdConnectProvider(
            self, construct_id,
            url=oidc_url,
            client_ids=['sts.amazonaws.com']
        )
        Tags.of(iam_oic).add(key='cfn.eks-dev.stack', value='iam-pid-stack')

        oidc_provider = re.sub("https://", "", oidc_url)

        def string_like(name_space, sa_name):
            string = CfnJson(
                self, f'JsonCondition{sa_name}',
                value={
                    f'{oidc_provider}:sub': f'system:serviceaccount:{name_space}:{sa_name}',
                    f'{oidc_provider}:aud': 'sts.amazonaws.com'
                }
            )
            return string

        oic_role = iam.Role(
            self, 'EksIAMServiceAccountRole',
            role_name='sel-eks-oic-dev-sa',
            assumed_by=iam.FederatedPrincipal(
                federated=f'arn:aws:iam::{env.account}:oidc-provider/{oidc_provider}',
                conditions={'StringEquals': string_like('dev', 'sel-eks-sa')},
                assume_role_action='sts:AssumeRoleWithWebIdentity'
            )
        )

        oic_role.add_managed_policy(iam.ManagedPolicy.from_aws_managed_policy_name('AmazonS3ReadOnlyAccess'))

        oic_role.add_to_policy(statement.cognito_power_statement())
        oic_role.add_to_policy(statement.admin_statement())
        oic_role.add_to_policy(statement.ddb_statement())

        daemonset_role = iam.Role(
            self, 'DaemonsetIamRole',
            role_name='sel-eks-oic-daemonset-sa',
            assumed_by=iam.FederatedPrincipal(
                federated=f'arn:aws:iam::{env.account}:oidc-provider/{oidc_provider}',
                conditions={'StringEquals': string_like('kube-system', 'aws-node')},
                assume_role_action='sts:AssumeRoleWithWebIdentity'
            )
        )
        daemonset_role.add_to_policy(statement.eks_cni())
示例#2
0
    def autoscaler_role(self, policy: iam.Policy) -> iam.Role:
        oidc_arn = cdk.CfnJson(
            self,
            "oidc-provider",
            value=self.cluster.open_id_connect_provider.
            open_id_connect_provider_arn,
        )

        oidc_arn_condition = cdk.CfnJson(
            self,
            "oidc-provider-condition",
            value={
                f"{self.cluster.open_id_connect_provider.open_id_connect_provider_issuer}:sub":
                "system:serviceaccount:kube-system:cluster-autoscaler"
            },
        )
        role = iam.Role(
            self,
            "ClusterAutoscalerRole",
            assumed_by=iam.FederatedPrincipal(
                oidc_arn.value.to_string(),
                {"StringEquals": oidc_arn_condition},
                "sts:AssumeRoleWithWebIdentity",
            ),
        )
        role.attach_inline_policy(policy)
        return role
    def create_github_action_role(self, bucket_arn: str) -> None:

        self.github_action_role = aws_iam.Role(
            self,
            "github_actions_role",
            role_name="amplifyios-githubaction-integtest",
            description=
            "Role assumed by GitHub action in the amplify-ios integration test",
            assumed_by=aws_iam.FederatedPrincipal(
                self.oidc.open_id_connect_provider_arn,
                assume_role_action="sts:AssumeRoleWithWebIdentity",
                conditions={
                    "StringEquals": {
                        "token.actions.githubusercontent.com:aud":
                        "sts.amazonaws.com"
                    },
                    "StringLike": {
                        "token.actions.githubusercontent.com:sub":
                        "repo:aws-amplify/amplify-ios:*"
                    }
                }))

        bucket_resource = bucket_arn + "/testconfiguration/*"
        bucket_policy = aws_iam.PolicyStatement(
            effect=aws_iam.Effect.ALLOW,
            actions=["s3:GetObject", "s3:ListBucket"],
            resources=[bucket_arn, bucket_resource],
        )
        self.github_action_role.add_to_policy(bucket_policy)
示例#4
0
def SamlFederatedPrincipal(federated: str) -> aws_iam.FederatedPrincipal:
    return aws_iam.FederatedPrincipal(
        federated=federated,
        conditions={
            'StringEquals': {
                'SAML:aud': 'https://signin.aws.amazon.com/saml'
            }
        },
        assume_role_action='sts:AssumeRoleWithSAML')
    def __init__(self, scope: core.Construct, id: str, common_stack: CommonStack, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        self._supported_in_region = self.is_service_supported_in_region()

        identity_pool = cognito.CfnIdentityPool(
            self, "pinpoint_integ_test_android", allow_unauthenticated_identities=True
        )

        unauthenticated_role = iam.Role(
            self,
            "CognitoDefaultUnauthenticatedRole",
            assumed_by=iam.FederatedPrincipal(
                "cognito-identity.amazonaws.com",
                {
                    "StringEquals": {"cognito-identity.amazonaws.com:aud": identity_pool.ref},
                    "ForAnyValue:StringLike": {
                        "cognito-identity.amazonaws.com:amr": "unauthenticated"
                    },
                },
                "sts:AssumeRoleWithWebIdentity",
            ),
        )
        unauthenticated_role.add_to_policy(
            iam.PolicyStatement(
                effect=iam.Effect.ALLOW,
                actions=[
                    "cognito-sync:*",
                    "iot:Connect",
                    "iot:Publish",
                    "iot:Subscribe",
                    "iot:Receive",
                    "iot:GetThingShadow",
                    "iot:DescribeEndpoint",
                    "iot:CreateKeysAndCertificate",
                    "iot:CreatePolicy",
                    "iot:AttachPolicy",
                ],
                resources=["*"],
            )
        )
        cognito.CfnIdentityPoolRoleAttachment(
            self,
            "DefaultValid",
            identity_pool_id=identity_pool.ref,
            roles={"unauthenticated": unauthenticated_role.role_arn},
        )

        self._parameters_to_save = {"identity_pool_id": identity_pool.ref}
        self.save_parameters_in_parameter_store(platform=Platform.ANDROID)

        stack_policy = iam.PolicyStatement(
            effect=iam.Effect.ALLOW, actions=["cognito-identity:*", "iot:*"], resources=["*"]
        )

        common_stack.add_to_common_role_policies(self, policy_to_add=stack_policy)
示例#6
0
 def principal_for(self, identity_pool: cognito.CfnIdentityPool,
                   state: str) -> iam.FederatedPrincipal:
     return iam.FederatedPrincipal(
         federated="cognito-identity.amazonaws.com",
         assume_role_action="sts:AssumeRoleWithWebIdentity",
         conditions={
             "StringEquals": {
                 "cognito-identity.amazonaws.com:aud": identity_pool.ref
             },
             "ForAnyValue:StringLike": {
                 "cognito-identity.amazonaws.com:amr": state
             },
         },
     )
示例#7
0
    def _build_cognito(self, *, stack):

        self.cognito_identity_pool = cognito.CfnIdentityPool(
            stack, 'identitypool', allow_unauthenticated_identities=True)

        self.unauthenticatedRole = iam.Role(
            stack,
            'CognitoDefaultUnauthenticatedRole',
            assumed_by=iam.FederatedPrincipal(
                'cognito-identity.amazonaws.com', {
                    "StringEquals": {
                        "cognito-identity.amazonaws.com:aud":
                        self.cognito_identity_pool.ref
                    },
                    "ForAnyValue:StringLike": {
                        "cognito-identity.amazonaws.com:amr": "unauthenticated"
                    },
                }, "sts:AssumeRoleWithWebIdentity"))

        self.unauthenticatedRole.add_to_policy(
            iam.PolicyStatement(
                effect=iam.Effect.ALLOW,
                actions=[
                    "mobiletargeting:PutEvents",
                    "mobiletargeting:GetUserEndpoints",
                    "mobiletargeting:UpdateEndpoint"
                ],
                resources=[self.pinpoint_instance.attr_arn + "/*"]))

        self.unauthenticatedRole.add_to_policy(
            iam.PolicyStatement(
                effect=iam.Effect.ALLOW,
                actions=[
                    "mobileanalytics:PutEvents",
                    # TODO : least privileged
                    "cognito-sync:*"
                ],
                resources=["*"]))

        self.default_policy_cognito_identity_pool = cognito.CfnIdentityPoolRoleAttachment(
            stack,
            'identitypoolrolatt',
            identity_pool_id=self.cognito_identity_pool.ref,
            roles={
                'unauthenticated': self.unauthenticatedRole.role_arn,
                # If you need authenticated users, you need to have a separate role
                # Here, both have the same
                'authenticated': self.unauthenticatedRole.role_arn
            })
示例#8
0
def get_default_role_for_identity_pool(
    scope: core.Construct,
    identity_pool_id: str,
    role_resource_id_prefix: str,
    auth: bool = True,
    kwargs={},
) -> aws_iam.Role:
    role_policy = get_default_role_policy(auth=auth, kwargs=kwargs)
    role_condition = get_default_role_condition(identity_pool_id=identity_pool_id, auth=auth)
    role = aws_iam.Role(
        scope,
        role_resource_id_prefix + ("auth" if auth else "unauth"),
        assumed_by=aws_iam.FederatedPrincipal(
            "cognito-identity.amazonaws.com", role_condition, "sts:AssumeRoleWithWebIdentity"
        ),
    )
    role.add_to_policy(role_policy)
    return role
示例#9
0
    def __init__(f, scope: Construct, construct_id: str, eks_cluster, env,
                 **kwargs) -> None:
        super().__init__(scope, construct_id, env=env, **kwargs)

        oidc_url = eks_cluster.cluster_open_id_connect_issuer_url
        iam_oic = iam.OpenIdConnectProvider(f,
                                            construct_id,
                                            url=oidc_url,
                                            client_ids=['sts.amazonaws.com'])
        oidc_arn = iam_oic.open_id_connect_provider_arn
        oidc_provider = re.sub("https://", "", oidc_url)

        def string_like(name_space, sa_name):
            string_like = CfnJson(
                f,
                f'JsonCondition{sa_name}',
                value={
                    f'{oidc_provider}:sub':
                    f'system:serviceaccount:{name_space}:{sa_name}',
                    f'{oidc_provider}:aud': 'sts.amazonaws.com'
                })
            return string_like

        alb_controller_role = iam.Role(
            f,
            'AlbControllerRole',
            role_name='eks-aws-load-balancer-controller-sa',
            assumed_by=iam.FederatedPrincipal(
                federated=f'{iam_oic.open_id_connect_provider_arn}',
                conditions={
                    'StringEquals':
                    string_like('kube-system', 'aws-load-balancer-controller')
                },
                assume_role_action='sts:AssumeRoleWithWebIdentity'))
        alb_controller_role.add_managed_policy(
            iam.ManagedPolicy.from_managed_policy_name(
                f,
                "EksAWSLoadBalancerController",
                managed_policy_name='EksAWSLoadBalancerController'))
        alb_controller_role.add_managed_policy(
            iam.ManagedPolicy.from_managed_policy_name(
                f,
                "EksAWSLoadBalancerControllerAdditional",
                managed_policy_name='EksAWSLoadBalancerControllerAdditional'))
示例#10
0
def base_federated_role(
    construct,
    resource_name: str,
    federated_resource: str,
    assume_role_action: str,
    conditions: dict,
    actions: list,
    resources: list,
):
    """
    Function that generates an IAM Federated Role with a Policy.
    :param construct: Custom construct that will use this function. From the external construct is usually 'self'.
    :param resource_name: Name of the resource. Used for naming purposes.
    :param federated_resource: Resource used to define a Federated Principal. Has to match an AWS Resource. For example, 'cognito-identity' -> 'cognito-identity.amazonaws.com'.
    :param assume_role_action: The conditions under which the policy is in effect.
    :param conditions: Generally an STS Action.
    :param actions: Action list containing AWS IAM defined actions. For example 'sns:Publish'
    :param resources: List of resources ARNs defined by AWS.
    :return: IAM Federated Role with an IAM Policy attached.
    """
    try:
        # Defining IAM Role
        # Defining Service Principal
        iam_role_name = construct.prefix + "_role_" + resource_name + "_" + construct.environment_
        iam_policy_name = construct.prefix + "_policy_" + resource_name + "_" + construct.environment_
        principal = iam.FederatedPrincipal(
            federated=f"{federated_resource}.amazonaws.com",
            conditions=conditions,
            assume_role_action=assume_role_action,
        )

        # Defining IAM Role
        role = iam.Role(construct, id=iam_role_name, role_name=iam_role_name, assumed_by=principal)

        # Defining Policy Statement, Policy and Attaching to Role
        policy_statements = iam.PolicyStatement(actions=actions, resources=resources)
        policy = iam.Policy(construct, id=iam_policy_name, policy_name=iam_policy_name, statements=[policy_statements])
        policy.attach_to_role(role=role)

    except Exception:
        print(traceback.format_exc())
    else:
        return role
    def create_wic_provider_test_role(self) -> None:
        wic_provider_test_role_condition = {
            "StringEquals": {"graph.facebook.com:app_id": self._facebook_app_id}
        }

        wic_provider_test_role = aws_iam.Role(
            self,
            "wic_provider_test_role",
            assumed_by=aws_iam.FederatedPrincipal(
                "graph.facebook.com",
                wic_provider_test_role_condition,
                "sts:AssumeRoleWithWebIdentity",
            ),
        )
        wic_provider_test_role.add_to_policy(
            aws_iam.PolicyStatement(
                effect=aws_iam.Effect.ALLOW, actions=["translate:TranslateText"], resources=["*"]
            )
        )

        self.parameters_to_save["WICProviderTestRoleArn"] = wic_provider_test_role.role_arn
示例#12
0
    def __init__(  # pylint: disable=too-many-arguments,too-many-locals
        self,
        scope: Construct,
        stack_id: str,
        *,
        botocore_lambda_layer: aws_lambda_python.PythonLayerVersion,
        datasets_table: Table,
        deploy_env: str,
        state_machine: aws_stepfunctions.StateMachine,
        state_machine_parameter: aws_ssm.StringParameter,
        storage_bucket: aws_s3.Bucket,
        storage_bucket_parameter: aws_ssm.StringParameter,
        validation_results_table: Table,
        **kwargs: Any,
    ) -> None:
        super().__init__(scope, stack_id, **kwargs)

        if saml_provider_arn := environ.get("DATALAKE_SAML_IDENTITY_PROVIDER_ARN"):
            principal = aws_iam.FederatedPrincipal(
                federated=saml_provider_arn,
                assume_role_action="sts:AssumeRoleWithSAML",
                conditions={"StringEquals": {"SAML:aud": "https://signin.aws.amazon.com/saml"}},
            )
示例#13
0
    def __init__(self,
                 scope: core.Construct,
                 id: str,
                 federated: str,
                 *,
                 description=None,
                 external_ids=None,
                 inline_policies=None,
                 managed_policies=None,
                 max_session_duration=None,
                 path=None,
                 permissions_boundary=None) -> None:
        """Same as CDK IAM Role above. The assumed_by is a FederatedPrincipal 'federated' (your IAM Idp arn).

        Args:
            federated: ARN of the AWS IAM Idp through which users assume this IAM Role
        """
        assumed_by = iam.FederatedPrincipal(
            federated=federated,
            conditions={
                'StringEquals': {
                    'SAML:aud': 'https://signin.aws.amazon.com/saml'
                }
            },
            assume_role_action='sts:AssumeRoleWithSAML')

        super().__init__(scope=scope,
                         id=id,
                         assumed_by=assumed_by,
                         description=description,
                         external_ids=external_ids,
                         inline_policies=inline_policies,
                         managed_policies=managed_policies,
                         max_session_duration=max_session_duration,
                         path=path,
                         permissions_boundary=permissions_boundary)
示例#14
0
    def setup_identity_pool(self):
        identity_pool = aws_cognito.CfnIdentityPool(
            self,
            "pinpoint_integ_test_android",
            allow_unauthenticated_identities=True)

        unauthenticated_role = aws_iam.Role(
            self,
            "CognitoDefaultUnauthenticatedRole",
            assumed_by=aws_iam.FederatedPrincipal(
                "cognito-identity.amazonaws.com",
                {
                    "StringEquals": {
                        "cognito-identity.amazonaws.com:aud": identity_pool.ref
                    },
                    "ForAnyValue:StringLike": {
                        "cognito-identity.amazonaws.com:amr": "unauthenticated"
                    },
                },
                "sts:AssumeRoleWithWebIdentity",
            ),
        )
        unauthenticated_role.add_to_policy(
            aws_iam.PolicyStatement(
                effect=aws_iam.Effect.ALLOW,
                actions=["cognito-sync:*", "iot:*"],
                resources=["*"],
            ))
        aws_cognito.CfnIdentityPoolRoleAttachment(
            self,
            "DefaultValid",
            identity_pool_id=identity_pool.ref,
            roles={"unauthenticated": unauthenticated_role.role_arn},
        )

        self._parameters_to_save["identity_pool_id"] = identity_pool.ref
示例#15
0
    def _create_identity_pool(self) -> cognito.CfnIdentityPool:
        provider_name = (
            self.user_pool.user_pool_provider_name
            if hasattr(self.user_pool, "user_pool_provider_name")
            else f"cognito-idp.{self.context.region}.amazonaws.com/{self.context.user_pool_id}"
        )

        pool = cognito.CfnIdentityPool(
            scope=self,
            id="identity-pool",
            identity_pool_name=self.id.replace("-", "_"),
            allow_unauthenticated_identities=False,
            allow_classic_flow=False,
            cognito_identity_providers=[
                cognito.CfnIdentityPool.CognitoIdentityProviderProperty(
                    provider_name=provider_name,
                    client_id=self.user_pool_client.user_pool_client_id,
                )
            ],
        )
        name = f"{self.id}-cognito-authenticated-identity-role"
        authenticated_role = iam.Role(
            scope=self,
            id=name,
            role_name=name,
            assumed_by=iam.FederatedPrincipal(
                federated="cognito-identity.amazonaws.com",
                conditions={
                    "StringEquals": {"cognito-identity.amazonaws.com:aud": pool.ref},
                    "ForAnyValue:StringLike": {"cognito-identity.amazonaws.com:amr": "authenticated"},
                },
                assume_role_action="sts:AssumeRoleWithWebIdentity",
            ),
            inline_policies={
                "cognito-default": iam.PolicyDocument(
                    statements=[
                        iam.PolicyStatement(
                            actions=[
                                "mobileanalytics:PutEvents",
                                "cognito-sync:*",
                                "cognito-identity:*",
                            ],
                            resources=["*"],
                        )
                    ]
                ),
                "team-context-parameter": iam.PolicyDocument(
                    statements=[
                        iam.PolicyStatement(
                            actions=["ssm:DescribeParameters", "ssm:GetParameters"],
                            resources=[
                                f"arn:aws:ssm:{self.context.region}:{self.context.account_id}:"
                                f"parameter/orbit/{self.context.name}/teams/*"
                            ],
                        )
                    ]
                ),
            },
        )
        name = f"{self.id}-cognito-unauthenticated-identity-role"
        unauthenticated_role = iam.Role(
            scope=self,
            id=name,
            role_name=name,
            assumed_by=iam.FederatedPrincipal(
                federated="cognito-identity.amazonaws.com",
                conditions={
                    "StringEquals": {"cognito-identity.amazonaws.com:aud": pool.ref},
                    "ForAnyValue:StringLike": {"cognito-identity.amazonaws.com:amr": "unauthenticated"},
                },
                assume_role_action="sts:AssumeRoleWithWebIdentity",
            ),
            inline_policies={
                "cognito-default": iam.PolicyDocument(
                    statements=[
                        iam.PolicyStatement(
                            actions=[
                                "mobileanalytics:PutEvents",
                                "cognito-sync:*",
                            ],
                            resources=["*"],
                        )
                    ]
                )
            },
        )
        cognito.CfnIdentityPoolRoleAttachment(
            scope=self,
            id=f"{self.id}-role-attachment",
            identity_pool_id=pool.ref,
            roles={
                "authenticated": authenticated_role.role_arn,
                "unauthenticated": unauthenticated_role.role_arn,
            },
        )
        return pool
示例#16
0
    def __init__(self, scope: Construct, construct_id: str, env, vpc,
                 **kwargs) -> None:
        super().__init__(scope, construct_id, env=env, **kwargs)

        efs_sg = ec2.SecurityGroup(self,
                                   'EfsSG',
                                   vpc=vpc,
                                   description='EKS EFS SG',
                                   security_group_name='eks-efs')
        efs_sg.add_ingress_rule(ec2.Peer.ipv4('10.3.0.0/16'),
                                ec2.Port.all_traffic(), "EFS VPC access")
        Tags.of(efs_sg).add(key='cfn.eks-dev.stack', value='sg-stack')
        Tags.of(efs_sg).add(key='Name', value='eks-efs')
        Tags.of(efs_sg).add(key='env', value='dev')

        file_system = efs.FileSystem(
            self,
            construct_id,
            vpc=vpc,
            file_system_name='eks-efs',
            lifecycle_policy=efs.LifecyclePolicy.AFTER_14_DAYS,
            removal_policy=RemovalPolicy.DESTROY,
            security_group=efs_sg)

        Tags.of(file_system).add(key='cfn.eks-dev.stack', value='efs-stack')
        Tags.of(file_system).add(key='efs.csi.aws.com/cluster', value='true')
        Tags.of(file_system).add(key='Name', value='eks-efs')
        Tags.of(file_system).add(key='env', value='dev')

        policy_statement_1 = iam.PolicyStatement(
            effect=iam.Effect.ALLOW,
            actions=[
                "elasticfilesystem:DescribeAccessPoints",
                "elasticfilesystem:DescribeFileSystems"
            ],
            resources=['*'],
            conditions={
                'StringEquals': {
                    "aws:RequestedRegion": "ap-northeast-2"
                }
            })
        policy_statement_2 = iam.PolicyStatement(
            effect=iam.Effect.ALLOW,
            actions=[
                "elasticfilesystem:CreateAccessPoint",
                "elasticfilesystem:DeleteAccessPoint"
            ],
            resources=['*'],
            conditions={
                'StringEquals': {
                    "aws:ResourceTag/efs.csi.aws.com/cluster": "true"
                }
            })

        # EFS CSI SA
        efs_csi_role = iam.Role(
            self,
            'EfsCSIRole',
            role_name='eks-efs-csi-sa',
            assumed_by=iam.FederatedPrincipal(
                federated=oidc_arn,
                assume_role_action='sts:AssumeRoleWithWebIdentity',
                conditions={
                    'StringEquals':
                    string_like('kube-system', 'efs-csi-controller-sa')
                },
            ))

        for stm in [policy_statement_1, policy_statement_2]:
            efs_csi_role.add_to_policy(stm)

        Tags.of(efs_csi_role).add(key='cfn.eks-dev.stack', value='role-stack')
    def __init__(self, scope: core.Construct, id: str,
                 common_stack: CommonStack, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        self._supported_in_region = self.is_service_supported_in_region()

        app = pinpoint.CfnApp(self,
                              "android-integ-test",
                              name="android-integ-test")

        identity_pool = cognito.CfnIdentityPool(
            self,
            "pinpoint_integ_test_android",
            allow_unauthenticated_identities=True)

        unauthenticated_role = iam.Role(
            self,
            "CognitoDefaultUnauthenticatedRole",
            assumed_by=iam.FederatedPrincipal(
                "cognito-identity.amazonaws.com",
                {
                    "StringEquals": {
                        "cognito-identity.amazonaws.com:aud": identity_pool.ref
                    },
                    "ForAnyValue:StringLike": {
                        "cognito-identity.amazonaws.com:amr": "unauthenticated"
                    },
                },
                "sts:AssumeRoleWithWebIdentity",
            ),
        )
        unauthenticated_role.add_to_policy(
            iam.PolicyStatement(
                effect=iam.Effect.ALLOW,
                actions=["cognito-sync:*", "mobiletargeting:PutEvents"],
                resources=["*"],
            ))
        cognito.CfnIdentityPoolRoleAttachment(
            self,
            "DefaultValid",
            identity_pool_id=identity_pool.ref,
            roles={"unauthenticated": unauthenticated_role.role_arn},
        )

        self._parameters_to_save = {
            "identity_pool_id": identity_pool.ref,
            "AppId": app.ref,
            "Region": core.Aws.REGION,
        }
        self.save_parameters_in_parameter_store(platform=Platform.ANDROID)

        stack_policy = iam.PolicyStatement(
            effect=iam.Effect.ALLOW,
            actions=[
                "mobileanalytics:PutEvents",
                "mobiletargeting:PutEvents",
                "mobiletargeting:UpdateEndpoint",
            ],
            resources=["*"],
        )

        common_stack.add_to_common_role_policies(self,
                                                 policy_to_add=stack_policy)
示例#18
0
    def build_team_role(
        scope: core.Construct,
        context: "Context",
        team_name: str,
        policy_names: List[str],
        scratch_bucket: s3.IBucket,
        team_kms_key: kms.Key,
    ) -> iam.Role:
        env_name = context.name
        partition = core.Aws.PARTITION
        account = core.Aws.ACCOUNT_ID
        region = core.Aws.REGION

        lake_role_name: str = f"orbit-{env_name}-{team_name}-role"
        kms_keys = [team_kms_key.key_arn]
        scratch_bucket_kms_key = IamBuilder.get_kms_key_scratch_bucket(context=context)
        if scratch_bucket_kms_key:
            kms_keys.append(scratch_bucket_kms_key)

        lake_operational_policy = iam.ManagedPolicy(
            scope=scope,
            id="lake_operational_policy",
            managed_policy_name=f"orbit-{env_name}-{team_name}-user-access",
            statements=[
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "s3:*",
                    ],
                    resources=[
                        f"arn:{partition}:s3:::sagemaker-{region}-{account}",
                        f"arn:{partition}:s3:::sagemaker-{region}-{account}/*",
                        scratch_bucket.bucket_arn,
                        f"{scratch_bucket.bucket_arn}/{team_name}/*",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=["s3:List*", "s3:Get*"],
                    resources=[
                        f"arn:{partition}:s3:::{context.toolkit.s3_bucket}",
                        f"arn:{partition}:s3:::{context.toolkit.s3_bucket}/*",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=["ssm:Get*"],
                    resources=[
                        f"arn:{partition}:ssm:{region}:{account}:parameter/orbit*",
                        f"arn:{partition}:ssm:{region}:{account}:parameter/emr_launch/",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=["ssm:PutParameter"],
                    resources=[
                        f"arn:{partition}:ssm:{region}:{account}:parameter/orbit/{env_name}/teams/{team_name}/user*",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "redshift:GetClusterCredentials",
                        "redshift:CreateClusterUser",
                        "redshift:DeleteCluster",
                    ],
                    resources=[
                        f"arn:{partition}:redshift:{region}:{account}:dbuser:{env_name}-{team_name}*",
                        f"arn:{partition}:redshift:{region}:{account}:dbuser:{env_name}-{team_name}*/master",
                        f"arn:{partition}:redshift:{region}:{account}:dbuser:{env_name}-{team_name}*/defaultdb",
                        f"arn:{partition}:redshift:{region}:{account}:dbname:{env_name}-{team_name}*/defaultdb",
                        f"arn:{partition}:redshift:{region}:{account}:cluster:{env_name}-{team_name}*",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "sns:*",
                    ],
                    resources=[
                        f"arn:{partition}:sns:{region}:{account}:{env_name}-{team_name}*",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=["iam:PassRole"],
                    resources=[f"arn:{partition}:iam::{account}:role/{lake_role_name}"],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "ssm:Describe*",
                        "codeartifact:Describe*",
                        "codeartifact:Get*",
                        "codeartifact:List*",
                        "codeartifact:Read*",
                        "sts:GetServiceBearerToken",
                        "s3:ListAllMyBuckets",
                        "lambda:List*",
                        "lambda:Get*",
                        "iam:List*",
                        "tag:GetResources",
                        "ecr:Get*",
                        "ecr:List*",
                        "ecr:Describe*",
                        "ecr:BatchGetImage",
                        "ecr:BatchCheckLayerAvailability",
                        "cloudwatch:PutMetricData",
                        "redshift:DescribeClusters",
                        "states:List*",
                        "states:Get*",
                        "states:Describe*",
                        "glue:Get*",
                        "glue:List*",
                        "glue:Search*",
                        "athena:*",
                        "ecs:Describe*",
                        "ecs:ListTasks",
                        "ec2:Describe*",
                        "redshift:DescribeClusters",
                        "elasticmapreduce:List*",
                        "elasticmapreduce:Get*",
                        "elasticmapreduce:Describe*",
                        "elasticmapreduce:TerminateJobFlows",
                        "elasticmapreduce:AddJobFlowSteps",
                        "sagemaker:List*",
                        "sagemaker:Get*",
                        "sagemaker:Describe*",
                        "sagemaker:CreateModel",
                        "sagemaker:DeleteModelPackage",
                        "sagemaker:UpdateEndpointWeightsAndCapacities",
                        "sagemaker:DeleteAlgorithm",
                        "sagemaker:Search",
                        "sagemaker:UpdateWorkteam",
                        "sagemaker:DeleteModel",
                        "sagemaker:CreateModelPackage",
                        "sagemaker:DeleteWorkteam",
                        "sagemaker:CreateEndpoint",
                        "sagemaker:CreateEndpointConfig",
                        "sagemaker:RenderUiTemplate",
                        "sagemaker:StopTransformJob",
                        "sagemaker:CreateLabelingJob",
                        "sagemaker:DeleteEndpointConfig",
                        "sagemaker:CreateAlgorithm",
                        "sagemaker:CreateTrainingJob",
                        "sagemaker:StopHyperParameterTuningJob",
                        "sagemaker:DeleteEndpoint",
                        "sagemaker:CreateTransformJob",
                        "sagemaker:InvokeEndpoint",
                        "sagemaker:CreateWorkteam",
                        "sagemaker:StopLabelingJob",
                        "sagemaker:UpdateEndpoint",
                        "sagemaker:CreateCompilationJob",
                        "sagemaker:StopCompilationJob",
                        "sagemaker:CreateHyperParameterTuningJob",
                        "lakeformation:GetDataAccess",
                    ],
                    resources=["*"],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "logs:List*",
                        "logs:Describe*",
                        "logs:StartQuery",
                        "logs:StopQuery",
                        "logs:Get*",
                        "logs:Filter*",
                        "events:*",
                    ],
                    resources=[
                        f"arn:{partition}:logs:{region}:{account}:log-group:/aws/sagemaker/*",
                        f"arn:{partition}:logs:{region}:{account}:log-group:/aws/sagemaker/*:log-stream:*",
                        f"arn:{partition}:logs:{region}:{account}:log-group:/aws/eks/orbit*",
                        f"arn:{partition}:events:{region}:{account}:rule/orbit-{env_name}-{team_name}-*",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "ecr:*",
                    ],
                    resources=[f"arn:{partition}:ecr:{region}:{account}:repository/orbit-{env_name}-users-*"],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=["kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt", "kms:GenerateDataKey", "kms:DescribeKey"],
                    resources=kms_keys,
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "lambda:InvokeFunction",
                    ],
                    resources=[
                        f"arn:{partition}:lambda:{region}:{account}:function:orbit-{env_name}-{team_name}-*",
                        f"arn:{partition}:lambda:{region}:{account}:function:orbit-{env_name}-token-validation",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "cloudformation:DescribeStacks",
                    ],
                    resources=[
                        f"arn:{partition}:cloudformation:{region}:{account}:stack/orbit-{env_name}/*",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "ssm:GetParameters",
                        "ssm:DescribeParameters",
                        "ssm:GetParameter",
                        "ssm:DescribeParameter",
                    ],
                    resources=[
                        f"arn:{partition}:ssm:{region}:{account}:parameter/orbit/{env_name}/teams/{team_name}/*",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "ssm:DeleteParameter",
                        "ssm:DeleteParameters",
                    ],
                    resources=[
                        f"arn:{partition}:ssm:{region}:{account}:parameter/orbit/{env_name}/changeset",
                        f"arn:{partition}:ssm:{region}:{account}:parameter/orbit/{env_name}/manifest",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "ssm:DescribeParameters",
                    ],
                    resources=[f"arn:{partition}:ssm:{region}:{account}:*"],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=["s3:Put*"],
                    resources=[
                        f"arn:{partition}:s3:::{context.toolkit.s3_bucket}",
                        f"arn:{partition}:s3:::{context.toolkit.s3_bucket}/cli/remote/*",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=["codebuild:StartBuild", "codebuild:BatchGetBuilds"],
                    resources=[f"arn:{partition}:codebuild:{region}:{account}:project/orbit-{env_name}"],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "logs:CreateLogStream",
                        "logs:CreateLogGroup",
                        "logs:DescribeLogStreams",
                        "logs:PutLogEvents",
                    ],
                    resources=[
                        f"arn:{partition}:logs:{region}:{account}:log-group:/aws/codebuild/orbit-{env_name}:log-stream:*"  # noqa
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "logs:List*",
                        "logs:Describe*",
                        "logs:StartQuery",
                        "logs:StopQuery",
                        "logs:Get*",
                        "logs:Filter*",
                        "events:*",
                    ],
                    resources=[
                        f"arn:{partition}:logs:{region}:{account}:log-group:/aws/codebuild/orbit-{env_name}*:log-stream:*",  # noqa
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "ecr:InitiateLayerUpload",
                    ],
                    resources=[
                        f"arn:{partition}:ecr:{region}:{account}:repository/*",
                    ],
                ),
            ],
        )

        managed_policies = [
            lake_operational_policy,
            # For EKS
            iam.ManagedPolicy.from_aws_managed_policy_name(managed_policy_name="AmazonEKS_CNI_Policy"),
        ]

        # Parse list to IAM policies
        aws_managed_user_policies = [
            iam.ManagedPolicy.from_aws_managed_policy_name(managed_policy_name=policy_name)
            for policy_name in policy_names
            if "orbit" not in policy_name
        ]

        orbit_custom_policies = [
            iam.ManagedPolicy.from_managed_policy_name(scope=scope, id=policy_name, managed_policy_name=policy_name)
            for policy_name in policy_names
            if "orbit" in policy_name
        ]

        managed_policies = managed_policies + aws_managed_user_policies + orbit_custom_policies

        role = iam.Role(
            scope=scope,
            id=lake_role_name,
            role_name=lake_role_name,
            assumed_by=iam.CompositePrincipal(
                iam.ServicePrincipal("ec2.amazonaws.com"),
                iam.ServicePrincipal("glue.amazonaws.com"),
                iam.ServicePrincipal("sagemaker.amazonaws.com"),
                iam.ServicePrincipal("redshift.amazonaws.com"),
                iam.ServicePrincipal("codepipeline.amazonaws.com"),
                iam.ServicePrincipal("personalize.amazonaws.com"),
            ),
            managed_policies=managed_policies,
        )
        if role.assume_role_policy:
            role.assume_role_policy.add_statements(
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=["sts:AssumeRoleWithWebIdentity"],
                    principals=[
                        iam.FederatedPrincipal(
                            federated=f"arn:{partition}:iam::{account}:oidc-provider/{context.eks_oidc_provider}",
                            conditions={
                                "StringLike": {
                                    f"{context.eks_oidc_provider}:sub": f"system:serviceaccount:{team_name}:*"
                                }
                            },
                        )
                    ],
                ),
            )
        return role
示例#19
0
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        aws_region = os.environ.get("CDK_DEPLOY_REGION", os.environ["CDK_DEFAULT_REGION"])
        account_id = os.environ.get("CDK_DEPLOY_ACCOUNT", os.environ["CDK_DEFAULT_ACCOUNT"])

        ssm_client = boto3.client('ssm', aws_region)
        # Prepare pipeline config details in SSM parameters
        if prefix == 'us':
            self.qs_reports_env_config = {"Permissions":
                                              [{"Group_Name": "critical",
                                                "Reports": ["Sales Results - Critical"],
                                                "ns_name": "default"},
                                               {"Group_Name": "highlyconfidential",
                                                "Reports": ["Field Operations Dashboard",
                                                            "Sales Results - Highly Confidential"
                                                            ],
                                                "ns_name": "default"},
                                               {"Group_Name": "bi-developer",
                                                "Reports": ["all"],
                                                "ns_name": "default"},
                                               {"Group_Name": "bi-admin",
                                                "Reports": ["all"],
                                                "ns_name": "default"},
                                               {"Group_Name": "power-reader",
                                                "Reports": ["read-all"],
                                                "ns_name": "default"},
                                               {"Group_Name": "3rd-party",
                                                "Reports": ["Marketing KPIs"],
                                                "ns_name": "3rd-party"}
                                               ]
                                          }
        if prefix == 'eu':
            self.qs_reports_env_config = {"Permissions":
                                              [{"Group_Name": "eu-critical",
                                                "Reports": ["EUResults - Critical"]},
                                               {"Group_Name": "bi-developer",
                                                "Reports": ["all"]},
                                               {"Group_Name": "bi-admin",
                                                "Reports": ["all"]},
                                               {"Group_Name": "eu-highlyconfidential",
                                                "Reports": ["EUField Operations Dashboard",
                                                            "EUResults - Highly Confidential"]},
                                               {"Group_Name": "power-reader",
                                                "Reports": ["read-all"]}]}

        self.qs_reports_env_config_ssm = ssm.StringParameter(
            self, '/qs/config/access',
            string_value=json.dumps(self.qs_reports_env_config),
            parameter_name='/qs/config/access'
        )

        #group-user mapping information is stored in s3 bucket. A ssm parameter stores the bucket name.
        self.qs_user_group_config = {'bucket-name':f'qs-granular-access-demo-{account_id}'}

        self.qs_user_group_config_ssm = ssm.StringParameter(
            self, '/qs/config/groups',
            string_value=json.dumps(self.qs_user_group_config),
            parameter_name='/qs/config/groups'
        )

        # group-role mapping information is stored in a ssm parameter.
        self.qs_role_config = {'default_bi-developer': 'AUTHOR',
                               'default_bi-admin': 'ADMIN',
                               'default_power-reader': 'AUTHOR',
                               'default_critical': 'READER',
                               'default_highlyconfidential': 'READER',
                               'default_marketing': 'AUTHOR',
                               '3rd-party_3rd-party': 'AUTHOR'
                               }

        self.qs_role_config_ssm = ssm.StringParameter(
            self, '/qs/config/roles',
            string_value=json.dumps(self.qs_role_config),
            parameter_name='/qs/config/roles'
        )

        # group-namespace mapping information is stored in a ssm parameter.
        self.qs_ns_config = {"ns":['default',
                             '3rd-party']}

        self.qs_ns_config_ssm = ssm.StringParameter(
            self, '/qs/config/ns',
            string_value=json.dumps(self.qs_ns_config),
            parameter_name='/qs/config/ns'
        )

        lambda_role = iam.Role(
            self,
            id='lambda-role',
            description='Role for the quicksight lambda',
            role_name=f'{aws_region}-role-quicksight-lambda',
            max_session_duration=core.Duration.seconds(3600),
            assumed_by=iam.ServicePrincipal('lambda.amazonaws.com'),
            inline_policies={
                'AllowS3Access': iam.PolicyDocument(
                    statements=[
                        iam.PolicyStatement(
                            effect=iam.Effect.ALLOW,
                            actions=["kms:GetParametersForImport",
                                     "kms:GetPublicKey",
                                     "kms:ListKeyPolicies",
                                     "kms:ListRetirableGrants",
                                     "kms:GetKeyPolicy",
                                     "kms:ListResourceTags",
                                     "kms:ListGrants",
                                     "kms:GetParametersForImport",
                                     "kms:GetKeyRotationStatus",
                                     "kms:DescribeKey",
                                     "kms:CreateGrant",
                                     "kms:ListAliases",
                                     "kms:ListKeys",
                                     "kms:DescribeCustomKeyStores",
                                     "ssm:GetParameters",
                                     "ssm:GetParameter",
                                     "ssm:GetParametersByPath"
                                     ],
                            resources=['*']
                        ),
                        iam.PolicyStatement(
                            effect=iam.Effect.ALLOW,
                            actions=["lambda:InvokeFunction",
                                     "logs:CreateLogStream",
                                     "logs:CreateLogGroup",
                                     "logs:PutLogEvents",
                                     "quicksight:*",
                                     "s3:HeadBucket",
                                     "s3:ListAllMyBuckets",
                                     "s3:PutObject",
                                     "s3:GetObject",
                                     "s3:ListBucket",
                                     "s3:GetObjectVersionForReplication",
                                     "s3:GetBucketPolicy",
                                     "s3:GetObjectVersion",
                                     "cloudwatch:PutMetricData",
                                     "sts:GetCallerIdentity"],
                            resources=['*']
                        )
                    ]
                )
            }
        )

        user_init = _lambda.Function(self, 'user_init',
                                           handler='user_init.lambda_handler',
                                           runtime=_lambda.Runtime.PYTHON_3_7,
                                           code=_lambda.Code.from_asset(os.path.join(current_dir,
                                                                                     '../lambda_functions/user_init/')),
                                           function_name='user_init',
                                           role=lambda_role,
                                           timeout=core.Duration.minutes(15),
                                           memory_size=512
                                           )

        check_team_members = _lambda.Function(self, 'check_team_members',
                                                    handler='check_team_members.lambda_handler',
                                                    runtime=_lambda.Runtime.PYTHON_3_7,
                                                    code=_lambda.Code.from_asset(os.path.join(current_dir,
                                                                                              '../lambda_functions/check_team_members/')),
                                                    function_name='check_team_members',
                                                    role=lambda_role,
                                                    timeout=core.Duration.minutes(15),
                                                    memory_size=512,
                                                    environment={'aws_region': f'{core.Aws.REGION}'}
                                                    )

        downgrade_user = _lambda.Function(self, 'downgrade_user',
                                                handler='downgrade_user.lambda_handler',
                                                runtime=_lambda.Runtime.PYTHON_3_8,
                                                code=_lambda.Code.from_asset(os.path.join(current_dir,
                                                                                          '../lambda_functions/downgrade_user/')),
                                                function_name='downgrade_user',
                                                role=lambda_role,
                                                timeout=core.Duration.minutes(15),
                                                memory_size=2048,
                                                environment={'aws_region': f'{core.Aws.REGION}'}
                                                )

        granular_user_govenance = _lambda.Function(self, 'granular_user_govenance',
                                                 handler='granular_user_govenance.lambda_handler',
                                                 runtime=_lambda.Runtime.PYTHON_3_7,
                                                 code=_lambda.Code.from_asset(os.path.join(current_dir,
                                                                                           '../lambda_functions/granular_user_govenance')),
                                                 function_name='granular_user_govenance',
                                                 role=lambda_role,
                                                 timeout=core.Duration.minutes(15),
                                                 memory_size=2048,
                                                 environment={'aws_region': f'{core.Aws.REGION}'}
                                                )

        granular_access_assets_govenance = _lambda.Function(self, 'granular_access_assets_govenance',
                                                   handler='granular_access_assets_govenance.lambda_handler',
                                                   runtime=_lambda.Runtime.PYTHON_3_7,
                                                   code=_lambda.Code.from_asset(os.path.join(current_dir,
                                                                                             '../lambda_functions/granular_access_assets_govenance')),
                                                   function_name='granular_access_assets_govenance',
                                                   role=lambda_role,
                                                   timeout=core.Duration.minutes(15),
                                                   memory_size=2048,
                                                   environment={'aws_region': f'{core.Aws.REGION}'}
                                                   )

        quicksight_event_rule = events.Rule(self, 'QuickSightCWEventRule',
                                             description='CloudWatch rule to detect new QuickSight user creation',
                                             rule_name='qs-gc-user-creation',
                                             targets=[targets.LambdaFunction(user_init)],
                                             event_pattern=events.EventPattern(source=['aws.quicksight'],
                                                                               detail_type=[
                                                                                   'AWS Service Event via CloudTrail'],
                                                                               detail={
                                                                                   "eventSource": [
                                                                                       "quicksight.amazonaws.com"],
                                                                                   "eventName": ["CreateUser"]
                                                                               }
                                                                               )
                                             )

        quicksight_schedule_rule = events.Rule(self, 'quicksight_schedule_rule',
                                               description='CloudWatch rule to run QS objects/groups assignment every hour',
                                               rule_name='qs-gc-every-hour',
                                               schedule=events.Schedule.cron(minute="0"),
                                               targets=[targets.LambdaFunction(granular_user_govenance)]
                                               )

        quicksight_assume_condition_object = {"StringEquals": {
            "SAML:aud": "https://signin.aws.amazon.com/saml"}}

        quicksight_federated_prin_with_conditionb_obj = iam.FederatedPrincipal(
            f'arn:aws:iam::{core.Aws.ACCOUNT_ID}:saml-provider/saml', quicksight_assume_condition_object,
            'sts:AssumeRoleWithSAML')

        quicksight_resource_scope = '${aws:userid}'
        quicksight_reader_saml_inline_policies = {
            'AllowQuicksightAccessSAML': iam.PolicyDocument(
                statements=[
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=['quicksight:CreateReader'],
                        resources=[
                            f'arn:aws:quicksight::{core.Aws.ACCOUNT_ID}:user/{quicksight_resource_scope}']
                    )
                ]
            )
        }

        quicksight_users = iam.Role(
            self,
            id=f"quicksight-fed-{prefix}-users",  # this is the default group with no access
            description='Role for the quicksight reader SAML',
            role_name=f"quicksight-fed-{prefix}-users",
            max_session_duration=core.Duration.seconds(3600),
            assumed_by=quicksight_federated_prin_with_conditionb_obj,
            inline_policies=quicksight_reader_saml_inline_policies
        )
示例#20
0
    def __init__(self, scope: core.Construct, id: str, props,
                 **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        power_transformers = aws_dynamodb.Table(
            self,
            "PowerTransformers",
            table_name="PowerTransformers",
            partition_key=aws_dynamodb.Attribute(
                name="name", type=aws_dynamodb.AttributeType.STRING),
            removal_policy=core.RemovalPolicy.DESTROY)

        function = _lambda.Function(
            self,
            "power_transformers_data_enrichment",
            function_name="power_transformers_data_enrichment",
            runtime=_lambda.Runtime.PYTHON_3_7,
            handler="lambda_function.handler",
            code=_lambda.Code.asset("./lambda/data-enrichment"))

        function.add_environment('TABLE_NAME', power_transformers.table_name)
        function.add_to_role_policy(
            iam.PolicyStatement(actions=['dynamodb:GetItem'],
                                resources=[f"{power_transformers.table_arn}"],
                                effect=iam.Effect.ALLOW))

        function.add_permission(
            principal=iam.ServicePrincipal('iotanalytics.amazonaws.com'),
            action='lambda:InvokeFunction',
            id='pt-iot-analytics')

        bucket = s3.Bucket(
            self,
            'PowerTransformersTelemetryBucket',
            bucket_name=f"{props['projectName'].lower()}-{core.Aws.ACCOUNT_ID}",
            removal_policy=core.RemovalPolicy.DESTROY)

        output_bucket = s3.Bucket(
            self,
            'PowerTransformersProcessedDataBucket',
            bucket_name=
            f"{props['projectName'].lower()}-output-{core.Aws.ACCOUNT_ID}",
            removal_policy=core.RemovalPolicy.DESTROY)

        # Apply least privilege
        s3_role = iam.Role(
            self,
            "IotAnalyticsS3Role",
            assumed_by=iam.ServicePrincipal("iotanalytics.amazonaws.com"),
            managed_policies=[
                iam.ManagedPolicy.from_aws_managed_policy_name(
                    'AmazonS3FullAccess')
            ])

        # s3_role.add_to_policy(iam.PolicyStatement(actions=["s3:PutObject", "s3:DeleteObject", "s3:GetBucketLocation"],
        #                       resources=[f"{bucket.bucket_arn}", f"{bucket.bucket_arn}/*"], effect=iam.Effect.ALLOW))

        # Apply least privilege
        s3_output_role = iam.Role(
            self,
            "IotAnalyticsS3OutputRole",
            assumed_by=iam.ServicePrincipal("iotanalytics.amazonaws.com"),
            managed_policies=[
                iam.ManagedPolicy.from_aws_managed_policy_name(
                    'AmazonS3FullAccess')
            ],
        )

        # s3_output_role.add_to_policy(iam.PolicyStatement(actions=["s3:PutObject", "s3:DeleteObject", "s3:GetBucketLocation"],
        #                       resources=[f"{output_bucket.bucket_arn}", f"{output_bucket.bucket_arn}/*"], effect=iam.Effect.ALLOW))

        project_name = props['projectName'].lower().replace('-', '_')

        channel_name = f"{project_name}_channel"
        datastore_name = f"{project_name}_datastore"

        channel_s3 = CHANNEL.CustomerManagedS3Property(
            bucket=bucket.bucket_name,
            key_prefix='raw/',
            role_arn=s3_role.role_arn)
        channel_storage = CHANNEL.ChannelStorageProperty(
            customer_managed_s3=channel_s3)

        CHANNEL(self,
                'iot_channel',
                channel_name=channel_name,
                channel_storage=channel_storage)

        datastore_s3 = DATASTORE.CustomerManagedS3Property(
            bucket=bucket.bucket_name,
            key_prefix='processed/',
            role_arn=s3_role.role_arn)

        datastore_storage = DATASTORE.DatastoreStorageProperty(
            customer_managed_s3=datastore_s3)

        datastore = DATASTORE(self,
                              'iot_datastore',
                              datastore_name=datastore_name,
                              datastore_storage=datastore_storage)

        channel_activity = PIPELINE.ChannelProperty(name='ChannelActivity',
                                                    channel_name=channel_name,
                                                    next='LambdaActivity')
        lambda_activity = PIPELINE.LambdaProperty(
            name='LambdaActivity',
            lambda_name='power_transformers_data_enrichment',
            next='DatastoreActivity',
            batch_size=10)
        datastore_activity = PIPELINE.DatastoreProperty(
            name='DatastoreActivity', datastore_name=datastore_name)

        pipeline_activities = PIPELINE.ActivityProperty(
            channel=channel_activity,
            lambda_=lambda_activity,
            datastore=datastore_activity)

        pipeline = PIPELINE(self,
                            'iot_pipeline',
                            pipeline_name=f"{project_name}_pipeline",
                            pipeline_activities=[pipeline_activities])

        pipeline.add_depends_on(datastore)

        query_action = DATASET.QueryActionProperty(
            sql_query=f"SELECT * FROM {datastore_name}")
        action = DATASET.ActionProperty(query_action=query_action,
                                        action_name='sqlAction')
        schedule_expression = DATASET.ScheduleProperty(
            schedule_expression='cron(1/5 * * * ? *)')
        trigger_schedule = DATASET.TriggerProperty(
            schedule=schedule_expression)

        dataset_s3_destination = DATASET.S3DestinationConfigurationProperty(
            bucket=output_bucket.bucket_name,
            key=
            'dataset/Version/!{iotanalytics:scheduleTime}_!{iotanalytics:versionId}.csv',
            role_arn=s3_output_role.role_arn)

        dataset_destination = DATASET.DatasetContentDeliveryRuleDestinationProperty(
            s3_destination_configuration=dataset_s3_destination)

        content_delivery_rules = DATASET.DatasetContentDeliveryRuleProperty(
            destination=dataset_destination)

        dataset = DATASET(self,
                          'iot_dataset',
                          dataset_name=f"{project_name}_dataset",
                          actions=[action],
                          triggers=[trigger_schedule],
                          content_delivery_rules=[content_delivery_rules])

        dataset.add_depends_on(datastore)

        user_pool = aws_cognito.UserPool(
            self,
            'kibanaUserPool',
            self_sign_up_enabled=False,
            sign_in_aliases=aws_cognito.SignInAliases(username=True,
                                                      email=True))

        aws_cognito.CfnUserPoolDomain(
            self,
            'userPoolDomain',
            user_pool_id=user_pool.user_pool_id,
            domain=
            f"{props['projectName'].lower()}-{''.join(random.choices(string.ascii_lowercase + string.digits, k=6))}"
        )

        user_pool_client = aws_cognito.UserPoolClient(self,
                                                      'kibanaClientId',
                                                      user_pool=user_pool,
                                                      generate_secret=True)

        identity_provider = aws_cognito.CfnIdentityPool.CognitoIdentityProviderProperty(
            client_id=user_pool_client.user_pool_client_id,
            provider_name=user_pool.user_pool_provider_name)

        identity_pool = aws_cognito.CfnIdentityPool(
            self,
            'identityPool',
            allow_unauthenticated_identities=False,
            cognito_identity_providers=[identity_provider])

        # Apply least privilege
        cognito_authenticated_role = iam.Role(
            self,
            "CognitoAuthRole",
            assumed_by=iam.FederatedPrincipal(
                "cognito-identity.amazonaws.com",
                assume_role_action='sts:AssumeRoleWithWebIdentity',
                conditions={
                    'StringEquals': {
                        'cognito-identity.amazonaws.com:aud': identity_pool.ref
                    },
                    'ForAnyValue:StringLike': {
                        'cognito-identity.amazonaws.com:amr': 'authenticated'
                    }
                }),
            managed_policies=[
                iam.ManagedPolicy.from_aws_managed_policy_name(
                    'AmazonESFullAccess')
            ])

        aws_cognito.CfnIdentityPoolRoleAttachment(
            self,
            'identityPoolRoleAttachment',
            identity_pool_id=identity_pool.ref,
            roles={'authenticated': cognito_authenticated_role.role_arn})

        cognito_options = DOMAIN.CognitoOptionsProperty(
            enabled=True,
            user_pool_id=user_pool.user_pool_id,
            identity_pool_id=identity_pool.ref,
            role_arn=
            f"arn:aws:iam::{core.Aws.ACCOUNT_ID}:role/service-role/CognitoAccessForAmazonES"
        )

        ebs_options = DOMAIN.EBSOptionsProperty(ebs_enabled=True,
                                                volume_size=10,
                                                volume_type='gp2')
        elasticsearch_cluster_config = DOMAIN.ElasticsearchClusterConfigProperty(
            instance_count=1, instance_type='r5.large.elasticsearch')
        encryption_at_rest_options = DOMAIN.EncryptionAtRestOptionsProperty(
            enabled=True)
        node_to_node_encryption_options = DOMAIN.NodeToNodeEncryptionOptionsProperty(
            enabled=True)
        snapshot_options = DOMAIN.SnapshotOptionsProperty(
            automated_snapshot_start_hour=0)

        es_domain_arn = f"arn:aws:es:{core.Aws.REGION}:{core.Aws.ACCOUNT_ID}:domain/{props['projectName'].lower()}/*"

        es_policy_statement = iam.PolicyStatement(actions=['es:*'],
                                                  resources=[es_domain_arn])

        es_policy_statement.add_arn_principal(
            cognito_authenticated_role.role_arn)

        policy_document = iam.PolicyDocument()

        policy_document.add_statements(es_policy_statement)

        domain = DOMAIN(
            self,
            'elasticsearch',
            domain_name=f"{props['projectName'].lower()}",
            cognito_options=cognito_options,
            ebs_options=ebs_options,
            elasticsearch_cluster_config=elasticsearch_cluster_config,
            encryption_at_rest_options=encryption_at_rest_options,
            node_to_node_encryption_options=node_to_node_encryption_options,
            snapshot_options=snapshot_options,
            elasticsearch_version='6.8',
            access_policies=policy_document)

        function = _lambda.Function(
            self,
            "load_data_from_s3_to_es",
            function_name="load_data_from_s3_to_es",
            runtime=_lambda.Runtime.PYTHON_3_7,
            handler="lambda_function.handler",
            code=_lambda.Code.asset("./lambda/load-data-from-s3-to-es.zip"))

        function.add_environment('ES_HOST', domain.attr_domain_endpoint)
        function.add_environment('ES_REGION', f"{core.Aws.REGION}")

        function.add_to_role_policy(
            iam.PolicyStatement(actions=['es:ESHttpPost'],
                                resources=[es_domain_arn],
                                effect=iam.Effect.ALLOW))
        function.add_to_role_policy(
            iam.PolicyStatement(actions=['s3:GetObject'],
                                resources=[f"{output_bucket.bucket_arn}/*"],
                                effect=iam.Effect.ALLOW))

        notification = aws_s3_notifications.LambdaDestination(function)

        output_bucket.add_event_notification(s3.EventType.OBJECT_CREATED,
                                             notification)

        load_ddb_custom_resource = LoadDDBDataCustomResource(
            self,
            "LoadDDBData",
            table_name=power_transformers.table_name,
            table_arn=power_transformers.table_arn)

        load_ddb_custom_resource.node.add_dependency(power_transformers)

        load_es_index_custom_resource = LoadESIndexCustomResource(
            self,
            "LoadESIndex",
            es_host=domain.attr_domain_endpoint,
            es_region=f"{core.Aws.REGION}",
            es_domain_arn=es_domain_arn)

        load_es_index_custom_resource.node.add_dependency(domain)

        load_kibana_dashboards_custom_resource = LoadKibanaDashboardsCustomResource(
            self,
            "LoadKibanaDashboards",
            es_host=domain.attr_domain_endpoint,
            es_region=f"{core.Aws.REGION}",
            es_domain_arn=es_domain_arn)

        load_kibana_dashboards_custom_resource.node.add_dependency(
            load_es_index_custom_resource)
def add_cognito(self):
    password_policy = _cognito.PasswordPolicy(
        require_lowercase=False,
        require_digits=False,
        require_symbols=False,
        require_uppercase=False,
    )

    user_pool = _cognito.UserPool(
        self,
        'UserPool',
        password_policy=password_policy,
        user_pool_name='UserPool',
        self_sign_up_enabled=True,
        user_verification={
            "email_subject": "Verify your email for our awesome app!",
            "email_body":
            "Hello {username}, Thanks for signing up to our awesome app! Your verification code is {####}",
            "email_style": _cognito.VerificationEmailStyle.CODE,
        },
        user_invitation={
            "email_subject":
            "Invite to join our awesome app!",
            "email_body":
            "Hello {username}, you have been invited to join our awesome app! Your temporary password is {####}",
        },
        sign_in_aliases={"email": True},
        auto_verify={"email": True},
    )

    user_pool_client = user_pool.add_client("AppClient",
                                            auth_flows={
                                                "user_password": True,
                                                "user_srp": True,
                                                "refresh_token": True,
                                                "admin_user_password": True
                                            })

    idp = _cognito.CfnIdentityPool.CognitoIdentityProviderProperty(
        client_id=user_pool_client.user_pool_client_id,
        provider_name=user_pool.user_pool_provider_name)
    identity_pool = _cognito.CfnIdentityPool(
        self,
        "IdPool",
        allow_unauthenticated_identities=False,
        cognito_identity_providers=[idp])

    authenticated_principal = _iam.FederatedPrincipal(
        'cognito-identity.amazonaws.com', {
            "StringEquals": {
                "cognito-identity.amazonaws.com:aud": identity_pool.ref
            },
            "ForAnyValue:StringLike": {
                "cognito-identity.amazonaws.com:amr": "authenticated"
            },
        }, "sts:AssumeRoleWithWebIdentity")

    unauthenticated_principal = _iam.FederatedPrincipal(
        'cognito-identity.amazonaws.com', {
            "StringEquals": {
                "cognito-identity.amazonaws.com:aud": identity_pool.ref
            },
            "ForAnyValue:StringLike": {
                "cognito-identity.amazonaws.com:amr": "authenticated"
            },
        }, "sts:AssumeRoleWithWebIdentity")

    authenticated_role = _iam.Role(self,
                                   "CognitoDefaultAuthenticatedRole",
                                   assumed_by=authenticated_principal)

    unauthenticated_role = _iam.Role(self,
                                     "CognitoDefaultUnAuthenticatedRole",
                                     assumed_by=unauthenticated_principal)

    authenticated_policy = _iam.PolicyStatement(
        effect=_iam.Effect.ALLOW,
        actions=[
            "mobileanalytics:PutEvents", "cognito-sync:*", "cognito-identity:*"
        ],
        resources=["*"])

    unauthenticated_policy = _iam.PolicyStatement(
        effect=_iam.Effect.ALLOW,
        actions=[
            "mobileanalytics:PutEvents",
            "cognito-sync:*",
        ],
        resources=["*"])

    authenticated_role.add_to_policy(authenticated_policy)

    unauthenticated_role.add_to_policy(unauthenticated_policy)

    _cognito.CfnIdentityPoolRoleAttachment(self,
                                           "DefaultValidRoleAttachment",
                                           identity_pool_id=identity_pool.ref,
                                           roles={
                                               "authenticated":
                                               authenticated_role.role_arn,
                                               "unauthenticated":
                                               unauthenticated_role.role_arn
                                           })

    return user_pool, identity_pool, user_pool_client
    def __init__(self, scope: core.Construct, id: str,
                 common_stack: CommonStack, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        self._supported_in_region = self.are_services_supported_in_region(
            ["cognito-identity", "kinesis"])

        identity_pool = cognito.CfnIdentityPool(
            self,
            "pinpoint_integ_test_android",
            allow_unauthenticated_identities=True)

        unauthenticated_role = iam.Role(
            self,
            "CognitoDefaultUnauthenticatedRole",
            assumed_by=iam.FederatedPrincipal(
                "cognito-identity.amazonaws.com",
                {
                    "StringEquals": {
                        "cognito-identity.amazonaws.com:aud": identity_pool.ref
                    },
                    "ForAnyValue:StringLike": {
                        "cognito-identity.amazonaws.com:amr": "unauthenticated"
                    },
                },
                "sts:AssumeRoleWithWebIdentity",
            ),
        )
        unauthenticated_role.add_to_policy(
            iam.PolicyStatement(
                effect=iam.Effect.ALLOW,
                actions=["cognito-sync:*", "kinesis:*", "firehose:*"],
                resources=["*"],
            ))
        cognito.CfnIdentityPoolRoleAttachment(
            self,
            "DefaultValid",
            identity_pool_id=identity_pool.ref,
            roles={"unauthenticated": unauthenticated_role.role_arn},
        )

        firehose_s3_role = iam.Role(
            self,
            "FirehoseS3Role",
            assumed_by=iam.ServicePrincipal("firehose.amazonaws.com"))
        firehose_s3_role.add_to_policy(
            iam.PolicyStatement(
                effect=iam.Effect.ALLOW,
                actions=[
                    "s3:AbortMultipartUpload",
                    "s3:GetBucketLocation",
                    "s3:GetObject",
                    "s3:ListBucket",
                    "s3:ListBucketMultipartUploads",
                    "s3:PutObject",
                ],
                resources=["*"],
            ))

        ingest_bucket = s3.Bucket(self, "test-aws-android-sdk-firehose-bucket")
        s3_dest_config = firehose.CfnDeliveryStream.S3DestinationConfigurationProperty(
            bucket_arn=ingest_bucket.bucket_arn,
            buffering_hints=firehose.CfnDeliveryStream.BufferingHintsProperty(
                interval_in_seconds=60, size_in_m_bs=5),
            compression_format="UNCOMPRESSED",
            role_arn=firehose_s3_role.role_arn,
        )

        firehose_test = firehose.CfnDeliveryStream(
            self,
            "kinesis_firehose_recorder_test",
            s3_destination_configuration=s3_dest_config)

        self._parameters_to_save = {
            "firehose_name": firehose_test.ref,
            "identity_pool_id": identity_pool.ref,
        }
        self.save_parameters_in_parameter_store(platform=Platform.ANDROID)

        common_stack.add_to_common_role_policies(self)
示例#23
0
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        ### S3 ###

        source_csv_bucket = _s3.Bucket(self,
                                       "BYODValidationSourceBucket",
                                       versioned=True)

        target_csv_bucket = _s3.Bucket(
            self,
            "BYODValidationTargetBucket",
            removal_policy=core.RemovalPolicy.RETAIN)

        ### Cognito ###

        userpool = _cognito.UserPool(self,
                                     "WebToolUserPool",
                                     user_pool_name="byod-webtool-userpool",
                                     self_sign_up_enabled=True,
                                     auto_verify={
                                         "email": True,
                                         "phone": False
                                     },
                                     user_verification={
                                         "email_subject":
                                         "Your verification code",
                                         "email_body":
                                         "Your verification code is {####}",
                                         "email_style":
                                         _cognito.VerificationEmailStyle.CODE
                                     },
                                     standard_attributes={
                                         "email": {
                                             "required": True,
                                             "mutable": False
                                         }
                                     },
                                     password_policy={})
        client = userpool.add_client("webtool-app-client",
                                     auth_flows={
                                         "custom": True,
                                         "user_password": True,
                                         "user_srp": True,
                                         "refresh_token": True
                                     })
        identity_pool = _cognito.CfnIdentityPool(
            self,
            "WebToolCognitoIdentityPool",
            allow_unauthenticated_identities=True)
        identity_pool.add_property_override(
            "CognitoIdentityProviders",
            [{
                "ClientId": client.user_pool_client_id,
                "ProviderName": userpool.user_pool_provider_name
            }])
        auth_role = _iam.Role(
            self,
            "CognitoAuthRole",
            assumed_by=FederatedPrincipal(
                "cognito-identity.amazonaws.com", {
                    "StringEquals": {
                        "cognito-identity.amazonaws.com:aud": identity_pool.ref
                    },
                    "ForAnyValue:StringLike": {
                        "cognito-identity.amazonaws.com:amr": "authenticated"
                    }
                }))
        auth_role.add_to_policy(
            PolicyStatement(effect=Effect.ALLOW,
                            actions=["s3:GetObject"],
                            resources=["%s/*" % target_csv_bucket.bucket_arn]))
        unauth_role = _iam.Role(
            self,
            "CognitoUnauthRole",
            assumed_by=_iam.FederatedPrincipal(
                "cognito-identity.amazonaws.com",
                conditions={
                    "StringEquals": {
                        "cognito-identity.amazonaws.com:aud": identity_pool.ref
                    },
                    "ForAnyValue:StringLike": {
                        "cognito-identity.amazonaws.com:amr": "unauthenticated"
                    }
                }))
        identity_pool_policy = _cognito.CfnIdentityPoolRoleAttachment(
            self,
            "WebToolCognitoIdentityPoolPolicy",
            identity_pool_id=identity_pool.ref,
            roles={
                'unauthenticated': unauth_role.role_arn,
                'authenticated': auth_role.role_arn
            })
        core.CfnOutput(self, "UserPoolId", value=userpool.user_pool_id)
        core.CfnOutput(self, "ClientId", value=client.user_pool_client_id)
        core.CfnOutput(self,
                       "ProviderName",
                       value=userpool.user_pool_provider_name)

        ### DynamoDB ###

        validation_job_table = _dynamodb.Table(
            self,
            "ValidationJobTable",
            partition_key=_dynamodb.Attribute(
                name="id", type=_dynamodb.AttributeType.STRING))

        ## AppSync ###

        api = _appsync.GraphqlApi(
            self,
            "Api",
            name="validation-job-api",
            schema=_appsync.Schema.from_asset(
                os.path.join(dirname, "api", "schema.graphql")),
            authorization_config=AuthorizationConfig(
                default_authorization=AuthorizationMode(
                    authorization_type=AuthorizationType.USER_POOL,
                    user_pool_config=UserPoolConfig(user_pool=userpool))),
            log_config=LogConfig(exclude_verbose_content=False,
                                 field_log_level=FieldLogLevel.ALL))
        api_ds = api.add_dynamo_db_data_source("ValidationJobDataSource",
                                               validation_job_table)
        core.CfnOutput(self, "GraphQLEndpoint", value=api.graphql_url)

        ### SQS ###

        validation_job_queue = _sqs.Queue(self, "ValidationJobQueue")

        profiling_job_queue = _sqs.Queue(self, "ProfilingJobQueue")

        ### Lambda ###

        validation_trigger_function = _lambda.Function(
            self,
            "ValidationTriggerFunction",
            runtime=_lambda.Runtime.PYTHON_3_8,
            code=_lambda.Code.from_asset(
                os.path.join(dirname, "lambda", "validation_trigger")),
            handler='lambda_function.lambda_handler')

        validation_trigger_function.add_environment(
            "TABLE_NAME", validation_job_table.table_name)
        validation_trigger_function.add_environment(
            "QUEUE_URL", validation_job_queue.queue_url)

        validation_trigger_function.add_event_source(
            _S3EventSource(source_csv_bucket,
                           events=[_s3.EventType.OBJECT_CREATED]))

        source_csv_bucket.grant_read(validation_trigger_function)
        validation_job_table.grant_read_write_data(validation_trigger_function)
        validation_job_queue.grant_send_messages(validation_trigger_function)

        stager_function = _lambda.Function(self,
                                           "StagerFunction",
                                           runtime=_lambda.Runtime.NODEJS_12_X,
                                           code=_lambda.Code.from_asset(
                                               os.path.join(
                                                   dirname, "lambda",
                                                   "stager")),
                                           handler='index.handler')

        stager_function.add_environment("REGION", self.region)
        stager_function.add_environment("SOURCE_BUCKET",
                                        source_csv_bucket.bucket_name)
        stager_function.add_environment("STAGE_BUCKET",
                                        target_csv_bucket.bucket_name)
        source_csv_bucket.grant_read(stager_function)
        target_csv_bucket.grant_put(stager_function)

        ### ECS Fargate ###

        validation_fargate_asset = _ecr_assets.DockerImageAsset(
            self,
            "ValidationBuildImage",
            directory=os.path.join(dirname, "fargate", "validation"))
        profiling_fargate_asset = _ecr_assets.DockerImageAsset(
            self,
            "ProfilingBuildImage",
            directory=os.path.join(dirname, "fargate", "profiling"))

        vpc = _ec2.Vpc(self, "VPC", max_azs=3)
        cluster = _ecs.Cluster(self, "ECSCluster", vpc=vpc)

        validation_fargate_service = _ecs_patterns.QueueProcessingFargateService(
            self,
            "ValidationFargateService",
            cluster=cluster,
            cpu=4096,
            memory_limit_mib=30720,
            enable_logging=True,
            image=_ecs.ContainerImage.from_docker_image_asset(
                validation_fargate_asset),
            environment={
                "TABLE_NAME": validation_job_table.table_name,
                "QUEUE_URL": validation_job_queue.queue_url,
                "SOURCE_BUCKET_NAME": source_csv_bucket.bucket_name,
                "TARGET_BUCKET_NAME": target_csv_bucket.bucket_name,
                "REGION": self.region
            },
            queue=validation_job_queue,
            max_scaling_capacity=2,
            max_healthy_percent=200,
            min_healthy_percent=66)
        validation_fargate_service.task_definition.task_role.add_managed_policy(
            _iam.ManagedPolicy.from_aws_managed_policy_name(
                "AmazonDynamoDBFullAccess"))
        validation_fargate_service.task_definition.task_role.add_managed_policy(
            _iam.ManagedPolicy.from_aws_managed_policy_name(
                "AmazonS3FullAccess"))

        profiling_fargate_service = _ecs_patterns.QueueProcessingFargateService(
            self,
            "ProfilingFargateService",
            cluster=cluster,
            cpu=4096,
            memory_limit_mib=30720,
            enable_logging=True,
            image=_ecs.ContainerImage.from_docker_image_asset(
                profiling_fargate_asset),
            environment={
                "TABLE_NAME": validation_job_table.table_name,
                "QUEUE_URL": profiling_job_queue.queue_url,
                "SOURCE_BUCKET_NAME": source_csv_bucket.bucket_name,
                "TARGET_BUCKET_NAME": target_csv_bucket.bucket_name,
                "REGION": self.region
            },
            queue=profiling_job_queue,
            max_scaling_capacity=2,
            max_healthy_percent=200,
            min_healthy_percent=66)
        profiling_fargate_service.task_definition.task_role.add_managed_policy(
            _iam.ManagedPolicy.from_aws_managed_policy_name(
                "AmazonDynamoDBFullAccess"))
        profiling_fargate_service.task_definition.task_role.add_managed_policy(
            _iam.ManagedPolicy.from_aws_managed_policy_name(
                "AmazonS3FullAccess"))
    def __init__(self, scope: core.Construct, id: str, domain_prefix: str, other_account: str,  **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        user_pool = cognito.CfnUserPool(
            scope=self,
            id="user-pool",
            admin_create_user_config=cognito.CfnUserPool.AdminCreateUserConfigProperty(
                allow_admin_create_user_only=True,
            ),
            policies=cognito.CfnUserPool.PoliciesProperty(
                password_policy=cognito.CfnUserPool.PasswordPolicyProperty(
                    minimum_length=20,
                )),
            username_attributes=["email"],
            auto_verified_attributes=["email"],
        )

        cognito.CfnUserPoolDomain(
            scope=self,
            id="cognito-user-pool-domain",
            domain=f"{domain_prefix}-{core.Aws.ACCOUNT_ID}",
            user_pool_id=user_pool.ref,
        )

        id_pool = cognito.CfnIdentityPool(
            scope=self,
            id="identity-pool",
            allow_unauthenticated_identities=False,
            cognito_identity_providers=[],
        )

        auth_role = iam.Role(
            scope=self,
            id="auth-role",
            assumed_by=iam.FederatedPrincipal(
                federated="cognito-identity.amazonaws.com",
                conditions={
                    "StringEquals": {"cognito-identity.amazonaws.com:aud": id_pool.ref},
                    "ForAnyValue:StringLike": {"cognito-identity.amazonaws.com:amr": "authenticated"},
                },
                assume_role_action="sts:AssumeRoleWithWebIdentity"),
        )

        es_role = iam.Role(
            scope=self,
            id="es-role",
            assumed_by=iam.ServicePrincipal('es.amazonaws.com'),
            managed_policies=[
                iam.ManagedPolicy.from_aws_managed_policy_name(
                    managed_policy_name="AmazonESCognitoAccess"
                )
            ],
        )

        es_domain = elasticsearch.CfnDomain(
            scope=self,
            id="search-domain",
            elasticsearch_cluster_config=elasticsearch.CfnDomain.ElasticsearchClusterConfigProperty(
                instance_count=2,
                instance_type="m5.large.elasticsearch",
            ),
            node_to_node_encryption_options=elasticsearch.CfnDomain.NodeToNodeEncryptionOptionsProperty(
                enabled=True),
            encryption_at_rest_options=elasticsearch.CfnDomain.EncryptionAtRestOptionsProperty(
                enabled=True),
            ebs_options=elasticsearch.CfnDomain.EBSOptionsProperty(
                ebs_enabled=True,
                volume_size=20),
            elasticsearch_version="7.4",
            domain_name=domain_prefix,
            access_policies={
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Principal": {
                            "AWS": auth_role.role_arn
                        },
                        "Action": [
                            "es:ESHttpGet",
                            "es:ESHttpPut",
                            "es:ESHttpPost",
                            "es:ESHttpDelete"
                        ],
                        "Resource": "arn:aws:es:" + core.Aws.REGION + ":" + core.Aws.ACCOUNT_ID + ":domain/" + domain_prefix + "/*"
                    },
                ]
            },
        )

        es_domain.add_property_override(
            'CognitoOptions.Enabled', True)
        es_domain.add_property_override(
            'CognitoOptions.IdentityPoolId', id_pool.ref)
        es_domain.add_property_override(
            'CognitoOptions.RoleArn', es_role.role_arn)
        es_domain.add_property_override(
            'CognitoOptions.UserPoolId', user_pool.ref)

        cognito.CfnIdentityPoolRoleAttachment(
            scope=self,
            id='user-pool-role-attachment',
            identity_pool_id=id_pool.ref,
            roles={
                'authenticated': auth_role.role_arn
            }
        )

        es_external_role = iam.Role(
            scope=self,
            id="logger-role",
            assumed_by=iam.CompositePrincipal(
                iam.ServicePrincipal("lambda.amazonaws.com"),
                iam.AccountPrincipal(other_account),
            ),
            description="role to use elastic search assumed by lambda",
            inline_policies={
                "es_policy": iam.PolicyDocument(statements=[
                    iam.PolicyStatement(
                        actions=[
                            "es:ESHttpPost",
                        ],
                        resources=[
                            es_domain.attr_arn + "/*",
                        ],
                    )]),
            },
        )

        core.CfnOutput(
            scope=self,
            id="es-host",
            value=es_domain.attr_domain_endpoint,
        )

        core.CfnOutput(
            scope=self,
            id="es-region",
            value=core.Aws.REGION,
        )

        core.CfnOutput(
            scope=self,
            id="es-external-role",
            value=es_external_role.role_arn,
        )
    def __init__(self, scope: core.Construct, id: str, aws_region: str,
                 account_id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        s3.Bucket(self,
                  'administrativedashboard',
                  bucket_name='administrative-dashboard' + account_id,
                  block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
                  versioned=True)

        lambda_role = iam.Role(
            self,
            id='lambda-role',
            description=
            'Role for the quicksight administrative dashboard lambda',
            role_name='qs-administrative-dashboard-lambda',
            max_session_duration=core.Duration.seconds(3600),
            assumed_by=iam.ServicePrincipal('lambda.amazonaws.com'),
            inline_policies={
                'AllowS3Access':
                iam.PolicyDocument(statements=[
                    iam.PolicyStatement(
                        effect=iam.Effect.ALLOW,
                        actions=[
                            "lambda:InvokeFunction", "logs:CreateLogStream",
                            "logs:CreateLogGroup", "logs:PutLogEvents",
                            "quicksight:*", "s3:HeadBucket",
                            "s3:ListAllMyBuckets", "s3:PutObject",
                            "s3:GetObject", "s3:ListBucket",
                            "s3:GetObjectVersionForReplication",
                            "s3:GetBucketPolicy", "s3:GetObjectVersion",
                            "cloudwatch:PutMetricData", "sts:GetCallerIdentity"
                        ],
                        resources=['*'])
                ])
            })

        user_initiation = _lambda.Function(
            self,
            'user_initiation',
            handler='user_initiation.lambda_handler',
            runtime=_lambda.Runtime.PYTHON_3_7,
            code=_lambda.Code.from_asset(
                os.path.join(current_dir,
                             '../lambda_functions/user_initiation/')),
            function_name='user_initiation',
            role=lambda_role,
            timeout=core.Duration.minutes(15),
            memory_size=512)

        group_initiation = _lambda.Function(
            self,
            'group_initiation',
            handler='group_initiation.lambda_handler',
            runtime=_lambda.Runtime.PYTHON_3_7,
            code=_lambda.Code.from_asset(
                os.path.join(current_dir,
                             '../lambda_functions/group_initiation/')),
            function_name='group_initiation',
            role=lambda_role,
            timeout=core.Duration.minutes(15),
            memory_size=512)

        data_prepare = _lambda.Function(
            self,
            'data_prepare',
            handler='data_prepare.lambda_handler',
            runtime=_lambda.Runtime.PYTHON_3_7,
            code=_lambda.Code.from_asset(
                os.path.join(current_dir,
                             '../lambda_functions/data_prepare/')),
            function_name='data_prepare',
            role=lambda_role,
            timeout=core.Duration.minutes(15),
            memory_size=512)

        usercreation = events.Rule(
            self,
            'usercreation',
            description=
            'CloudWatch rule to detect new QuickSight user creation',
            rule_name='quicksight-user-creation',
            targets=[
                targets.LambdaFunction(user_initiation),
                targets.LambdaFunction(data_prepare)
            ],
            event_pattern=events.EventPattern(
                source=['aws.quicksight'],
                detail_type=['AWS Service Event via CloudTrail'],
                detail={
                    "eventSource": ["quicksight.amazonaws.com"],
                    "eventName": ["CreateUser"]
                }))
        groupcreation = events.Rule(
            self,
            'groupcreation',
            description=
            'CloudWatch rule to detect new QuickSight group creation',
            rule_name='quicksight-group-creation',
            targets=[
                targets.LambdaFunction(group_initiation),
                targets.LambdaFunction(data_prepare)
            ],
            event_pattern=events.EventPattern(
                source=['aws.quicksight'],
                detail_type=['AWS API Call via CloudTrail'],
                detail={
                    "eventSource": ["quicksight.amazonaws.com"],
                    "eventName": ["CreateGroup"]
                }))
        groupdeletion = events.Rule(
            self,
            'groupdeletion',
            description='CloudWatch rule to detect QuickSight group deletion',
            rule_name='quicksight-group-deletion',
            targets=[targets.LambdaFunction(data_prepare)],
            event_pattern=events.EventPattern(
                source=['aws.quicksight'],
                detail_type=['AWS API Call via CloudTrail'],
                detail={
                    "eventSource": ["quicksight.amazonaws.com"],
                    "eventName": ["DeleteGroup"]
                }))
        userdeletion = events.Rule(
            self,
            'userdeletion',
            description='CloudWatch rule to detect QuickSight user deletion',
            rule_name='quicksight-user-deletion',
            targets=[targets.LambdaFunction(data_prepare)],
            event_pattern=events.EventPattern(
                source=['aws.quicksight'],
                detail_type=['AWS Service Event via CloudTrail'],
                detail={
                    "eventSource": ["quicksight.amazonaws.com"],
                    "eventName": ["DeleteUser"]
                }))
        addgroupmember = events.Rule(
            self,
            'addgroupmember',
            description=
            'CloudWatch rule to detect QuickSight user add into a group',
            rule_name='quicksight-user-add-into-a-group',
            targets=[targets.LambdaFunction(data_prepare)],
            event_pattern=events.EventPattern(
                source=['aws.quicksight'],
                detail_type=['AWS API Call via CloudTrail'],
                detail={
                    "eventSource": ["quicksight.amazonaws.com"],
                    "eventName": ["CreateGroupMembership"]
                }))
        dashboardupdate = events.Rule(
            self,
            'dashboardupdate',
            description=
            'CloudWatch rule to detect QuickSight dashboard permissions update',
            rule_name='quicksight-dashboard-permissions-update',
            targets=[targets.LambdaFunction(data_prepare)],
            event_pattern=events.EventPattern(
                source=['aws.quicksight'],
                detail_type=['AWS API Call via CloudTrail'],
                detail={
                    "eventSource": ["quicksight.amazonaws.com"],
                    "eventName": ["UpdateDashboardPermissions"]
                }))
        datasetupdate = events.Rule(
            self,
            'datasetupdate',
            description=
            'CloudWatch rule to detect QuickSight dataset permissions update',
            rule_name='quicksight-dataset-permissions-update',
            targets=[targets.LambdaFunction(data_prepare)],
            event_pattern=events.EventPattern(
                source=['aws.quicksight'],
                detail_type=['AWS API Call via CloudTrail'],
                detail={
                    "eventSource": ["quicksight.amazonaws.com"],
                    "eventName": ["UpdateDataSetPermissions"]
                }))
        datasourceupdate = events.Rule(
            self,
            'datasourceupdate',
            description=
            'CloudWatch rule to detect QuickSight data source permissions update',
            rule_name='quicksight-datasource-permissions-update',
            targets=[targets.LambdaFunction(data_prepare)],
            event_pattern=events.EventPattern(
                source=['aws.quicksight'],
                detail_type=['AWS API Call via CloudTrail'],
                detail={
                    "eventSource": ["quicksight.amazonaws.com"],
                    "eventName": ["UpdateDataSourcePermissions"]
                }))

        quicksight_assume_condition_object = {
            "StringEquals": {
                "SAML:aud": "https://signin.aws.amazon.com/saml"
            }
        }

        quicksight_federated_prin_with_conditionb_obj = iam.FederatedPrincipal(
            f'arn:aws:iam::{core.Aws.ACCOUNT_ID}:saml-provider/saml',
            quicksight_assume_condition_object, 'sts:AssumeRoleWithSAML')

        quicksight_resource_scope = '${aws:userid}'
        quicksight_reader_saml_inline_policies = {
            'AllowQuicksightAccessSAML':
            iam.PolicyDocument(statements=[
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=['quicksight:CreateReader'],
                    resources=[
                        f'arn:aws:quicksight::{core.Aws.ACCOUNT_ID}:user/{quicksight_resource_scope}'
                    ])
            ])
        }

        Marketing = iam.Role(
            self,
            id="Marketing",  #quicksight-saml-<ldap group name>
            description='Role for the quicksight reader SAML',
            role_name="Marketing",
            max_session_duration=core.Duration.seconds(3600),
            assumed_by=quicksight_federated_prin_with_conditionb_obj,
            inline_policies=quicksight_reader_saml_inline_policies)
        HR = iam.Role(self,
                      id="HR",
                      description='Role for the quicksight reader SAML',
                      role_name="HR",
                      max_session_duration=core.Duration.seconds(3600),
                      assumed_by=quicksight_federated_prin_with_conditionb_obj,
                      inline_policies=quicksight_reader_saml_inline_policies)

        quicksight_author_saml_inline_policies = {
            'AllowQuicksightAuthorAccessSAML':
            iam.PolicyDocument(statements=[
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=['quicksight:CreateUser'],
                    resources=[
                        f'arn:aws:quicksight::{core.Aws.ACCOUNT_ID}:user/{quicksight_resource_scope}'
                    ])
            ])
        }

        bideveloper = iam.Role(
            self,
            id="bideveloper",
            description='Role for the quicksight author SAML',
            role_name="BI-Developer",
            max_session_duration=core.Duration.seconds(3600),
            assumed_by=quicksight_federated_prin_with_conditionb_obj,
            inline_policies=quicksight_author_saml_inline_policies)
        quicksight_admin_saml_inline_policies = {
            'AllowQuicksightAdminAccessSAML':
            iam.PolicyDocument(statements=[
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=['quicksight:CreateAdmin'],
                    resources=[
                        f'arn:aws:quicksight::{core.Aws.ACCOUNT_ID}:user/{quicksight_resource_scope}'
                    ])
            ])
        }

        Admin = iam.Role(
            self,
            id="Admin",
            description='Role for the quicksight admin SAML',
            role_name="BI-Admin",
            max_session_duration=core.Duration.seconds(3600),
            assumed_by=quicksight_federated_prin_with_conditionb_obj,
            inline_policies=quicksight_admin_saml_inline_policies)
示例#26
0
    def build_team_role(
        scope: core.Construct,
        context: "Context",
        team_name: str,
        policy_names: List[str],
        scratch_bucket: s3.IBucket,
        team_kms_key: kms.Key,
        session_timeout: core.Duration,
    ) -> iam.Role:
        env_name = context.name
        partition = core.Aws.PARTITION
        account = core.Aws.ACCOUNT_ID
        region = core.Aws.REGION
        lake_role_name: str = f"orbit-{env_name}-{team_name}-{region}-role"
        role_prefix: str = f"/{context.role_prefix}/" if context.role_prefix else "/"
        kms_keys = [team_kms_key.key_arn]
        scratch_bucket_kms_key = IamBuilder.get_kms_key_scratch_bucket(
            context=context)
        if scratch_bucket_kms_key:
            kms_keys.append(scratch_bucket_kms_key)

        lake_operational_policy = iam.ManagedPolicy(
            scope=scope,
            id="lake_operational_policy",
            managed_policy_name=
            f"orbit-{env_name}-{team_name}-{region}-user-access",
            statements=[
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "s3:*",
                    ],
                    resources=[
                        f"arn:{partition}:s3:::sagemaker-{region}-{account}",
                        f"arn:{partition}:s3:::sagemaker-{region}-{account}/*",
                        scratch_bucket.bucket_arn,
                        f"{scratch_bucket.bucket_arn}/{team_name}/*",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=["s3:List*", "s3:Get*", "s3:Put*"],
                    resources=[
                        f"arn:{partition}:s3:::{context.toolkit.s3_bucket}",
                        f"arn:{partition}:s3:::{context.toolkit.s3_bucket}/samples/*",
                        f"arn:{partition}:s3:::{context.toolkit.s3_bucket}/codeseeder/*",
                        f"arn:{partition}:s3:::{context.toolkit.s3_bucket}/teams/{team_name}/*",
                        f"arn:{partition}:s3:::{context.toolkit.s3_bucket}/helm/repositories/env/*",
                        f"arn:{partition}:s3:::{context.toolkit.s3_bucket}/helm/repositories/teams/{team_name}/*",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=["ssm:Get*"],
                    resources=[
                        f"arn:{partition}:ssm:{region}:{account}:parameter/orbit*",
                        f"arn:{partition}:ssm:{region}:{account}:parameter/emr_launch/",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=["ssm:PutParameter"],
                    resources=[
                        f"arn:{partition}:ssm:{region}:{account}:parameter/orbit/{env_name}/teams/{team_name}/user*",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "redshift:GetClusterCredentials",
                        "redshift:CreateClusterUser",
                        "redshift:DeleteCluster",
                    ],
                    resources=[
                        f"arn:{partition}:redshift:{region}:{account}:dbuser:orbit-{env_name}-{team_name}*",
                        f"arn:{partition}:redshift:{region}:{account}:dbuser:orbit-{env_name}-{team_name}*/master",
                        f"arn:{partition}:redshift:{region}:{account}:dbuser:orbit-{env_name}-{team_name}*/defaultdb",
                        f"arn:{partition}:redshift:{region}:{account}:dbname:orbit-{env_name}-{team_name}*/defaultdb",
                        f"arn:{partition}:redshift:{region}:{account}:cluster:orbit-{env_name}-{team_name}*",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "sns:*",
                    ],
                    resources=[
                        f"arn:{partition}:sns:{region}:{account}:{env_name}-{team_name}*",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=["iam:PassRole"],
                    resources=[
                        f"arn:{partition}:iam::{account}:role{role_prefix}{lake_role_name}"
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "ssm:Describe*",
                        "codeartifact:Describe*",
                        "codeartifact:Get*",
                        "codeartifact:List*",
                        "codeartifact:Read*",
                        "sts:GetServiceBearerToken",
                        "s3:ListAllMyBuckets",
                        "lambda:List*",
                        "lambda:Get*",
                        "iam:List*",
                        "tag:GetResources",
                        "ecr:Get*",
                        "ecr:List*",
                        "ecr:Describe*",
                        "ecr:BatchGetImage",
                        "ecr:BatchCheckLayerAvailability",
                        "cloudwatch:PutMetricData",
                        "redshift:DescribeClusters",
                        "states:List*",
                        "states:Get*",
                        "states:Describe*",
                        "glue:Get*",
                        "glue:List*",
                        "glue:Search*",
                        "athena:*",
                        "ecs:Describe*",
                        "ecs:ListTasks",
                        "ec2:Describe*",
                        "redshift:DescribeClusters",
                        "elasticmapreduce:List*",
                        "elasticmapreduce:Get*",
                        "elasticmapreduce:Describe*",
                        "elasticmapreduce:TerminateJobFlows",
                        "elasticmapreduce:AddJobFlowSteps",
                        "sagemaker:*",
                        "databrew:*",
                        "lakeformation:GetDataAccess",
                        "fsx:Describe*",
                        "fsx:List*",
                    ],
                    resources=["*"],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "ecr:*",
                    ],
                    resources=[
                        f"arn:{partition}:ecr:{region}:{account}:repository/orbit-{env_name}/users/*"
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt",
                        "kms:GenerateDataKey", "kms:DescribeKey"
                    ],
                    resources=kms_keys,
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "lambda:InvokeFunction",
                    ],
                    resources=[
                        f"arn:{partition}:lambda:{region}:{account}:function:orbit-{env_name}-{team_name}-*",
                        f"arn:{partition}:lambda:{region}:{account}:function:orbit-{env_name}-token-validation",
                        f"arn:{partition}:lambda:{region}:{account}:function:orbit-{env_name}-eks-service-handler",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "cloudformation:DescribeStacks",
                    ],
                    resources=[
                        f"arn:{partition}:cloudformation:{region}:{account}:stack/orbit-{env_name}/*",
                        f"arn:{partition}:cloudformation:{region}:{account}:stack/aws-codeseeder-orbit*",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "ssm:GetParameters",
                        "ssm:DescribeParameters",
                        "ssm:GetParameter",
                        "ssm:DescribeParameter",
                    ],
                    resources=[
                        f"arn:{partition}:ssm:{region}:{account}:parameter/orbit/{env_name}/teams/{team_name}/*",
                        f"arn:{partition}:ssm:{region}:{account}:parameter/Orbit-Slack-Notifications",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "ssm:DeleteParameter",
                        "ssm:DeleteParameters",
                    ],
                    resources=[
                        f"arn:{partition}:ssm:{region}:{account}:parameter/orbit/{env_name}/changeset",
                        f"arn:{partition}:ssm:{region}:{account}:parameter/orbit/{env_name}/manifest",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "ssm:DescribeParameters",
                    ],
                    resources=[f"arn:{partition}:ssm:{region}:{account}:*"],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=["s3:Put*"],
                    resources=[
                        f"arn:{partition}:s3:::{context.toolkit.s3_bucket}",
                        f"arn:{partition}:s3:::{context.toolkit.s3_bucket}/cli/remote/*",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "codebuild:StartBuild", "codebuild:BatchGetBuilds"
                    ],
                    resources=[
                        f"arn:{partition}:codebuild:{region}:{account}:project/orbit-{env_name}",
                        f"arn:{partition}:codebuild:{region}:{account}:project/codeseeder-orbit",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "logs:CreateLogStream",
                        "logs:CreateLogGroup",
                        "logs:DescribeLogStreams",
                        "logs:PutLogEvents",
                    ],
                    resources=[
                        f"arn:{partition}:logs:{region}:{account}:log-group:/aws/codebuild/orbit-{env_name}:log-stream:*",  # noqa
                        f"arn:{partition}:logs:{region}:{account}:log-group:/aws/codebuild/codeseeder-orbit:log-stream:*",  # noqa
                        f"arn:{partition}:logs:{region}:{account}:log-group:/aws-glue-databrew/*:log-stream:*",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "logs:List*",
                        "logs:Describe*",
                        "logs:StartQuery",
                        "logs:StopQuery",
                        "logs:Get*",
                        "logs:Filter*",
                        "events:*",
                    ],
                    resources=[
                        f"arn:{partition}:logs:{region}:{account}:log-group:/aws/codebuild/orbit-{env_name}*:log-stream:*",  # noqa
                        f"arn:{partition}:logs:{region}:{account}:log-group:/aws/codebuild/codeseeder-orbit:log-stream:*",  # noqa
                        f"arn:{partition}:logs:{region}:{account}:log-group:/aws-glue-databrew/*:log-stream:*",
                        f"arn:{partition}:logs:{region}:{account}:log-group:/aws/sagemaker/*",
                        f"arn:{partition}:logs:{region}:{account}:log-group:/aws/sagemaker/*:log-stream:*",
                        f"arn:{partition}:logs:{region}:{account}:log-group:/aws/eks/orbit*",
                        f"arn:{partition}:events:{region}:{account}:rule/orbit-{env_name}-{team_name}-*",
                        f"arn:{partition}:logs:{region}:{account}:log-group:/aws-glue-databrew/*",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "ecr:InitiateLayerUpload",
                    ],
                    resources=[
                        f"arn:{partition}:ecr:{region}:{account}:repository/*",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "eks:DescribeCluster",
                    ],
                    resources=[
                        f"arn:{partition}:eks:{region}:{account}:cluster/orbit-{env_name}",
                    ],
                ),
            ],
        )

        managed_policies = [
            lake_operational_policy,
            # For EKS
            iam.ManagedPolicy.from_aws_managed_policy_name(
                managed_policy_name="AmazonEKS_CNI_Policy"),
        ]

        # Parse list to IAM policies
        # First check if the policies are AWS managed or not, and if they have a tag
        aws_policies, customer_policies = process_policies(
            policy_names=policy_names, account_id=context.account_id)

        aws_managed_user_policies = [
            iam.ManagedPolicy.from_aws_managed_policy_name(
                managed_policy_name=policy_name)
            for policy_name in aws_policies
        ]

        orbit_custom_policies = [
            iam.ManagedPolicy.from_managed_policy_name(
                scope=scope, id=policy_name, managed_policy_name=policy_name)
            for policy_name in customer_policies
        ]

        managed_policies = (managed_policies +
                            cast(List[object], aws_managed_user_policies) +
                            cast(List[object], orbit_custom_policies))

        role = iam.Role(
            scope=scope,
            id=f"lakerole-for-{env_name}-{team_name}",
            role_name=lake_role_name,
            assumed_by=cast(
                iam.IPrincipal,
                iam.CompositePrincipal(
                    iam.ServicePrincipal("ec2.amazonaws.com"),
                    iam.ServicePrincipal("glue.amazonaws.com"),
                    iam.ServicePrincipal("sagemaker.amazonaws.com"),
                    iam.ServicePrincipal("redshift.amazonaws.com"),
                    iam.ServicePrincipal("codepipeline.amazonaws.com"),
                    iam.ServicePrincipal("codebuild.amazonaws.com"),
                    iam.ServicePrincipal("personalize.amazonaws.com"),
                    iam.ServicePrincipal("databrew.amazonaws.com"),
                ),
            ),
            managed_policies=cast(Optional[Sequence[iam.IManagedPolicy]],
                                  managed_policies),
            max_session_duration=session_timeout,
        )
        if role.assume_role_policy:
            role.assume_role_policy.add_statements(
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=["sts:AssumeRoleWithWebIdentity"],
                    principals=[
                        cast(
                            iam.IPrincipal,
                            iam.FederatedPrincipal(
                                federated=
                                f"arn:{partition}:iam::{account}:oidc-provider/{context.eks_oidc_provider}",
                                conditions={
                                    "StringLike": {
                                        f"{context.eks_oidc_provider}:sub":
                                        f"system:serviceaccount:{team_name}*:*"
                                    }
                                },
                            ),
                        )
                    ],
                ), )
        return role
    def __init__(
        self,
        scope: core.Construct,
        id: str,
        host_name,
        cert_arn,
        zone_id,
        admin_user: str,
        admin_password: str,
        cloud9_instance_size: str,
        participant_limit: str,
        **kwargs,
    ) -> None:
        super().__init__(scope, id, **kwargs)

        stack = core.Stack.of(self)
        stack.template_options.description = "Connected Drink Dispenser Workshop"

        # Static Website
        props: StaticSiteProps = StaticSiteProps(
            fqdn=host_name,
            hosted_zone_id=zone_id,
            certificate_arn=cert_arn,
            error_configuration=[
                {
                    "error_code": 403,
                    "error_caching_min_ttl": 300,
                    "response_code": 200,
                    "response_page_path": "/index.html",
                },
                {
                    "error_code": 404,
                    "error_caching_min_ttl": 300,
                    "response_code": 200,
                    "response_page_path": "/index.html",
                },
            ],
            output_name="CDDWebSite",
        )
        cdd_site = StaticSiteConstruct(self, "StaticSite", props)

        # Custom resource to clean out static website bucket prior to delete
        # TODO: Move this to the StaticSiteConstruct as option
        props: CustomResourceProps = CustomResourceProps(
            name=id + "-CR-S3DeleteObjects",
            lambda_directory="./lambda_functions/cr_s3_delete",
            handler="index.main",
            timeout=30,
            runtime=lambda_.Runtime.PYTHON_3_7,
            environment={"BUCKET_NAME": cdd_site.bucket_name},
        )
        s3_delete_cr = CustomResourceConstruct(self, "EmptyCddS3Bucket", props)
        # DependsOn the bucket (we need to delete objects before the bucket is deleted)
        s3_delete_cr.resource.node.add_dependency(cdd_site.bucket_resource)
        policy_statement = iam.PolicyStatement()
        policy_statement.add_actions("s3:GetBucket*")
        policy_statement.add_actions("s3:GetObject*")
        policy_statement.add_actions("s3:DeleteObject*")
        policy_statement.add_actions("s3:List*")
        policy_statement.add_resources(cdd_site.bucket_resource.bucket_arn)
        policy_statement.add_resources(
            f"{cdd_site.bucket_resource.bucket_arn}/*")
        s3_delete_cr.add_policy_to_role(policy_statement)

        # IAM Constructs
        user_group = iam.Group(
            self,
            "UserGroup",
            group_name=id + "-CDDUserGroup",
            managed_policies=[
                iam.ManagedPolicy.from_aws_managed_policy_name(
                    "ReadOnlyAccess")
            ],
        )

        # DynamoDB tables
        user_db = dynamodb.Table(
            # UserId as key, user "admin" tracks next available dispenser id
            # No access to users, RW to Cognito Lambda
            self,
            "UserTable",
            table_name=id + "-UserTable",
            partition_key={
                "name": "userName",
                "type": dynamodb.AttributeType.STRING
            },
            billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST,
            removal_policy=core.RemovalPolicy.DESTROY,
        )
        dispenser_db = dynamodb.Table(
            # Dispenser ID and credit amount - RO to users, RW to APIs
            self,
            "DispenserTable",
            table_name=id + "-DispenserTable",
            partition_key={
                "name": "dispenserId",
                "type": dynamodb.AttributeType.STRING,
            },
            billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST,
            removal_policy=core.RemovalPolicy.DESTROY,
        )
        dispenser_events = dynamodb.Table(
            # Recorded events from dispenser actions
            self,
            "DispenserEvents",
            table_name=id + "-DispenserEvents",
            partition_key={
                "name": "dispenserId",
                "type": dynamodb.AttributeType.STRING,
            },
            sort_key={
                "name": "timestamp",
                "type": dynamodb.AttributeType.STRING
            },
            billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST,
            removal_policy=core.RemovalPolicy.DESTROY,
        )

        # Cognito Resources
        # User pool with phone_number as username
        props: CognitoUserPoolProps = CognitoUserPoolProps(
            user_pool_name=id + "-users",
            client_name=id + "-webclient",
            auto_verified_attributes=["phone_number"],
            schema=[
                {
                    "name": "group",
                    "attributeDataType": "String",
                    "mutable": True,
                    "required": False,
                },
                {
                    "name": "dispenserId",
                    "attributeDataType": "String",
                    "mutable": True,
                    "required": False,
                },
            ],
            policies={
                "passwordPolicy": {
                    "minimumLength": 6,
                    "requireLowercase": True,
                    "requireNumbers": True,
                    "requireSymbols": False,
                    "requireUppercase": False,
                }
            },
        )
        user_pool = CognitoUserPoolConstruct(self, "UserPool", props)

        # Role and lambda triggers
        lambda_cognito_access_role = iam.Role(
            # Access to IDP calls (for triggers)
            self,
            "LambdaCognitoAccessRole",
            assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"),
            inline_policies=[
                iam.PolicyDocument(statements=[
                    iam.PolicyStatement(
                        actions=[
                            "logs:CreateLogGroup",
                            "logs:CreateLogStream",
                            "logs:PutLogEvents",
                        ],
                        resources=["arn:aws:logs:*:*:*"],
                    ),
                    iam.PolicyStatement(actions=["cognito-idp:*"],
                                        resources=["*"]),
                    iam.PolicyStatement(actions=["dynamodb:*"],
                                        resources=["*"]),
                ])
            ],
        )

        # Triggers for UserPool
        # Pre-sign-up: triggered when username, password, and phone number submitted
        lambda_cognito_trigger_pre_signup = lambda_.Function(
            self,
            "CogntioTriggerPreSignUp",
            function_name=id + "-CogntioTriggerPreSignUp",
            code=lambda_.AssetCode("./lambda_functions/cog_pre_signup"),
            handler="lambda.handler",
            runtime=lambda_.Runtime.PYTHON_3_7,
            role=lambda_cognito_access_role,
            timeout=core.Duration.seconds(6),
            environment={
                "USER_TABLE": user_db.table_name,
                "PARTICIPANT_LIMIT": participant_limit,
            },
        )
        lambda_cognito_trigger_pre_signup.add_permission(
            "AllowCognitoPreSign",
            principal=iam.ServicePrincipal("cognito-idp.amazonaws.com"),
            source_arn=user_pool.user_pool_arn,
        )
        # Post confirmation: triggered after validation code provided
        lambda_cognito_trigger_post_confirm = lambda_.Function(
            self,
            "CogntioTriggerPostConfirm",
            function_name=id + "-CogntioTriggerPostConfirm",
            code=lambda_.AssetCode("./lambda_functions/cog_post_confirm"),
            handler="lambda.handler",
            runtime=lambda_.Runtime.PYTHON_3_7,
            role=lambda_cognito_access_role,
            timeout=core.Duration.seconds(6),
            environment={
                "USER_TABLE": user_db.table_name,
                "PARTICIPANT_LIMIT": participant_limit,
            },
        )
        lambda_cognito_trigger_post_confirm.add_permission(
            "AllowCognitoPostConfirm",
            principal=iam.ServicePrincipal("cognito-idp.amazonaws.com"),
            source_arn=user_pool.user_pool_arn,
        )

        # Attach triggers to pool
        user_pool.user_pool.lambda_config = cognito.CfnUserPool.LambdaConfigProperty(
            pre_sign_up=lambda_cognito_trigger_pre_signup.function_arn,
            post_confirmation=lambda_cognito_trigger_post_confirm.function_arn,
        )

        cognito.CfnUserPoolGroup(
            self,
            "UserPoolCDDUser",
            group_name="cdd_user",
            description="General users of CDD (participants)",
            user_pool_id=user_pool.user_pool_id,
        )
        cognito.CfnUserPoolGroup(
            self,
            "UserPoolCDDAdmin",
            group_name="cdd_admin",
            description="CDD administrators",
            user_pool_id=user_pool.user_pool_id,
        )
        identity_pool = cognito.CfnIdentityPool(
            self,
            "IdentityPool",
            identity_pool_name=id.replace("-", "") + "_idpool",
            allow_unauthenticated_identities=False,
            cognito_identity_providers=[{
                "clientId":
                user_pool.client_id,
                "providerName":
                user_pool.provider_name,
            }],
        )
        core.CfnOutput(
            self,
            "CognitoIdentityPoolId",
            export_name="CognitoIdentityPoolId",
            value=identity_pool.ref,
        )

        # Custom resource to create admin user - cannot do via CFn to set password
        props: CustomResourceProps = CustomResourceProps(
            name=id + "-CR-CreateCognitoAdminUser",
            lambda_directory="./lambda_functions/cr_create_admin_user",
            handler="index.main",
            timeout=30,
            runtime=lambda_.Runtime.PYTHON_3_7,
            environment={
                "COGNITO_USER_POOL_ID": user_pool.user_pool_id,
                "COGNITO_CLIENT_ID": user_pool.client_id,
                "ADMIN_USERNAME": admin_user,
                "ADMIN_PASSWORD": admin_password,
            },
        )
        create_admin_user_cr = CustomResourceConstruct(self, "CreateAdminUser",
                                                       props)
        # DependsOn the user pool
        create_admin_user_cr.resource.node.add_dependency(user_pool)
        policy_statement = iam.PolicyStatement()
        policy_statement.add_actions("cognito-idp:SignUp")
        policy_statement.add_actions("cognito-idp:AdminConfirmSignUp")
        policy_statement.add_resources("*")
        create_admin_user_cr.add_policy_to_role(policy_statement)

        # IAM roles for identity pool auth/unauth
        cog_unauth_role = iam.Role(
            self,
            "cognitoUnauthRole",
            role_name=f"Cognito_{identity_pool.identity_pool_name}_Unauth_Role",
            assumed_by=iam.FederatedPrincipal(
                "cognito-identity.amazonaws.com",
                conditions={
                    "StringEquals": {
                        "cognito-identity.amazonaws.com:aud": identity_pool.ref
                    },
                    "ForAnyValue:StringLike": {
                        "cognito-identity.amazonaws.com:amr": "unauthenticated"
                    },
                },
                assume_role_action="sts:AssumeRoleWithWebIdentity",
            ),
        )
        cog_unauth_role.attach_inline_policy(
            iam.Policy(
                self,
                "cognitoUnauth",
                policy_name="cognitoUnauth",
                statements=[
                    iam.PolicyStatement(
                        actions=[
                            "mobileanalytics:PutEvents", "cognito-sync:*"
                        ],
                        resources=["*"],
                    )
                ],
            ))
        cog_auth_role = iam.Role(
            self,
            "cognitoAuthRole",
            role_name=f"Cognito_{identity_pool.identity_pool_name}_Auth_Role",
            managed_policies=[
                iam.ManagedPolicy.from_aws_managed_policy_name(
                    "AmazonESCognitoAccess")
            ],
            assumed_by=iam.FederatedPrincipal(
                "cognito-identity.amazonaws.com",
                conditions={
                    "StringEquals": {
                        "cognito-identity.amazonaws.com:aud": identity_pool.ref
                    },
                    "ForAnyValue:StringLike": {
                        "cognito-identity.amazonaws.com:amr": "authenticated"
                    },
                },
                assume_role_action="sts:AssumeRoleWithWebIdentity",
            ),
        )
        cog_auth_role.attach_inline_policy(
            iam.Policy(
                self,
                "cognitoAuth",
                policy_name="cognitoAuth",
                statements=[
                    iam.PolicyStatement(
                        actions=[
                            "mobileanalytics:PutEvents",
                            "cognito-sync:*",
                            "execute-api:*",
                        ],
                        resources=["*"],
                    ),
                    # Provide full access to IoT for the authenticated user
                    # The AWS IoT policy scopes down the access
                    iam.PolicyStatement(actions=["iot:*"], resources=["*"]),
                ],
            ))
        # Finally, attach auth and unauth roles to Identity pool
        cognito.CfnIdentityPoolRoleAttachment(
            self,
            "CDDIdentityPoolRoleAttach",
            identity_pool_id=identity_pool.ref,
            roles={
                "authenticated": cog_auth_role.role_arn,
                "unauthenticated": cog_unauth_role.role_arn,
            },
        )

        ### Supporting IAM Roles and Policies
        lambda_full_access_role = iam.Role(
            # Wide open role for Lambda's to access other services
            self,
            "LambdaFullAccessRole",
            assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"),
            inline_policies=[
                iam.PolicyDocument(statements=[
                    iam.PolicyStatement(
                        actions=[
                            "logs:CreateLogGroup",
                            "logs:CreateLogStream",
                            "logs:PutLogEvents",
                        ],
                        resources=["arn:aws:logs:*:*:*"],
                    ),
                    iam.PolicyStatement(actions=["*"], resources=["*"]),
                ])
            ],
        )
        lambda_iot_full_access_role = iam.Role(
            # Wide open role for Lambda's to access other services
            self,
            "LambdaIoTFullAccessRole",
            assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"),
            inline_policies=[
                iam.PolicyDocument(statements=[
                    iam.PolicyStatement(
                        actions=[
                            "logs:CreateLogGroup",
                            "logs:CreateLogStream",
                            "logs:PutLogEvents",
                        ],
                        resources=["arn:aws:logs:*:*:*"],
                    ),
                    iam.PolicyStatement(actions=["dynamodb:*", "iot:*"],
                                        resources=["*"]),
                ])
            ],
        )
        lambda_api_app_role = iam.Role(
            # Role for APIG Lambda functions - make specific per Lambda/method if needed
            self,
            "ApiAppRole",
            assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"),
            inline_policies=[
                iam.PolicyDocument(statements=[
                    iam.PolicyStatement(
                        actions=[
                            "logs:CreateLogGroup",
                            "logs:CreateLogStream",
                            "logs:PutLogEvents",
                        ],
                        resources=["arn:aws:logs:*:*:*"],
                    ),
                    iam.PolicyStatement(
                        actions=["dynamodb:*"],
                        resources=[
                            f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{dispenser_db.table_name}",
                            f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{dispenser_events.table_name}",
                            f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{user_db.table_name}",
                        ],
                    ),
                    iam.PolicyStatement(actions=["iot:*"], resources=["*"]),
                ])
            ],
        )
        lambda_api_delete_user_role = iam.Role(
            # Role for APIG Lambda delete user - specific as this has to delete multiple services
            self,
            "ApiDeleteUserRole",
            assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"),
            inline_policies=[
                iam.PolicyDocument(statements=[
                    iam.PolicyStatement(
                        actions=[
                            "logs:CreateLogGroup",
                            "logs:CreateLogStream",
                            "logs:PutLogEvents",
                        ],
                        resources=["arn:aws:logs:*:*:*"],
                    ),
                    iam.PolicyStatement(
                        actions=["dynamodb:*"],
                        resources=[
                            f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{dispenser_db.table_name}",
                            f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{dispenser_events.table_name}",
                            f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{user_db.table_name}",
                        ],
                    ),
                    iam.PolicyStatement(actions=["cloud9:DeleteEnvironment"],
                                        resources=["*"]),
                    iam.PolicyStatement(
                        actions=[
                            "iam:DeleteLoginProfile",
                            "iam:ListGroupsForUser",
                            "iam:RemoveUserFromGroup",
                            "iam:DeleteUser",
                        ],
                        resources=["*"],
                    ),
                    iam.PolicyStatement(
                        actions=["cognito-idp:AdminDeleteUser"],
                        resources=["*"]),
                    iam.PolicyStatement(actions=["iot:*"], resources=["*"]),
                ])
            ],
        )
        lambda_api_dispense_role = iam.Role(
            # Role for lambda
            self,
            "CommandRole",
            assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"),
            inline_policies=[
                iam.PolicyDocument(statements=[
                    iam.PolicyStatement(
                        actions=[
                            "logs:CreateLogGroup",
                            "logs:CreateLogStream",
                            "logs:PutLogEvents",
                        ],
                        resources=["arn:aws:logs:*:*:*"],
                    ),
                    iam.PolicyStatement(
                        actions=["dynamodb:*"],
                        resources=[
                            f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{dispenser_db.table_name}",
                            f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{dispenser_events.table_name}",
                        ],
                    ),
                    iam.PolicyStatement(actions=["iot:*"], resources=["*"]),
                ])
            ],
        )

        # IoT Policies
        iot_policy_dispenser_limited = iot.CfnPolicy(
            self,
            "IoTDispenserLimitedPolicy",
            policy_name=id + "-DispenserLimitedAccess",
            policy_document={
                "Version":
                "2012-10-17",
                "Statement": [
                    {
                        "Effect":
                        "Allow",
                        "Action": ["iot:Connect"],
                        "Resource": [
                            f"arn:aws:iot:{stack.region}:{stack.account}:client/${{iot:Connection.Thing.ThingName}}"
                        ],
                        "Condition": {
                            "Bool": {
                                "iot:Connection.Thing.IsAttached": [True]
                            }
                        },
                    },
                    {
                        "Effect": "Allow",
                        "Action": ["iot:Receive"],
                        "Resource": ["*"]
                    },
                    {
                        "Effect":
                        "Allow",
                        "Action": ["iot:Subscribe"],
                        "Resource": [
                            f"arn:aws:iot:{stack.region}:{stack.account}:topicfilter/$aws/things/${{iot:Certificate.Subject.CommonName}}/shadow/*",
                            f"arn:aws:iot:{stack.region}:{stack.account}:topicfilter/$aws/things/${{iot:Certificate.Subject.CommonName}}/cmd/${{iot:Certificate.Subject.CommonName}}",
                        ],
                    },
                    {
                        "Effect":
                        "Allow",
                        "Action": ["iot:Publish"],
                        "Resource": [
                            f"arn:aws:iot:{stack.region}:{stack.account}:topic/$aws/things/${{iot:Certificate.Subject.CommonName}}/shadow/update",
                            f"arn:aws:iot:{stack.region}:{stack.account}:topic/$aws/things/${{iot:Certificate.Subject.CommonName}}/shadow/get",
                            f"arn:aws:iot:{stack.region}:{stack.account}:topic/test/${{iot:Certificate.Subject.CommonName}}",
                            f"arn:aws:iot:{stack.region}:{stack.account}:topic/cmd/${{iot:Certificate.Subject.CommonName}}/response",
                        ],
                    },
                ],
            },
        )
        iot_policy_client = iot.CfnPolicy(
            self,
            "IoTClientPolicy",
            policy_name=id + "-IoTClientAccess",
            policy_document={
                "Version":
                "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Action": ["iot:Connect", "iot:Receive"],
                        "Resource": ["*"],
                    },
                    {
                        "Effect":
                        "Allow",
                        "Action": ["iot:Subscribe"],
                        "Resource": [
                            f"arn:aws:iot:{stack.region}:{stack.account}:topicfilter/events/*",
                            f"arn:aws:iot:{stack.region}:{stack.account}:topicfilter/$aws/things/*/shadow/update/accepted",
                        ],
                    },
                ],
            },
        )

        ### Lambda Functions
        # General Lambda Functions NOT associated with APIG
        lambda_process_events = lambda_.Function(
            self,
            "ProcessEvents",
            function_name=id + "-ProcessEvents",
            code=lambda_.AssetCode("./lambda_functions/process_events"),
            handler="process_events.handler",
            runtime=lambda_.Runtime.PYTHON_3_7,
            role=lambda_iot_full_access_role,
            timeout=core.Duration.seconds(20),
            environment={
                "EVENT_TABLE": dispenser_events.table_name,
                "STATUS_TABLE": dispenser_db.table_name,
            },
        )

        ## API Lambda functions
        # Return credit for dispenser
        api_credit_dispenser_function = lambda_.Function(
            self,
            "ApiCreditDispenserFunction",
            function_name=id + "-ApiCreditDispenserFunction",
            code=lambda_.AssetCode("./lambda_functions/api_credit_dispenser"),
            handler="credit_dispenser.handler",
            runtime=lambda_.Runtime.PYTHON_3_7,
            role=lambda_api_app_role,
            timeout=core.Duration.seconds(15),
            memory_size=128,
            environment={
                "DISPENSER_TABLE": dispenser_db.table_name,
                "EVENT_TABLE": dispenser_events.table_name,
            },
        )
        # Command
        api_command_function = lambda_.Function(
            self,
            "ApiCommandFunction",
            function_name=id + "-ApiCommandFunction",
            code=lambda_.AssetCode("./lambda_functions/api_command"),
            handler="command.handler",
            runtime=lambda_.Runtime.PYTHON_3_7,
            role=lambda_api_app_role,
            timeout=core.Duration.seconds(15),
            memory_size=128,
            environment={
                "DispenserTable": dispenser_db.table_name,
                "EventTable": dispenser_events.table_name,
            },
        )
        # Request dispense operation (set shadow or command to dispense)
        api_dispense_function = lambda_.Function(
            self,
            "ApiDispenseFunction",
            function_name=id + "-ApiDispenseFunction",
            code=lambda_.AssetCode("./lambda_functions/api_dispense"),
            handler="dispense.handler",
            runtime=lambda_.Runtime.PYTHON_3_7,
            role=lambda_api_dispense_role,
            timeout=core.Duration.seconds(15),
            memory_size=128,
            environment={
                "DISPENSER_TABLE": dispenser_db.table_name,
                "EVENT_TABLE": dispenser_events.table_name,
            },
        )
        # Request dispense operation (set shadow or command to dispense)
        api_dispenser_status_function = lambda_.Function(
            self,
            "ApiDispenserStatusFunction",
            function_name=id + "-ApiDispenserStatusFunction",
            code=lambda_.AssetCode("./lambda_functions/api_dispenser_status"),
            handler="dispenser_status.handler",
            runtime=lambda_.Runtime.PYTHON_3_7,
            role=lambda_api_app_role,
            timeout=core.Duration.seconds(15),
            memory_size=128,
            environment={"DISPENSER_TABLE": dispenser_db.table_name},
        )
        # Request user details from user table, create resources if needed
        # NOTE: This uses an overley permissive policy to create the resources needed
        api_get_resources_function = lambda_.Function(
            self,
            "ApiGetResourcesFunction",
            function_name=id + "-ApiGetResourcesFunction",
            code=lambda_.AssetCode("./lambda_functions/api_get_resources"),
            handler="get_resources.handler",
            runtime=lambda_.Runtime.PYTHON_3_7,
            role=lambda_full_access_role,
            # Timeout is for user creation: certain tasks such as Cloud9 may take longer
            timeout=core.Duration.seconds(300),
            memory_size=128,
            environment={
                "DISPENSER_TABLE": dispenser_db.table_name,
                "EVENT_TABLE": dispenser_events.table_name,
                "USER_TABLE": user_db.table_name,
                "USER_PERMISSIONS_GROUP": user_group.group_name,
                "IOT_POLICY_DISPENSER_LIMITED":
                iot_policy_dispenser_limited.policy_name,
                "IOT_POLICY_CLIENT": iot_policy_client.policy_name,
                "CLOUD9_INSTANCE_SIZE": cloud9_instance_size,
            },
        )
        # Request user details from user table
        api_delete_user_function = lambda_.Function(
            self,
            "ApiDeleteUserFunction",
            function_name=id + "-ApiDeleteUserFunction",
            code=lambda_.AssetCode("./lambda_functions/api_delete_user"),
            handler="delete_user.handler",
            runtime=lambda_.Runtime.PYTHON_3_7,
            role=lambda_api_delete_user_role,
            timeout=core.Duration.seconds(28),
            memory_size=256,
            environment={
                "DISPENSER_TABLE": dispenser_db.table_name,
                "EVENT_TABLE": dispenser_events.table_name,
                "USER_TABLE": user_db.table_name,
                "USER_POOL_ID": user_pool.user_pool_id,
            },
        )

        ### API Gateway
        api = apigateway.RestApi(
            self,
            id + "-API",
            api_key_source_type=apigateway.ApiKeySourceType.HEADER,
            deploy_options=apigateway.StageOptions(stage_name="prod"),
        )

        core.CfnOutput(
            self,
            "APIEndpoint",
            export_name="APIEndpoint",
            value=
            f"https://{api.rest_api_id}.execute-api.{stack.region}.amazonaws.com/prod/",
        )
        # Although / is not used as method, provide OPTIONS for hinting CORS
        add_cors_options(api.root)
        # Define Cognito authorizer and attach to gateway
        cog_authorizer = apigateway.CfnAuthorizer(
            self,
            "CognitoAuth",
            name="CognitoAuthName",
            rest_api_id=api.rest_api_id,
            type="COGNITO_USER_POOLS",
            identity_source="method.request.header.Authorization",
            provider_arns=[user_pool.user_pool_arn],
        )

        # # Resources (paths) and methods (GET, POST, etc.), for the API
        api_credit_resource = api.root.add_resource("credit")
        add_resource_method(
            api_credit_resource,
            http_method="GET",
            integration=apigateway.LambdaIntegration(
                api_credit_dispenser_function),
            authorization_type=apigateway.AuthorizationType.COGNITO,
            authorizer=cog_authorizer,
        )
        add_cors_options(api_credit_resource)
        # command
        api_command_resource = api.root.add_resource("command")
        add_resource_method(
            api_command_resource,
            http_method="GET",
            integration=apigateway.LambdaIntegration(api_command_function),
            authorization_type=apigateway.AuthorizationType.COGNITO,
            authorizer=cog_authorizer,
        )
        add_cors_options(api_command_resource)
        # Actuate dispenser
        api_dispense_resource = api.root.add_resource("dispense")
        add_resource_method(
            api_dispense_resource,
            http_method="GET",
            integration=apigateway.LambdaIntegration(api_dispense_function),
            authorization_type=apigateway.AuthorizationType.COGNITO,
            authorizer=cog_authorizer,
        )
        add_cors_options(api_dispense_resource)
        # Return dispenser status (from DynamoDB)
        api_dispenser_status_resource = api.root.add_resource("status")
        add_resource_method(
            api_dispenser_status_resource,
            http_method="GET",
            integration=apigateway.LambdaIntegration(
                api_dispenser_status_function),
            authorization_type=apigateway.AuthorizationType.COGNITO,
            authorizer=cog_authorizer,
        )
        add_cors_options(api_dispenser_status_resource)
        # Return user details from User Table
        api_get_resources_resource = api.root.add_resource("getResources")
        add_resource_method(
            api_get_resources_resource,
            http_method="POST",
            integration=apigateway.LambdaIntegration(
                api_get_resources_function),
            authorization_type=apigateway.AuthorizationType.COGNITO,
            authorizer=cog_authorizer,
        )
        add_cors_options(api_get_resources_resource)
        # Create a user based on valid token
        api_delete_user_resource = api.root.add_resource("deleteUser")
        add_resource_method(
            api_delete_user_resource,
            http_method="POST",
            integration=apigateway.LambdaIntegration(api_delete_user_function),
            authorization_type=apigateway.AuthorizationType.COGNITO,
            authorizer=cog_authorizer,
        )
        add_cors_options(api_delete_user_resource)

        # Create policy and reference group
        iam.Policy(
            self,
            "UserPermissionsPolicy",
            groups=[user_group],
            policy_name=id + "-UserPermissions",
            statements=[
                # Elevated permissions beyond the ReadOnlyUser
                # Allow seeing all MQTT messages
                iam.PolicyStatement(
                    actions=["iot:Subscribe", "iot:Connect", "iot:Receive"],
                    resources=["*"],
                ),
                # Allow search indexing
                iam.PolicyStatement(
                    actions=["iot:SearchIndex"],
                    resources=[
                        f"arn:aws:iot:{stack.region}:{stack.account}:index/AWS_Things"
                    ],
                ),
                # Allow changing of security group ingress on EC2 (Cloud9) to support mapping 443 to
                iam.PolicyStatement(
                    actions=[
                        "ec2:AuthorizeSecurityGroupIngress",
                        "ec2:RevokeSecurityGroupIngress",
                    ],
                    resources=[
                        f"arn:aws:ec2:{stack.region}:{stack.account}:security-group/*"
                    ],
                ),
                # DENY access to credentials table
                iam.PolicyStatement(
                    effect=iam.Effect.DENY,
                    actions=["dynamodb:*"],
                    resources=[
                        f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{user_db.table_name}"
                    ],
                ),
                # DENY access to S3 overall
                iam.PolicyStatement(effect=iam.Effect.DENY,
                                    actions=["s3:*"],
                                    resources=["*"]),
            ],
        )

        # IoT Constructs
        # Rule to process shadow events and send to logging
        iot_rule_log_shadow_events = iot.CfnTopicRule(
            self,
            "LogShadowEventsRule",
            rule_name=id.replace("-", "") + "_LogShadowEvents",
            topic_rule_payload=iot.CfnTopicRule.TopicRulePayloadProperty(
                description=
                "Based on shadow topic and content, process messages via Lambda",
                rule_disabled=False,
                aws_iot_sql_version="2016-03-23",
                sql=
                "select *, topic() AS topic FROM '$aws/things/+/shadow/update/documents'",
                actions=[
                    iot.CfnTopicRule.ActionProperty(
                        lambda_=iot.CfnTopicRule.LambdaActionProperty(
                            function_arn=lambda_process_events.function_arn))
                ],
            ),
        )
        # Allow rule to invoke the logging function
        lambda_process_events.add_permission(
            "AllowIoTRule1",
            principal=iam.ServicePrincipal("iot.amazonaws.com"),
            source_arn=iot_rule_log_shadow_events.attr_arn,
        )
        # Rule to process generic events and send to logging
        iot_rule_log_generic_events = iot.CfnTopicRule(
            self,
            "LogGenericEventsRule",
            rule_name=id.replace("-", "") + "_LogGenericEvents",
            topic_rule_payload=iot.CfnTopicRule.TopicRulePayloadProperty(
                description="Log generic events, enrich, then send to Lambda",
                rule_disabled=False,
                aws_iot_sql_version="2016-03-23",
                sql=
                "select *, timestamp() AS ts, topic() AS topic FROM 'events'",
                actions=[
                    iot.CfnTopicRule.ActionProperty(
                        lambda_=iot.CfnTopicRule.LambdaActionProperty(
                            function_arn=lambda_process_events.function_arn))
                ],
            ),
        )
        # Allow generic_events rule to Invoke the process_events function
        lambda_process_events.add_permission(
            "AllowIoTRule2",
            principal=iam.ServicePrincipal("iot.amazonaws.com"),
            source_arn=iot_rule_log_generic_events.attr_arn,
        )
        # Rule to process dispenser specific events and send to logging
        iot_rule_log_dispenser_events = iot.CfnTopicRule(
            self,
            "LogDispenserEventsRule",
            rule_name=id.replace("-", "") + "_LogDispenserEvents",
            topic_rule_payload=iot.CfnTopicRule.TopicRulePayloadProperty(
                description=
                "Log specific dispenser events, enrich, then send to Lambda",
                rule_disabled=False,
                aws_iot_sql_version="2016-03-23",
                sql=
                "select *, timestamp() AS ts, topic() AS topic FROM 'events/+'",
                actions=[
                    iot.CfnTopicRule.ActionProperty(
                        lambda_=iot.CfnTopicRule.LambdaActionProperty(
                            function_arn=lambda_process_events.function_arn))
                ],
            ),
        )
        # Allow log_dispenser_events rule to Invoke the process_events function
        lambda_process_events.add_permission(
            "AllowIoTRule3",
            principal=iam.ServicePrincipal("iot.amazonaws.com"),
            source_arn=iot_rule_log_dispenser_events.attr_arn,
        )
        # Rule to process cmd/NNN/response WHERE "command=dispense"
        iot_rule_command_response_dispense = iot.CfnTopicRule(
            self,
            "DispenseCommandResponseRule",
            rule_name=id.replace("-", "") + "_DispenseCommandResponse",
            topic_rule_payload=iot.CfnTopicRule.TopicRulePayloadProperty(
                description=
                "Invoke Lambda to process dispense commands from dispenser",
                rule_disabled=False,
                aws_iot_sql_version="2016-03-23",
                sql=
                "select *, topic() AS topic FROM '$aws/things/+/shadow/update/accepted' WHERE isUndefined(state.reported.response) = False",
                actions=[
                    iot.CfnTopicRule.ActionProperty(
                        lambda_=iot.CfnTopicRule.LambdaActionProperty(
                            function_arn=api_dispense_function.function_arn))
                ],
            ),
        )
        # Allow command_response rule to Invoke the dispense function to reconcile outstanding requests
        api_dispense_function.add_permission(
            "AllowIoTCommandResponseRule",
            principal=iam.ServicePrincipal("iot.amazonaws.com"),
            source_arn=iot_rule_command_response_dispense.attr_arn,
        )

        # Custom resource to delete workshop users - run to clean up any lingering ones
        # if the admin user didn't clean up. A lot of dependsOn as users are created with bindings
        # to other resources
        props: CustomResourceProps = CustomResourceProps(
            name=id + "-CR-DeleteParticipantUsers",
            lambda_directory="./lambda_functions/cr_delete_participant_users",
            handler="index.main",
            timeout=30,
            runtime=lambda_.Runtime.PYTHON_3_7,
            environment={
                # Read user records from UserTable
                "USER_TABLE":
                user_db.table_name,
                # Invoke the api_delete_user function
                "DELETE_USER_LAMBDA_FUNCTION":
                api_delete_user_function.function_arn,
            },
        )
        delete_participant_users_cr = CustomResourceConstruct(
            self, "DeleteParticpantUsers", props)
        # DependsOn the API Delete User Function
        delete_participant_users_cr.resource.node.add_dependency(
            api_delete_user_function)
        # DependsOn the user pool to delete Cognito users
        delete_participant_users_cr.resource.node.add_dependency(user_pool)
        # DependsOn the DynamoDB UserTable
        delete_participant_users_cr.resource.node.add_dependency(user_db)
        # DependsOn the IoT dispenser and client policies
        delete_participant_users_cr.resource.node.add_dependency(
            iot_policy_dispenser_limited)
        delete_participant_users_cr.resource.node.add_dependency(
            iot_policy_client)
        # DependsOn the IoT IAM user group
        delete_participant_users_cr.resource.node.add_dependency(user_group)
        # Permissions for function to delete users
        policy_statement = iam.PolicyStatement()
        policy_statement.add_actions("dynamodb:*")
        policy_statement.add_resources(
            f"arn:aws:dynamodb:{stack.region}:{stack.account}:table/{user_db.table_name}"
        )
        delete_participant_users_cr.add_policy_to_role(policy_statement)
        policy_statement = iam.PolicyStatement()
        policy_statement.add_actions("lambda:InvokeFunction")
        policy_statement.add_resources(api_delete_user_function.function_arn)
        delete_participant_users_cr.add_policy_to_role(policy_statement)
示例#28
0
    def __init__(self, scope: cdk.Construct, construct_id: str,
                 **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        # Image Bucket
        image_bucket = s3.Bucket(self,
                                 IMG_BUCKET_NAME,
                                 removal_policy=cdk.RemovalPolicy.DESTROY)
        cdk.CfnOutput(self, "imageBucket", value=image_bucket.bucket_name)

        image_bucket.add_cors_rule(
            allowed_methods=[s3.HttpMethods.GET, s3.HttpMethods.PUT],
            allowed_origins=["*"],
            allowed_headers=["*"],
            max_age=3000,
        )

        # Thumbnail Bucket
        resized_image_bucket = s3.Bucket(
            self,
            RESIZED_IMG_BUCKET_NAME,
            removal_policy=cdk.RemovalPolicy.DESTROY)
        cdk.CfnOutput(self,
                      "resizedBucket",
                      value=resized_image_bucket.bucket_name)

        resized_image_bucket.add_cors_rule(
            allowed_methods=[s3.HttpMethods.GET, s3.HttpMethods.PUT],
            allowed_origins=["*"],
            allowed_headers=["*"],
            max_age=3000,
        )
        # S3 Static bucket for website code
        web_bucket = s3.Bucket(
            self,
            WEBSITE_BUCKET_NAME,
            website_index_document="index.html",
            website_error_document="index.html",
            removal_policy=cdk.RemovalPolicy.DESTROY,
            # uncomment this and delete the policy statement below to allow public access to our
            # static website
            # public_read_access=true
        )

        web_policy_statement = iam.PolicyStatement(
            actions=["s3:GetObject"],
            resources=[web_bucket.arn_for_objects("*")],
            principals=[iam.AnyPrincipal()],
            conditions={"IpAddress": {
                "aws:SourceIp": ["139.138.203.36"]
            }},
        )

        web_bucket.add_to_resource_policy(web_policy_statement)

        cdk.CfnOutput(self,
                      "bucketURL",
                      value=web_bucket.bucket_website_domain_name)

        # Deploy site contents to S3 Bucket
        s3_dep.BucketDeployment(
            self,
            "DeployWebsite",
            sources=[s3_dep.Source.asset("./public")],
            destination_bucket=web_bucket,
        )

        # DynamoDB to store image labels
        partition_key = dynamodb.Attribute(name="image",
                                           type=dynamodb.AttributeType.STRING)
        table = dynamodb.Table(
            self,
            "ImageLabels",
            partition_key=partition_key,
            removal_policy=cdk.RemovalPolicy.DESTROY,
        )
        cdk.CfnOutput(self, "ddbTable", value=table.table_name)

        # Lambda layer for Pillow library
        layer = lb.LayerVersion(
            self,
            "pil",
            code=lb.Code.from_asset("reklayer"),
            compatible_runtimes=[lb.Runtime.PYTHON_3_7],
            license="Apache-2.0",
            description=
            "A layer to enable the PIL library in our Rekognition Lambda",
        )

        # Lambda function
        rek_fn = lb.Function(
            self,
            "rekognitionFunction",
            code=lb.Code.from_asset("rekognitionFunction"),
            runtime=lb.Runtime.PYTHON_3_7,
            handler="index.handler",
            timeout=cdk.Duration.seconds(30),
            memory_size=1024,
            layers=[layer],
            environment={
                "TABLE": table.table_name,
                "BUCKET": image_bucket.bucket_name,
                "THUMBBUCKET": resized_image_bucket.bucket_name,
            },
        )

        image_bucket.grant_read(rek_fn)
        resized_image_bucket.grant_write(rek_fn)
        table.grant_write_data(rek_fn)

        rek_fn.add_to_role_policy(
            iam.PolicyStatement(effect=iam.Effect.ALLOW,
                                actions=["rekognition:DetectLabels"],
                                resources=["*"]))

        # Lambda for Synchronous front end
        serviceFn = lb.Function(
            self,
            "serviceFunction",
            code=lb.Code.from_asset("servicelambda"),
            runtime=lb.Runtime.PYTHON_3_7,
            handler="index.handler",
            environment={
                "TABLE": table.table_name,
                "BUCKET": image_bucket.bucket_name,
                "RESIZEDBUCKET": resized_image_bucket.bucket_name,
            },
        )

        image_bucket.grant_write(serviceFn)
        resized_image_bucket.grant_write(serviceFn)
        table.grant_read_write_data(serviceFn)

        # Cognito User Pool Auth
        auto_verified_attrs = cognito.AutoVerifiedAttrs(email=True)
        sign_in_aliases = cognito.SignInAliases(email=True, username=True)
        user_pool = cognito.UserPool(
            self,
            "UserPool",
            self_sign_up_enabled=True,
            auto_verify=auto_verified_attrs,
            sign_in_aliases=sign_in_aliases,
        )

        user_pool_client = cognito.UserPoolClient(self,
                                                  "UserPoolClient",
                                                  user_pool=user_pool,
                                                  generate_secret=False)

        identity_pool = cognito.CfnIdentityPool(
            self,
            "ImageRekognitionIdentityPool",
            allow_unauthenticated_identities=False,
            cognito_identity_providers=[{
                "clientId":
                user_pool_client.user_pool_client_id,
                "providerName":
                user_pool.user_pool_provider_name,
            }],
        )

        # API Gateway
        cors_options = apigw.CorsOptions(allow_origins=apigw.Cors.ALL_ORIGINS,
                                         allow_methods=apigw.Cors.ALL_METHODS)
        api = apigw.LambdaRestApi(
            self,
            "imageAPI",
            default_cors_preflight_options=cors_options,
            handler=serviceFn,
            proxy=False,
        )

        auth = apigw.CfnAuthorizer(
            self,
            "ApiGatewayAuthorizer",
            name="customer-authorizer",
            identity_source="method.request.header.Authorization",
            provider_arns=[user_pool.user_pool_arn],
            rest_api_id=api.rest_api_id,
            # type=apigw.AuthorizationType.COGNITO,
            type="COGNITO_USER_POOLS",
        )

        assumed_by = iam.FederatedPrincipal(
            "cognito-identity.amazon.com",
            conditions={
                "StringEquals": {
                    "cognito-identity.amazonaws.com:aud": identity_pool.ref
                },
                "ForAnyValue:StringLike": {
                    "cognito-identity.amazonaws.com:amr": "authenticated"
                },
            },
            assume_role_action="sts:AssumeRoleWithWebIdentity",
        )
        authenticated_role = iam.Role(
            self,
            "ImageRekognitionAuthenticatedRole",
            assumed_by=assumed_by,
        )
        # IAM policy granting users permission to get and put their pictures
        policy_statement = iam.PolicyStatement(
            actions=["s3:GetObject", "s3:PutObject"],
            effect=iam.Effect.ALLOW,
            resources=[
                image_bucket.bucket_arn +
                "/private/${cognito-identity.amazonaws.com:sub}/*",
                image_bucket.bucket_arn +
                "/private/${cognito-identity.amazonaws.com:sub}/",
                resized_image_bucket.bucket_arn +
                "/private/${cognito-identity.amazonaws.com:sub}/*",
                resized_image_bucket.bucket_arn +
                "/private/${cognito-identity.amazonaws.com:sub}/",
            ],
        )

        # IAM policy granting users permission to list their pictures
        list_policy_statement = iam.PolicyStatement(
            actions=["s3:ListBucket"],
            effect=iam.Effect.ALLOW,
            resources=[
                image_bucket.bucket_arn, resized_image_bucket.bucket_arn
            ],
            conditions={
                "StringLike": {
                    "s3:prefix":
                    ["private/${cognito-identity.amazonaws.com:sub}/*"]
                }
            },
        )

        authenticated_role.add_to_policy(policy_statement)
        authenticated_role.add_to_policy(list_policy_statement)

        # Attach role to our Identity Pool
        cognito.CfnIdentityPoolRoleAttachment(
            self,
            "IdentityPoolRoleAttachment",
            identity_pool_id=identity_pool.ref,
            roles={"authenticated": authenticated_role.role_arn},
        )

        # Get some outputs from cognito
        cdk.CfnOutput(self, "UserPoolId", value=user_pool.user_pool_id)
        cdk.CfnOutput(self,
                      "AppClientId",
                      value=user_pool_client.user_pool_client_id)
        cdk.CfnOutput(self, "IdentityPoolId", value=identity_pool.ref)

        # New Amazon API Gateway with AWS Lambda Integration
        success_response = apigw.IntegrationResponse(
            status_code="200",
            response_parameters={
                "method.response.header.Access-Control-Allow-Origin": "'*'"
            },
        )
        error_response = apigw.IntegrationResponse(
            selection_pattern="(\n|.)+",
            status_code="500",
            response_parameters={
                "method.response.header.Access-Control-Allow-Origin": "'*'"
            },
        )

        request_template = json.dumps({
            "action":
            "$util.escapeJavaScript($input.params('action'))",
            "key":
            "$util.escapeJavaScript($input.params('key'))",
        })

        lambda_integration = apigw.LambdaIntegration(
            serviceFn,
            proxy=False,
            request_parameters={
                "integration.request.querystring.action":
                "method.request.querystring.action",
                "integration.request.querystring.key":
                "method.request.querystring.key",
            },
            request_templates={"application/json": request_template},
            passthrough_behavior=apigw.PassthroughBehavior.WHEN_NO_TEMPLATES,
            integration_responses=[success_response, error_response],
        )

        imageAPI = api.root.add_resource("images")

        success_resp = apigw.MethodResponse(
            status_code="200",
            response_parameters={
                "method.response.header.Access-Control-Allow-Origin": True
            },
        )
        error_resp = apigw.MethodResponse(
            status_code="500",
            response_parameters={
                "method.response.header.Access-Control-Allow-Origin": True
            },
        )

        # GET /images
        get_method = imageAPI.add_method(
            "GET",
            lambda_integration,
            authorization_type=apigw.AuthorizationType.COGNITO,
            request_parameters={
                "method.request.querystring.action": True,
                "method.request.querystring.key": True,
            },
            method_responses=[success_resp, error_resp],
        )
        # DELETE /images
        delete_method = imageAPI.add_method(
            "DELETE",
            lambda_integration,
            authorization_type=apigw.AuthorizationType.COGNITO,
            request_parameters={
                "method.request.querystring.action": True,
                "method.request.querystring.key": True,
            },
            method_responses=[success_resp, error_resp],
        )

        # Override the authorizer id because it doesn't work when defininting it as a param
        # in add_method
        get_method_resource = get_method.node.find_child("Resource")
        get_method_resource.add_property_override("AuthorizerId", auth.ref)
        delete_method_resource = delete_method.node.find_child("Resource")
        delete_method_resource.add_property_override("AuthorizerId", auth.ref)

        # Building SQS queue and DeadLetter Queue
        dl_queue = sqs.Queue(
            self,
            "ImageDLQueue",
            queue_name="ImageDLQueue",
        )

        dl_queue_opts = sqs.DeadLetterQueue(max_receive_count=2,
                                            queue=dl_queue)

        queue = sqs.Queue(
            self,
            "ImageQueue",
            queue_name="ImageQueue",
            visibility_timeout=cdk.Duration.seconds(30),
            receive_message_wait_time=cdk.Duration.seconds(20),
            dead_letter_queue=dl_queue_opts,
        )

        # S3 Bucket Create Notification to SQS
        # Whenever an image is uploaded add it to the queue

        image_bucket.add_object_created_notification(
            s3n.SqsDestination(queue),
            s3.NotificationKeyFilter(prefix="private/"))
    def __init__(self, scope: core.Construct, id: str, instance_id: str,
                 contact_flow_id: str, source_phone_number: str, timeout: int,
                 **kwargs):
        super().__init__(scope, id, **kwargs)

        web_bucket = _s3.Bucket(self,
                                "StaticWebBucket",
                                website_index_document="index.html",
                                website_error_document="index.html",
                                removal_policy=core.RemovalPolicy.DESTROY,
                                public_read_access=True)

        core.CfnOutput(self,
                       'WebBucketUrl',
                       value=web_bucket.bucket_domain_name)

        web_distribution = _clf.CloudFrontWebDistribution(
            self,
            'StaticWebDistribution',
            origin_configs=[
                _clf.SourceConfiguration(
                    s3_origin_source=_clf.S3OriginConfig(
                        s3_bucket_source=web_bucket),
                    behaviors=[_clf.Behavior(is_default_behavior=True)])
            ],
            viewer_protocol_policy=_clf.ViewerProtocolPolicy.REDIRECT_TO_HTTPS)

        _s3d.BucketDeployment(
            self,
            "S3StaticWebContentDeploymentWithInvalidation",
            sources=[
                _s3d.Source.asset(
                    f"{pathlib.Path(__file__).parent.absolute()}/site-content/build"
                )
            ],
            destination_bucket=web_bucket,
            distribution=web_distribution,
            distribution_paths=["/*"])

        file_bucket = _s3.Bucket(self,
                                 "FileBucket",
                                 removal_policy=core.RemovalPolicy.DESTROY)

        call_dead_letter_queue = _sqs.Queue(self,
                                            "CallDeadLetterQueue",
                                            fifo=True,
                                            content_based_deduplication=True)

        call_sqs_queue = _sqs.Queue(
            self,
            "CallSqsQueue",
            fifo=True,
            content_based_deduplication=True,
            visibility_timeout=core.Duration.seconds(120),
            dead_letter_queue=_sqs.DeadLetterQueue(
                max_receive_count=1, queue=call_dead_letter_queue))

        async_call_dead_letter_queue = _sqs.Queue(
            self,
            "AsyncCallDeadLetterQueue",
            fifo=True,
            content_based_deduplication=True)

        async_callout_queue = _sqs.Queue(
            self,
            "AsyncCalloutQueue",
            fifo=True,
            content_based_deduplication=True,
            visibility_timeout=core.Duration.seconds(120),
            dead_letter_queue=_sqs.DeadLetterQueue(
                max_receive_count=1, queue=async_call_dead_letter_queue))

        call_job_complete_sns_topic = _sns.Topic(
            self, "CallJobCompleteSnsTopic", display_name="CallJobCompletion")

        call_result_table = _dynamodb.Table(
            self,
            "CallResultDynamodbTable",
            billing_mode=_dynamodb.BillingMode.PAY_PER_REQUEST,
            partition_key=_dynamodb.Attribute(
                name="task_id", type=_dynamodb.AttributeType.STRING),
            sort_key=_dynamodb.Attribute(name="receiver_id",
                                         type=_dynamodb.AttributeType.STRING),
            removal_policy=core.RemovalPolicy.DESTROY)

        callout_record_table = _dynamodb.Table(
            self,
            "CallTaskDynamodbTable",
            billing_mode=_dynamodb.BillingMode.PAY_PER_REQUEST,
            partition_key=_dynamodb.Attribute(
                name="task_id", type=_dynamodb.AttributeType.STRING),
            sort_key=_dynamodb.Attribute(name="created_at",
                                         type=_dynamodb.AttributeType.NUMBER),
            removal_policy=core.RemovalPolicy.DESTROY)
        callout_record_table.add_global_secondary_index(
            partition_key=_dynamodb.Attribute(
                name='call_type', type=_dynamodb.AttributeType.STRING),
            sort_key=_dynamodb.Attribute(name='created_at',
                                         type=_dynamodb.AttributeType.NUMBER),
            index_name='CallTypeCreatedAtGlobalIndex',
            projection_type=_dynamodb.ProjectionType.ALL)

        python_function_layer = _lambda.LayerVersion(
            self,
            "LambdaPythonFunctionLayer",
            code=_lambda.Code.asset("aws_callouts_cdk/layer/_python"),
            compatible_runtimes=[
                _lambda.Runtime.PYTHON_3_7, _lambda.Runtime.PYTHON_3_8
            ],
            license="Available under the MIT-0 license")

        nodejs_function_layer = _lambda.LayerVersion(
            self,
            "LambdaNodeJsFunctionLayer",
            code=_lambda.Code.asset("aws_callouts_cdk/layer/_nodejs"),
            compatible_runtimes=[
                _lambda.Runtime.NODEJS_10_X, _lambda.Runtime.NODEJS_12_X
            ],
            license="Available under the MIT-0 license")

        global_python_function_arguments = {
            "code": _lambda.Code.asset("aws_callouts_cdk/src/python"),
            "layers": [python_function_layer],
            "runtime": _lambda.Runtime.PYTHON_3_7
        }

        global_nodeje_function_arguments = {
            "code": _lambda.Code.asset("aws_callouts_cdk/src/nodejs"),
            "layers": [nodejs_function_layer],
            "runtime": _lambda.Runtime.NODEJS_12_X
        }

        get_callout_job_function = _lambda.Function(
            self,
            "GetCalloutJobFunction",
            handler="get_call_job.lambda_handler",
            **global_python_function_arguments)
        get_callout_job_function.add_environment(key="S3Bucket",
                                                 value=file_bucket.bucket_name)
        file_bucket.grant_read(get_callout_job_function)

        callout_function = _lambda.Function(self,
                                            "CalloutFunction",
                                            handler="send_call.lambda_handler",
                                            **global_python_function_arguments)
        callout_function.add_environment(
            key="ContactFlowArn",
            value=
            f"arn:aws:connect:{self.region}:{self.account}:instance/{instance_id}/contact-flow/{contact_flow_id}"
        )
        callout_function.add_environment(key="SourcePhoneNumber",
                                         value=source_phone_number)
        callout_function.add_environment(key="ExcelFileBucket",
                                         value=file_bucket.bucket_name)
        callout_function.add_environment(key="AsynCalloutQueueUrl",
                                         value=async_callout_queue.queue_url)
        callout_function.add_to_role_policy(statement=_iam.PolicyStatement(
            resources=[
                f"arn:aws:connect:{self.region}:{self.account}:instance/{instance_id}/*"
            ],
            actions=["connect:StartOutboundVoiceContact"]))
        callout_function.add_event_source(source=_les.SqsEventSource(
            queue=async_callout_queue, batch_size=1))
        'arn:aws:connect:751225572132:ap-southeast-2:instance/9d0c7cc5-7d2a-42e4-a3dd-70f402e0d040'
        file_bucket.grant_read_write(callout_function)

        response_handler_function = _lambda.Function(
            self,
            "ResponseHandlerFunction",
            handler="response_handler.lambda_handler",
            **global_python_function_arguments)
        response_handler_function.add_permission(
            id="ResponseHandlerFunctionLambdaInvokePermission",
            principal=_iam.ServicePrincipal(service="connect.amazonaws.com"),
            action="lambda:InvokeFunction",
            source_account=self.account,
            source_arn=
            f"arn:aws:connect:{self.region}:{self.account}:instance/{instance_id}"
        )

        send_task_success_function = _lambda.Function(
            self,
            "SendTaskSuccessFunction",
            handler="send_task_success.lambda_handler",
            **global_python_function_arguments)
        send_task_success_function.add_permission(
            id="SendTaskSuccessFunctionLambdaInvokePermission",
            principal=_iam.ServicePrincipal(service="connect.amazonaws.com"),
            action="lambda:InvokeFunction",
            source_account=self.account,
            source_arn=
            f"arn:aws:connect:{self.region}:{self.account}:instance/{instance_id}"
        )

        get_call_result_function = _lambda.Function(
            self,
            "GetCallResultFunction",
            handler="get_call_result.lambda_handler",
            memory_size=512,
            **global_python_function_arguments)
        get_call_result_function.add_environment(
            key="CallResultDynamoDBTable", value=call_result_table.table_name)
        get_call_result_function.add_environment(key="S3Bucket",
                                                 value=file_bucket.bucket_name)
        call_result_table.grant_read_data(grantee=get_call_result_function)
        file_bucket.grant_read_write(get_call_result_function)

        iterator_function = _lambda.Function(
            self,
            "IteratorFunction",
            handler="iterator.lambda_handler",
            **global_python_function_arguments)
        iterator_function.add_permission(
            id="IteratorFunctionLambdaInvokePermission",
            principal=_iam.ServicePrincipal(service="connect.amazonaws.com"),
            action="lambda:InvokeFunction",
            source_account=self.account,
            source_arn=
            f"arn:aws:connect:{self.region}:{self.account}:instance/{instance_id}"
        )

        create_appsync_call_task_function = _lambda.Function(
            self,
            "CreateAppSyncCallTaskFunction",
            handler="create_appsync_call_task.lambda_handler",
            **global_nodeje_function_arguments)
        create_appsync_call_task_function.add_environment(
            key="CallSqsQueueUrl", value=call_sqs_queue.queue_url)
        create_appsync_call_task_function.add_environment(
            key="CallRecordTableName", value=callout_record_table.table_name)
        call_sqs_queue.grant_send_messages(create_appsync_call_task_function)
        callout_record_table.grant_write_data(
            create_appsync_call_task_function)

        create_call_report_record_function = _lambda.Function(
            self,
            "CreateCallReportRecordFunction",
            handler="create_call_report_record.lambda_handler",
            **global_nodeje_function_arguments)

        create_excel_call_task_function = _lambda.Function(
            self,
            "CreateExcelCallTaskFunction",
            handler="create_excel_call_task.lambda_handler",
            **global_python_function_arguments)
        create_excel_call_task_function.add_environment(
            key="CallSqsQueueUrl", value=call_sqs_queue.queue_url)
        call_sqs_queue.grant_send_messages(create_excel_call_task_function)

        create_excel_call_task_function.add_event_source(
            source=_les.S3EventSource(bucket=file_bucket,
                                      events=[_s3.EventType.OBJECT_CREATED],
                                      filters=[
                                          _s3.NotificationKeyFilter(
                                              prefix="call_task",
                                              suffix=".xlsx")
                                      ]))

        start_callout_flow_function = _lambda.Function(
            self,
            "StartCalloutFlowFunction",
            handler="start_call_out_flow.lambda_handler",
            reserved_concurrent_executions=1,
            **global_python_function_arguments)
        start_callout_flow_function.add_environment(
            key="CallSqsQueueUrl", value=call_sqs_queue.queue_url)
        start_callout_flow_function.add_environment(
            key="ResponseHandlerFunctionArn",
            value=response_handler_function.function_arn)
        start_callout_flow_function.add_environment(
            key="IteratorFunctionArn", value=iterator_function.function_arn)
        start_callout_flow_function.add_environment(
            key="SendTaskSuccessFunctionArn",
            value=send_task_success_function.function_arn)
        start_callout_flow_function.add_environment(
            key="S3Bucket", value=file_bucket.bucket_name)
        start_callout_flow_function.add_event_source(
            source=_les.SqsEventSource(queue=call_sqs_queue, batch_size=1))
        file_bucket.grant_read_write(start_callout_flow_function)

        call_state_machine_definition = {
            "Comment":
            "Reading messages from an SQS queue and iteratively processing each message.",
            "StartAt": "Start",
            "States": {
                "Start": {
                    "Type": "Pass",
                    "Next": "Process Call Messages"
                },
                "Process Call Messages": {
                    "Type": "Map",
                    "Next": "Get Call Result",
                    "InputPath": "$",
                    "ItemsPath": "$",
                    "OutputPath": "$.[0]",
                    "Iterator": {
                        "StartAt": "Get Call out job",
                        "States": {
                            "Get Call out job": {
                                "Type": "Task",
                                "Resource":
                                get_callout_job_function.function_arn,
                                "Next": "Callout with AWS Connect"
                            },
                            "Callout with AWS Connect": {
                                "Type":
                                "Task",
                                "Resource":
                                "arn:aws:states:::sqs:sendMessage.waitForTaskToken",
                                "TimeoutSeconds":
                                timeout,
                                "Parameters": {
                                    "QueueUrl": async_callout_queue.queue_url,
                                    "MessageGroupId": "1",
                                    "MessageBody": {
                                        "Message.$": "$",
                                        "TaskToken.$": "$$.Task.Token"
                                    }
                                },
                                "Catch": [{
                                    "ErrorEquals": ["States.Timeout"],
                                    "ResultPath": None,
                                    "Next": "Call Timeout"
                                }],
                                "Next":
                                "Save call result"
                            },
                            "Call Timeout": {
                                "Type": "Pass",
                                "ResultPath": None,
                                "Next": "Save call result"
                            },
                            "Save call result": {
                                "Type": "Task",
                                "Resource":
                                "arn:aws:states:::dynamodb:putItem",
                                "Parameters": {
                                    "TableName": call_result_table.table_name,
                                    "Item": {
                                        "receiver_id": {
                                            "S.$": "$.receiver_id"
                                        },
                                        "task_id": {
                                            "S.$": "$.task_id"
                                        },
                                        "username": {
                                            "S.$": "$.username"
                                        },
                                        "phone_number": {
                                            "S.$": "$.phone_number"
                                        },
                                        "status": {
                                            "S.$": "$.status"
                                        },
                                        "answers": {
                                            "S.$": "$.answers"
                                        },
                                        "error": {
                                            "S.$": "$.error"
                                        },
                                        "call_at": {
                                            "S.$": "$.call_at"
                                        }
                                    }
                                },
                                "ResultPath": "$.Result",
                                "OutputPath": "$.task_id",
                                "End": True
                            }
                        }
                    }
                },
                "Get Call Result": {
                    "Type": "Task",
                    "Resource": get_call_result_function.function_arn,
                    "Next": "Create Call Report Record"
                },
                "Create Call Report Record": {
                    "Type": "Task",
                    "Resource":
                    create_call_report_record_function.function_arn,
                    "Next": "Send Completion message to SNS"
                },
                "Send Completion message to SNS": {
                    "Type": "Task",
                    "Resource": "arn:aws:states:::sns:publish",
                    "Parameters": {
                        "TopicArn": call_job_complete_sns_topic.topic_arn,
                        "Message.$": "$"
                    },
                    "Next": "Finish"
                },
                "Finish": {
                    "Type": "Succeed"
                }
            }
        }
        callout_state_machine_role = _iam.Role(
            self,
            "CalloutStatesExecutionRole",
            assumed_by=_iam.ServicePrincipal(
                f"states.{self.region}.amazonaws.com"))
        callout_state_machine_role.add_to_policy(
            _iam.PolicyStatement(
                actions=[
                    "sqs:SendMessage", "dynamodb:PutItem",
                    "lambda:InvokeFunction", "SNS:Publish"
                ],
                resources=[
                    async_callout_queue.queue_arn, call_result_table.table_arn,
                    get_callout_job_function.function_arn,
                    get_call_result_function.function_arn,
                    call_job_complete_sns_topic.topic_arn,
                    create_appsync_call_task_function.function_arn,
                    create_call_report_record_function.function_arn
                ]))
        callout_state_machine = _sfn.CfnStateMachine(
            self,
            "CalloutStateMachine",
            role_arn=callout_state_machine_role.role_arn,
            definition_string=json.dumps(call_state_machine_definition))
        send_task_success_function.add_to_role_policy(
            _iam.PolicyStatement(actions=["states:SendTaskSuccess"],
                                 resources=[callout_state_machine.ref]))

        start_callout_flow_function.add_environment(
            key="CalloutStateMachineArn", value=callout_state_machine.ref)
        start_callout_flow_function.add_to_role_policy(
            _iam.PolicyStatement(effect=_iam.Effect.ALLOW,
                                 resources=[callout_state_machine.ref],
                                 actions=['states:StartExecution']))

        user_pool = _cognito.UserPool(
            self, "UserPool", sign_in_type=_cognito.SignInType.USERNAME)

        user_pool_client = _cognito.UserPoolClient(self,
                                                   "UserPoolClient",
                                                   user_pool=user_pool)

        appsync_api = _appsync.GraphQLApi(
            self,
            "AppSyncApi",
            name="AWSCalloutApi",
            user_pool_config=_appsync.UserPoolConfig(
                user_pool=user_pool,
                default_action=_appsync.UserPoolDefaultAction.ALLOW),
            log_config=_appsync.LogConfig(
                field_log_level=_appsync.FieldLogLevel.ALL),
            schema_definition_file=
            f"{pathlib.Path(__file__).parent.absolute()}/schema.graphql")

        callout_record_ddb_ds = appsync_api.add_dynamo_db_data_source(
            name="CalloutRecordDdb",
            description="Callout Record DynamoDB Data Source",
            table=callout_record_table)
        callout_record_ddb_ds.create_resolver(
            type_name="Query",
            field_name="getLatestCallTaskRecords",
            request_mapping_template=_appsync.MappingTemplate.from_string(
                '{"version":"2017-02-28","operation":"Query","index":"CallTypeCreatedAtGlobalIndex","query":{"expression":"call_type = :call_type","expressionValues":{":call_type":{"S":"TASK"}}},"scanIndexForward":false,"limit":${ctx.args.limit}}'
            ),
            response_mapping_template=_appsync.MappingTemplate.
            dynamo_db_result_list())
        callout_record_ddb_ds.create_resolver(
            type_name="Query",
            field_name="getLatestCallReportRecords",
            request_mapping_template=_appsync.MappingTemplate.from_string(
                '{"version":"2017-02-28","operation":"Query","index":"CallTypeCreatedAtGlobalIndex","query":{"expression":"call_type = :call_type","expressionValues":{":call_type":{"S":"REPORT"}}},"scanIndexForward":false,"limit":${ctx.args.limit}}'
            ),
            response_mapping_template=_appsync.MappingTemplate.
            dynamo_db_result_list())
        callout_record_ddb_ds.create_resolver(
            type_name="Mutation",
            field_name="createCallReport",
            request_mapping_template=_appsync.MappingTemplate.from_string(
                '{"version":"2017-02-28","operation":"PutItem","key":{"task_id":{"S":"${ctx.args.report.task_id}"},"created_at":{"N":"${ctx.args.report.created_at}"}},"attributeValues":$util.dynamodb.toMapValuesJson($ctx.args.report)}'
            ),
            response_mapping_template=_appsync.MappingTemplate.
            dynamo_db_result_item())

        call_task_lambda_ds = appsync_api.add_lambda_data_source(
            name="CallTaskLambda",
            description="Call Task Lambda Data Source",
            lambda_function=create_appsync_call_task_function)
        call_task_lambda_ds.create_resolver(
            type_name="Mutation",
            field_name="createCallTask",
            request_mapping_template=_appsync.MappingTemplate.lambda_request(
                "$utils.toJson($ctx.args)"),
            response_mapping_template=_appsync.MappingTemplate.lambda_result())

        create_call_report_record_function.add_environment(
            value=appsync_api.graph_ql_url, key="AppSyncGraphQlApiUrl")

        create_call_report_record_function.add_to_role_policy(
            statement=_iam.PolicyStatement(
                effect=_iam.Effect.ALLOW,
                actions=['appsync:GraphQL'],
                resources=[
                    f"{appsync_api.arn}/types/Mutation/fields/createCallReport"
                ]))

        core.CfnOutput(self,
                       id="OutputCallSqsQueue",
                       value=call_sqs_queue.queue_arn)
        core.CfnOutput(self,
                       id="OutputCallJobCompletionSNSTopic",
                       value=call_job_complete_sns_topic.topic_arn)
        core.CfnOutput(self,
                       id="OutputExcelFileS3Bucket",
                       value=file_bucket.bucket_name)
        core.CfnOutput(self,
                       id="OutputStaticWebS3Bucket",
                       value=web_bucket.bucket_name)
        core.CfnOutput(self,
                       id="OutputStaticWebUrl",
                       value=web_bucket.bucket_website_url)

        identity_pool = _cognito.CfnIdentityPool(
            self,
            "IdentityPool",
            allow_unauthenticated_identities=True,
            cognito_identity_providers=[
                _cognito.CfnIdentityPool.CognitoIdentityProviderProperty(
                    provider_name=user_pool.user_pool_provider_name,
                    client_id=user_pool_client.user_pool_client_id)
            ])
        identity_pool_unauthorized_role = _iam.Role(
            self,
            'IdentityPoolUnAuthorizedRole',
            assumed_by=_iam.FederatedPrincipal(
                federated="cognito-identity.amazonaws.com",
                assume_role_action="sts:AssumeRoleWithWebIdentity",
                conditions={
                    "StringEquals": {
                        "cognito-identity.amazonaws.com:aud": identity_pool.ref
                    },
                    "ForAnyValue:StringLike": {
                        "cognito-identity.amazonaws.com:amr": "unauthenticated"
                    }
                }))
        identity_pool_unauthorized_role.add_to_policy(
            _iam.PolicyStatement(
                effect=_iam.Effect.ALLOW,
                actions=["appsync:GraphQL"],
                resources=[
                    f"{appsync_api.arn}/types/*",
                    # f"{appsync_api.arn}/types/Query/fields/getLatestCallTaskRecords",
                    # f"{appsync_api.arn}/types/Query/fields/getLatestCallReportRecords",
                    # f"{appsync_api.arn}/types/Mutation/fields/createCallRecord",
                    # f"{appsync_api.arn}/types/Subscription/fields/createCallTask",
                    # f"{appsync_api.arn}/types/Subscription/fields/createCallReport"
                ]))

        _cognito.CfnIdentityPoolRoleAttachment(
            self,
            "CognitoIdentityPoolRoleAttachment",
            identity_pool_id=identity_pool.ref,
            roles={
                "unauthenticated": identity_pool_unauthorized_role.role_arn
            })

        core.CfnOutput(self, id="UserPoolId", value=user_pool.user_pool_id)
        core.CfnOutput(self,
                       id="UserPoolClientId",
                       value=user_pool_client.user_pool_client_id)
        core.CfnOutput(self, id="IdentityPoolId", value=identity_pool.ref)
    def __init__(self, scope: core.Construct, construct_id: str,
                 **kwargs) -> None:
        """
        initialize function for CDK
        """
        super().__init__(scope, construct_id, **kwargs)

        # -------------------------------
        # S3 Bucket for Manifests
        # -------------------------------

        qs_gov_bucket = s3.Bucket(
            self,
            id=f"{cf.PROJECT}-Bucket",
        )
        bucket_name = qs_gov_bucket.bucket_name

        # -------------------------------
        # IAM
        # -------------------------------

        list_roles_policy = iam.ManagedPolicy(
            self,
            id=f"{cf.PROJECT}-ListRolesPolicy",
            managed_policy_name=f"{cf.PROJECT}-ListRolesPolicy",
            description=None,
            path="/",
            statements=[
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    resources=["*"],
                    actions=["iam:ListRoles", "iam:ListAccountAliases"],
                )
            ],
        )

        federated_quicksight_policy = iam.ManagedPolicy(
            self,
            id=f"{cf.PROJECT}-FederatedQuickSightPolicy",
            managed_policy_name=f"{cf.PROJECT}-FederatedQuickSightPolicy",
            statements=[
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    resources=[
                        f"arn:aws:iam::{cf.ACCOUNT}:saml-provider/{cf.OKTA_IDP_NAME}"
                    ],
                    actions=["sts:AssumeRoleWithSAML"],
                    conditions={
                        "StringEquals": {
                            "saml:aud": "https://signin.aws.amazon.com/saml"
                        }
                    },
                )
            ],
        )

        okta_federated_principal = iam.FederatedPrincipal(
            federated=
            f"arn:aws:iam::{cf.ACCOUNT}:saml-provider/{cf.OKTA_IDP_NAME}",
            assume_role_action="sts:AssumeRoleWithSAML",
            conditions={
                "StringEquals": {
                    "SAML:aud": "https://signin.aws.amazon.com/saml"
                }
            },
        )

        federated_quicksight_role = iam.Role(
            self,
            id=f"{cf.PROJECT}-{cf.QS_FEDERATED_ROLE_NAME}",
            role_name=f"{cf.QS_FEDERATED_ROLE_NAME}",
            assumed_by=okta_federated_principal,
            description=
            "Allow Okta to Federate Login & User Creation to QuickSight",
            managed_policies=[federated_quicksight_policy],
        )

        iam.User(
            self,
            id=f"{cf.PROJECT}-OktaSSOUser",
            user_name=f"{cf.PROJECT}-OktaSSOUser",
            managed_policies=[list_roles_policy],
        )

        # -------------------------------
        # Lambda Layers
        # -------------------------------

        path_src = os.path.join(cf.PATH_SRC, "")

        sp.call(["make", "bundle"], cwd=path_src)

        requests_layer = _lambda.LayerVersion(
            self,
            f"{cf.PROJECT}-requests-layer",
            code=_lambda.Code.from_asset(os.path.join(path_src,
                                                      "requests.zip")))

        sp.call(["make", "clean"], cwd=path_src)

        # -------------------------------
        # Lambda Functions
        # -------------------------------

        # iam role for Lambdas

        qs_governance_policy = iam.ManagedPolicy(
            self,
            id=f"{cf.PROJECT}-LambdaPolicy",
            managed_policy_name=f"{cf.PROJECT}-LambdaPolicy",
            statements=[
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    resources=["*"],
                    actions=["ses:SendEmail", "ses:SendRawEmail"],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    resources=[
                        f"arn:aws:secretsmanager:{cf.REGION}:{cf.ACCOUNT}:secret:{cf.OKTA_SECRET_ID}*",
                        f"arn:aws:secretsmanager:{cf.REGION}:{cf.ACCOUNT}:secret:{cf.SLACK_SECRET_ID}*"
                    ],
                    actions=[
                        "secretsmanager:GetSecretValue",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    resources=[
                        f"arn:aws:iam::{cf.ACCOUNT}:policy/{cf.QS_PREFIX}*"
                    ],
                    actions=[
                        "iam:CreatePolicy",
                        "iam:GetPolicy",
                        "iam:DeletePolicy",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    resources=["*"],
                    actions=["iam:ListPolicies"],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    resources=["*"],
                    actions=[
                        "glue:GetDatabase",
                        "glue:GetDatabases",
                        "glue:GetTable",
                        "glue:GetPartitions",
                        "glue:GetPartition",
                        "glue:GetTables",
                    ],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    resources=["*"],
                    actions=["quicksight:*", "ds:*"],
                ),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    resources=[
                        f"arn:aws:s3:::{bucket_name}/*",
                        f"arn:aws:s3:::{bucket_name}*"
                    ],
                    actions=["s3:*"],
                ),
            ],
        )

        qs_governance_role = iam.Role(
            self,
            id=f"{cf.PROJECT}-QuickSightPermissionMappingRole",
            assumed_by=iam.ServicePrincipal("lambda.amazonaws.com"),
            managed_policies=[
                iam.ManagedPolicy.from_aws_managed_policy_name(
                    "service-role/AWSLambdaBasicExecutionRole"),
                qs_governance_policy,
            ],
        )

        # Lambdas

        get_okta_information_lambda = _lambda.Function(
            self,
            id=f"{cf.PROJECT}-GetOktaInfo",
            function_name=f"{cf.PROJECT}-GetOktaInformation",
            handler="get_okta_information.handler",
            role=qs_governance_role,
            runtime=_lambda.Runtime.PYTHON_3_8,
            code=_lambda.Code.asset(os.path.join(cf.PATH_SRC, "pkg")),
            environment={
                "OKTA_SECRET_ID": cf.OKTA_SECRET_ID,
                "QS_PREFIX": cf.QS_PREFIX,
                "QS_GOVERNANCE_BUCKET": bucket_name,
                "QS_FEDERATED_ROLE_NAME": f"{cf.QS_FEDERATED_ROLE_NAME}",
                "S3_PREFIX_USERS": cf.S3_PREFIX_USERS,
                "SLACK_SECRET_ID": cf.SLACK_SECRET_ID
            },
            memory_size=256,
            timeout=core.Duration.seconds(180),
            layers=[requests_layer],
        )

        # Lamda Okta to QuickSight Mappers

        qs_user_governance_lambda = _lambda.Function(
            self,
            id=f"{cf.PROJECT}-QSUserGovernance",
            function_name=f"{cf.PROJECT}-QSUserGovernance",
            handler="qs_user_governance.handler",
            role=qs_governance_role,
            runtime=_lambda.Runtime.PYTHON_3_8,
            code=_lambda.Code.asset(os.path.join(cf.PATH_SRC, "pkg")),
            environment={
                "QS_FEDERATED_ROLE_NAME": f"{cf.QS_FEDERATED_ROLE_NAME}",
                "QS_PREFIX": cf.QS_PREFIX,
                "QS_ADMIN_OKTA_GROUP": cf.QS_ADMIN_OKTA_GROUP,
                "QS_AUTHOR_OKTA_GROUP": cf.QS_AUTHOR_OKTA_GROUP,
                "QS_READER_OKTA_GROUP": cf.QS_READER_OKTA_GROUP,
                "SLACK_SECRET_ID": cf.SLACK_SECRET_ID
            },
            memory_size=256,
            timeout=core.Duration.seconds(180),
            layers=[requests_layer],
        )

        qs_asset_governance_lambda = _lambda.Function(
            self,
            id=f"{cf.PROJECT}-QSAssetGovernance",
            function_name=f"{cf.PROJECT}-QSAssetGovernance",
            handler="qs_asset_governance.handler",
            role=qs_governance_role,
            runtime=_lambda.Runtime.PYTHON_3_8,
            code=_lambda.Code.asset(os.path.join(cf.PATH_SRC, "pkg")),
            memory_size=256,
            timeout=core.Duration.seconds(180),
            layers=[requests_layer],
            environment={
                "QS_GOVERNANCE_BUCKET": f"{bucket_name}",
                "S3_PREFIX_USERS": cf.S3_PREFIX_USERS,
                "S3_PREFIX_POLICIES": cf.S3_PREFIX_POLICIES,
                "STAGE": cf.DEPLOYMENT_STAGE,
                "SLACK_SECRET_ID": cf.SLACK_SECRET_ID,
                "VERIFIED_EMAIL": cf.VERIFIED_EMAIL
            })

        qs_policy_governance_lambda = _lambda.Function(
            self,
            id=f"{cf.PROJECT}-QSPolicyGovernance",
            function_name=f"{cf.PROJECT}-QSPolicyGovernance",
            handler="qs_policy_governance.handler",
            role=qs_governance_role,
            runtime=_lambda.Runtime.PYTHON_3_8,
            code=_lambda.Code.asset(os.path.join(cf.PATH_SRC, "pkg")),
            environment={
                "QS_ADMIN_OKTA_GROUP": cf.QS_ADMIN_OKTA_GROUP,
                "QS_AUTHOR_OKTA_GROUP": cf.QS_AUTHOR_OKTA_GROUP,
                "QS_AUTHOR_BASE_POLICY": cf.QS_AUTHOR_BASE_POLICY,
                "QS_PREFIX": cf.QS_PREFIX,
                "QS_GOVERNANCE_BUCKET": f"{bucket_name}",
                "S3_PREFIX_POLICIES": cf.S3_PREFIX_POLICIES,
                "QS_SUPERUSER": cf.QS_SUPERUSER,
                "DEPLOYMENT_STAGE": cf.DEPLOYMENT_STAGE,
                "SLACK_SECRET_ID": cf.SLACK_SECRET_ID
            },
            memory_size=256,
            timeout=core.Duration.seconds(180),
            layers=[requests_layer])

        qs_resource_cleanup_lambda = _lambda.Function(
            self,
            id=f"{cf.PROJECT}-QSResourceCleanup",
            function_name=f"{cf.PROJECT}-QSResourceCleanup",
            handler="qs_resource_cleanup.handler",
            role=qs_governance_role,
            runtime=_lambda.Runtime.PYTHON_3_8,
            code=_lambda.Code.asset(os.path.join(cf.PATH_SRC, "pkg")),
            memory_size=256,
            timeout=core.Duration.seconds(180),
            layers=[requests_layer],
            environment={
                "QS_PREFIX": cf.QS_PREFIX,
                "QS_SUPERUSER": cf.QS_SUPERUSER,
                "QS_GOVERNANCE_BUCKET": f"{bucket_name}",
                "S3_PREFIX_ASSETS": cf.S3_PREFIX_ASSETS,
                "S3_PREFIX_USERS": cf.S3_PREFIX_USERS,
                "S3_PREFIX_POLICIES": cf.S3_PREFIX_POLICIES,
                "SLACK_SECRET_ID": cf.SLACK_SECRET_ID
            })

        # -------------------------------
        # S3 Event Triggers
        # -------------------------------

        qs_user_governance_lambda.add_event_source(
            lambda_event_sources.S3EventSource(
                bucket=qs_gov_bucket,
                events=[s3.EventType.OBJECT_CREATED],
                filters=[s3.NotificationKeyFilter(prefix=cf.S3_PREFIX_USERS)],
            ))

        qs_policy_governance_lambda.add_event_source(
            lambda_event_sources.S3EventSource(
                bucket=qs_gov_bucket,
                events=[s3.EventType.OBJECT_CREATED],
                filters=[
                    s3.NotificationKeyFilter(prefix=cf.S3_PREFIX_POLICIES)
                ],
            ))

        # -------------------------------
        # CloudWatch Event Rules (30 minutes)
        # -------------------------------

        lambda_schedule = events.Schedule.rate(core.Duration.minutes(30))
        get_okta_info_target = events_targets.LambdaFunction(
            handler=get_okta_information_lambda)
        events.Rule(
            self,
            id=f"{cf.PROJECT}-GetOktaInfoScheduledEvent",
            description="The scheduled CloudWatch event trigger for the Lambda",
            enabled=True,
            schedule=lambda_schedule,
            targets=[get_okta_info_target],
        )

        qs_asset_gov_target = events_targets.LambdaFunction(
            handler=qs_asset_governance_lambda)
        events.Rule(
            self,
            id=f"{cf.PROJECT}-QSAssetGovScheduledEvent",
            description="The scheduled CloudWatch event trigger for the Lambda",
            enabled=True,
            schedule=lambda_schedule,
            targets=[qs_asset_gov_target],
        )

        qs_cleanup_target = events_targets.LambdaFunction(
            handler=qs_resource_cleanup_lambda)
        events.Rule(
            self,
            id=f"{cf.PROJECT}-QSCleanupScheduledEvent",
            description="The scheduled CloudWatch event trigger for the Lambda",
            enabled=True,
            schedule=lambda_schedule,
            targets=[qs_cleanup_target],
        )

        # -------------------------------
        # Athena WorkGroup
        # -------------------------------

        workgroup_encryption = athena.CfnWorkGroup.EncryptionConfigurationProperty(
            encryption_option='SSE_S3')

        workgroup_output = athena.CfnWorkGroup.ResultConfigurationProperty(
            output_location="s3://" + cf.ATHENA_OUTPUT_LOC,
            encryption_configuration=workgroup_encryption)

        quicksight_athena_workgroup = athena.CfnWorkGroup(
            self,
            id=f"{cf.PROJECT}-workgroup",
            name=f"{cf.QS_PREFIX}-workgroup",
            description="workgroup for QuickSight Data Source operations",
            recursive_delete_option=True,
            work_group_configuration=athena.CfnWorkGroup.
            WorkGroupConfigurationProperty(
                result_configuration=workgroup_output))
        workgroup_name = quicksight_athena_workgroup.name

        # -------------------------------
        # S3 Object Deployments
        # -------------------------------

        policy_manifest_deploy = s3_deploy.BucketDeployment(
            self,
            id=f"{cf.PROJECT}-PolicyManifestDeploy",
            sources=[
                s3_deploy.Source.asset(
                    os.path.join(cf.PATH_ROOT, "qs_config/policies"))
            ],
            destination_bucket=qs_gov_bucket,
            destination_key_prefix=cf.S3_PREFIX_POLICIES)

        # -------------------------------
        # QuickSight IAM Policy Assignments
        # -------------------------------

        qs_author_base_policy = iam.ManagedPolicy(
            self,
            id=f"{cf.PROJECT}-{cf.QS_AUTHOR_BASE_POLICY}",
            managed_policy_name=f"{cf.QS_AUTHOR_BASE_POLICY}",
            statements=[
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    resources=[
                        f"arn:aws:athena:{cf.REGION}:{cf.ACCOUNT}:workgroup/{workgroup_name}"
                    ],
                    actions=[
                        "athena:GetNamedQuery", "athena:CancelQueryExecution",
                        "athena:CreateNamedQuery", "athena:DeleteNamedQuery",
                        "athena:StartQueryExecution",
                        "athena:StopQueryExecution", "athena:GetWorkGroup",
                        "athena:GetNamedQuery", "athena:GetQueryResults",
                        "athena:GetQueryExecution",
                        "athena:BatchGetQueryExecution",
                        "athena:BatchGetNamedQuery", "athena:ListNamedQueries",
                        "athena:ListQueryExecutions",
                        "athena:GetQueryResultsStream"
                    ]),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    resources=["*"],
                    actions=[
                        "athena:ListWorkGroups", "athena:ListDataCatalogs",
                        "athena:StartQueryExecution",
                        "athena:GetQueryExecution",
                        "athena:GetQueryResultsStream",
                        "athena:ListTableMetadata", "athena:GetTableMetadata"
                    ]),
                iam.PolicyStatement(effect=iam.Effect.ALLOW,
                                    resources=[
                                        "arn:aws:s3:::ast-datalake-*",
                                        "arn:aws:s3:::aws-athena-query-*",
                                        f"arn:aws:s3:::{bucket_name}*",
                                        f"arn:aws:s3:::{cf.ATHENA_OUTPUT_LOC}*"
                                    ],
                                    actions=[
                                        "s3:List*",
                                        "s3:Get*",
                                    ]),
                iam.PolicyStatement(effect=iam.Effect.ALLOW,
                                    resources=["*"],
                                    actions=["s3:ListAllMyBuckets"]),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    resources=[
                        "arn:aws:s3:::aws-athena-query-*",
                        f"arn:aws:s3:::{bucket_name}/{cf.QS_PREFIX}/athena-results*"
                    ],
                    actions=["s3:Put*"]),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    resources=[
                        f"arn:aws:athena:{cf.REGION}:{cf.ACCOUNT}:datacatalog/AwsDataCatalog"
                    ],
                    actions=["athena:ListDatabases"]),
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    resources=["*"],
                    actions=["kms:Decrypt", "kms:GenerateDataKey"]),
            ],
        )