Esempio n. 1
0
    def __init__(self, scope: Construct, id: str, envs: EnvSettings):
        super().__init__(scope, id)

        self.key = Key(self, id="Key", alias=f"alias/{envs.project_name}")

        self.key.add_to_resource_policy(
            PolicyStatement(actions=["kms:Encrypt", "kms:Decrypt"],
                            principals=[AccountRootPrincipal()],
                            resources=["*"]))

        CfnOutput(
            self,
            "KmsKeyArnOutput",
            export_name=self.get_kms_arn_output_export_name(envs),
            value=self.key.key_arn,
        )
Esempio n. 2
0
 def _create_ecr_repos(self) -> List[ecr.Repository]:
     current_images_names = extract_images_names(env_name=self.context.name)
     current_images_names = list(set(current_images_names) - set(self.remove_images))
     repos = [self.create_repo(image_name=r) for r in current_images_names]
     for image_name in self.add_images:
         if image_name not in current_images_names:
             repos.append(self.create_repo(image_name=image_name))
             current_images_names.append(image_name)
     if current_images_names:
         current_images_names.sort()
         CfnOutput(
             scope=self,
             id="repos",
             export_name=f"orbit-{self.context.name}-repos",
             value=",".join([x for x in current_images_names]),
         )
     return repos
Esempio n. 3
0
    def __init__(
        self, scope: Construct, id: str, shared_airflow_env: dict, **kwargs
    ) -> None:
        super().__init__(scope, id, **kwargs)
        task_definition = FargateTaskDefinition(
            self,
            "initdb",
            cpu=512,
            memory_limit_mib=1024,
        )
        task_definition.add_container(
            "initdb",
            image=ContainerImage.from_registry("apache/airflow:1.10.12-python3.8"),
            command=["initdb"],
            logging=LogDriver.aws_logs(stream_prefix="initdb"),
            environment=shared_airflow_env,
        )

        CfnOutput(self, "init-task-def", value=task_definition.task_definition_arn)
    def __init__(self, scope: Construct, construct_id: str, stage: str,
                 **kwargs) -> None:
        super().__init__(scope, construct_id)

        _access_logs_bucket = self.get_access_logs_bucket(stage)
        _staticsite_bucket = self.create_bucket(_access_logs_bucket, stage)
        _cfront_oai = self.create_origin_access_identity()
        _policy_statement = self.create_s3_cfront_policy(
            _staticsite_bucket, _cfront_oai)
        _staticsite_bucket.add_to_resource_policy(_policy_statement)

        _cfront_dist = _cfront.CloudFrontWebDistribution(
            self,
            "staticsitedistribution",
            http_version=_cfront.HttpVersion.HTTP2,
            origin_configs=[
                _cfront.SourceConfiguration(
                    behaviors=[
                        _cfront.Behavior(
                            allowed_methods=_cfront.CloudFrontAllowedMethods.
                            GET_HEAD_OPTIONS,
                            is_default_behavior=True)
                    ],
                    s3_origin_source=_cfront.S3OriginConfig(
                        s3_bucket_source=_staticsite_bucket),
                )
            ],
            default_root_object="index.html",
            viewer_protocol_policy=_cfront.ViewerProtocolPolicy.
            REDIRECT_TO_HTTPS,
            price_class=_cfront.PriceClass.PRICE_CLASS_ALL,
            enable_ip_v6=False,
        )

        self.bucket = _staticsite_bucket
        self.access_logs_bucket = _access_logs_bucket
        self.cfront_dist = _cfront_dist

        self.sourcebucketname = CfnOutput(self,
                                          "sourceBucketName",
                                          value=_staticsite_bucket.bucket_name)
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        functions_hello = lambda_function.Function(
            self,
            "IntroCdkpipelinesStack_HelloHandler",
            function_name="IntroCdkpipelinesStack_HelloHandler",
            runtime=lambda_function.Runtime.PYTHON_3_7,
            code=lambda_function.Code.asset("lambda"),
            handler="hello.handler")

        api_gw_hello = apigw.LambdaRestApi(
            self,
            "IntroCdkpipelinesStack_HelloApi",
            rest_api_name="IntroCdkpipelinesStack_HelloApi",
            description="Endpoint for a simple Lambda-powered web service",
            handler=functions_hello)

        self.output_url = CfnOutput(self,
                                    "IntroCdkpipelinesStack_HelloApi__url",
                                    value=api_gw_hello.url)
Esempio n. 6
0
    def __init__(
        self, scope: Construct, id: str, vpc: Vpc, rds: Rds, batch, **kwargs
    ) -> None:
        super().__init__(scope, id, **kwargs)

        cluster = _Cluster(self, "cluster", vpc=vpc.vpc)

        cluster.add_capacity(
            "ag",
            instance_type=InstanceType.of(InstanceClass.STANDARD5, InstanceSize.LARGE),
        )

        shared_airflow_env = {
            "AIRFLOW__CORE__SQL_ALCHEMY_CONN": rds.uri,
            "AIRFLOW__CORE__LOAD_EXAMPLES": "True",
            "AIRFLOW__CORE__EXECUTOR": "LocalExecutor",
        }

        init_db = InitTask(self, "init_db", shared_airflow_env=shared_airflow_env)

        scheduler_service = SchedulerService(
            self,
            "scheduler_service",
            cluster=cluster,
            shared_airflow_env=shared_airflow_env,
        )

        webserver_service = WebserverService(
            self,
            "webserver_service",
            cluster=cluster,
            shared_airflow_env=shared_airflow_env,
            vpc=vpc.vpc,
        )

        CfnOutput(self, "cluster-name", value=cluster.cluster_name)
