Example #1
0
 def create_user_pool_domain(self, user_pool: aws_cognito.CfnUserPool,
                             tag: str):
     domain_prefix = self._secrets["hostedui.domain_prefix"]
     domain = aws_cognito.CfnUserPoolDomain(
         self,
         f"user_pool_domain_{tag}",
         domain=domain_prefix,
         user_pool_id=user_pool.ref,
     )
     return domain
Example #2
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, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # Get the hosted Zone and create a certificate for our domain

        hosted_zone = route53.HostedZone.from_hosted_zone_attributes(
            self,
            "HostedZone",
            hosted_zone_id=HOSTED_ZONE_ID,
            zone_name=HOSTED_ZONE_NAME)

        cert = certificatemanager.DnsValidatedCertificate(
            self,
            "Certificate",
            hosted_zone=hosted_zone,
            domain_name=APP_DNS_NAME)

        # Set up a new VPC

        vpc = ec2.Vpc(self, "FargateDemoVpc", max_azs=2)

        # Set up an ECS Cluster for fargate

        cluster = ecs.Cluster(self, "FargateCluster", vpc=vpc)

        # Configure the user pool and related entities for authentication

        user_pool = cognito.UserPool(
            self,
            "UserPool",
            self_sign_up_enabled=True,
            user_pool_name="FargateDemoUserPool",
        )

        user_pool_custom_domain = cognito.CfnUserPoolDomain(
            self,
            "CustomDomain",
            domain=COGNITO_CUSTOM_DOMAIN,
            user_pool_id=user_pool.user_pool_id)

        user_pool_client = cognito.UserPoolClient(
            self,
            "AppClient",
            user_pool=user_pool,
            user_pool_client_name="AlbAuthentication",
            generate_secret=True)

        # Set the attributes on the user pool client that can't be updated via the construct
        user_pool_client_cf: cognito.CfnUserPoolClient = user_pool_client.node.default_child
        user_pool_client_cf.allowed_o_auth_flows = ["code"]
        user_pool_client_cf.allowed_o_auth_scopes = ["openid"]
        user_pool_client_cf.callback_ur_ls = [
            f"https://{APP_DNS_NAME}/oauth2/idpresponse",
            f"https://{APP_DNS_NAME}"
        ]
        user_pool_client_cf.default_redirect_uri = f"https://{APP_DNS_NAME}/oauth2/idpresponse"
        user_pool_client_cf.logout_ur_ls = [
            f"https://{APP_DNS_NAME}/logout", f"https://{APP_DNS_NAME}/"
        ]
        user_pool_client_cf.supported_identity_providers = [
            # This is where you'd add external identity providers as well.
            "COGNITO"
        ]
        user_pool_client_cf.allowed_o_auth_flows_user_pool_client = True

        # Define the Docker Image for our container (the CDK will do the build and push for us!)
        docker_image = ecr_assets.DockerImageAsset(
            self,
            "JwtApp",
            directory=os.path.join(os.path.dirname(__file__), "..", "src"))

        user_pool_domain = f"{user_pool_custom_domain.domain}.auth.{self.region}.amazoncognito.com"

        # Define the fargate service + ALB

        fargate_service = ecs_patterns.ApplicationLoadBalancedFargateService(
            self,
            "FargateService",
            cluster=cluster,
            certificate=cert,
            domain_name=f"{APP_DNS_NAME}",
            domain_zone=hosted_zone,
            task_image_options={
                "image":
                ecs.ContainerImage.from_docker_image_asset(docker_image),
                "environment": {
                    "PORT":
                    "80",
                    "LOGOUT_URL":
                    f"https://{user_pool_domain}/logout?" +
                    f"client_id={user_pool_client.user_pool_client_id}&" +
                    f"redirect_uri={ urllib.parse.quote(f'https://{APP_DNS_NAME}')}&"
                    + f"response_type=code&state=STATE&scope=openid"
                }
            })

        # Add an additional HTTPS egress rule to the Load Balancers security group to talk to Cognito
        lb_security_group = fargate_service.load_balancer.connections.security_groups[
            0]

        lb_security_group.add_egress_rule(
            peer=ec2.Peer.any_ipv4(),
            connection=ec2.Port(protocol=ec2.Protocol.TCP,
                                string_representation="443",
                                from_port=443,
                                to_port=443),
            description="Outbound HTTPS traffic to get to Cognito")

        # Allow 10 seconds for in flight requests before termination, the default of 5 minutes is much too high.
        fargate_service.target_group.set_attribute(
            key="deregistration_delay.timeout_seconds", value="10")

        # Enable authentication on the Load Balancer
        alb_listener: elb.CfnListener = fargate_service.listener.node.default_child

        elb.CfnListenerRule(
            self,
            "AuthenticateRule",
            actions=[{
                "type":
                "authenticate-cognito",
                "authenticateCognitoConfig":
                elb.CfnListenerRule.AuthenticateCognitoConfigProperty(
                    user_pool_arn=user_pool.user_pool_arn,
                    user_pool_client_id=user_pool_client.user_pool_client_id,
                    user_pool_domain=user_pool_custom_domain.domain),
                "order":
                1
            }, {
                "type":
                "forward",
                "order":
                10,
                "targetGroupArn":
                fargate_service.target_group.target_group_arn
            }],
            conditions=[{
                "field": "host-header",
                "hostHeaderConfig": {
                    "values": [f"{APP_DNS_NAME}"]
                }
            }],
            # Reference the Listener ARN
            listener_arn=alb_listener.ref,
            priority=1000)
    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,
        )