Exemplo n.º 1
0
 def __create_api_authorizer(self, user_pool_arn: str,
                             api: apigw.RestApi) -> apigw.CfnAuthorizer:
     authorizer = apigw.CfnAuthorizer(
         scope=self,
         name="KesherApiAuth",
         id="KesherApiAuth",
         type="COGNITO_USER_POOLS",
         provider_arns=[user_pool_arn],
         rest_api_id=api.rest_api_id,
         identity_source="method.request.header.Authorization")
     return authorizer
Exemplo n.º 2
0
 def create_root_api_authorizer(
         self, user_pool: aws_cognito.UserPool,
         api: aws_apigateway.RestApi) -> aws_apigateway.CfnAuthorizer:
     authorizer = aws_apigateway.CfnAuthorizer(
         scope=self,
         name="MasterStackApiAuth",
         id="MasterStackApiAuth",
         type="COGNITO_USER_POOLS",
         provider_arns=[user_pool.user_pool_arn],
         rest_api_id=api.rest_api_id,
         identity_source="method.request.header.Authorization")
     return authorizer
    def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # Ensure the Cognito ARN is set
        cognito_arn = os.environ.get('COGNITO_ARN')
        if cognito_arn is None:
            raise ValueError(
                'An environment variable for COGNITO_ARN is required')

        # Deploy Lambda
        my_lambda = _lambda.Function(
            self, 'HelloHandler',
            runtime=_lambda.Runtime.PYTHON_3_7,
            code=_lambda.Code.asset('lambda'),
            handler='hello.handler',
        )

        # create the API gateway
        api = apigw.RestApi(
            self,
            'HelloHandler2',
            rest_api_name="LCAOA_Demo",
            description="Demo of lambda, cognito, Azure AD and OpenId auth"
        )

        # configure the Authorizer
        auth = apigw.CfnAuthorizer(
            scope=self,
            id='cognito_authorizer',
            rest_api_id=api.rest_api_id,
            name='MyAuth',
            type='COGNITO_USER_POOLS',
            identity_source='method.request.header.Authorization',
            provider_arns=[
                cognito_arn
            ]
        )
        resource = api.root.add_resource("endpoint")
        lambda_integration = apigw.LambdaIntegration(
            my_lambda, proxy=True)
        method = resource.add_method("GET", lambda_integration)
        method_resource = method.node.find_child('Resource')
        method_resource.add_property_override(
            'AuthorizationType', 'COGNITO_USER_POOLS')
        method_resource.add_property_override(
            'AuthorizerId', {"Ref": auth.logical_id})
 def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
     super().__init__(scope, id, **kwargs)
     # Create simple, publically available API gateway resource, with CORS on preflight requests
     api = aws_apigateway.RestApi(self, 'testAPI', rest_api_name='testAPI',
                                  default_cors_preflight_options={
                                      "allow_origins": ["*"],
                                      "allow_methods": ["GET", "POST", "OPTIONS", "PUT", "DELETE"]
                                  })
     # Create a COGNITO_USER_POOLS Authorizer to use with the API
     auth = aws_apigateway.CfnAuthorizer(
                                             self, 
                                             "testAuth", 
                                             rest_api_id=api.rest_api_id,
                                             type='COGNITO_USER_POOLS', 
                                             identity_source='method.request.header.Authorization',
                                             provider_arns=[
                                                 'arn:aws:cognito-idp:...'
                                             ],
                                             name="testAuth"
                                         )
     # Add a resource (path) to the API
     resource = api.root.add_resource("endpoint")
     # Create lambda
     lambda_function = aws_lambda.Function(
                                               self, 
                                               "lambdaFunction",
                                               handler='app.lambda_handler',
                                               runtime=aws_lambda.Runtime.PYTHON_3_8,
                                               code=aws_lambda.Code.from_asset(
                                                   "path/to/code"
                                               )
                                           )
     # Lambda integration
     lambda_integration = aws_apigateway.LambdaIntegration(lambda_function, proxy=True)
     method = resource.add_method("GET", lambda_integration)
     # Get the Resource subkey of the method
     method_resource = method.node.find_child('Resource')
     # Override the properties
     method_resource.add_property_override('AuthorizationType', 'COGNITO_USER_POOLS')
     method_resource.add_property_override('AuthorizerId', {"Ref": auth.logical_id})
    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)