Esempio n. 7
0
    def __init__(self, scope: Construct, id: str, vpc: Vpc, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        self.rds = DatabaseInstance(
            self,
            "rds",
            master_username=USER,
            master_user_password=SecretValue(PASSWORD),
            database_name=DATABASE,
            vpc=vpc.vpc,
            engine=DatabaseInstanceEngine.POSTGRES,
            port=PORT,
            instance_type=InstanceType.of(InstanceClass.BURSTABLE3,
                                          InstanceSize.MICRO),
            security_groups=[vpc.sg_rds],
            deletion_protection=False,
        )

        CfnOutput(
            self,
            "rds_address",
            value=
            f"postgres://{USER}:{PASSWORD}@{self.rds.db_instance_endpoint_address}:{PORT}/{DATABASE}",
        )
    def __init__(self, scope: core.Construct, id: str, application_prefix: str,
                 suffix: str, kda_role: Role, **kwargs):
        super().__init__(scope, id, **kwargs)

        stack = Stack.of(self)
        region = stack.region

        # Create Cognito User Pool
        self.__user_pool = CfnUserPool(
            scope=self,
            id='UserPool',
            admin_create_user_config={'allowAdminCreateUserOnly': True},
            policies={'passwordPolicy': {
                'minimumLength': 8
            }},
            username_attributes=['email'],
            auto_verified_attributes=['email'],
            user_pool_name=application_prefix + '_user_pool')

        # Create a Cognito User Pool Domain using the newly created Cognito User Pool
        CfnUserPoolDomain(scope=self,
                          id='CognitoDomain',
                          domain=application_prefix + '-' + suffix,
                          user_pool_id=self.user_pool.ref)

        # Create Cognito Identity Pool
        self.__id_pool = CfnIdentityPool(
            scope=self,
            id='IdentityPool',
            allow_unauthenticated_identities=False,
            cognito_identity_providers=[],
            identity_pool_name=application_prefix + '_identity_pool')

        trust_relationship = FederatedPrincipal(
            federated='cognito-identity.amazonaws.com',
            conditions={
                'StringEquals': {
                    'cognito-identity.amazonaws.com:aud': self.id_pool.ref
                },
                'ForAnyValue:StringLike': {
                    'cognito-identity.amazonaws.com:amr': 'authenticated'
                }
            },
            assume_role_action='sts:AssumeRoleWithWebIdentity')
        # IAM role for master user
        master_auth_role = Role(scope=self,
                                id='MasterAuthRole',
                                assumed_by=trust_relationship)
        # Role for authenticated user
        limited_auth_role = Role(scope=self,
                                 id='LimitedAuthRole',
                                 assumed_by=trust_relationship)
        # Attach Role to Identity Pool
        CfnIdentityPoolRoleAttachment(
            scope=self,
            id='userPoolRoleAttachment',
            identity_pool_id=self.id_pool.ref,
            roles={'authenticated': limited_auth_role.role_arn})
        # Create master-user-group
        CfnUserPoolGroup(scope=self,
                         id='AdminsGroup',
                         user_pool_id=self.user_pool.ref,
                         group_name='master-user-group',
                         role_arn=master_auth_role.role_arn)
        # Create limited-user-group
        CfnUserPoolGroup(scope=self,
                         id='UsersGroup',
                         user_pool_id=self.user_pool.ref,
                         group_name='limited-user-group',
                         role_arn=limited_auth_role.role_arn)
        # Role for the Elasticsearch service to access Cognito
        es_role = Role(scope=self,
                       id='EsRole',
                       assumed_by=ServicePrincipal(service='es.amazonaws.com'),
                       managed_policies=[
                           ManagedPolicy.from_aws_managed_policy_name(
                               'AmazonESCognitoAccess')
                       ])

        # Use the following command line to generate the python dependencies layer content
        # pip3 install -t lambda-layer/python/lib/python3.8/site-packages -r lambda/requirements.txt
        # Build the lambda layer assets
        subprocess.call([
            'pip', 'install', '-t',
            'streaming/streaming_cdk/lambda-layer/python/lib/python3.8/site-packages',
            '-r', 'streaming/streaming_cdk/bootstrap-lambda/requirements.txt',
            '--upgrade'
        ])

        requirements_layer = _lambda.LayerVersion(
            scope=self,
            id='PythonRequirementsTemplate',
            code=_lambda.Code.from_asset(
                'streaming/streaming_cdk/lambda-layer'),
            compatible_runtimes=[_lambda.Runtime.PYTHON_3_8])

        # This lambda function will bootstrap the Elasticsearch cluster
        bootstrap_function_name = 'AESBootstrap'
        register_template_lambda = _lambda.Function(
            scope=self,
            id='RegisterTemplate',
            runtime=_lambda.Runtime.PYTHON_3_8,
            code=_lambda.Code.from_asset(
                'streaming/streaming_cdk/bootstrap-lambda'),
            handler='es-bootstrap.lambda_handler',
            environment={
                'REGION': region,
                'KDA_ROLE_ARN': kda_role.role_arn,
                'MASTER_ROLE_ARN': master_auth_role.role_arn
            },
            layers=[requirements_layer],
            timeout=Duration.minutes(15),
            function_name=bootstrap_function_name)

        lambda_role = register_template_lambda.role
        lambda_role.add_to_policy(
            PolicyStatement(
                actions=['logs:CreateLogGroup'],
                resources=[stack.format_arn(service='logs', resource='*')]))
        lambda_role.add_to_policy(
            PolicyStatement(
                actions=['logs:CreateLogStream', 'logs:PutLogEvents'],
                resources=[
                    stack.format_arn(service='logs',
                                     resource='log_group',
                                     resource_name='/aws/lambda/' +
                                     bootstrap_function_name + ':*')
                ]))

        # Let the lambda assume the master role so that actions can be executed on the cluster
        # https://aws.amazon.com/premiumsupport/knowledge-center/lambda-function-assume-iam-role/
        lambda_role.add_to_policy(
            PolicyStatement(actions=['sts:AssumeRole'],
                            resources=[master_auth_role.role_arn]))

        master_auth_role.assume_role_policy.add_statements(
            PolicyStatement(actions=['sts:AssumeRole'],
                            principals=[lambda_role]))

        # List all the roles that are allowed to access the Elasticsearch cluster.
        roles = [
            ArnPrincipal(limited_auth_role.role_arn),
            ArnPrincipal(master_auth_role.role_arn),
            ArnPrincipal(kda_role.role_arn)
        ]  # The users
        if register_template_lambda and register_template_lambda.role:
            roles.append(ArnPrincipal(
                lambda_role.role_arn))  # The lambda used to bootstrap
        # Create kms key
        kms_key = Key(scope=self,
                      id='kms-es',
                      alias='custom/es',
                      description='KMS key for Elasticsearch domain',
                      enable_key_rotation=True)

        # AES Log Groups
        es_app_log_group = logs.LogGroup(scope=self,
                                         id='EsAppLogGroup',
                                         retention=logs.RetentionDays.ONE_WEEK,
                                         removal_policy=RemovalPolicy.RETAIN)

        # Create the Elasticsearch domain
        es_domain_arn = stack.format_arn(service='es',
                                         resource='domain',
                                         resource_name=application_prefix +
                                         '/*')

        es_access_policy = PolicyDocument(statements=[
            PolicyStatement(principals=roles,
                            actions=[
                                'es:ESHttpGet', 'es:ESHttpPut',
                                'es:ESHttpPost', 'es:ESHttpDelete'
                            ],
                            resources=[es_domain_arn])
        ])
        self.__es_domain = es.CfnDomain(
            scope=self,
            id='searchDomain',
            elasticsearch_cluster_config={
                'instanceType': 'r5.large.elasticsearch',
                'instanceCount': 2,
                'dedicatedMasterEnabled': True,
                'dedicatedMasterCount': 3,
                'dedicatedMasterType': 'r5.large.elasticsearch',
                'zoneAwarenessEnabled': True,
                'zoneAwarenessConfig': {
                    'AvailabilityZoneCount': '2'
                },
            },
            encryption_at_rest_options={
                'enabled': True,
                'kmsKeyId': kms_key.key_id
            },
            node_to_node_encryption_options={'enabled': True},
            ebs_options={
                'volumeSize': 10,
                'ebsEnabled': True
            },
            elasticsearch_version='7.9',
            domain_name=application_prefix,
            access_policies=es_access_policy,
            cognito_options={
                'enabled': True,
                'identityPoolId': self.id_pool.ref,
                'roleArn': es_role.role_arn,
                'userPoolId': self.user_pool.ref
            },
            advanced_security_options={
                'enabled': True,
                'internalUserDatabaseEnabled': False,
                'masterUserOptions': {
                    'masterUserArn': master_auth_role.role_arn
                }
            },
            domain_endpoint_options={
                'enforceHttps': True,
                'tlsSecurityPolicy': 'Policy-Min-TLS-1-2-2019-07'
            },
            # log_publishing_options={
            #     # 'ES_APPLICATION_LOGS': {
            #     #     'enabled': True,
            #     #     'cloud_watch_logs_log_group_arn': es_app_log_group.log_group_arn
            #     # },
            #     # 'AUDIT_LOGS': {
            #     #     'enabled': True,
            #     #     'cloud_watch_logs_log_group_arn': ''
            #     # },
            #     # 'SEARCH_SLOW_LOGS': {
            #     #     'enabled': True,
            #     #     'cloud_watch_logs_log_group_arn': ''
            #     # },
            #     # 'INDEX_SLOW_LOGS': {
            #     #     'enabled': True,
            #     #     'cloud_watch_logs_log_group_arn': ''
            #     # }
            # }
        )

        # Not yet on the roadmap...
        # See https://github.com/aws-cloudformation/aws-cloudformation-coverage-roadmap/issues/283
        # self.es_domain.add_property_override('ElasticsearchClusterConfig.WarmEnabled', True)
        # self.es_domain.add_property_override('ElasticsearchClusterConfig.WarmCount', 2)
        # self.es_domain.add_property_override('ElasticsearchClusterConfig.WarmType', 'ultrawarm1.large.elasticsearch')

        # Deny all roles from the authentication provider - users must be added to groups
        # This lambda function will bootstrap the Elasticsearch cluster
        cognito_function_name = 'CognitoFix'
        cognito_template_lambda = _lambda.Function(
            scope=self,
            id='CognitoFixLambda',
            runtime=_lambda.Runtime.PYTHON_3_8,
            code=_lambda.Code.from_asset(
                'streaming/streaming_cdk/cognito-lambda'),
            handler='handler.handler',
            environment={
                'REGION': scope.region,
                'USER_POOL_ID': self.__user_pool.ref,
                'IDENTITY_POOL_ID': self.__id_pool.ref,
                'LIMITED_ROLE_ARN': limited_auth_role.role_arn
            },
            timeout=Duration.minutes(15),
            function_name=cognito_function_name)

        lambda_role = cognito_template_lambda.role
        lambda_role.add_to_policy(
            PolicyStatement(
                actions=['logs:CreateLogGroup'],
                resources=[stack.format_arn(service='logs', resource='*')]))
        lambda_role.add_to_policy(
            PolicyStatement(
                actions=['logs:CreateLogStream', 'logs:PutLogEvents'],
                resources=[
                    stack.format_arn(service='logs',
                                     resource='log_group',
                                     resource_name='/aws/lambda/' +
                                     cognito_function_name + ':*')
                ]))
        lambda_role.add_to_policy(
            PolicyStatement(actions=['cognito-idp:ListUserPoolClients'],
                            resources=[self.user_pool.attr_arn]))
        lambda_role.add_to_policy(
            PolicyStatement(actions=['iam:PassRole'],
                            resources=[limited_auth_role.role_arn]))

        cognito_id_res = Fn.join(':', [
            'arn:aws:cognito-identity', scope.region, scope.account,
            Fn.join('/', ['identitypool', self.__id_pool.ref])
        ])

        lambda_role.add_to_policy(
            PolicyStatement(actions=['cognito-identity:SetIdentityPoolRoles'],
                            resources=[cognito_id_res]))

        # Get the Domain Endpoint and register it with the lambda as environment variable.
        register_template_lambda.add_environment(
            'DOMAIN', self.__es_domain.attr_domain_endpoint)

        CfnOutput(scope=self,
                  id='createUserUrl',
                  description="Create a new user in the user pool here.",
                  value="https://" + scope.region +
                  ".console.aws.amazon.com/cognito/users?region=" +
                  scope.region + "#/pool/" + self.user_pool.ref + "/users")
        CfnOutput(scope=self,
                  id='kibanaUrl',
                  description="Access Kibana via this URL.",
                  value="https://" + self.__es_domain.attr_domain_endpoint +
                  "/_plugin/kibana/")

        bootstrap_lambda_provider = Provider(
            scope=self,
            id='BootstrapLambdaProvider',
            on_event_handler=register_template_lambda)
        CustomResource(scope=self,
                       id='ExecuteRegisterTemplate',
                       service_token=bootstrap_lambda_provider.service_token,
                       properties={'Timeout': 900})

        cognito_lambda_provider = Provider(
            scope=self,
            id='CognitoFixLambdaProvider',
            on_event_handler=cognito_template_lambda)
        cognito_fix_resource = CustomResource(
            scope=self,
            id='ExecuteCognitoFix',
            service_token=cognito_lambda_provider.service_token)
        cognito_fix_resource.node.add_dependency(self.__es_domain)
    def _add_private_hosted_zone(self):
        if self._condition_custom_cluster_dns():
            hosted_zone_id = self.config.scheduling.settings.dns.hosted_zone_id
            cluster_hosted_zone = CustomDns(
                ref=hosted_zone_id,
                name=self.cluster_dns_domain.value_as_string)
        else:
            cluster_hosted_zone = route53.CfnHostedZone(
                self.stack_scope,
                "Route53HostedZone",
                name=self.cluster_dns_domain.value_as_string,
                vpcs=[
                    route53.CfnHostedZone.VPCProperty(
                        vpc_id=self.config.vpc_id,
                        vpc_region=self._stack_region)
                ],
            )

        # If Headnode InstanceRole is created by ParallelCluster, add Route53 policy for InstanceRole
        head_node_role_info = self.instance_roles.get("HeadNode")
        if head_node_role_info:
            iam.CfnPolicy(
                self.stack_scope,
                "ParallelClusterSlurmRoute53Policies",
                policy_name="parallelcluster-slurm-route53",
                policy_document=iam.PolicyDocument(statements=[
                    iam.PolicyStatement(
                        sid="Route53Add",
                        effect=iam.Effect.ALLOW,
                        actions=["route53:ChangeResourceRecordSets"],
                        resources=[
                            self._format_arn(
                                service="route53",
                                region="",
                                account="",
                                resource=
                                f"hostedzone/{cluster_hosted_zone.ref}",
                            ),
                        ],
                    ),
                ]),
                roles=[head_node_role_info.get("RoleRef")],
            )

        cleanup_route53_lambda_execution_role = None
        if self.cleanup_lambda_role:
            cleanup_route53_lambda_execution_role = add_lambda_cfn_role(
                scope=self.stack_scope,
                function_id="CleanupRoute53",
                statements=[
                    iam.PolicyStatement(
                        actions=[
                            "route53:ListResourceRecordSets",
                            "route53:ChangeResourceRecordSets"
                        ],
                        effect=iam.Effect.ALLOW,
                        resources=[
                            self._format_arn(
                                service="route53",
                                region="",
                                account="",
                                resource=
                                f"hostedzone/{cluster_hosted_zone.ref}",
                            ),
                        ],
                        sid="Route53DeletePolicy",
                    ),
                    get_cloud_watch_logs_policy_statement(
                        resource=self._format_arn(service="logs",
                                                  account="*",
                                                  region="*",
                                                  resource="*")),
                ],
            )

        cleanup_route53_lambda = PclusterLambdaConstruct(
            scope=self.stack_scope,
            id="CleanupRoute53FunctionConstruct",
            function_id="CleanupRoute53",
            bucket=self.bucket,
            config=self.config,
            execution_role=cleanup_route53_lambda_execution_role.attr_arn
            if cleanup_route53_lambda_execution_role else
            self.config.iam.roles.custom_lambda_resources,
            handler_func="cleanup_resources",
        ).lambda_func

        self.cleanup_route53_custom_resource = CfnCustomResource(
            self.stack_scope,
            "CleanupRoute53CustomResource",
            service_token=cleanup_route53_lambda.attr_arn,
        )
        self.cleanup_route53_custom_resource.add_property_override(
            "ClusterHostedZone", cluster_hosted_zone.ref)
        self.cleanup_route53_custom_resource.add_property_override(
            "Action", "DELETE_DNS_RECORDS")
        self.cleanup_route53_custom_resource.add_property_override(
            "ClusterDNSDomain", cluster_hosted_zone.name)

        CfnOutput(
            self.stack_scope,
            "ClusterHostedZone",
            description=
            "Id of the private hosted zone created within the cluster",
            value=cluster_hosted_zone.ref,
        )

        return cluster_hosted_zone
    def __init__(self, scope: Construct, id: str, context: "FoundationContext",
                 **kwargs: Any) -> None:
        self.env_name = context.name
        self.context = context
        super().__init__(scope, id, **kwargs)
        Tags.of(scope=cast(core.IConstruct, self)).add(
            key="Env", value=f"orbit-{self.env_name}")
        self.vpc: ec2.Vpc = self._create_vpc()

        self.public_subnets = (self.vpc.select_subnets(
            subnet_type=ec2.SubnetType.PUBLIC) if self.vpc.public_subnets else
                               self.vpc.select_subnets(subnet_name=""))
        self.private_subnets = (self.vpc.select_subnets(
            subnet_type=ec2.SubnetType.PRIVATE) if self.vpc.private_subnets
                                else self.vpc.select_subnets(subnet_name=""))
        self.isolated_subnets = (self.vpc.select_subnets(
            subnet_type=ec2.SubnetType.ISOLATED) if self.vpc.isolated_subnets
                                 else self.vpc.select_subnets(subnet_name=""))
        self.nodes_subnets = (self.private_subnets
                              if context.networking.data.internet_accessible
                              else self.isolated_subnets)

        self._create_vpc_endpoints()

        if context.toolkit.s3_bucket is None:
            raise ValueError("context.toolkit_s3_bucket is not defined")
        toolkit_s3_bucket_name: str = context.toolkit.s3_bucket
        acct: str = core.Aws.ACCOUNT_ID
        self.bucket_names: Dict[str, Any] = {
            "scratch-bucket":
            f"orbit-foundation-{self.env_name}-scratch-{acct}-{context.toolkit.deploy_id}",
            "toolkit-bucket": toolkit_s3_bucket_name,
        }
        self._build_kms_key_for_env()
        self.scratch_bucket: s3.Bucket = S3Builder.build_s3_bucket(
            scope=self,
            id="scratch_bucket",
            name=self.bucket_names["scratch-bucket"],
            scratch_retention_days=30,
            kms_key=self.env_kms_key,
        )

        self.efs_fs = EfsBuilder.build_file_system(
            scope=self,
            name="orbit-fs",
            efs_life_cycle="AFTER_7_DAYS",
            vpc=self.vpc,
            efs_security_group=self._vpc_security_group,
            subnets=self.nodes_subnets.subnets,
            team_kms_key=self.env_kms_key,
        )

        self.user_pool: cognito.UserPool = self._create_user_pool()

        self._ssm_parameter = ssm.StringParameter(
            self,
            id="/orbit/DemoParams",
            string_value=json.dumps({
                "VpcId":
                self.vpc.vpc_id,
                "PublicSubnets":
                self.public_subnets.subnet_ids,
                "PrivateSubnets":
                self.private_subnets.subnet_ids,
                "IsolatedSubnets":
                self.isolated_subnets.subnet_ids,
                "NodesSubnets":
                self.nodes_subnets.subnet_ids,
                "LoadBalancersSubnets":
                self.public_subnets.subnet_ids,
                "KMSKey":
                self.env_kms_key.key_arn,
                "SharedEfsFsId":
                self.efs_fs.file_system_id,
                "ScratchBucketArn":
                self.scratch_bucket.bucket_arn,
                "ScratchBucketName":
                self.scratch_bucket.bucket_name,
                "UserPoolId":
                self.user_pool.user_pool_id,
                "SharedEfsSgId":
                self._vpc_security_group.security_group_id,
                "UserPoolProviderName":
                self.user_pool.user_pool_provider_name,
            }),
            type=ssm.ParameterType.STRING,
            description="Orbit Workbench Demo resources.",
            parameter_name=context.resources_ssm_parameter_name,
            simple_name=False,
            tier=ssm.ParameterTier.INTELLIGENT_TIERING,
        )

        CfnOutput(
            scope=self,
            id=f"{id}vpcid",
            export_name=f"orbit-foundation-{self.env_name}-vpc-id",
            value=self.vpc.vpc_id,
        )

        CfnOutput(
            scope=self,
            id=f"{id}publicsubnetsids",
            export_name=f"orbit-foundation-{self.env_name}-public-subnet-ids",
            value=",".join(self.public_subnets.subnet_ids),
        )
        CfnOutput(
            scope=self,
            id=f"{id}privatesubnetsids",
            export_name=f"orbit-foundation-{self.env_name}-private-subnet-ids",
            value=",".join(self.private_subnets.subnet_ids),
        )
        CfnOutput(
            scope=self,
            id=f"{id}isolatedsubnetsids",
            export_name=f"orbit-foundation-{self.env_name}-isolated-subnet-ids",
            value=",".join(self.isolated_subnets.subnet_ids),
        )
        CfnOutput(
            scope=self,
            id=f"{id}nodesubnetsids",
            export_name=f"orbit-foundation-{self.env_name}-nodes-subnet-ids",
            value=",".join(self.nodes_subnets.subnet_ids),
        )
Esempio n. 11
0
    def __init__(self,
                 scope: Construct,
                 id: str,
                 *,
                 sources: str,
                 hosted_zone: HostedZone = None,
                 site_domain: str = None,
                 website_index: str = "index.html",
                 website_error: str = "error.html"):
        """
        This constructs creates:
            - S3 bucket
            - DNS Validated certificate
            - CloudFront web distribution
            - Route53 A record

        :param scope: The scope in which to define this construct.
        :param id: The scoped construct ID. Must be unique amongst siblings. If the ID includes a path separator (``/``), then it will be replaced by double dash ``--``.
        :param hosted_zone: A route53 hosted zone
        :param site_domain: The domain or subdomain you want to use for the website
        :param sources: A path to the location of the code
        :param website_index: Name of the index page, defaults to "index.html"
        :param website_error: Name of the error page, defaults to "error.html"
        """
        super().__init__(scope, id)

        # Construct code goes here
        if site_domain:
            CfnOutput(self, "Site", value=f"https://{site_domain}")

        # Content bucket
        self.site_bucket = aws_s3.Bucket(self,
                                         "SiteBucket",
                                         bucket_name=site_domain,
                                         website_index_document=website_index,
                                         website_error_document=website_error,
                                         public_read_access=True,
                                         removal_policy=RemovalPolicy.DESTROY)
        CfnOutput(self, "BucketArn", value=self.site_bucket.bucket_arn)
        CfnOutput(self,
                  "BucketWebsiteUrl",
                  value=self.site_bucket.bucket_website_url)
        CfnOutput(self,
                  "BucketWebsiteDomainName",
                  value=self.site_bucket.bucket_website_domain_name)

        # Certificate
        alias_config = None
        if hosted_zone:
            self.cert = DnsValidatedCertificate(self,
                                                f"{id}-bucket",
                                                domain_name=site_domain,
                                                hosted_zone=hosted_zone)
            CfnOutput(self, 'CertificateArn', value=self.cert.certificate_arn)
            alias_config = AliasConfiguration(
                acm_cert_ref=self.cert.certificate_arn,
                names=[site_domain],
                ssl_method=aws_cloudfront.SSLMethod.SNI,
                security_policy=aws_cloudfront.SecurityPolicyProtocol.
                TLS_V1_1_2016,
            )

        self.distr = CloudFrontWebDistribution(
            self,
            "SiteDistribution",
            alias_configuration=alias_config,
            origin_configs=[
                SourceConfiguration(
                    s3_origin_source=aws_cloudfront.S3OriginConfig(
                        s3_bucket_source=self.site_bucket),
                    behaviors=[
                        aws_cloudfront.Behavior(is_default_behavior=True)
                    ])
            ])

        CfnOutput(self, "DistributionId", value=self.distr.distribution_id)

        # Route 53 alias record for the cloudfront distribution
        if hosted_zone:
            aws_route53.ARecord(
                self,
                "SiteAliasRecord",
                zone=hosted_zone,
                target=aws_route53.AddressRecordTarget.from_alias(
                    aws_route53_targets.CloudFrontTarget(self.distr)),
                record_name=site_domain)

        aws_s3_deployment.BucketDeployment(
            self,
            "DeployWithInvalidation",
            sources=[aws_s3_deployment.Source.asset(sources)],
            destination_bucket=self.site_bucket,
            distribution=self.distr,
            distribution_paths=["/*"])
Esempio n. 12
0
    def __init__(
        self,
        scope: App,
        id: str,
        envs: EnvSettings,
        components: ComponentsStack,
        base_resources: BaseResources,
    ):
        super().__init__(scope, id)

        self.db_secret_arn = Fn.import_value(
            BaseResources.get_database_secret_arn_output_export_name(envs))

        self.job_processing_queues = components.data_processing_queues
        self.vpc = base_resources.vpc
        self.db = base_resources.db

        self.app_bucket = Bucket(self, "App", versioned=True)

        if self.app_bucket.bucket_arn:
            CfnOutput(
                self,
                id="AppBucketOutput",
                export_name=self.get_app_bucket_arn_output_export_name(envs),
                value=self.app_bucket.bucket_arn,
            )

        self.pages_bucket = Bucket(self, "Pages", public_read_access=True)

        self.domain_name = StringParameter.from_string_parameter_name(
            self,
            "DomainNameParameter",
            string_parameter_name="/schema-cms-app/DOMAIN_NAME").string_value

        self.certificate_arn = StringParameter.from_string_parameter_name(
            self,
            "CertificateArnParameter",
            string_parameter_name="/schema-cms-app/CERTIFICATE_ARN"
        ).string_value

        django_secret = Secret(self,
                               "DjangoSecretKey",
                               secret_name="SCHEMA_CMS_DJANGO_SECRET_KEY")
        lambda_auth_token_secret = Secret(
            self,
            "LambdaAuthToken",
            secret_name="SCHEMA_CMS_LAMBDA_AUTH_TOKEN")

        if lambda_auth_token_secret.secret_arn:
            CfnOutput(
                self,
                id="lambdaAuthTokenArnOutput",
                export_name=self.get_lambda_auth_token_arn_output_export_name(
                    envs),
                value=lambda_auth_token_secret.secret_arn,
            )

        self.django_secret_key = EcsSecret.from_secrets_manager(django_secret)
        self.lambda_auth_token = EcsSecret.from_secrets_manager(
            lambda_auth_token_secret)

        tag_from_context = self.node.try_get_context("app_image_tag")
        tag = tag_from_context if tag_from_context != "undefined" else None

        api_image = ContainerImage.from_ecr_repository(
            repository=Repository.from_repository_name(
                self,
                id="BackendRepository",
                repository_name=BaseECR.get_backend_repository_name(envs)),
            tag=tag,
        )
        nginx_image = ContainerImage.from_ecr_repository(
            repository=Repository.from_repository_name(
                self,
                id="NginxRepository",
                repository_name=BaseECR.get_nginx_repository_name(envs)),
            tag=tag,
        )

        self.api = ApplicationLoadBalancedFargateService(
            self,
            "ApiService",
            service_name=f"{envs.project_name}-api-service",
            cluster=Cluster.from_cluster_attributes(
                self,
                id="WorkersCluster",
                cluster_name="schema-ecs-cluster",
                vpc=self.vpc,
                security_groups=[],
            ),
            task_image_options=ApplicationLoadBalancedTaskImageOptions(
                image=nginx_image,
                container_name="nginx",
                container_port=80,
                enable_logging=True,
            ),
            desired_count=1,
            cpu=512,
            memory_limit_mib=1024,
            certificate=Certificate.from_certificate_arn(
                self, "Cert", certificate_arn=self.certificate_arn),
            domain_name=self.domain_name,
            domain_zone=PrivateHostedZone(
                self,
                "zone",
                vpc=self.vpc,
                zone_name=self.domain_name,
            ),
        )

        self.api.task_definition.add_container(
            "backend",
            image=api_image,
            command=[
                "sh", "-c",
                "/bin/chamber exec $CHAMBER_SERVICE_NAME -- ./scripts/run.sh"
            ],
            logging=AwsLogDriver(stream_prefix="backend-container"),
            environment={
                "POSTGRES_DB": envs.data_base_name,
                "AWS_STORAGE_BUCKET_NAME": self.app_bucket.bucket_name,
                "AWS_STORAGE_PAGES_BUCKET_NAME": self.pages_bucket.bucket_name,
                "SQS_WORKER_QUEUE_URL":
                self.job_processing_queues[0].queue_url,
                "SQS_WORKER_EXT_QUEUE_URL":
                self.job_processing_queues[1].queue_url,
                "SQS_WORKER_MAX_QUEUE_URL":
                self.job_processing_queues[2].queue_url,
                "CHAMBER_SERVICE_NAME": "schema-cms-app",
                "CHAMBER_KMS_KEY_ALIAS": envs.project_name,
            },
            secrets={
                "DB_CONNECTION":
                EcsSecret.from_secrets_manager(
                    Secret.from_secret_arn(self,
                                           id="DbSecret",
                                           secret_arn=self.db_secret_arn)),
                "DJANGO_SECRET_KEY":
                self.django_secret_key,
                "LAMBDA_AUTH_TOKEN":
                self.lambda_auth_token,
            },
            cpu=512,
            memory_limit_mib=1024,
        )

        self.django_secret_key.grant_read(
            self.api.service.task_definition.task_role)

        self.app_bucket.grant_read_write(
            self.api.service.task_definition.task_role)
        self.pages_bucket.grant_read_write(
            self.api.service.task_definition.task_role)

        for queue in self.job_processing_queues:
            queue.grant_send_messages(
                self.api.service.task_definition.task_role)

        self.api.service.connections.allow_to(self.db.connections,
                                              Port.tcp(5432))
        self.api.task_definition.add_to_task_role_policy(
            PolicyStatement(
                actions=["ses:SendRawEmail", "ses:SendBulkTemplatedEmail"],
                resources=["*"],
            ))

        self.api.task_definition.add_to_task_role_policy(
            PolicyStatement(
                actions=[
                    "kms:Get*", "kms:Describe*", "kms:List*", "kms:Decrypt"
                ],
                resources=[
                    Fn.import_value(
                        BaseKMS.get_kms_arn_output_export_name(envs))
                ],
            ))

        self.api.task_definition.add_to_task_role_policy(
            PolicyStatement(actions=["ssm:DescribeParameters"],
                            resources=["*"]))

        self.api.task_definition.add_to_task_role_policy(
            PolicyStatement(
                actions=["ssm:GetParameters*"],
                resources=[
                    f"arn:aws:ssm:{self.region}:{self.account}:parameter/schema-cms-app/*"
                ],
            ))
Esempio n. 13
0
    def __init__(self, scope: Construct, id: str, **kwargs) -> None:

        # Set Stack description
        deployment_tracking_param = scope.node.try_get_context(
            "EnableDeploymentTracking")
        stack_description = "Analytics Ref Arch - DataLake stack"
        if is_module_enabled(deployment_tracking_param):
            stack_description = stack_description + " (uksb-1scq97upu)"

        super().__init__(scope, id, description=stack_description, **kwargs)

        data_lake = DataLakeFoundations(self, "Foundations")

        # Parameters to enable/disable modules
        batch_module_param = self.node.try_get_context("EnableBatch")
        dwh_module_param = self.node.try_get_context("EnableDWH")
        dataviz_module_param = self.node.try_get_context("EnableDataviz")
        streaming_module_param = self.node.try_get_context("EnableStreaming")

        datagen_config_table = _dynamodb.Table(
            self,
            'DatagenConfigTable',
            partition_key=_dynamodb.Attribute(
                name="param", type=_dynamodb.AttributeType.STRING),
            billing_mode=_dynamodb.BillingMode.PAY_PER_REQUEST)

        # BATCH module
        if is_module_enabled(batch_module_param):
            BatchModule(self,
                        "Batch",
                        raw_bucket=data_lake.raw_s3_bucket,
                        clean_bucket=data_lake.clean_s3_bucket,
                        raw_db=data_lake.raw_glue_db,
                        clean_db=data_lake.clean_glue_db)

            BatchDataGenerator(self,
                               "BatchDatagen",
                               config_table=datagen_config_table,
                               tshirt_size='SMALL',
                               log_bucket=data_lake.logs_s3_bucket,
                               sink_bucket=data_lake.raw_s3_bucket,
                               vpc=data_lake.vpc)

        # DATA WAREHOUSE module
        if is_module_enabled(dwh_module_param):
            dwh_stack = DwhModule(self,
                                  "dwh",
                                  vpc=data_lake.vpc,
                                  clean_bucket=data_lake.clean_s3_bucket,
                                  clean_glue_db=data_lake.clean_glue_db)

            CfnOutput(self,
                      'Redshift-QuickSight-Secret-Arn',
                      value=dwh_stack.quicksight_redshift_secret_arn,
                      export_name='ara-QuickSight-Redshift-Secret-Arn')
            CfnOutput(self,
                      'bastion_dns',
                      value=dwh_stack.bastion_dns,
                      export_name='ara-Redshift-bastion-dns')
            CfnOutput(self,
                      'bastion-host-key-pair',
                      export_name='ara-Redshift-bastion-keypair-secret',
                      value=dwh_stack.bastion_keypair_secret)
            CfnOutput(self,
                      'redshift-hostname',
                      export_name='ara-Redshift-hostname',
                      value=dwh_stack.redshift_endpoint.hostname)
            CfnOutput(self,
                      'redshift-port',
                      export_name='ara-Redshift-port',
                      value=str(dwh_stack.redshift_endpoint.port))

        # DATA VISUALIZATION module
        if is_module_enabled(dataviz_module_param):
            quicksight_username = self.node.try_get_context(
                'QuickSightUsername')

            quicksight_identity_region = self.node.try_get_context(
                'QuickSightIdentityRegion')

            if quicksight_username is None or quicksight_identity_region is None:
                raise Exception(
                    'QuickSightUsername and QuickSightIdentityRegion must be specified if data visualization is enabled'
                )

            dataviz_stack = DataVizModule(
                self,
                "dataviz",
                vpc=data_lake.vpc,
                clean_glue_db_name=data_lake.clean_glue_db.database_name,
                redshift_sg_id=dwh_stack.redshift_sg_id,
                quicksight_username=quicksight_username,
                quicksight_identity_region=quicksight_identity_region)

            CfnOutput(self,
                      'QuickSight-Security-Group-Id',
                      value=dataviz_stack.quicksight_security_group_id)

            CfnOutput(self,
                      'QuickSight-Group-Arn',
                      value=dataviz_stack.quicksight_group_arn,
                      export_name='ara-QuickSight-Group-Arn')

        # STREAMING module
        if is_module_enabled(streaming_module_param):
            streaming_stack = StreamingModule(
                self,
                id="Streaming",
                prefix=id,
                source_bucket=data_lake.raw_s3_bucket,
                dest_bucket=data_lake.curated_s3_bucket)

            StreamDataGenerator(
                self,
                "StreamDatagen",
                config_table=datagen_config_table,
                tshirt_size='SMALL',
                log_bucket=data_lake.logs_s3_bucket,
                sink_bucket=data_lake.raw_s3_bucket,
                web_sale_stream=streaming_stack.sale_stream.stream_name,
                web_customer_stream=streaming_stack.customer_stream.
                stream_name,
                web_customer_address_stream=streaming_stack.address_stream.
                stream_name,
                kinesis_key=streaming_stack.kinesis_kms_key,
                vpc=data_lake.vpc)

        CfnOutput(self,
                  'Clean-S3-Bucket',
                  value=data_lake.clean_s3_bucket.bucket_name)
        CfnOutput(self,
                  "VPC-ID",
                  value=data_lake.vpc.vpc_id,
                  export_name='ara-Vpc-Id')

        for idx, val in enumerate(data_lake.private_subnets_selection.subnets):
            CfnOutput(self, 'SUBNET-ID{}'.format(idx), value=val.subnet_id)

        Tags.of(self).add('project-name', 'ara')
Esempio n. 14
0
    def __init__(self, scope, id, **kwargs):

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

        # Each publisher instance authenticates as an individual user stored in this pool
        user_pool = UserPool(self,
                             'user-pool',
                             user_pool_name='amazon-cloudwatch-publisher')

        # Set up the client app with simple username/password authentication
        user_pool_client = UserPoolClient(
            self,
            'user-pool-client',
            user_pool=user_pool,
            enabled_auth_flows=[AuthFlow.USER_PASSWORD],
            user_pool_client_name='amazon-cloudwatch-publisher')

        # The identity pool exists to associate users to roles they can assume
        identity_pool = CfnIdentityPool(
            self,
            'identity-pool',
            identity_pool_name='amazon-cloudwatch-publisher',
            allow_unauthenticated_identities=False)

        # Setting this property links the identity pool to the user pool client app
        identity_pool.add_property_override(
            property_path='CognitoIdentityProviders',
            value=[{
                'ClientId':
                user_pool_client.user_pool_client_id,
                'ProviderName':
                'cognito-idp.{0}.amazonaws.com/{1}'.format(
                    self.region, user_pool.user_pool_id)
            }])

        # Only identities that come from Congito users should be able to assume the publisher role
        principal = FederatedPrincipal(
            federated='cognito-identity.amazonaws.com',
            assume_role_action='sts:AssumeRoleWithWebIdentity',
            conditions={
                'StringEquals': {
                    'cognito-identity.amazonaws.com:aud': identity_pool.ref
                },
                'ForAnyValue:StringLike': {
                    'cognito-identity.amazonaws.com:amr': 'authenticated'
                }
            })

        # Minimum set of permissions required to push metrics and logs
        policy = PolicyDocument(statements=[
            PolicyStatement(effect=Effect.ALLOW,
                            actions=[
                                'cloudwatch:PutMetricData',
                                'logs:CreateLogGroup',
                                'logs:CreateLogStream',
                                'logs:DescribeLogGroups',
                                'logs:DescribeLogStreams',
                                'logs:PutLogEvents',
                            ],
                            resources=[
                                '*',
                            ])
        ])

        # Create the role itself using the principal and policy defined above
        role = Role(self,
                    'role',
                    assumed_by=principal,
                    inline_policies=[policy])

        # Associate the above role with the identity pool; we don't want
        # any unauthenticated access so explicitly ensure it's set to None
        CfnIdentityPoolRoleAttachment(
            self,
            'identity-pool-role-attachment-authenticated',
            identity_pool_id=identity_pool.ref,
            roles={
                'authenticated': role.role_arn,
                'unauthenticated': None
            })

        # Defining outputs here allows them to be scraped from the `cdk deploy` command
        CfnOutput(self, 'Region', value=self.region)
        CfnOutput(self, 'AccountId', value=self.account)
        CfnOutput(self, 'UserPoolId', value=user_pool.user_pool_id)
        CfnOutput(self, 'IdentityPoolId', value=identity_pool.ref)
        CfnOutput(self,
                  'AppClientId',
                  value=user_pool_client.user_pool_client_id)
Esempio n. 15
0
    def __init__(self,
                 scope: Stack,
                 id: str,
                 ws_api: WsApi,
                 stage_name: str,
                 access_log_settings: Optional[
                     CfnStage.AccessLogSettingsProperty] = None,
                 create_default_access_log_settings: Optional[bool] = None,
                 auto_deploy: Optional[bool] = None,
                 client_certificate_id: Optional[str] = None,
                 default_route_settings: Optional[
                     CfnStage.RouteSettingsProperty] = None,
                 description: Optional[str] = None,
                 route_settings: Optional[Dict[str, Any]] = None,
                 stage_variables: Optional[Dict[str, Any]] = None,
                 tags: Optional[Dict[str, Any]] = None,
                 *args,
                 **kwargs) -> None:
        """
        Constructor.

        :param scope: CloudFormation-equivalent stack.
        :param id: AWS-CDK-specific id.
        :param ws_api: The web socket API for with this stage should be created.
        :param stage_name: The stage name. Stage names can only contain alphanumeric characters, hyphens,
        and underscores. Maximum length is 128 characters.
        :param access_log_settings: Settings for logging access in this stage.
        :param create_default_access_log_settings: Indicate whether to create default access log settings.
        :param auto_deploy: Specifies whether updates to an API automatically trigger a new deployment.
        The default value is false.
        :param client_certificate_id: The identifier of a client certificate for a Stage.
        :param default_route_settings: The default route settings for the stage.
        :param description: The description for the API stage.
        :param route_settings: Route settings for the stage.
        :param stage_variables: A map that defines the stage variables for a Stage. Variable names can have
        alphanumeric and underscore characters, and the values must match [A-Za-z0-9-._~:/?#&=,]+.
        :param tags: The collection of tags. Each tag element is associated with a given resource.
        """
        self.__scope = scope
        self.__ws_api = ws_api

        default_route_settings = default_route_settings or CfnStage.RouteSettingsProperty(
            data_trace_enabled=True,
            detailed_metrics_enabled=True,
            logging_level='INFO',
        )

        if access_log_settings and create_default_access_log_settings:
            raise ValueError(
                'Access log settings supplied. Can not request to create default ones.'
            )

        if create_default_access_log_settings:
            log_group = LogGroup(scope=scope,
                                 id=f'{id}LogGroup',
                                 log_group_name=f'{id}LogGroup',
                                 removal_policy=RemovalPolicy.DESTROY,
                                 retention=RetentionDays.ONE_MONTH)

            access_log_settings = CfnStage.AccessLogSettingsProperty(
                destination_arn=log_group.log_group_arn,
                format=("{"
                        "\"requestId\":\"$context.requestId\", "
                        "\"ip\": \"$context.identity.sourceIp\", "
                        "\"caller\":\"$context.identity.caller\", "
                        "\"user\":\"$context.identity.user\","
                        "\"requestTime\":\"$context.requestTime\", "
                        "\"eventType\":\"$context.eventType\","
                        "\"routeKey\":\"$context.routeKey\", "
                        "\"status\":\"$context.status\","
                        "\"connectionId\":\"$context.connectionId\""
                        "}"))

        super().__init__(scope=scope,
                         id=id,
                         api_id=ws_api.ref,
                         stage_name=stage_name,
                         access_log_settings=access_log_settings,
                         auto_deploy=auto_deploy,
                         client_certificate_id=client_certificate_id,
                         default_route_settings=default_route_settings,
                         description=description,
                         route_settings=route_settings,
                         stage_variables=stage_variables,
                         tags=tags,
                         *args,
                         **kwargs)

        CfnOutput(scope=scope,
                  id=f'{ws_api.name}Url',
                  value=self.ws_url,
                  description='A websocket URL.',
                  export_name=f'{ws_api.name}Url')

        CfnOutput(scope=scope,
                  id=f'{ws_api.name}Id',
                  value=self.api_id,
                  description='A websocket ID.',
                  export_name=f'{ws_api.name}Id')

        CfnOutput(scope=scope,
                  id=f'{ws_api.name}HttpUrl',
                  value=self.http_url,
                  description='A websocket http URL.',
                  export_name=f'{ws_api.name}HttpUrl')
Esempio n. 16
0
 def _export(self, name, value):
     CfnOutput(self._stack,
               name,
               value=value,
               export_name=self._config.stack_name + name)
    def __init__(self, app: App, id: str, **kwargs) -> None:
        super().__init__(app, id, **kwargs)

        self.template_options.description = "(SO0123) Improving Forecast Accuracy with Machine Learning %%VERSION%% - This solution provides a mechanism to automate Amazon Forecast predictor and forecast generation and visualize it via an Amazon SageMaker Jupyter Notebook"

        # set up the template parameters
        email = CfnParameter(
            self,
            id="Email",
            type="String",
            description="Email to notify with forecast results",
            default="",
            max_length=50,
            allowed_pattern=
            r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$|^$)",
            constraint_description="Must be a valid email address or blank",
        )

        lambda_log_level = CfnParameter(
            self,
            id="LambdaLogLevel",
            type="String",
            description="Change the verbosity of the logs output to CloudWatch",
            default="WARNING",
            allowed_values=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
        )

        notebook_deploy = CfnParameter(
            self,
            id="NotebookDeploy",
            type="String",
            description="Deploy an Amazon SageMaker Jupyter Notebook instance",
            default="No",
            allowed_values=["Yes", "No"],
        )

        notebook_volume_size = CfnParameter(
            self,
            id="NotebookVolumeSize",
            type="Number",
            description=
            "Enter the size of the notebook instance EBS volume in GB",
            default=10,
            min_value=5,
            max_value=16384,
            constraint_description=
            "Must be an integer between 5 (GB) and 16384 (16 TB)",
        )

        notebook_instance_type = CfnParameter(
            self,
            id="NotebookInstanceType",
            type="String",
            description="Enter the type of the notebook instance",
            default="ml.t2.medium",
            allowed_values=[
                "ml.t2.medium",
                "ml.t3.medium",
                "ml.r5.large",
                "ml.c5.large",
            ],
        )

        quicksight_analysis_owner = CfnParameter(
            self,
            id="QuickSightAnalysisOwner",
            description=
            "With QuickSight Enterprise enabled, provide a QuickSight ADMIN user ARN to automatically create QuickSight analyses",
            default="",
            allowed_pattern="(^arn:.*:quicksight:.*:.*:user.*$|^$)",
        )

        # set up the metadata/ cloudformation interface
        template_options = TemplateOptions()
        template_options.add_parameter_group(
            label=
            "Improving Forecast Accuracy with Machine Learning Configuration",
            parameters=[email],
        )
        template_options.add_parameter_group(
            label="Visualization Options",
            parameters=[
                quicksight_analysis_owner,
                notebook_deploy,
                notebook_instance_type,
                notebook_volume_size,
            ],
        )
        template_options.add_parameter_group(label="Deployment Configuration",
                                             parameters=[lambda_log_level])
        template_options.add_parameter_label(email, "Email")
        template_options.add_parameter_label(lambda_log_level,
                                             "CloudWatch Log Level")
        template_options.add_parameter_label(notebook_deploy,
                                             "Deploy Jupyter Notebook")
        template_options.add_parameter_label(notebook_volume_size,
                                             "Jupyter Notebook volume size")
        template_options.add_parameter_label(notebook_instance_type,
                                             "Jupyter Notebook instance type")
        template_options.add_parameter_label(quicksight_analysis_owner,
                                             "Deploy QuickSight Dashboards")
        self.template_options.metadata = template_options.metadata

        solution_mapping = CfnMapping(
            self,
            "Solution",
            mapping={
                "Data": {
                    "ID": "SO0123",
                    "Version": "%%VERSION%%",
                    "SendAnonymousUsageData": "Yes",
                }
            },
        )

        source_mapping = CfnMapping(
            self,
            "SourceCode",
            mapping={
                "General": {
                    "S3Bucket": "%%BUCKET_NAME%%",
                    "KeyPrefix": "%%SOLUTION_NAME%%/%%VERSION%%",
                    "QuickSightSourceTemplateArn": "%%QUICKSIGHT_SOURCE%%",
                }
            },
        )

        # conditions
        create_notebook = CfnCondition(
            self,
            "CreateNotebook",
            expression=Fn.condition_equals(notebook_deploy, "Yes"),
        )
        email_provided = CfnCondition(
            self,
            "EmailProvided",
            expression=Fn.condition_not(Fn.condition_equals(email, "")),
        )
        send_anonymous_usage_data = CfnCondition(
            self,
            "SendAnonymousUsageData",
            expression=Fn.condition_equals(
                Fn.find_in_map("Solution", "Data", "SendAnonymousUsageData"),
                "Yes"),
        )
        create_analysis = CfnCondition(
            self,
            "CreateAnalysis",
            expression=Fn.condition_not(
                Fn.condition_equals(quicksight_analysis_owner, ""), ),
        )

        # Step function and state machine
        fns = LambdaFunctions(self, "Functions", log_level=lambda_log_level)

        # SNS
        notifications = Notifications(
            self,
            "NotificationConfiguration",
            lambda_function=fns.functions["SNS"],
            email=email,
            email_provided=email_provided,
        )

        # Custom Resources
        unique_name = CfnResource(
            self,
            "UniqueName",
            type="Custom::UniqueName",
            properties={
                "ServiceToken":
                fns.functions["CfnResourceUniqueName"].function_arn
            },
        )
        unique_name.override_logical_id("UniqueName")

        data_bucket_name_resource = CfnResource(
            self,
            "DataBucketName",
            type="Custom::BucketName",
            properties={
                "ServiceToken":
                fns.functions["CfnResourceBucketName"].function_arn,
                "BucketPurpose": "data-bucket",
                "StackName": Aws.STACK_NAME,
                "Id": unique_name.get_att("Id"),
            },
        )
        data_bucket_name_resource.override_logical_id("DataBucketName")

        # Buckets
        access_logs_bucket = self.secure_bucket(
            "AccessLogsBucket",
            suppressions=[
                CfnNagSuppression(
                    "W35",
                    "This bucket is used as the logging destination for forecast datasets and exports",
                )
            ],
            access_control=BucketAccessControl.LOG_DELIVERY_WRITE,
        )

        athena_bucket = self.secure_bucket(
            "AthenaBucket",
            server_access_logs_bucket=access_logs_bucket,
            server_access_logs_prefix="athena-bucket-access-logs/",
        )

        data_bucket = self.secure_bucket(
            "ForecastBucket",
            lifecycle_rules=[
                LifecycleRule(
                    abort_incomplete_multipart_upload_after=Duration.days(3),
                    enabled=True,
                ),
                LifecycleRule(expiration=Duration.days(1),
                              prefix="raw/",
                              enabled=True),
            ],
            bucket_name=data_bucket_name_resource.get_att("Name").to_string(),
            server_access_logs_bucket=access_logs_bucket,
            server_access_logs_prefix="forecast-bucket-access-logs/",
        )
        data_bucket.node.default_child.add_property_override(
            "NotificationConfiguration",
            {
                "LambdaConfigurations": [{
                    "Function":
                    fns.functions["S3NotificationLambda"].function_arn,
                    "Event":
                    "s3:ObjectCreated:*",
                    "Filter": {
                        "S3Key": {
                            "Rules": [
                                {
                                    "Name": "prefix",
                                    "Value": "train/"
                                },
                                {
                                    "Name": "suffix",
                                    "Value": ".csv"
                                },
                            ]
                        }
                    },
                }]
            },
        )

        # Glue and Athena
        glue = Glue(self, "GlueResources", unique_name)
        athena = Athena(self, "AthenaResources", athena_bucket=athena_bucket)

        # Configure permissions for functions
        fns.set_s3_notification_permissions(data_bucket_name_resource)
        fns.set_forecast_s3_access_permissions(
            name="DatasetImport",
            function=fns.functions["CreateDatasetImportJob"],
            data_bucket_name_resource=data_bucket_name_resource,
        )
        fns.set_forecast_s3_access_permissions(
            name="ForecastExport",
            function=fns.functions["CreateForecast"],
            data_bucket_name_resource=data_bucket_name_resource,
        )
        fns.set_forecast_etl_permissions(
            function=fns.functions["PrepareForecastExport"],
            database=glue.database,
            workgroup=athena.workgroup,
            quicksight_principal=quicksight_analysis_owner,
            quicksight_source=source_mapping,
            athena_bucket=athena_bucket,
            data_bucket_name_resource=data_bucket_name_resource,
        )
        fns.set_forecast_permissions(
            "CreateDatasetGroup",
            data_bucket_name_resource=data_bucket_name_resource)
        fns.set_forecast_permissions(
            "CreateDatasetImportJob",
            data_bucket_name_resource=data_bucket_name_resource,
        )
        fns.set_forecast_permissions(
            "CreateForecast",
            data_bucket_name_resource=data_bucket_name_resource)
        fns.set_forecast_permissions(
            "CreatePredictor",
            data_bucket_name_resource=data_bucket_name_resource)
        fns.set_forecast_permissions(
            "PrepareForecastExport",
            data_bucket_name_resource=data_bucket_name_resource)

        # notebook (conditional on 'create_notebook')
        notebook = Notebook(
            self,
            "Notebook",
            buckets=[data_bucket],
            instance_type=notebook_instance_type.value_as_string,
            instance_volume_size=notebook_volume_size.value_as_number,
            notebook_path=Path(__file__).parent.parent.parent.joinpath(
                "notebook", "samples", "notebooks"),
            notebook_destination_bucket=data_bucket,
            notebook_destination_prefix="notebooks",
        )
        Aspects.of(notebook).add(ConditionalResources(create_notebook))

        # solutions metrics (conditional on 'send_anonymous_usage_data')
        metrics = Metrics(
            self,
            "SolutionMetrics",
            metrics_function=fns.functions["CfnResourceSolutionMetrics"],
            metrics={
                "Solution":
                solution_mapping.find_in_map("Data", "ID"),
                "Version":
                solution_mapping.find_in_map("Data", "Version"),
                "Region":
                Aws.REGION,
                "NotebookDeployed":
                Fn.condition_if(create_notebook.node.id, "Yes", "No"),
                "NotebookType":
                Fn.condition_if(
                    create_notebook.node.id,
                    notebook_instance_type.value_as_string,
                    Aws.NO_VALUE,
                ),
                "QuickSightDeployed":
                Fn.condition_if(create_analysis.node.id, "Yes", "No"),
            },
        )
        Aspects.of(metrics).add(
            ConditionalResources(send_anonymous_usage_data))

        # outputs
        CfnOutput(self, "ForecastBucketName", value=data_bucket.bucket_name)
        CfnOutput(self, "AthenaBucketName", value=athena_bucket.bucket_name)
        CfnOutput(self,
                  "StepFunctionsName",
                  value=fns.state_machine.state_machine_name)
