Example #1
0
def base_cognito_user_identity_pool_attach_role(construct, identity_pool,
                                                unauth_role_config,
                                                auth_role_config):
    """
    Function that attach Roles to an Identity Pool.
    :param construct: Custom construct that will use this function. From the external construct is usually 'self'.
    :param identity_pool:
    :param unauth_role_config:
    :param auth_role_config:
    :return:
    """
    base_role_name = identity_pool.identity_pool_name
    unauth_actions = unauth_role_config["actions"]
    unauth_resources = unauth_role_config["resources"]
    auth_actions = auth_role_config["actions"]
    auth_resources = auth_role_config["resources"]

    unauth_role = base_cognito_identity_pool_unauth_role(
        construct,
        identity_pool,
        resource_name=base_role_name,
        actions=unauth_actions,
        resources=unauth_resources)
    auth_role = base_cognito_identity_pool_auth_role(
        construct,
        identity_pool,
        resource_name=base_role_name,
        actions=auth_actions,
        resources=auth_resources)

    role_attachment = cognito.CfnIdentityPoolRoleAttachment(
        construct,
        id=identity_pool.identity_pool_name + "_roleattach",
        identity_pool_id=identity_pool.ref,
        roles={
            "unauthenticated": unauth_role.role_arn,
            "authenticated": auth_role.role_arn
        },
    )

    return role_attachment
    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
    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)