Exemplo n.º 6
0
    def __init__(self, scope: core.Construct, id: str, unicorn_user_pool_arn,
                 unicorn_user_pool_res_srv_identifier, unicorn_read_scope,
                 unicorn_write_scope, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # Create Serverless Event Processor using Lambda):
        # Read Lambda Code
        try:
            with open("premium_api/lambda_src/premium_content.py",
                      mode="r") as f:
                premium_content_fn_code = f.read()
        except OSError:
            print("Unable to read Lambda Function Code")
            raise

        premium_content_fn = _lambda.Function(
            self,
            "premiumContentFunction",
            function_name="premium_function",
            runtime=_lambda.Runtime.PYTHON_3_7,
            handler="index.lambda_handler",
            code=_lambda.InlineCode(premium_content_fn_code),
            timeout=core.Duration.seconds(3),
            reserved_concurrent_executions=1,
            environment={
                "LOG_LEVEL": "INFO",
                "Environment": "Production"
            })

        # Create Custom Loggroup
        # /aws/lambda/function-name
        premium_content_fn_lg = _logs.LogGroup(
            self,
            "premiumContentFnLoggroup",
            log_group_name=f"/aws/lambda/{premium_content_fn.function_name}",
            retention=_logs.RetentionDays.ONE_WEEK,
            removal_policy=core.RemovalPolicy.DESTROY)

        # Add API GW front end for the Lambda
        walled_garden_api_stage_options = _apigw.StageOptions(
            stage_name="prod",
            throttling_rate_limit=10,
            throttling_burst_limit=100,
            logging_level=_apigw.MethodLoggingLevel.INFO)

        # Create API Gateway
        api_01 = _apigw.LambdaRestApi(
            self,
            "walledGardenApi",
            rest_api_name="walled-garden-api",
            deploy_options=walled_garden_api_stage_options,
            endpoint_types=[_apigw.EndpointType.REGIONAL],
            handler=premium_content_fn,
            proxy=False)

        # Add the wall to the garden - API Authorizer
        api_01_authorizer = _apigw.CfnAuthorizer(
            self,
            "walledGardenApiAuthorizer",
            name="walledGardenSentry",
            rest_api_id=api_01.rest_api_id,
            type="COGNITO_USER_POOLS",
            provider_arns=[unicorn_user_pool_arn],
            authorizer_result_ttl_in_seconds=15,
            identity_source="method.request.header.Authorization")

        get_content = api_01.root.add_resource("home")
        # premium_content = get_content.add_resource("{premium}")
        premium_content = get_content.add_resource("premium")
        premium_content_method_get = premium_content.add_method(
            "GET",
            authorization_type=_apigw.AuthorizationType.COGNITO,
            authorization_scopes=[
                f"{unicorn_user_pool_res_srv_identifier}/{unicorn_read_scope}"
            ])

        premium_content_method_get.node.find_child(
            "Resource").add_property_override("AuthorizerId",
                                              api_01_authorizer.ref)
        # Add POST method
        premium_content_method_post = premium_content.add_method(
            "POST",
            authorization_type=_apigw.AuthorizationType.COGNITO,
            authorization_scopes=[
                f"{unicorn_user_pool_res_srv_identifier}/{unicorn_write_scope}"
            ])
        premium_content_method_post.node.find_child(
            "Resource").add_property_override("AuthorizerId",
                                              api_01_authorizer.ref)

        # Export API Endpoint URL
        self.premium_content_api_url = premium_content.url

        # Outputs
        output_1 = core.CfnOutput(
            self,
            "PremiumApiUrl",
            value=f"{premium_content.url}",
            description="Use a browser to access this url")
Exemplo n.º 7
0
    def __init__(self, scope: core.Construct, id_: str, profile: str) -> None:
        super().__init__(scope, id_)
        self._profile = profile
        self._username = getpass.getuser().capitalize()

        # define DynamoDB table with streams
        self.table = aws_dynamodb.Table(
            self,
            TenantCrudAPI._TABLE_NAME,
            partition_key=aws_dynamodb.Attribute(
                name="id", type=aws_dynamodb.AttributeType.STRING),
            billing_mode=aws_dynamodb.BillingMode.PAY_PER_REQUEST,
            removal_policy=core.RemovalPolicy.DESTROY,
            stream=aws_dynamodb.StreamViewType.NEW_AND_OLD_IMAGES,
        )

        # add table name to stack outputs
        table_output = core.CfnOutput(self,
                                      id="TenantsTableName",
                                      value=self.table.table_name)
        table_output.override_logical_id("TenantsTableName")

        # define API GW
        self.rest_api: aws_apigateway.LambdaRestApi = aws_apigateway.RestApi(
            self,
            "tenant-mgmt-rest-api",
            rest_api_name="Tenant Management Rest API",
            description="This service handles tenant managemnet")

        # create congnito authorizer
        self.user_pool_arn = get_stack_resource(get_user_pool_stack_name(),
                                                'TenantManagementUserPoolArn',
                                                True)
        self.api_authorizer: aws_apigateway.CfnAuthorizer = aws_apigateway.CfnAuthorizer(
            scope=self,
            name="TenantMgmtApiAuth",
            id="TenantMgmtApiAuth",
            type="COGNITO_USER_POOLS",
            provider_arns=[self.user_pool_arn],
            rest_api_id=self.rest_api.rest_api_id,
            identity_source="method.request.header.Authorization",
        )

        default_cors_preflight_options = {
            "allow_headers": aws_apigateway.Cors.DEFAULT_HEADERS,
            "allow_origins": aws_apigateway.Cors.ALL_ORIGINS
        }

        # add API GW - create "{domain}/tenants"
        self.tenants_resource: aws_apigateway.Resource = self.rest_api.root.add_resource(
            "tenants",
            default_cors_preflight_options=default_cors_preflight_options)

        # add API GW /tenants/{tenant_id} path
        self.tenant_id_resource = self.tenants_resource.add_resource(
            "{tenant_id}",
            default_cors_preflight_options=default_cors_preflight_options)
        # add API GW endpoint to outputs
        endpoint_output = core.CfnOutput(self,
                                         id=self._API_GW_NAME,
                                         value=self.rest_api.url)
        endpoint_output.override_logical_id(self._API_GW_NAME)

        # create REST API Lambdas
        self._create_func = self._add_create_lambda_integration(
            id_, self.table)
        self._list_func = self._add_list_lambda_integration(id_, self.table)
        self._get_func = self._add_get_lambda_integration(id_, self.table)
Exemplo n.º 8
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/"))
Exemplo n.º 9
0
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # ######## Dynamo DB ###############
        events_table = Dynamo.Table(
            self,
            "events_table",
            table_name='events',
            partition_key=Dynamo.Attribute(
                name="name",
                type=Dynamo.AttributeType.STRING
            ),
            billing_mode=Dynamo.BillingMode.PAY_PER_REQUEST,
            removal_policy=core.RemovalPolicy.DESTROY  # Don't use in production
        )

        # ######## Lambda Function ###############
        lambda_function = Lambda.Function(
            self,
            'lambda_events',
            code=Lambda.Code.asset('./resources/events'),
            handler='events_info.handler',
            runtime=Lambda.Runtime.PYTHON_3_8,
            description='Lambda function to get information about Python events.',
            environment={
                "EVENTS_TABLE_NAME": events_table.table_name
            }
        )

        # grant permission for reading on Dynamo table
        events_table.grant_read_data(lambda_function)

        events_authorizer = Lambda.Function(
            self,
            'authorizer',
            code=Lambda.Code.asset('./resources/events'),
            handler='authorizer.handler',
            runtime=Lambda.Runtime.PYTHON_3_8,
            description='Authorizer for events endpoint'
        )

        auth_role = Iam.Role(
            self,
            'auth_role_handler',
            assumed_by=Iam.ServicePrincipal("apigateway.amazonaws.com")
        )
        events_authorizer.grant_invoke(auth_role)

        # ######## API Gateway ###############
        api = Apigateway.RestApi(
            self,
            'api_events',
            description='REST API for Python events',
            deploy_options={
                "method_options": {
                    "/*/*": {
                        "throttling_rate_limit": 10,
                        "throttling_burst_limit": 5
                    }
                }
            }
        )

        api_authorizer = Apigateway.CfnAuthorizer(
            self,
            'api_authorizer',
            authorizer_credentials=auth_role.role_arn,
            authorizer_uri=f"arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/{events_authorizer.function_arn}/invocations",
            identity_source="method.request.header.Authorization",
            name="rest-api-authorizer",
            rest_api_id=api.rest_api_id,
            type="REQUEST"
        )

        events_integration = Apigateway.LambdaIntegration(
            lambda_function)
        root_v1 = api.root.add_resource("v1")
        get_events = root_v1.add_resource("events")
        # {URL}/v1/events
        # {URL}/v1/event/{1}

        get_events_method = get_events.add_method(
            "GET",
            events_integration,
            authorization_type=Apigateway.AuthorizationType.CUSTOM
        )
        get_events_method.node.find_child('Resource').add_property_override(
            'AuthorizerId', api_authorizer.ref
        )
Exemplo n.º 10
0
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)
        # Add SNS Topic for emails list
        sns_topic = aws_sns.Topic(self, "RescueCentreAPISNS")

        for email in emails_list["Parameter"]["Value"].split(","):
            sns_topic.add_subscription(
                aws_sns_subscriptions.EmailSubscription(email))

        # ******* API gateway
        # Create simple, publically available API gateway resource, with CORS on preflight requests
        rescue_centre_api = aws_apigateway.RestApi(
            self,
            'rescueCentreAPI',
            rest_api_name='rescueCentreAPI',
            default_cors_preflight_options={
                "allow_origins": ["*"],
                "allow_methods": ["GET", "POST", "OPTIONS", "PUT", "DELETE"]
            })
        # ******* COGNITO_USER_POOLS Auth
        auth = aws_apigateway.CfnAuthorizer(
            self,
            "adminSectionAuth",
            rest_api_id=rescue_centre_api.rest_api_id,
            type='COGNITO_USER_POOLS',
            identity_source='method.request.header.Authorization',
            provider_arns=[
                'arn:aws:cognito-idp:eu-west-2:040684591284:userpool/eu-west-2_3T4vtfKJE'
            ],
            name="adminSectionAuth")
        # ******* Create the databases and all the API resources We use the same Lambda code multiple times
        # and create very simlar resources so this is the simplest way of doing it. Minimizes effect of typos also
        resources = {
            "rehomers": {
                "methodsNotRequiringAuth": ["POST"],
                "methodsToExclude": []
            },
            "horses": {
                "methodsNotRequiringAuth": ["GET"],
                "methodsToExclude": ["PUT"]
            },
            "queries": {
                "methodsNotRequiringAuth": ["POST"],
                "methodsToExclude": ["PUT"]
            },
            "volunteers": {
                "methodsNotRequiringAuth": ["POST"],
                "methodsToExclude": []
            }
        }
        # The method each lambda is supposed to handle
        methods = {
            "POST": "lambdas/createItemLambda",
            "PUT": "lambdas/createItemLambda",
            "DELETE": "lambdas/deleteItemLambda",
            "GET": "lambdas/getItemLambda"
        }
        for i in resources:
            # Add the resource (API endpoint) to the API GW API
            resource = rescue_centre_api.root.add_resource(i)
            # Create a dynamodb table for the resource
            Table = aws_dynamodb.Table(
                self,
                i + "Table",
                partition_key=aws_dynamodb.Attribute(
                    name="id", type=aws_dynamodb.AttributeType.STRING),
                read_capacity=2,
                write_capacity=2,
                billing_mode=aws_dynamodb.BillingMode.PROVISIONED)
            # Add the methods to the resource
            for method in methods:
                if method not in resources[i]["methodsToExclude"]:
                    details = {
                        "table": Table,
                        "method": method,
                        "resource": resource,
                        "lambdaName": i + method,
                        "lambdaCode": methods[method],
                        "requiresAuth": True,
                        "topic": sns_topic
                    }
                    if method in resources[i]["methodsNotRequiringAuth"]:
                        details["requiresAuth"] = False
                        self.makeLambda(details, auth)
                    else:
                        self.makeLambda(details, auth)

        # ******* S3 bucket for website content
        websiteBucket = aws_s3.Bucket(
            self,
            "websiteBucket",
            bucket_name="www.leighrescuecentre.co.uk",
            public_read_access=True,
            website_index_document="index.html",
            access_control=aws_s3.BucketAccessControl.PUBLIC_READ,
            cors=[{
                "allowedMethods": [
                    aws_s3.HttpMethods.GET, aws_s3.HttpMethods.PUT,
                    aws_s3.HttpMethods.HEAD, aws_s3.HttpMethods.POST,
                    aws_s3.HttpMethods.DELETE
                ],
                "allowedOrigins": ["*"],
                "allowedHeader": ["*"],
                "exposeHeader": ["ETag"]
            }])

        # ******* S3 bucket for image and video content
        imagesBucket = aws_s3.Bucket(
            self,
            "imagesBucket",
            bucket_name="media.leighrescuecentre.co.uk",
            public_read_access=True,
            access_control=aws_s3.BucketAccessControl.PUBLIC_READ,
            cors=[{
                "allowedMethods": [
                    aws_s3.HttpMethods.GET, aws_s3.HttpMethods.PUT,
                    aws_s3.HttpMethods.HEAD, aws_s3.HttpMethods.POST,
                    aws_s3.HttpMethods.DELETE
                ],
                "allowedOrigins": ["*"],
                "allowedHeader": ["*"],
                "exposeHeader": ["ETag"]
            }])

        # ******* CloudFront distribution
        distribution = aws_cloudfront.CloudFrontWebDistribution(
            self,
            "websiteBucketDistribution",
            origin_configs=[
                aws_cloudfront.SourceConfiguration(
                    s3_origin_source=aws_cloudfront.S3OriginConfig(
                        s3_bucket_source=websiteBucket),
                    behaviors=[
                        aws_cloudfront.Behavior(is_default_behavior=True)
                    ]),
                aws_cloudfront.SourceConfiguration(
                    s3_origin_source=aws_cloudfront.S3OriginConfig(
                        s3_bucket_source=imagesBucket),
                    behaviors=[
                        aws_cloudfront.Behavior(path_pattern="/media/*")
                    ])
            ])

        # ******* Code to automatically deploy the frontend code to the website bucket
        deployment = aws_s3_deployment.BucketDeployment(
            self,
            "deployStaticWebsite",
            sources=[aws_s3_deployment.Source.asset("../frontend")],
            destination_bucket=websiteBucket,
            distribution=distribution)