# and limitations under the License.  																				#                                                                              #
######################################################################################################################

#!/usr/bin/env python3
from aws_cdk.core import (App, Tags, CfnOutput)
from lib.spark_on_eks_stack import SparkOnEksStack
from lib.cloud_front_stack import NestedStack

app = App()

eks_name = app.node.try_get_context('cluster_name')
solution_id = app.node.try_get_context('solution_id')
solution_version = app.node.try_get_context('version')

# main stack
eks_stack = SparkOnEksStack(app,
                            'sql-based-etl-with-apache-spark-on-amazon-eks',
                            eks_name, solution_id, solution_version)
# Recommend to remove the CloudFront nested stack. Setup your own SSL certificate and add it to ALB.
cf_nested_stack = NestedStack(eks_stack, 'CreateCloudFront',
                              eks_stack.code_bucket, eks_stack.argo_url,
                              eks_stack.jhub_url)
Tags.of(eks_stack).add('project', 'sqlbasedetl')
Tags.of(cf_nested_stack).add('project', 'sqlbasedetl')

# Deployment Output
CfnOutput(eks_stack, 'CODE_BUCKET', value=eks_stack.code_bucket)
CfnOutput(eks_stack, 'ARGO_URL', value='https://' + cf_nested_stack.argo_cf)
CfnOutput(eks_stack, 'JUPYTER_URL', value='https://' + cf_nested_stack.jhub_cf)

