def add_policy(self, lambda_function: _lambda.Function) -> None: """Add necessary permissions to the lambda function. If you need your functions to have more permissions, you can add them below. Args: lambda_function: The lambda function Returns: None. """ smt1 = _iam.PolicyStatement( resources=[self.db.secret.secret_arn], actions=["secretsmanager:GetSecretValue"], ) lambda_function.add_to_role_policy(smt1) smt2 = _iam.PolicyStatement( resources=[ f"arn:aws:rds:{self.region}:{self.account}:cluster:{self.db.db.ref}" ], actions=[ "rds-data:ExecuteStatement", "rds-data:BatchExecuteStatement", "rds-data:BeginTransaction", "rds-data:CommitTransaction", "rds-data:ExecuteSql", "rds-data:RollbackTransaction", "rds:DescribeDBClusters", ], ) lambda_function.add_to_role_policy(smt2)
class Stack(core.Stack): def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) self.event_bus = EventBus(scope=self, id='CustomEventBus', event_bus_name='CustomEventBus') self.source = Function( scope=self, id=f'SourceFunction', function_name=f'SourceFunction', code=Code.from_asset(path='./code_source/'), handler='index.handler', runtime=Runtime.PYTHON_3_6, ) self.source.add_to_role_policy(statement=PolicyStatement( actions=['events:PutEvents'], resources=[self.event_bus.event_bus_arn])) """ Define rule. """ self.rule = Rule( scope=self, id='EventBusRule', description='Sample description.', enabled=True, event_bus=self.event_bus, event_pattern=EventPattern(detail={ 'Domain': ["MedInfo"], 'Reason': ["InvokeTarget"] }), rule_name='EventBusRule', ) """ Add target. """ self.target = Function( scope=self, id=f'TargetFunction', function_name=f'TargetFunction', code=Code.from_asset(path='./code_target/'), handler='index.handler', runtime=Runtime.PYTHON_3_6, ) self.target: Union[IRuleTarget, LambdaFunction] = LambdaFunction( handler=self.target) self.rule.add_target(target=self.target)
def deploy_aws_ecs_public_dns(self): code_path = join(dirname(dirname(__file__)), 'build', 'aws-ecs-public-dns.zip') func = Function(self._stack, 'public_dns', runtime=Runtime.NODEJS_12_X, handler='src/update-task-dns.handler', memory_size=128, code=Code.from_asset(path=code_path)) self._tag_it(func) func.add_to_role_policy( statement=self.get_public_dns_policy_statement()) self.create_event_rule(func)
def create_ecs_lambda(self, cluster: ICluster, auto_scaling_group: AutoScalingGroup): lambda_func = Function( self, "LambdaECS", code=Code.from_asset("./lambdas/nlb-ecs"), handler="index.lambda_handler", runtime=Runtime.PYTHON_3_8, timeout=Duration.seconds(30), environment={ "AUTO_SCALING_GROUP_NAME": auto_scaling_group.auto_scaling_group_name, }, ) lambda_func.add_to_role_policy( PolicyStatement( actions=[ "autoscaling:DescribeAutoScalingGroups", "ssm:SendCommand", "ssm:GetCommandInvocation", ], resources=[ "*", ], )) Rule( self, "ECS", event_pattern=EventPattern( detail_type=["ECS Task State Change"], detail={ "clusterArn": [cluster.cluster_arn], }, source=["aws.ecs"], ), targets=[LambdaFunction(lambda_func)], )
def __init__(self, scope: Construct, id: str, elasticsearch_index: ElasticsearchIndexResource, dynamodb_table: Table, kms_key: Optional[Key] = None, *, sagemaker_endpoint_name: str = None, sagemaker_endpoint_arn: str = None, sagemaker_embeddings_key: str = None) -> None: super().__init__(scope=scope, id=id) elasticsearch_layer = BElasticsearchLayer( scope=self, name=f"{id}ElasticsearchLayer") if bool(sagemaker_endpoint_name) ^ bool(sagemaker_embeddings_key): raise ValueError( f'In order to use sentence embedding, all of the following enviroment variables are required: ' f'SAGEMAKER_ENDPOINT_NAME, SAGEMAKER_EMBEDDINGS_KEY. ' f'Else, provide none of above.') if sagemaker_endpoint_name and not sagemaker_endpoint_arn: sagemaker_endpoint_arn = self.__resolve_sagemaker_endpoints_arn( '*') optional_sagemaker_parameters = { 'SAGEMAKER_ENDPOINT_NAME': sagemaker_endpoint_name or None, 'SAGEMAKER_EMBEDDINGS_KEY': sagemaker_embeddings_key or None } initial_cloner_function = SingletonFunction( scope=self, id='InitialClonerFunction', uuid='e01116a4-f939-43f2-8f5b-cc9f862c9e01', lambda_purpose='InitialClonerSingletonLambda', code=Code.from_asset(initial_cloner_root), handler='index.handler', runtime=Runtime.PYTHON_3_8, layers=[elasticsearch_layer], log_retention=RetentionDays.ONE_MONTH, memory_size=128, timeout=Duration.minutes(15), role=Role( scope=self, id='InitialClonerFunctionRole', assumed_by=ServicePrincipal('lambda.amazonaws.com'), inline_policies={ 'LogsPolicy': PolicyDocument(statements=[ PolicyStatement( actions=[ 'logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents', 'logs:DescribeLogStreams', ], resources=['arn:aws:logs:*:*:*'], effect=Effect.ALLOW, ) ]), 'ElasticsearchPolicy': PolicyDocument(statements=[ PolicyStatement( actions=[ 'es:ESHttpDelete', 'es:ESHttpGet', 'es:ESHttpHead', 'es:ESHttpPatch', 'es:ESHttpPost', 'es:ESHttpPut', ], resources=['*'], effect=Effect.ALLOW, ) ]), 'DynamodbPolicy': PolicyDocument(statements=[ PolicyStatement( actions=['dynamodb:*'], resources=['*'], effect=Effect.ALLOW, ) ]), }, description='Role for DynamoDB Initial Cloner Function', ), ) if kms_key: initial_cloner_function.add_to_role_policy( PolicyStatement( actions=['kms:Decrypt'], resources=[kms_key.key_arn], effect=Effect.ALLOW, ), ) initial_cloner = CustomResource( scope=self, id='InitialCloner', service_token=initial_cloner_function.function_arn, removal_policy=RemovalPolicy.DESTROY, properties={ 'DynamodbTableName': dynamodb_table.table_name, 'ElasticsearchIndexName': elasticsearch_index.index_name, 'ElasticsearchEndpoint': elasticsearch_index.elasticsearch_domain.domain_endpoint, }, resource_type='Custom::ElasticsearchInitialCloner', ) primary_key_field = initial_cloner.get_att_string('PrimaryKeyField') dynamodb_stream_arn = dynamodb_table.table_stream_arn if not dynamodb_stream_arn: raise Exception('DynamoDB streams must be enabled for the table') dynamodb_event_source = DynamoEventSource( table=dynamodb_table, starting_position=StartingPosition.LATEST, enabled=True, max_batching_window=Duration.seconds(10), bisect_batch_on_error=True, parallelization_factor=2, batch_size=1000, retry_attempts=10, ) cloner_inline_policies = { 'LogsPolicy': PolicyDocument(statements=[ PolicyStatement( actions=[ 'logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents', 'logs:DescribeLogStreams', ], resources=['arn:aws:logs:*:*:*'], effect=Effect.ALLOW, ) ]), 'ElasticsearchPolicy': PolicyDocument(statements=[ PolicyStatement( actions=[ 'es:ESHttpDelete', 'es:ESHttpGet', 'es:ESHttpHead', 'es:ESHttpPatch', 'es:ESHttpPost', 'es:ESHttpPut', ], resources=[ f'{elasticsearch_index.elasticsearch_domain.domain_arn}/*' ], effect=Effect.ALLOW, ) ]), 'DynamodbStreamsPolicy': PolicyDocument(statements=[ PolicyStatement( actions=[ 'dynamodb:DescribeStream', 'dynamodb:GetRecords', 'dynamodb:GetShardIterator', 'dynamodb:ListStreams', ], resources=[dynamodb_stream_arn], effect=Effect.ALLOW, ) ]), } if sagemaker_endpoint_arn: cloner_inline_policies['SagemakerPolicy'] = PolicyDocument( statements=[ PolicyStatement(actions=['sagemaker:InvokeEndpoint'], resources=[sagemaker_endpoint_arn], effect=Effect.ALLOW) ]) cloner_function = Function( scope=self, id='ClonerFunction', code=Code.from_asset(cloner_root), handler='index.handler', runtime=Runtime.PYTHON_3_8, environment={ 'ES_INDEX_NAME': elasticsearch_index.index_name, 'ES_DOMAIN_ENDPOINT': elasticsearch_index.elasticsearch_domain.domain_endpoint, 'PRIMARY_KEY_FIELD': primary_key_field, **{ k: optional_sagemaker_parameters[k] for k in optional_sagemaker_parameters if all(optional_sagemaker_parameters.values( )) } }, events=[dynamodb_event_source], layers=[elasticsearch_layer], log_retention=RetentionDays.ONE_MONTH, memory_size=128, role=Role( scope=self, id='ClonerFunctionRole', assumed_by=ServicePrincipal('lambda.amazonaws.com'), inline_policies=cloner_inline_policies, description='Role for DynamoDB Cloner Function', ), timeout=Duration.seconds(30), ) if kms_key: cloner_function.add_to_role_policy( PolicyStatement( actions=['kms:Decrypt'], resources=[kms_key.key_arn], effect=Effect.ALLOW, ))
def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: super().__init__(scope, id, **kwargs) with open("stack/config.yml", 'r') as stream: configs = yaml.safe_load(stream) ### S3 core images_S3_bucket = _s3.Bucket(self, "ICS_IMAGES") images_S3_bucket.add_cors_rule( allowed_methods=[_s3.HttpMethods.POST], allowed_origins=["*"] # add API gateway web resource URL ) ### SQS core image_deadletter_queue = _sqs.Queue(self, "ICS_IMAGES_DEADLETTER_QUEUE") image_queue = _sqs.Queue(self, "ICS_IMAGES_QUEUE", dead_letter_queue={ "max_receive_count": configs["DeadLetterQueue"]["MaxReceiveCount"], "queue": image_deadletter_queue }) ### api gateway core api_gateway = RestApi(self, 'ICS_API_GATEWAY', rest_api_name='ImageContentSearchApiGateway') api_gateway_resource = api_gateway.root.add_resource(configs["ProjectName"]) api_gateway_landing_page_resource = api_gateway_resource.add_resource('web') api_gateway_get_signedurl_resource = api_gateway_resource.add_resource('signedUrl') api_gateway_image_search_resource = api_gateway_resource.add_resource('search') ### landing page function get_landing_page_function = Function(self, "ICS_GET_LANDING_PAGE", function_name="ICS_GET_LANDING_PAGE", runtime=Runtime.PYTHON_3_7, handler="main.handler", code=Code.asset("./src/landingPage")) get_landing_page_integration = LambdaIntegration( get_landing_page_function, proxy=True, integration_responses=[{ 'statusCode': '200', 'responseParameters': { 'method.response.header.Access-Control-Allow-Origin': "'*'", } }]) api_gateway_landing_page_resource.add_method('GET', get_landing_page_integration, method_responses=[{ 'statusCode': '200', 'responseParameters': { 'method.response.header.Access-Control-Allow-Origin': True, } }]) ### cognito required_attribute = _cognito.StandardAttribute(required=True) users_pool = _cognito.UserPool(self, "ICS_USERS_POOL", auto_verify=_cognito.AutoVerifiedAttrs(email=True), #required for self sign-up standard_attributes=_cognito.StandardAttributes(email=required_attribute), #required for self sign-up self_sign_up_enabled=configs["Cognito"]["SelfSignUp"]) user_pool_app_client = _cognito.CfnUserPoolClient(self, "ICS_USERS_POOL_APP_CLIENT", supported_identity_providers=["COGNITO"], allowed_o_auth_flows=["implicit"], allowed_o_auth_scopes=configs["Cognito"]["AllowedOAuthScopes"], user_pool_id=users_pool.user_pool_id, callback_ur_ls=[api_gateway_landing_page_resource.url], allowed_o_auth_flows_user_pool_client=True, explicit_auth_flows=["ALLOW_REFRESH_TOKEN_AUTH"]) user_pool_domain = _cognito.UserPoolDomain(self, "ICS_USERS_POOL_DOMAIN", user_pool=users_pool, cognito_domain=_cognito.CognitoDomainOptions(domain_prefix=configs["Cognito"]["DomainPrefix"])) ### get signed URL function get_signedurl_function = Function(self, "ICS_GET_SIGNED_URL", function_name="ICS_GET_SIGNED_URL", environment={ "ICS_IMAGES_BUCKET": images_S3_bucket.bucket_name, "DEFAULT_SIGNEDURL_EXPIRY_SECONDS": configs["Functions"]["DefaultSignedUrlExpirySeconds"] }, runtime=Runtime.PYTHON_3_7, handler="main.handler", code=Code.asset("./src/getSignedUrl")) get_signedurl_integration = LambdaIntegration( get_signedurl_function, proxy=True, integration_responses=[{ 'statusCode': '200', 'responseParameters': { 'method.response.header.Access-Control-Allow-Origin': "'*'", } }]) api_gateway_get_signedurl_authorizer = CfnAuthorizer(self, "ICS_API_GATEWAY_GET_SIGNED_URL_AUTHORIZER", rest_api_id=api_gateway_get_signedurl_resource.rest_api.rest_api_id, name="ICS_API_GATEWAY_GET_SIGNED_URL_AUTHORIZER", type="COGNITO_USER_POOLS", identity_source="method.request.header.Authorization", provider_arns=[users_pool.user_pool_arn]) api_gateway_get_signedurl_resource.add_method('GET', get_signedurl_integration, authorization_type=AuthorizationType.COGNITO, method_responses=[{ 'statusCode': '200', 'responseParameters': { 'method.response.header.Access-Control-Allow-Origin': True, } }] ).node.find_child('Resource').add_property_override('AuthorizerId', api_gateway_get_signedurl_authorizer.ref) images_S3_bucket.grant_put(get_signedurl_function, objects_key_pattern="new/*") ### image massage function image_massage_function = Function(self, "ICS_IMAGE_MASSAGE", function_name="ICS_IMAGE_MASSAGE", timeout=core.Duration.seconds(6), runtime=Runtime.PYTHON_3_7, environment={"ICS_IMAGE_MASSAGE": image_queue.queue_name}, handler="main.handler", code=Code.asset("./src/imageMassage")) images_S3_bucket.grant_write(image_massage_function, "processed/*") images_S3_bucket.grant_delete(image_massage_function, "new/*") images_S3_bucket.grant_read(image_massage_function, "new/*") new_image_added_notification = _s3notification.LambdaDestination(image_massage_function) images_S3_bucket.add_event_notification(_s3.EventType.OBJECT_CREATED, new_image_added_notification, _s3.NotificationKeyFilter(prefix="new/") ) image_queue.grant_send_messages(image_massage_function) ### image analyzer function image_analyzer_function = Function(self, "ICS_IMAGE_ANALYSIS", function_name="ICS_IMAGE_ANALYSIS", runtime=Runtime.PYTHON_3_7, timeout=core.Duration.seconds(10), environment={ "ICS_IMAGES_BUCKET": images_S3_bucket.bucket_name, "DEFAULT_MAX_CALL_ATTEMPTS": configs["Functions"]["DefaultMaxApiCallAttempts"], "REGION": core.Aws.REGION, }, handler="main.handler", code=Code.asset("./src/imageAnalysis")) image_analyzer_function.add_event_source(_lambda_event_source.SqsEventSource(queue=image_queue, batch_size=10)) image_queue.grant_consume_messages(image_massage_function) lambda_rekognition_access = _iam.PolicyStatement( effect=_iam.Effect.ALLOW, actions=["rekognition:DetectLabels", "rekognition:DetectModerationLabels"], resources=["*"] ) image_analyzer_function.add_to_role_policy(lambda_rekognition_access) images_S3_bucket.grant_read(image_analyzer_function, "processed/*") ### API gateway finalizing self.add_cors_options(api_gateway_get_signedurl_resource) self.add_cors_options(api_gateway_landing_page_resource) self.add_cors_options(api_gateway_image_search_resource) ### database database_secret = _secrets_manager.Secret(self, "ICS_DATABASE_SECRET", secret_name="rds-db-credentials/image-content-search-rds-secret", generate_secret_string=_secrets_manager.SecretStringGenerator( generate_string_key='password', secret_string_template='{"username": "******"}', exclude_punctuation=True, exclude_characters='/@\" \\\'', require_each_included_type=True ) ) database = _rds.CfnDBCluster(self, "ICS_DATABASE", engine=_rds.DatabaseClusterEngine.aurora_mysql(version=_rds.AuroraMysqlEngineVersion.VER_5_7_12).engine_type, engine_mode="serverless", database_name=configs["Database"]["Name"], enable_http_endpoint=True, deletion_protection=configs["Database"]["DeletionProtection"], master_username=database_secret.secret_value_from_json("username").to_string(), master_user_password=database_secret.secret_value_from_json("password").to_string(), scaling_configuration=_rds.CfnDBCluster.ScalingConfigurationProperty( auto_pause=configs["Database"]["Scaling"]["AutoPause"], min_capacity=configs["Database"]["Scaling"]["Min"], max_capacity=configs["Database"]["Scaling"]["Max"], seconds_until_auto_pause=configs["Database"]["Scaling"]["SecondsToAutoPause"] ), ) database_cluster_arn = "arn:aws:rds:{}:{}:cluster:{}".format(core.Aws.REGION, core.Aws.ACCOUNT_ID, database.ref) secret_target = _secrets_manager.CfnSecretTargetAttachment(self,"ICS_DATABASE_SECRET_TARGET", target_type="AWS::RDS::DBCluster", target_id=database.ref, secret_id=database_secret.secret_arn ) secret_target.node.add_dependency(database) ### database function image_data_function_role = _iam.Role(self, "ICS_IMAGE_DATA_FUNCTION_ROLE", role_name="ICS_IMAGE_DATA_FUNCTION_ROLE", assumed_by=_iam.ServicePrincipal("lambda.amazonaws.com"), managed_policies=[ _iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AWSLambdaVPCAccessExecutionRole"), _iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AWSLambdaBasicExecutionRole"), _iam.ManagedPolicy.from_aws_managed_policy_name("AmazonRDSDataFullAccess") ] ) image_data_function = Function(self, "ICS_IMAGE_DATA", function_name="ICS_IMAGE_DATA", runtime=Runtime.PYTHON_3_7, timeout=core.Duration.seconds(5), role=image_data_function_role, environment={ "DEFAULT_MAX_CALL_ATTEMPTS": configs["Functions"]["DefaultMaxApiCallAttempts"], "CLUSTER_ARN": database_cluster_arn, "CREDENTIALS_ARN": database_secret.secret_arn, "DB_NAME": database.database_name, "REGION": core.Aws.REGION }, handler="main.handler", code=Code.asset("./src/imageData") ) image_search_integration = LambdaIntegration( image_data_function, proxy=True, integration_responses=[{ 'statusCode': '200', 'responseParameters': { 'method.response.header.Access-Control-Allow-Origin': "'*'", } }]) api_gateway_image_search_authorizer = CfnAuthorizer(self, "ICS_API_GATEWAY_IMAGE_SEARCH_AUTHORIZER", rest_api_id=api_gateway_image_search_resource.rest_api.rest_api_id, name="ICS_API_GATEWAY_IMAGE_SEARCH_AUTHORIZER", type="COGNITO_USER_POOLS", identity_source="method.request.header.Authorization", provider_arns=[users_pool.user_pool_arn]) api_gateway_image_search_resource.add_method('POST', image_search_integration, authorization_type=AuthorizationType.COGNITO, method_responses=[{ 'statusCode': '200', 'responseParameters': { 'method.response.header.Access-Control-Allow-Origin': True, } }] ).node.find_child('Resource').add_property_override('AuthorizerId', api_gateway_image_search_authorizer.ref) lambda_access_search = _iam.PolicyStatement( effect=_iam.Effect.ALLOW, actions=["translate:TranslateText"], resources=["*"] ) image_data_function.add_to_role_policy(lambda_access_search) ### custom resource lambda_provider = Provider(self, 'ICS_IMAGE_DATA_PROVIDER', on_event_handler=image_data_function ) core.CustomResource(self, 'ICS_IMAGE_DATA_RESOURCE', service_token=lambda_provider.service_token, pascal_case_properties=False, resource_type="Custom::SchemaCreation", properties={ "source": "Cloudformation" } ) ### event bridge event_bus = _events.EventBus(self, "ICS_IMAGE_CONTENT_BUS") event_rule = _events.Rule(self, "ICS_IMAGE_CONTENT_RULE", rule_name="ICS_IMAGE_CONTENT_RULE", description="The event from image analyzer to store the data", event_bus=event_bus, event_pattern=_events.EventPattern(resources=[image_analyzer_function.function_arn]), ) event_rule.add_target(_event_targets.LambdaFunction(image_data_function)) event_bus.grant_put_events(image_analyzer_function) image_analyzer_function.add_environment("EVENT_BUS", event_bus.event_bus_name) ### outputs core.CfnOutput(self, 'CognitoHostedUILogin', value='https://{}.auth.{}.amazoncognito.com/login?client_id={}&response_type=token&scope={}&redirect_uri={}'.format(user_pool_domain.domain_name, core.Aws.REGION, user_pool_app_client.ref, '+'.join(user_pool_app_client.allowed_o_auth_scopes), api_gateway_landing_page_resource.url), description='The Cognito Hosted UI Login Page' )
def __init__( self, scope: Construct, id: str, *, vpc: IVpc, cluster: ICluster, service: IEc2Service, ecs_security_group: SecurityGroup, deployment: Deployment, **kwargs, ) -> None: super().__init__(scope, id, **kwargs) Tags.of(self).add("Application", self.application_name) Tags.of(self).add("Deployment", deployment.value) security_group = SecurityGroup( self, "LambdaSG", vpc=vpc, ) lambda_func = Function( self, "ReloadLambda", code=Code.from_asset("./lambdas/bananas-reload"), handler="index.lambda_handler", runtime=Runtime.PYTHON_3_8, timeout=Duration.seconds(120), environment={ "CLUSTER": cluster.cluster_arn, "SERVICE": service.service_arn, }, vpc=vpc, security_groups=[security_group, ecs_security_group], reserved_concurrent_executions=1, ) lambda_func.add_to_role_policy( PolicyStatement( actions=[ "ec2:DescribeInstances", "ecs:DescribeContainerInstances", "ecs:DescribeTasks", "ecs:ListContainerInstances", "ecs:ListServices", "ecs:ListTagsForResource", "ecs:ListTasks", ], resources=[ "*", ], ) ) policy = ManagedPolicy(self, "Policy") policy.add_statements( PolicyStatement( actions=[ "lambda:InvokeFunction", ], resources=[lambda_func.function_arn], ) )
def __init__( self, scope: Construct, id: str, elasticsearch_index: ElasticsearchIndexResource, dynamodb_table: Table, kms_key: Optional[Key] = None, ) -> None: super().__init__(scope=scope, id=id) elasticsearch_layer = BElasticsearchLayer( scope=self, name=f"{id}ElasticsearchLayer") initial_cloner_function = SingletonFunction( scope=self, id="InitialClonerFunction", uuid="e01116a4-f939-43f2-8f5b-cc9f862c9e01", lambda_purpose="InitialClonerSingletonLambda", code=Code.from_asset(initial_cloner_root), handler="index.handler", runtime=Runtime.PYTHON_3_8, layers=[elasticsearch_layer], log_retention=RetentionDays.ONE_MONTH, memory_size=128, timeout=Duration.minutes(15), role=Role( scope=self, id="InitialClonerFunctionRole", assumed_by=ServicePrincipal("lambda.amazonaws.com"), inline_policies={ "LogsPolicy": PolicyDocument(statements=[ PolicyStatement( actions=[ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", "logs:DescribeLogStreams", ], resources=["arn:aws:logs:*:*:*"], effect=Effect.ALLOW, ) ]), "ElasticsearchPolicy": PolicyDocument(statements=[ PolicyStatement( actions=[ "es:ESHttpDelete", "es:ESHttpGet", "es:ESHttpHead", "es:ESHttpPatch", "es:ESHttpPost", "es:ESHttpPut", ], resources=["*"], effect=Effect.ALLOW, ) ]), "DynamodbPolicy": PolicyDocument(statements=[ PolicyStatement( actions=["dynamodb:*"], resources=["*"], effect=Effect.ALLOW, ) ]), }, description="Role for DynamoDB Initial Cloner Function", ), ) if kms_key: initial_cloner_function.add_to_role_policy( PolicyStatement( actions=["kms:Decrypt"], resources=[kms_key.key_arn], effect=Effect.ALLOW, ), ) initial_cloner = CustomResource( scope=self, id="InitialCloner", service_token=initial_cloner_function.function_arn, removal_policy=RemovalPolicy.DESTROY, properties={ "DynamodbTableName": dynamodb_table.table_name, "ElasticsearchIndexName": elasticsearch_index.index_name, "ElasticsearchEndpoint": elasticsearch_index.elasticsearch_domain.domain_endpoint, }, resource_type="Custom::ElasticsearchInitialCloner", ) primary_key_field = initial_cloner.get_att_string("PrimaryKeyField") dynamodb_stream_arn = dynamodb_table.table_stream_arn if not dynamodb_stream_arn: raise Exception("DynamoDB streams must be enabled for the table") dynamodb_event_source = DynamoEventSource( table=dynamodb_table, starting_position=StartingPosition.LATEST, enabled=True, max_batching_window=Duration.seconds(10), bisect_batch_on_error=True, parallelization_factor=2, batch_size=1000, retry_attempts=10, ) cloner_function = Function( scope=self, id="ClonerFunction", code=Code.from_asset(cloner_root), handler="index.handler", runtime=Runtime.PYTHON_3_8, environment={ "ES_INDEX_NAME": elasticsearch_index.index_name, "ES_DOMAIN_ENDPOINT": elasticsearch_index.elasticsearch_domain.domain_endpoint, "PRIMARY_KEY_FIELD": primary_key_field, }, events=[dynamodb_event_source], layers=[elasticsearch_layer], log_retention=RetentionDays.ONE_MONTH, memory_size=128, role=Role( scope=self, id="ClonerFunctionRole", assumed_by=ServicePrincipal("lambda.amazonaws.com"), inline_policies={ "LogsPolicy": PolicyDocument(statements=[ PolicyStatement( actions=[ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", "logs:DescribeLogStreams", ], resources=["arn:aws:logs:*:*:*"], effect=Effect.ALLOW, ) ]), "ElasticsearchPolicy": PolicyDocument(statements=[ PolicyStatement( actions=[ "es:ESHttpDelete", "es:ESHttpGet", "es:ESHttpHead", "es:ESHttpPatch", "es:ESHttpPost", "es:ESHttpPut", ], resources=[ f"{elasticsearch_index.elasticsearch_domain.domain_arn}/*" ], effect=Effect.ALLOW, ) ]), "DynamodbStreamsPolicy": PolicyDocument(statements=[ PolicyStatement( actions=[ "dynamodb:DescribeStream", "dynamodb:GetRecords", "dynamodb:GetShardIterator", "dynamodb:ListStreams", ], resources=[dynamodb_stream_arn], effect=Effect.ALLOW, ) ]), }, description="Role for DynamoDB Cloner Function", ), timeout=Duration.seconds(30), ) if kms_key: cloner_function.add_to_role_policy( PolicyStatement( actions=["kms:Decrypt"], resources=[kms_key.key_arn], effect=Effect.ALLOW, ), )
def create_asg_lambda( self, lifecycle_transition: LifecycleTransition, timeout: Duration, vpc: IVpc, security_group: SecurityGroup, auto_scaling_group: AutoScalingGroup, ) -> None: if lifecycle_transition == LifecycleTransition.INSTANCE_LAUNCHING: name = "Launch" else: name = "Terminate" lambda_func = Function( self, f"Lambda{name}", code=Code.from_asset("./lambdas/nlb-asg-lch"), handler="index.lambda_handler", runtime=Runtime.PYTHON_3_8, timeout=Duration.seconds( timeout.to_seconds() + 20), # Slightly more than the Lifecycle Hook timeout environment={ "DOMAIN_NAME": dns.subdomain_to_fqdn(self.subdomain_name), "HOSTED_ZONE_ID": dns.get_hosted_zone().hosted_zone_id, "PRIVATE_DOMAIN_NAME": f"{self.subdomain_name}.openttd.internal", "PRIVATE_HOSTED_ZONE_ID": self.private_zone.hosted_zone_id, }, vpc=vpc, security_groups=[security_group], ) auto_scaling_group.add_lifecycle_hook( f"LH{name}", lifecycle_transition=lifecycle_transition, notification_target=FunctionHook(lambda_func), default_result=DefaultResult.ABANDON if lifecycle_transition == LifecycleTransition.INSTANCE_LAUNCHING else DefaultResult.CONTINUE, heartbeat_timeout=timeout, ) lambda_func.add_to_role_policy( PolicyStatement( actions=[ "ec2:DescribeInstances", "autoscaling:DescribeAutoScalingGroups", ], resources=[ "*", ], )) lambda_func.add_to_role_policy( PolicyStatement( actions=[ "autoscaling:CompleteLifecycleAction", ], resources=[ auto_scaling_group.auto_scaling_group_arn, ], )) lambda_func.add_to_role_policy( PolicyStatement( actions=[ "route53:GetChange", "route53:ChangeResourceRecordSets" ], resources=[ dns.get_hosted_zone().hosted_zone_arn, self.private_zone.hosted_zone_arn, "arn:aws:route53:::change/*", ], ))