Exemplo n.º 11
0
    def __init__(
        self,
        scope: core.Construct,
        id: str,
        create_dependency_layer: Callable[[], None],
        **kwargs
    ) -> None:
        super().__init__(scope, id, **kwargs)

        # create dependency layer zip for lambda function
        create_dependency_layer()

        api_secret = secretsmanager.Secret(
            self,
            "ActualApiSecret",
            description="Secrets required to communicate with Aarogya Setu OpenAPI",
        )

        # create cognito user pool for authentication
        user_pool = cognito.UserPool(
            self,
            "AppUserPool",
            self_sign_up_enabled=True,
            account_recovery=cognito.AccountRecovery.PHONE_AND_EMAIL,
            user_verification=cognito.VerificationEmailStyle.CODE,
            auto_verify={"email": True},
            standard_attributes={"email": {"required": True, "mutable": True}},
        )

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

        # Create storage and queue
        bulk_request_queue = sqs.Queue(
            self,
            "BulkRequestQueue",
        )

        user_status_table = ddb.Table(
            self,
            "UserStatusTable",
            partition_key={"name": "mobile_number", "type": ddb.AttributeType.STRING},
            time_to_live_attribute="expdate",
        )
        self._user_status_table = user_status_table

        requests_table = ddb.Table(
            self,
            "RequestsTable",
            partition_key={"name": "mobile_number", "type": ddb.AttributeType.STRING},
            time_to_live_attribute="expdate",
        )

        # Create layer for lambda run time dependencies
        dependency_layer = _lambda.LayerVersion(
            self,
            "PythonDependencies",
            code=_lambda.Code.from_asset(path.join("lambda", "dependency-layer.zip")),
            compatible_runtimes=[_lambda.Runtime.PYTHON_3_7],
            description="The layer contains requests and pyjwt dependencies",
        )

        # Create Lambda functions
        single_request = _lambda.Function(
            self,
            "SingleRequesetHandler",
            runtime=_lambda.Runtime.PYTHON_3_7,
            code=_lambda.Code.asset("lambda"),
            handler="single_request.handler",
            timeout=core.Duration.seconds(10),
            layers=[dependency_layer],
            environment={
                "USER_STATUS_TABLE": user_status_table.table_name,
                "REQUESTS_TABLE": requests_table.table_name,
                "API_SECRET_ARN": api_secret.secret_full_arn,
            },
        )

        # give lambda access permissions to ddb tables and secrets
        user_status_table.grant_read_write_data(single_request)
        requests_table.grant_read_write_data(single_request)
        api_secret.grant_read(single_request)

        bulk_request = _lambda.Function(
            self,
            "BulkRequestHandler",
            runtime=_lambda.Runtime.PYTHON_3_7,
            code=_lambda.Code.asset("lambda"),
            handler="bulk_request.handler",
            timeout=core.Duration.seconds(30),
            environment={
                "QUEUE_URL": bulk_request_queue.queue_url,
            },
        )

        # give lambda access to write to queue
        bulk_request_queue.grant_send_messages(bulk_request)

        queue_receiver = _lambda.Function(
            self,
            "QueueReceiverHandler",
            runtime=_lambda.Runtime.PYTHON_3_7,
            code=_lambda.Code.asset("lambda"),
            handler="queue_receiver.handler",
            timeout=core.Duration.seconds(10),
            layers=[dependency_layer],
            environment={
                "USER_STATUS_TABLE": user_status_table.table_name,
                "REQUESTS_TABLE": requests_table.table_name,
                "QUEUE_URL": bulk_request_queue.queue_url,
                "API_SECRET_ARN": api_secret.secret_full_arn,
            },
        )

        # lambda gets triggered by sqs queue and writes to both tables
        queue_receiver.add_event_source(
            events.SqsEventSource(bulk_request_queue, batch_size=1)
        )

        # give queue receiver access to tables, queue and secrets
        bulk_request_queue.grant_consume_messages(queue_receiver)
        user_status_table.grant_read_write_data(queue_receiver)
        requests_table.grant_read_write_data(queue_receiver)

        api_secret.grant_read(queue_receiver)

        scan_table = _lambda.Function(
            self,
            "ScanTableHandler",
            runtime=_lambda.Runtime.PYTHON_3_7,
            code=_lambda.Code.asset("lambda"),
            handler="scan_table.handler",
            timeout=core.Duration.seconds(30),
            environment={
                "USER_STATUS_TABLE": user_status_table.table_name,
            },
        )

        user_status_table.grant_read_data(scan_table)

        # create api endpoints with authorization
        api = apigw.RestApi(
            self,
            "ASetuApiGateway",
            default_cors_preflight_options=apigw.CorsOptions(
                allow_origins=apigw.Cors.ALL_ORIGINS
            ),
        )

        auth = apigw.CfnAuthorizer(
            self,
            "ApiCognitoAuthorizer",
            name="CognitoAuthorizer",
            type="COGNITO_USER_POOLS",
            authorizer_result_ttl_in_seconds=300,
            identity_source="method.request.header.Authorization",
            rest_api_id=api.rest_api_id,
            provider_arns=[user_pool.user_pool_arn],
        )

        single_request_integration = apigw.LambdaIntegration(single_request, proxy=True)
        single_request_resource = api.root.add_resource("status")
        single_method = single_request_resource.add_method(
            "POST",
            single_request_integration,
            api_key_required=False,
            authorizer=auth,
            authorization_type=apigw.AuthorizationType.COGNITO,
        )

        bulk_request_integration = apigw.LambdaIntegration(bulk_request, proxy=True)
        bulk_request_resource = api.root.add_resource("bulk_status")
        bulk_method = bulk_request_resource.add_method(
            "POST",
            bulk_request_integration,
            api_key_required=False,
            authorizer=auth,
            authorization_type=apigw.AuthorizationType.COGNITO,
        )

        scan_table_integration = apigw.LambdaIntegration(scan_table, proxy=True)
        scan_table_resource = api.root.add_resource("scan")
        scan_method = scan_table_resource.add_method(
            "GET",
            scan_table_integration,
            api_key_required=False,
            authorizer=auth,
            authorization_type=apigw.AuthorizationType.COGNITO,
        )

        # Override authorizer to use COGNITO to authorize apis
        # Solution from: https://github.com/aws/aws-cdk/issues/9023#issuecomment-658309644
        methods = [single_method, bulk_method, scan_method]
        for method in methods:
            method.node.find_child("Resource").add_property_override(
                "AuthorizationType", "COGNITO_USER_POOLS"
            )
            method.node.find_child("Resource").add_property_override(
                "AuthorizerId", {"Ref": auth.logical_id}
            )

        # Export output values for frontend application
        core.CfnOutput(
            self,
            "user-pool-id",
            value=user_pool.user_pool_id,
            export_name="USER-POOL-ID",
        )
        core.CfnOutput(
            self,
            "user-pool-web-client",
            value=user_pool_client.user_pool_client_id,
            export_name="WEB-CLIENT-ID",
        )
        core.CfnOutput(
            self, "api-endpoint-url", value=api.url, export_name="API-ENDPOINT-URL"
        )
        core.CfnOutput(
            self,
            "deployment-region",
            value=self.region,
            export_name="REGION",
        )
        core.CfnOutput(
            self, "stack-name", value=self.stack_name, export_name="STACK-NAME"
        )
        core.CfnOutput(
            self,
            "api-secret-arn",
            value=api_secret.secret_full_arn,
            export_name="API-SECRET-ARN",
        )