app.synth()
Esempio n. 19
0
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # set variables for site s3 bucket
        bucket_name = DOMAIN_WEBSITE['bucket_name']
        website_index = DOMAIN_WEBSITE['website_index']
        website_error = DOMAIN_WEBSITE['website_error']
        website_code_folder = DOMAIN_WEBSITE['website_code_folder']
        site_domain = DOMAIN_WEBSITE['site_domain']
        certificate_domain = DOMAIN_WEBSITE['certificate_domain']
        api_domain = DOMAIN_WEBSITE['api_domain']
        hosted_zone_name = DOMAIN_WEBSITE['hosted_zone_name']
        hosted_zone_id = DOMAIN_WEBSITE['hosted_zone_id']

        #self.lambda_code = aws_lambda.Code.from_cfn_parameters()

        # retrieve hosted zone
        hosted_zone = aws_route53.HostedZone.from_hosted_zone_attributes(
            self,
            'hostedZone',
            hosted_zone_id=hosted_zone_id,
            zone_name=hosted_zone_name)

        # set variables for backend
        lambda_code_location = "jukebike/backend/"

        # Construct code goes here
        CfnOutput(self, "Site", value=f"https://{site_domain}")

        # Content bucket
        site_bucket = aws_s3.Bucket(self,
                                    "websitebucket",
                                    bucket_name=bucket_name,
                                    website_index_document=website_index,
                                    website_error_document=website_error,
                                    public_read_access=True,
                                    removal_policy=RemovalPolicy.DESTROY)
        CfnOutput(self, "BucketArn", value=site_bucket.bucket_arn)
        CfnOutput(self, "WebsiteUrl", value=site_bucket.bucket_website_url)

        # Certificate
        cert = aws_certificatemanager.DnsValidatedCertificate(
            self,
            "certificate_website",
            domain_name=site_domain,
            hosted_zone=hosted_zone,
            region="us-east-1")
        CfnOutput(self, 'CertificateArn', value=cert.certificate_arn)

        distr = CloudFrontWebDistribution(
            self,
            "SiteDistribution",
            alias_configuration=AliasConfiguration(
                acm_cert_ref=cert.certificate_arn,
                names=[site_domain],
                ssl_method=aws_cloudfront.SSLMethod.SNI,
                security_policy=aws_cloudfront.SecurityPolicyProtocol.
                TLS_V1_1_2016,
            ),
            origin_configs=[
                SourceConfiguration(
                    s3_origin_source=aws_cloudfront.S3OriginConfig(
                        s3_bucket_source=site_bucket),
                    behaviors=[
                        aws_cloudfront.Behavior(is_default_behavior=True)
                    ])
            ])
        CfnOutput(self, "DistributionId", value=distr.distribution_id)
        #
        # Route 53 alias record for the cloudfront distribution
        aws_route53.ARecord(self,
                            "SiteAliasRecord",
                            zone=hosted_zone,
                            target=aws_route53.AddressRecordTarget.from_alias(
                                aws_route53_targets.CloudFrontTarget(distr)),
                            record_name=site_domain)

        aws_s3_deployment.BucketDeployment(
            self,
            "DeployWithInvalidation",
            sources=[aws_s3_deployment.Source.asset(website_code_folder)],
            destination_bucket=site_bucket,
            distribution=distr,
            distribution_paths=["/*"])

        ########################### Backend #################

        certificate = aws_certificatemanager.DnsValidatedCertificate(
            self,
            "domaincertificate",
            hosted_zone=hosted_zone,
            region='us-east-1',
            domain_name=certificate_domain,
            validation_method=aws_certificatemanager.ValidationMethod.DNS)

        ############# Search API ###################

        search_lambda = aws_lambda.Function(
            self,
            "SearchLambda",
            code=aws_lambda.Code.from_asset(lambda_code_location),
            handler="search.handler",
            runtime=aws_lambda.Runtime.PYTHON_3_7)

        CfnOutput(self, "SearchLambda_", value=search_lambda.function_arn)

        search_api = aws_apigateway.LambdaRestApi(
            self,
            'SearchSpotifyEndpoint',
            handler=search_lambda,
        )

        ############# Whats-Next API ###################
        whats_next_lambda = aws_lambda.Function(
            self,
            "WhatsNextLambda",
            code=aws_lambda.Code.from_asset(lambda_code_location),
            handler="whats_next.handler",
            runtime=aws_lambda.Runtime.PYTHON_3_7)
        CfnOutput(self,
                  "WhatsNextLambda_",
                  value=whats_next_lambda.function_arn)

        whats_next_api = aws_apigateway.LambdaRestApi(
            self,
            'WhatsNextEndpoint',
            handler=whats_next_lambda,
        )

        ############# Whats-Next API ###################
        wish_track_lambda = aws_lambda.Function(
            self,
            "WishTrackLambda",
            code=aws_lambda.Code.from_asset(lambda_code_location),
            handler="wish_track.handler",
            runtime=aws_lambda.Runtime.PYTHON_3_7)
        CfnOutput(self,
                  "WishTrackLambda_",
                  value=wish_track_lambda.function_arn)

        wish_track_api = aws_apigateway.LambdaRestApi(
            self,
            'WishTrackEndpoint',
            handler=wish_track_lambda,
        )

        ################## Publish APIS with custom domain name ##############
        # Pre-Requirements:
        # [Manual] 1) Registered Domain with Route 53 (e.g. jacubasch.com)
        # 2) Certificate in North Virgina for domain (e.g. api.jacubasch.com) -> AWS Certification Manager
        # 3) API Gateway Custom Domain with Edge
        # 4) Alias-Record in Route53 forwarding to Cloudfront Target Domain Name (can be found in API Gateway)
        # TODO: in separaten Base-Stack auslagern?
        # https://medium.com/@maciejtreder/custom-domain-in-aws-api-gateway-a2b7feaf9c74

        domain = aws_apigateway.DomainName(
            self,
            'searchDomain',
            certificate=certificate,
            endpoint_type=aws_apigateway.EndpointType.EDGE,
            domain_name=api_domain)
        domain.add_base_path_mapping(target_api=search_api, base_path="search")
        domain.add_base_path_mapping(target_api=whats_next_api,
                                     base_path="whats-next")
        domain.add_base_path_mapping(target_api=wish_track_api,
                                     base_path="wish-track")

        target = aws_route53_targets.ApiGatewayDomain(domain)
        record_target = aws_route53.RecordTarget.from_alias(target)
        alias_record = aws_route53.ARecord(self,
                                           'aliasRecord',
                                           target=record_target,
                                           record_name=api_domain,
                                           zone=hosted_zone)

        CfnOutput(self, "AliasRecord_", value=alias_record.to_string())

        ################## Dynamo DB ##############

        # create dynamo table
        track_table = aws_dynamodb.Table(
            self,
            "track_table",
            partition_key=aws_dynamodb.Attribute(
                name="track_uri", type=aws_dynamodb.AttributeType.STRING))

        # grant permission to lambda  & provide environment variable
        track_table.grant_write_data(wish_track_lambda)
        wish_track_lambda.add_environment("TRACK_TABLE_NAME",
                                          track_table.table_name)

        track_table.grant_read_write_data(whats_next_lambda)
        whats_next_lambda.add_environment("TRACK_TABLE_NAME",
                                          track_table.table_name)