Example #4
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
Example #5
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 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
Example #7
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 __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, 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)

        webtool_bucket = _s3.Bucket(
            self,
            "WebToolBucket",
            website_index_document="index.html",
            website_error_document="index.html",
            public_read_access=True,
        )

        core.CfnOutput(self, "DVTRegion", value=self.region)
        core.CfnOutput(self,
                       "SourceS3Bucket",
                       value=source_csv_bucket.bucket_name)
        core.CfnOutput(self,
                       "TargetS3Bucket",
                       value=target_csv_bucket.bucket_name)
        core.CfnOutput(self,
                       "WebToolS3Bucket",
                       value=webtool_bucket.bucket_name)
        core.CfnOutput(self,
                       "WebToolUrl",
                       value=webtool_bucket.bucket_website_url)

        ### Stager 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)
        core.CfnOutput(self,
                       "StagerLambdaFunction",
                       value=stager_function.function_name)

        ### Profiling Queue
        profiling_job_queue = _sqs.Queue(self, "ProfilingJobQueue")
        core.CfnOutput(self,
                       "SQSProfileQueue",
                       value=profiling_job_queue.queue_url)

        ### 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=WebIdentityPrincipal(
                "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", "s3:PutObject"],
                            resources=["%s/*" % target_csv_bucket.bucket_arn]))

        auth_role.add_to_policy(
            PolicyStatement(effect=Effect.ALLOW,
                            actions=["lambda:invokeFunction"],
                            resources=[stager_function.function_arn]))

        auth_role.add_to_policy(
            PolicyStatement(effect=Effect.ALLOW,
                            actions=["sqs:*"],
                            resources=[profiling_job_queue.queue_arn]))

        unauth_role = _iam.Role(
            self,
            "CognitoUnauthRole",
            assumed_by=_iam.WebIdentityPrincipal(
                "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, "IdentityPoolId", value=identity_pool.ref)
        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)

        from aws_cdk.aws_appsync import MappingTemplate

        api_ds.create_resolver(
            type_name="Query",
            field_name="listJobss",
            request_mapping_template=MappingTemplate.from_file(
                os.path.join(dirname, "api", "resolvers",
                             "Query.listJobss.req.vtl")),
            response_mapping_template=MappingTemplate.from_file(
                os.path.join(dirname, "api", "resolvers",
                             "Query.listJobss.res.vtl")))

        api_ds.create_resolver(
            type_name="Query",
            field_name="getJobs",
            request_mapping_template=MappingTemplate.from_file(
                os.path.join(dirname, "api", "resolvers",
                             "Query.getJobs.req.vtl")),
            response_mapping_template=MappingTemplate.from_file(
                os.path.join(dirname, "api", "resolvers",
                             "Query.getJobs.res.vtl")))

        core.CfnOutput(self, "GraphQLEndpoint", value=api.graphql_url)

        ### SQS ###

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

        ### 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)

        ### 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,
                 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)
    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)
    def __init__(self, scope: core.Construct, construct_id: str,
                 cognito_prefix: str, stack_log_level: str, **kwargs) -> None:

        super().__init__(scope, construct_id, **kwargs)

        # The code that defines your stack goes here
        # Cognito User Pool for Kibana Access
        self.es_user_pool = _cognito.UserPool(
            self,
            "esCognitoUserPool",
            user_pool_name=f"{cognito_prefix}-log-parser-es-users",
            self_sign_up_enabled=False,
            sign_in_aliases=_cognito.SignInAliases(username=True, email=True),
            sign_in_case_sensitive=False,
            standard_attributes={
                "email": {
                    "required": True,
                    "mutable": False
                },
                "fullname": {
                    "required": False,
                    "mutable": True
                }
            },
            password_policy=_cognito.PasswordPolicy(min_length=8),
            auto_verify=_cognito.AutoVerifiedAttrs(email=True))

        # Create User Pool Domain to enable SignUp, SignIn, Auth & Callback Urls
        es_auth_domain = _cognito.UserPoolDomain(
            self,
            "esCognitoUserPoolDomain",
            user_pool=self.es_user_pool,
            cognito_domain=_cognito.CognitoDomainOptions(
                domain_prefix=f"{cognito_prefix}-{core.Aws.ACCOUNT_ID}"))

        # Role Authenticated Cognito Users will assume in ES Service
        self.es_role = _iam.Role(
            self,
            "esKibanaIamRole",
            assumed_by=_iam.ServicePrincipal("es.amazonaws.com"),
            managed_policies=[
                _iam.ManagedPolicy.from_aws_managed_policy_name(
                    managed_policy_name="AmazonESCognitoAccess")
            ],
        )

        # Create Cognito Federated Identity Pool to exchange Cognito auth token for AWS Token
        self.es_id_pool = _cognito.CfnIdentityPool(
            self,
            "esIdentityPool",
            allow_unauthenticated_identities=False,
            cognito_identity_providers=[],
        )

        # Role For Cognito Federated Idenity Pool Authenticated Users
        self.es_auth_role = _iam.Role(
            self,
            "esAuthIamRole",
            assumed_by=_iam.FederatedPrincipal(
                federated="cognito-identity.amazonaws.com",
                conditions={
                    "StringEquals": {
                        "cognito-identity.amazonaws.com:aud":
                        self.es_id_pool.ref
                    },
                    "ForAnyValue:StringLike": {
                        "cognito-identity.amazonaws.com:amr": "authenticated"
                    },
                },
                assume_role_action="sts:AssumeRoleWithWebIdentity"),
        )

        # Attach a Role to Cognito Federated Idenity Pool Authenticated Users
        _cognito.CfnIdentityPoolRoleAttachment(
            self,
            "cognitoFederatedIdentityPoolRoleAttachment",
            identity_pool_id=self.es_id_pool.ref,
            roles={"authenticated": self.es_auth_role.role_arn})

        ###########################################
        ################# OUTPUTS #################
        ###########################################
        output_0 = core.CfnOutput(
            self,
            "AutomationFrom",
            value=f"{GlobalArgs.SOURCE_INFO}",
            description=
            "To know more about this automation stack, check out our github page."
        )

        output_1 = core.CfnOutput(
            self,
            "CreateCognitoUserConsole",
            value=
            f"https://{core.Aws.REGION}.console.aws.amazon.com/cognito/users?region={core.Aws.REGION}#/pool/{self.es_user_pool.user_pool_id}/users",
            description="Create a new user in the user pool here.")