Exemplo n.º 12
0
    def __init__(self, scope: core.Construct, construct_id: str,
                 **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # Create repo for Amplify static site
        amplify_repo = codecommit.Repository(
            self,
            'amplify-wild-rydes-repo',
            repository_name='amplify-wild-rydes',
            description='Repo for the Wild Rydes static site for Amplify')

        # Create repo for holding the code for this project
        app_repo = codecommit.Repository(
            self,
            'app-serverless-workshop-repo',
            repository_name='app-wild-rydes-serverless-workshop',
            description=
            'Repo for project from webapp.serverlessworkshops.io/staticwebhosting/overview/'
        )

        # IAM Role & Policy for Amplify
        amplify_role = iam.Role(
            self,
            'amplify-wild-rydes-role',
            role_name='amplify-wild-rydes-role',
            assumed_by=iam.ServicePrincipal('amplify.amazonaws.com'))

        # Amplify
        amplify_static_site = amplify.App(
            self,
            'amplify-wild-rydes-site',
            source_code_provider=amplify.CodeCommitSourceCodeProvider(
                repository=amplify_repo),
            description='Wild Rydes Amplify Static Site',
            role=amplify_role,
            app_name='wild-rydes-site')

        master = amplify_static_site.add_branch("master")

        # Policy is fairly open
        # Ran into issues when I deployed the cognito user pools through the amplify cli
        # It creates a new CloudFormation stack and deploys several resources
        amplify_policy = iam.Policy(
            self,
            'amplify-wild-rydes-policy',
            roles=[amplify_role],
            statements=[
                iam.PolicyStatement(effect=iam.Effect.ALLOW,
                                    actions=['codecommit:GitPull'],
                                    resources=[amplify_repo.repository_arn]),
                iam.PolicyStatement(effect=iam.Effect.ALLOW,
                                    actions=[
                                        'amplify:GetApp',
                                        'amplify:CreateBackendEnvironment',
                                        'cloudformation:*', 'cognito:*',
                                        'lambda:*', 's3:*', 'iam:*'
                                    ],
                                    resources=['*'])
            ])

        # DynamoDB
        # removal_policy=core.RemovalPolicy.DESTROY is to ensure it is deleted since this is only a lab
        # table_name is required to be Rides, its configured in the nodejs code that the lambda function runs
        rides_table = ddb.Table(self,
                                'Table',
                                table_name='Rides',
                                partition_key=ddb.Attribute(
                                    name='RideId',
                                    type=ddb.AttributeType.STRING),
                                removal_policy=core.RemovalPolicy.DESTROY)

        # Lambda Functions
        request_unicorn_role = iam.Role(
            self,
            'RequestUnicornRole',
            role_name='wild-rydes-lambda-role',
            assumed_by=iam.ServicePrincipal('lambda.amazonaws.com'),
            managed_policies=[
                iam.ManagedPolicy.from_aws_managed_policy_name(
                    'service-role/AWSLambdaBasicExecutionRole')
            ])

        # Grant write access to the lambda role
        rides_table.grant_write_data(request_unicorn_role)

        request_unicorn = _lambda.Function(
            self,
            'request-unicorn',
            handler='requestUnicorn.handler',
            runtime=_lambda.Runtime.NODEJS_12_X,
            code=_lambda.AssetCode('request_unicorn'),
            role=request_unicorn_role,
            function_name='request-unicorn-wild-rydes')

        # Rest API
        ride_api_gw = apigw.RestApi(
            self,
            'wild-rydes-apigw',
            rest_api_name='WildRydes',
            endpoint_types=[apigw.EndpointType.REGIONAL])

        # APIGW Lambda Integration
        # proxy enabled for the workshop
        ride_api_gw_lambda_integration = apigw.LambdaIntegration(
            request_unicorn,
            proxy=True,
            integration_responses=[{
                'statusCode': '200',
                'responseParameters': {
                    'method.response.header.Access-Control-Allow-Origin':
                    "'*'",
                }
            }])

        post_ride_resource = ride_api_gw.root.add_resource('ride')
        post_ride_resource_method = post_ride_resource.add_method(
            'POST',
            ride_api_gw_lambda_integration,
            method_responses=[{
                'statusCode': '200',
                'responseParameters': {
                    'method.response.header.Access-Control-Allow-Origin': True,
                }
            }])

        # This needs to be created after the Amplify site unless you create the cognito userpool in the cdk
        # I went through the Amplify CLI to create the backend
        ride_api_gw_authorizer = apigw.CfnAuthorizer(
            self,
            'wild-rydes-apigw-authorizer',
            rest_api_id=ride_api_gw.rest_api_id,
            name='wild-rydes-apigw-authorizer',
            type='COGNITO_USER_POOLS',
            identity_source='method.request.header.name.Authorization',
            identity_validation_expression="Bearer (.*)",
            provider_arns=[
                'arn:aws:cognito-idp:us-east-1:<ACCOUNT_ID>:userpool/<USER_POOL_ID>'
            ])

        # https://github.com/aws/aws-cdk/issues/5618
        post_ride_resource_fix = post_ride_resource_method.node.find_child(
            'Resource')
        post_ride_resource_fix.add_property_override('AuthorizationType',
                                                     'COGNITO_USER_POOLS')
        post_ride_resource_fix.add_property_override(
            'AuthorizerId', {"Ref": ride_api_gw_authorizer.logical_id})

        # Enable CORS for the workshop
        post_ride_resource.add_method(
            'OPTIONS',
            apigw.MockIntegration(integration_responses=[{
                'statusCode': '200',
                'responseParameters': {
                    'method.response.header.Access-Control-Allow-Headers':
                    "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
                    'method.response.header.Access-Control-Allow-Origin':
                    "'*'",
                    'method.response.header.Access-Control-Allow-Methods':
                    "'POST,OPTIONS'"
                }
            }],
                                  passthrough_behavior=apigw.
                                  PassthroughBehavior.WHEN_NO_MATCH,
                                  request_templates={
                                      "application/json":
                                      "{\"statusCode\":200}"
                                  }),
            method_responses=[{
                'statusCode': '200',
                'responseParameters': {
                    'method.response.header.Access-Control-Allow-Headers':
                    True,
                    'method.response.header.Access-Control-Allow-Methods':
                    True,
                    'method.response.header.Access-Control-Allow-Origin': True,
                }
            }])

        # Outputs
        amplify_repo_url = core.CfnOutput(
            self,
            'amplify-repo-url',
            value=amplify_repo.repository_clone_url_http)

        app_repo_url = core.CfnOutput(self,
                                      'app-repo-url',
                                      value=app_repo.repository_clone_url_http)

        amplify_default_domain = core.CfnOutput(
            self,
            'amplify-default-domain',
            value=amplify_static_site.default_domain)

        request_unicorn_apigw = core.CfnOutput(self,
                                               'request-unicorn-apigw',
                                               value=request_unicorn_apigw.url)
Exemplo n.º 13
0
    def __init__(self, scope: core.App, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # create Cognito user pool
        user_pool = cognito.UserPool(
            self,
            "testUserPool",
            auto_verified_attributes=[cognito.UserPoolAttribute.EMAIL],
            sign_in_type=cognito.SignInType.EMAIL)
        cfn_user_pool = user_pool.node.default_child
        cfn_user_pool.policies = {
            "passwordPolicy": {
                "minimumLength": 8,
                "requireLowercase": True,
                "requireNumbers": True,
                "requireUppercase": True,
                "requireSymbols": False
            }
        }

        # attach a domain
        # random.seed(20191113)
        # random_domain = ''.join(random.choice(string.ascii_lowercase) for i in range(20))
        # cfn_user_pool_domain = cognito.CfnUserPoolDomain(
        #     self, "testUserPoolDomain",
        #     domain=random_domain,
        #     user_pool_id=user_pool.user_pool_id
        # )

        # create pool client
        pool_client = cognito.CfnUserPoolClient(
            self,
            'testUserPoolClient',
            user_pool_id=user_pool.user_pool_id,
            supported_identity_providers=["COGNITO"],
            generate_secret=False,
            refresh_token_validity=1,
            explicit_auth_flows=["USER_PASSWORD_AUTH"],
            allowed_o_auth_flows_user_pool_client=True,
            allowed_o_auth_flows=["implicit"],
            allowed_o_auth_scopes=[
                "email", "openid", "aws.cognito.signin.user.admin"
            ],
            callback_ur_ls=["http://localhost"],
            logout_ur_ls=["http://localhost"])
        # output some stuff
        core.CfnOutput(self, "User Pool ID", value=user_pool.user_pool_id)
        core.CfnOutput(self, "Pool Client ID", value=pool_client.ref)
        # core.CfnOutput(self, "User pool domain", value=cfn_user_pool_domain.domain)

        # create REST API resource
        api = apigw.RestApi(self, 'my_API')

        # new resource - /test
        test_resource = api.root.add_resource('test')

        # Cognito authorizer
        cfn_authorizer = apigw.CfnAuthorizer(
            self,
            "my_cognito",
            name='API_authorizer',
            type='COGNITO_USER_POOLS',
            identity_source='method.request.header.Authorization',
            rest_api_id=api.rest_api_id,
            provider_arns=[user_pool.user_pool_arn])

        # lambda handler
        hello_world_handler = _lambda.Function(
            self,
            'my_handler',
            code=_lambda.AssetCode('lambda'),
            handler='index.handler',
            runtime=_lambda.Runtime.PYTHON_3_7)

        # attach GET method
        hello_world_integration = apigw.LambdaIntegration(hello_world_handler)
        meth = test_resource.add_method(
            "GET",
            hello_world_integration,
            authorization_type=apigw.AuthorizationType.COGNITO)
        meth.node.find_child('Resource').add_property_override(
            'AuthorizerId', cfn_authorizer.ref)
Exemplo n.º 14
0
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # Create a VPC for Aurora (required for serverless Aurora)
        aurora_vpc = ec2.Vpc(self, 'AuroraVpc')

        # Create serverless MySQL Aurora cluster
        aurora_cluster = rds.ServerlessCluster(
            self,
            'AuroraCluster',
            vpc=aurora_vpc,
            engine=rds.DatabaseClusterEngine.AURORA_MYSQL,
            default_database_name='core')

        # Handler for the hello endpoint
        hello_lambda = _lambda.Function(
            self,
            'HelloHandler',
            runtime=_lambda.Runtime.PYTHON_3_8,
            code=_lambda.Code.asset('lambda'),
            handler='hello.handler',
        )

        # Handler for the GET /user_accounts endpoint
        get_user_account_lambda = _lambda.Function(
            self,
            'GetUserAccount',
            runtime=_lambda.Runtime.PYTHON_3_8,
            code=_lambda.Code.asset('lambda'),
            handler='get_user_account.handler',
            environment={
                'CLUSTER_ARN': aurora_cluster.cluster_arn,
                'SECRET_ARN': aurora_cluster.secret.secret_arn,
                'DB_NAME': 'core',
            })

        # Grant the lambda function the required permissions to access the database
        aurora_cluster.grant_data_api_access(
            get_user_account_lambda)  # This also enables the data api

        # Cognito User Pool for sign up and authorization
        user_pool = cognito.UserPool(
            self,
            'user-pool',
            self_sign_up_enabled=True,
            auto_verify=cognito.AutoVerifiedAttrs(email=True, phone=False),
            sign_in_aliases=cognito.SignInAliases(email=True, username=True))

        # User pool client - used for sign up and sign in and password recovery
        user_pool.add_client('app-client',
                             auth_flows=cognito.AuthFlow(user_password=True,
                                                         user_srp=True))

        # Create the API Gateway REST API
        api = apigw.RestApi(self, 'test-api')

        # Create custom authorizer, modeled after work around detailed here:
        # https://github.com/aws/aws-cdk/issues/723#issuecomment-504753280
        cognito_authorizer = apigw.CfnAuthorizer(
            self,
            'CognitoAuthorizer',
            type='COGNITO_USER_POOLS',
            provider_arns=[user_pool.user_pool_arn],
            identity_source='method.request.header.Authorization',
            rest_api_id=api.rest_api_id,
            name='CognitoAuthorizer')

        # Hello resource and endpoint, handled by the hello lambda
        hello_resource = api.root.add_resource('hello')
        hello_method = hello_resource.add_method(
            'GET', integration=apigw.LambdaIntegration(hello_lambda))
        hello_method_resource = hello_method.node.find_child('Resource')
        hello_method_resource.add_property_override(
            'AuthorizationType', apigw.AuthorizationType.COGNITO)
        hello_method_resource.add_property_override(
            'AuthorizerId', {"Ref": cognito_authorizer.logical_id})

        # Add user_account resource
        user_account_resource = api.root.add_resource('user_account')

        # Add GET handler for user_account
        get_user_account_method = user_account_resource.add_method(
            'GET',
            integration=apigw.LambdaIntegration(get_user_account_lambda))
        get_user_account_method_resource = get_user_account_method.node.find_child(
            'Resource')
        get_user_account_method_resource.add_property_override(
            'AuthorizationType', apigw.AuthorizationType.COGNITO)
        get_user_account_method_resource.add_property_override(
            'AuthorizerId', {"Ref": cognito_authorizer.logical_id})