Esempio n. 20
0
    def __init__(self, app: App, id: str, env: Environment) -> None:
        super().__init__(app, id, env=env)

        # start by getting the DNS zone we're going to work with
        zone = HostedZone.from_lookup(self, "Dominick", domain_name=DOMAIN)

        # create a certificate for the web service which matches its hostname
        cert = Certificate(self,
                           "Cletus",
                           domain_name=HOSTNAME,
                           validation=CertificateValidation.from_dns(zone))

        # the services will live in a vpc, of course
        vpc = ec2.Vpc(self, "Virgil")

        # we're going to scale this web-service automatically
        asg = AutoScalingGroup(
            self,
            "Alice",
            vpc=vpc,
            user_data=http_service(),
            instance_type=ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2,
                                              ec2.InstanceSize.MICRO),
            machine_image=ec2.AmazonLinuxImage(
                generation=ec2.AmazonLinuxGeneration.AMAZON_LINUX_2))

        # explicitly allow internal access from the vpc just to be safe
        asg.connections.allow_internally(Port.tcp(WEB_PORT), "web-service")
        asg.connections.allow_internally(Port.tcp(NOT_WEB), "not-web")

        # expose the scaling group ports and permit egress
        asg.connections.allow_from_any_ipv4(Port.tcp(WEB_PORT))
        asg.connections.allow_from_any_ipv4(Port.tcp(NOT_WEB))

        # create a health check for the not-web service that currently
        if NOT_WEB_HEALTH_CHECKS:
            # points to the not-web service
            checker = HealthCheck(interval=Duration.seconds(10),
                                  port=NOT_WEB,
                                  protocol=Protocol.TCP)
        else:
            # points to the web port where our demo server listens
            checker = HealthCheck(interval=Duration.seconds(10),
                                  port=str(WEB_PORT),
                                  protocol=WEB_PROT)

        # put the scaling group behind a network target group for the LB
        notwebish = NetworkTargetGroup(self,
                                       "Allison",
                                       vpc=vpc,
                                       health_check=checker,
                                       targets=[asg],
                                       port=NOT_WEB,
                                       protocol=Protocol.TCP)

        # for the web-like ports, we can use the default health check
        webish = NetworkTargetGroup(
            self,
            "Alicen",
            vpc=vpc,
            health_check=HealthCheck(interval=Duration.seconds(10)),
            targets=[asg],
            port=WEB_PORT,
            protocol=WEB_PROT)

        if True:
            # create the load balancer and put it into dns
            lb = NetworkLoadBalancer(self,
                                     "Lisa",
                                     vpc=vpc,
                                     internet_facing=True)

            # create a hostname for the service
            CnameRecord(self,
                        "Carl",
                        domain_name=lb.load_balancer_dns_name,
                        zone=zone,
                        record_name=HOSTNAME.split('.')[0],
                        ttl=Duration.seconds(60))
        else:
            # a multi-step deployment could allow using an alias in R53
            lb = NetworkLoadBalancer.from_network_load_balancer_attributes(
                self,
                "Larry",
                vpc=vpc,
                load_balancer_arn=some.load_balancer_arn,
                load_balancer_dns_name=HOSTNAME,
                load_balancer_canonical_hosted_zone_id=zone.hosted_zone_id)

            # create a hostname for the service
            AaaaRecord(self,
                       "Eric",
                       zone=zone,
                       record_name=HOSTNAME.split('.')[0],
                       target=RecordTarget.from_alias(LoadBalancerTarget(lb)))

        # point the load balancer to the target group for the ssl service
        #
        # TODO: determine if we need to use the same cert for pub-facing
        #       and internal service
        listener_cert = ListenerCertificate(cert.certificate_arn)
        lb.add_listener("Cecil",
                        port=443,
                        certificates=[listener_cert],
                        default_target_groups=[webish])

        # point the load balancer to the target group for the web service
        lb.add_listener("Webster", port=80, default_target_groups=[webish])

        # point the load balancer to the group for the not-web service
        lb.add_listener("NotWeb",
                        default_target_groups=[notwebish],
                        port=NOT_WEB,
                        protocol=Protocol.TCP)

        # auto scale the, uh, autoscaling group
        asg.scale_on_cpu_utilization("ScaleCPU", target_utilization_percent=80)

        # emit some output values, largely for console use
        CfnOutput(self,
                  "LB",
                  export_name="LB",
                  value=lb.load_balancer_dns_name)
        CfnOutput(self,
                  "HTTP",
                  export_name="HTTP",
                  value="http://{}/".format(HOSTNAME))
        CfnOutput(self,
                  "HTTPS",
                  export_name="HTTPS",
                  value="https://{}/".format(HOSTNAME))
        CfnOutput(self,
                  "TCP",
                  export_name="TCP",
                  value="tcp://{}:{}/".format(HOSTNAME, NOT_WEB))
        CfnOutput(self, "Cert", export_name="Cert", value=cert.certificate_arn)