def get_policy_statements(self): if not self.SourceAccessConfigurations: raise InvalidEventException( self.relative_id, "No SourceAccessConfigurations for ActiveMQ provided.", ) if not type(self.SourceAccessConfigurations) is list: raise InvalidEventException( self.relative_id, "Provided SourceAccessConfigurations cannot be parsed into a list.", ) # MQ only supports SourceAccessConfigurations with list size of 1 if not (len(self.SourceAccessConfigurations) == 1): raise InvalidEventException( self.relative_id, "SourceAccessConfigurations for ActiveMQ only supports single configuration entry.", ) if not self.SourceAccessConfigurations[0].get("URI"): raise InvalidEventException( self.relative_id, "No URI property specified in SourceAccessConfigurations for ActiveMQ.", ) document = { "PolicyName": "SamAutoGeneratedAMQPolicy", "PolicyDocument": { "Statement": [ { "Action": [ "secretsmanager:GetSecretValue", ], "Effect": "Allow", "Resource": self.SourceAccessConfigurations[0].get("URI"), }, { "Action": [ "mq:DescribeBroker", ], "Effect": "Allow", "Resource": self.Broker, }, ] }, } if self.SecretsManagerKmsKeyId: kms_policy = { "Action": "kms:Decrypt", "Effect": "Allow", "Resource": { "Fn::Sub": "arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/" + self.SecretsManagerKmsKeyId }, } document["PolicyDocument"]["Statement"].append(kms_policy) return [document]
def to_cloudformation(self, **kwargs): """Returns the Lambda EventSourceMapping to which this pull event corresponds. Adds the appropriate managed policy to the function's execution role, if such a role is provided. :param dict kwargs: a dict containing the execution role generated for the function :returns: a list of vanilla CloudFormation Resources, to which this pull event expands :rtype: list """ function = kwargs.get('function') if not function: raise TypeError("Missing required keyword argument: function") resources = [] lambda_eventsourcemapping = LambdaEventSourceMapping(self.logical_id) resources.append(lambda_eventsourcemapping) try: # Name will not be available for Alias resources function_name_or_arn = function.get_runtime_attr("name") except NotImplementedError: function_name_or_arn = function.get_runtime_attr("arn") if not self.Stream and not self.Queue: raise InvalidEventException( self.relative_id, "No Queue (for SQS) or Stream (for Kinesis or DynamoDB) provided." ) if self.Stream and not self.StartingPosition: raise InvalidEventException( self.relative_id, "StartingPosition is required for Kinesis and DynamoDB.") lambda_eventsourcemapping.FunctionName = function_name_or_arn lambda_eventsourcemapping.EventSourceArn = self.Stream or self.Queue lambda_eventsourcemapping.StartingPosition = self.StartingPosition lambda_eventsourcemapping.BatchSize = self.BatchSize lambda_eventsourcemapping.Enabled = self.Enabled lambda_eventsourcemapping.MaximumBatchingWindowInSeconds = self.MaximumBatchingWindowInSeconds lambda_eventsourcemapping.MaximumRetryAttempts = self.MaximumRetryAttempts lambda_eventsourcemapping.BisectBatchOnFunctionError = self.BisectBatchOnFunctionError lambda_eventsourcemapping.MaximumRecordAgeInSeconds = self.MaximumRecordAgeInSeconds lambda_eventsourcemapping.DestinationConfig = self.DestinationConfig lambda_eventsourcemapping.ParallelizationFactor = self.ParallelizationFactor if 'Condition' in function.resource_attributes: lambda_eventsourcemapping.set_resource_attribute( 'Condition', function.resource_attributes['Condition']) if 'role' in kwargs: self._link_policy(kwargs['role']) return resources
def _add_auth_to_openapi_integration(self, api, editor): """Adds authorization to the lambda integration :param api: api object :param editor: OpenApiEditor object that contains the OpenApi definition """ method_authorizer = self.Auth.get("Authorizer") api_auth = api.get("Auth") if not method_authorizer: if api_auth.get("DefaultAuthorizer"): self.Auth["Authorizer"] = method_authorizer = api_auth.get("DefaultAuthorizer") else: # currently, we require either a default auth or auth in the method raise InvalidEventException( self.relative_id, "'Auth' section requires either " "an explicit 'Authorizer' set or a 'DefaultAuthorizer' " "configured on the HttpApi.", ) # Default auth should already be applied, so apply any other auth here or scope override to default api_authorizers = api_auth and api_auth.get("Authorizers") if method_authorizer != "NONE" and not api_authorizers: raise InvalidEventException( self.relative_id, "Unable to set Authorizer [{authorizer}] on API method [{method}] for path [{path}] " "because the related API does not define any Authorizers.".format( authorizer=method_authorizer, method=self.Method, path=self.Path ), ) if method_authorizer != "NONE" and not api_authorizers.get(method_authorizer): raise InvalidEventException( self.relative_id, "Unable to set Authorizer [{authorizer}] on API method [{method}] for path [{path}] " "because it wasn't defined in the API's Authorizers.".format( authorizer=method_authorizer, method=self.Method, path=self.Path ), ) if method_authorizer == "NONE" and not api_auth.get("DefaultAuthorizer"): raise InvalidEventException( self.relative_id, "Unable to set Authorizer on API method [{method}] for path [{path}] because 'NONE' " "is only a valid value when a DefaultAuthorizer on the API is specified.".format( method=self.Method, path=self.Path ), ) if self.Auth.get("AuthorizationScopes") and not isinstance(self.Auth.get("AuthorizationScopes"), list): raise InvalidEventException( self.relative_id, "Unable to set Authorizer on API method [{method}] for path [{path}] because " "'AuthorizationScopes' must be a list of strings.".format(method=self.Method, path=self.Path), ) editor.add_auth_to_method(api=api, path=self.Path, method_name=self.Method, auth=self.Auth)
def _generate_event_resources(self): """Generates and returns the resources associated with this state machine's event sources. :returns: a list containing the state machine's event resources :rtype: list """ resources = [] if self.events: for logical_id, event_dict in self.events.items(): kwargs = { "intrinsics_resolver": self.intrinsics_resolver, "permissions_boundary": self.permissions_boundary, } try: eventsource = self.event_resolver.resolve_resource_type( event_dict).from_dict( self.state_machine.logical_id + logical_id, event_dict, logical_id) for name, resource in self.event_resources[ logical_id].items(): kwargs[name] = resource except (TypeError, AttributeError) as e: raise InvalidEventException(logical_id, "{}".format(e)) resources += eventsource.to_cloudformation( resource=self.state_machine, **kwargs) return resources
def _add_swagger_integration(self, api, function): """Adds the path and method for this Api event source to the Swagger body for the provided RestApi. :param model.apigateway.ApiGatewayRestApi rest_api: the RestApi to which the path and method should be added. """ swagger_body = api.get("DefinitionBody") if swagger_body is None: return function_arn = function.get_runtime_attr('arn') partition = ArnGenerator.get_partition_name() uri = fnSub( 'arn:' + partition + ':apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/' + make_shorthand(function_arn) + '/invocations') editor = SwaggerEditor(swagger_body) if editor.has_integration(self.Path, self.Method): # Cannot add the Lambda Integration, if it is already present raise InvalidEventException( self.relative_id, 'API method "{method}" defined multiple times for path "{path}".' .format(method=self.Method, path=self.Path)) editor.add_lambda_integration(self.Path, self.Method, uri) api["DefinitionBody"] = editor.swagger
def _inject_lambda_config(self, function, userpool): event_triggers = self.Trigger if isinstance(self.Trigger, string_types): event_triggers = [self.Trigger] # TODO can these be conditional? properties = userpool.get('Properties', None) if properties is None: properties = {} userpool['Properties'] = properties lambda_config = properties.get('LambdaConfig', None) if lambda_config is None: lambda_config = {} properties['LambdaConfig'] = lambda_config for event_trigger in event_triggers: if event_trigger not in lambda_config: lambda_config[event_trigger] = function.get_runtime_attr("arn") else: raise InvalidEventException( self.relative_id, 'Cognito trigger "{trigger}" defined multiple times.'.format( trigger=self.Trigger)) return userpool
def _add_openapi_integration(self, api, function, manage_swagger=False): """Adds the path and method for this Api event source to the OpenApi body for the provided RestApi. :param model.apigateway.ApiGatewayRestApi rest_api: the RestApi to which the path and method should be added. """ open_api_body = api.get("DefinitionBody") if open_api_body is None: return function_arn = function.get_runtime_attr('arn') uri = fnSub('arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/' + make_shorthand(function_arn) + '/invocations') editor = OpenApiEditor(open_api_body) if manage_swagger and editor.has_integration(self.Path, self.Method): # Cannot add the Lambda Integration, if it is already present raise InvalidEventException( self.relative_id, "API method '{method}' defined multiple times for path '{path}'.".format( method=self.Method, path=self.Path)) condition = None if CONDITION in function.resource_attributes: condition = function.resource_attributes[CONDITION] editor.add_lambda_integration(self.Path, self.Method, uri, self.Auth, api.get('Auth'), condition=condition) if self.Auth: self._add_auth_to_openapi_integration(api, editor) api["DefinitionBody"] = editor.openapi
def _generate_event_resources(self, lambda_function, execution_role, event_resources, lambda_alias=None): """Generates and returns the resources associated with this function's events. :param model.lambda_.LambdaFunction lambda_function: generated Lambda function :param iam.IAMRole execution_role: generated Lambda execution role :param implicit_api: Global Implicit API resource where the implicit APIs get attached to, if necessary :param implicit_api_stage: Global implicit API stage resource where implicit APIs get attached to, if necessary :param event_resources: All the event sources associated with this Lambda function :param model.lambda_.LambdaAlias lambda_alias: Optional Lambda Alias resource if we want to connect the event sources to this alias :returns: a list containing the function's event resources :rtype: list """ resources = [] if self.Events: for logical_id, event_dict in self.Events.items(): try: eventsource = self.event_resolver.resolve_resource_type(event_dict).from_dict( lambda_function.logical_id + logical_id, event_dict, logical_id) except TypeError as e: raise InvalidEventException(logical_id, "{}".format(e)) kwargs = { # When Alias is provided, connect all event sources to the alias and *not* the function 'function': lambda_alias or lambda_function, 'role': execution_role, } for name, resource in event_resources[logical_id].items(): kwargs[name] = resource resources += eventsource.to_cloudformation(**kwargs) return resources
def resources_to_link(self, resources): if isinstance(self.Bucket, dict) and 'Ref' in self.Bucket: bucket_id = self.Bucket['Ref'] if bucket_id in resources: return {'bucket': resources[bucket_id], 'bucket_id': bucket_id} raise InvalidEventException( self.relative_id, "S3 events must reference an S3 bucket in the same template.")
def resources_to_link(self, resources): if isinstance(self.UserPool, dict) and "Ref" in self.UserPool: userpool_id = self.UserPool["Ref"] if userpool_id in resources: return {"userpool": resources[userpool_id], "userpool_id": userpool_id} raise InvalidEventException( self.relative_id, "Cognito events must reference a Cognito UserPool in the same template." )
def validate_dlq_config(source_logical_id, dead_letter_config): supported_types = ["SQS"] is_arn_defined = "Arn" in dead_letter_config is_type_defined = "Type" in dead_letter_config if is_arn_defined and is_type_defined: raise InvalidEventException( source_logical_id, "You can either define 'Arn' or 'Type' property of DeadLetterConfig" ) if is_type_defined and dead_letter_config.get( "Type") not in supported_types: raise InvalidEventException( source_logical_id, "The only valid value for 'Type' property of DeadLetterConfig is 'SQS'", ) if not is_arn_defined and not is_type_defined: raise InvalidEventException( source_logical_id, "No 'Arn' or 'Type' property provided for DeadLetterConfig")
def _event_resources_to_link(self, resources): event_resources = {} if self.Events: for logical_id, event_dict in self.Events.items(): try: event_source = self.event_resolver.resolve_resource_type(event_dict).from_dict( self.logical_id + logical_id, event_dict, logical_id) except (TypeError, AttributeError) as e: raise InvalidEventException(logical_id, "{}".format(e)) event_resources[logical_id] = event_source.resources_to_link(resources) return event_resources
def resources_to_link(self, resources): """ If this API Event Source refers to an explicit API resource, resolve the reference and grab necessary data from the explicit API """ rest_api_id = self.RestApiId if isinstance(rest_api_id, dict) and "Ref" in rest_api_id: rest_api_id = rest_api_id["Ref"] # If RestApiId is a resource in the same template, then we try find the StageName by following the reference # Otherwise we default to a wildcard. This stage name is solely used to construct the permission to # allow this stage to invoke the Lambda function. If we are unable to resolve the stage name, we will # simply permit all stages to invoke this Lambda function # This hack is necessary because customers could use !ImportValue, !Ref or other intrinsic functions which # can be sometimes impossible to resolve (ie. when it has cross-stack references) permitted_stage = "*" stage_suffix = "AllStages" explicit_api = None if isinstance(rest_api_id, string_types): if rest_api_id in resources \ and "Properties" in resources[rest_api_id] \ and "StageName" in resources[rest_api_id]["Properties"]: explicit_api = resources[rest_api_id]["Properties"] permitted_stage = explicit_api["StageName"] # Stage could be a intrinsic, in which case leave the suffix to default value if isinstance(permitted_stage, string_types): if not permitted_stage: raise InvalidResourceException( rest_api_id, 'StageName cannot be empty.') stage_suffix = permitted_stage else: stage_suffix = "Stage" else: # RestApiId is a string, not an intrinsic, but we did not find a valid API resource for this ID raise InvalidEventException( self.relative_id, "RestApiId property of Api event must reference a valid " "resource in the same template.") return { 'explicit_api': explicit_api, 'explicit_api_stage': { 'permitted_stage': permitted_stage, 'suffix': stage_suffix } }
def to_cloudformation(self, **kwargs): """Returns the Lambda EventSourceMapping to which this pull event corresponds. Adds the appropriate managed policy to the function's execution role, if such a role is provided. :param dict kwargs: a dict containing the execution role generated for the function :returns: a list of vanilla CloudFormation Resources, to which this pull event expands :rtype: list """ function = kwargs.get("function") if not function: raise TypeError("Missing required keyword argument: function") resources = [] lambda_eventsourcemapping = LambdaEventSourceMapping(self.logical_id) resources.append(lambda_eventsourcemapping) try: # Name will not be available for Alias resources function_name_or_arn = function.get_runtime_attr("name") except NotImplementedError: function_name_or_arn = function.get_runtime_attr("arn") if not self.Stream and not self.Queue: raise InvalidEventException( self.relative_id, "No Queue (for SQS) or Stream (for Kinesis or DynamoDB) provided." ) if self.Stream and not self.StartingPosition: raise InvalidEventException( self.relative_id, "StartingPosition is required for Kinesis and DynamoDB.") lambda_eventsourcemapping.FunctionName = function_name_or_arn lambda_eventsourcemapping.EventSourceArn = self.Stream or self.Queue lambda_eventsourcemapping.StartingPosition = self.StartingPosition lambda_eventsourcemapping.BatchSize = self.BatchSize lambda_eventsourcemapping.Enabled = self.Enabled lambda_eventsourcemapping.MaximumBatchingWindowInSeconds = self.MaximumBatchingWindowInSeconds lambda_eventsourcemapping.MaximumRetryAttempts = self.MaximumRetryAttempts lambda_eventsourcemapping.BisectBatchOnFunctionError = self.BisectBatchOnFunctionError lambda_eventsourcemapping.MaximumRecordAgeInSeconds = self.MaximumRecordAgeInSeconds lambda_eventsourcemapping.ParallelizationFactor = self.ParallelizationFactor destination_config_policy = None if self.DestinationConfig: # `Type` property is for sam to attach the right policies destination_type = self.DestinationConfig.get("OnFailure").get( "Type") # SAM attaches the policies for SQS or SNS only if 'Type' is given if destination_type: # delete this field as its used internally for SAM to determine the policy del self.DestinationConfig["OnFailure"]["Type"] # the values 'SQS' and 'SNS' are allowed. No intrinsics are allowed if destination_type not in ["SQS", "SNS"]: raise InvalidEventException( self.logical_id, "The only valid values for 'Type' are 'SQS' and 'SNS'") if self.DestinationConfig.get("OnFailure") is None: raise InvalidEventException( self.logical_id, "'OnFailure' is a required field for " "'DestinationConfig'") if destination_type == "SQS": queue_arn = self.DestinationConfig.get("OnFailure").get( "Destination") destination_config_policy = IAMRolePolicies( ).sqs_send_message_role_policy(queue_arn, self.logical_id) elif destination_type == "SNS": sns_topic_arn = self.DestinationConfig.get( "OnFailure").get("Destination") destination_config_policy = IAMRolePolicies( ).sns_publish_role_policy(sns_topic_arn, self.logical_id) lambda_eventsourcemapping.DestinationConfig = self.DestinationConfig if "Condition" in function.resource_attributes: lambda_eventsourcemapping.set_resource_attribute( "Condition", function.resource_attributes["Condition"]) if "role" in kwargs: self._link_policy(kwargs["role"], destination_config_policy) return resources
def _add_swagger_integration(self, api, resource, role, intrinsics_resolver): """Adds the path and method for this Api event source to the Swagger body for the provided RestApi. :param model.apigateway.ApiGatewayRestApi rest_api: the RestApi to which the path and method should be added. """ swagger_body = api.get("DefinitionBody") if swagger_body is None: return resource_arn = resource.get_runtime_attr("arn") integration_uri = fnSub( "arn:${AWS::Partition}:apigateway:${AWS::Region}:states:action/StartExecution" ) editor = SwaggerEditor(swagger_body) if editor.has_integration(self.Path, self.Method): # Cannot add the integration, if it is already present raise InvalidEventException( self.relative_id, 'API method "{method}" defined multiple times for path "{path}".' .format(method=self.Method, path=self.Path), ) condition = None if CONDITION in resource.resource_attributes: condition = resource.resource_attributes[CONDITION] editor.add_state_machine_integration( self.Path, self.Method, integration_uri, role.get_runtime_attr("arn"), self._generate_request_template(resource), condition=condition, ) # Note: Refactor and combine the section below with the Api eventsource for functions if self.Auth: method_authorizer = self.Auth.get("Authorizer") api_auth = api.get("Auth") api_auth = intrinsics_resolver.resolve_parameter_refs(api_auth) if method_authorizer: api_authorizers = api_auth and api_auth.get("Authorizers") if method_authorizer != "AWS_IAM": if method_authorizer != "NONE" and not api_authorizers: raise InvalidEventException( self.relative_id, "Unable to set Authorizer [{authorizer}] on API method [{method}] for path [{path}] " "because the related API does not define any Authorizers." .format(authorizer=method_authorizer, method=self.Method, path=self.Path), ) if method_authorizer != "NONE" and not api_authorizers.get( method_authorizer): raise InvalidEventException( self.relative_id, "Unable to set Authorizer [{authorizer}] on API method [{method}] for path [{path}] " "because it wasn't defined in the API's Authorizers." .format(authorizer=method_authorizer, method=self.Method, path=self.Path), ) if method_authorizer == "NONE": if not api_auth or not api_auth.get( "DefaultAuthorizer"): raise InvalidEventException( self.relative_id, "Unable to set Authorizer on API method [{method}] for path [{path}] because 'NONE' " "is only a valid value when a DefaultAuthorizer on the API is specified." .format(method=self.Method, path=self.Path), ) if self.Auth.get("AuthorizationScopes") and not isinstance( self.Auth.get("AuthorizationScopes"), list): raise InvalidEventException( self.relative_id, "Unable to set Authorizer on API method [{method}] for path [{path}] because " "'AuthorizationScopes' must be a list of strings.".format( method=self.Method, path=self.Path), ) apikey_required_setting = self.Auth.get("ApiKeyRequired") apikey_required_setting_is_false = apikey_required_setting is not None and not apikey_required_setting if apikey_required_setting_is_false and ( not api_auth or not api_auth.get("ApiKeyRequired")): raise InvalidEventException( self.relative_id, "Unable to set ApiKeyRequired [False] on API method [{method}] for path [{path}] " "because the related API does not specify any ApiKeyRequired." .format(method=self.Method, path=self.Path), ) if method_authorizer or apikey_required_setting is not None: editor.add_auth_to_method(api=api, path=self.Path, method_name=self.Method, auth=self.Auth) if self.Auth.get("ResourcePolicy"): resource_policy = self.Auth.get("ResourcePolicy") editor.add_resource_policy(resource_policy=resource_policy, path=self.Path, api_id=self.RestApiId.get("Ref"), stage=self.Stage) if resource_policy.get("CustomStatements"): editor.add_custom_statements( resource_policy.get("CustomStatements")) api["DefinitionBody"] = editor.swagger
def _add_swagger_integration(self, api, function): """Adds the path and method for this Api event source to the Swagger body for the provided RestApi. :param model.apigateway.ApiGatewayRestApi rest_api: the RestApi to which the path and method should be added. """ swagger_body = api.get("DefinitionBody") if swagger_body is None: return function_arn = function.get_runtime_attr('arn') partition = ArnGenerator.get_partition_name() uri = fnSub('arn:' + partition + ':apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/' + make_shorthand(function_arn) + '/invocations') editor = SwaggerEditor(swagger_body) if editor.has_integration(self.Path, self.Method): # Cannot add the Lambda Integration, if it is already present raise InvalidEventException( self.relative_id, 'API method "{method}" defined multiple times for path "{path}".'.format( method=self.Method, path=self.Path)) condition = None if CONDITION in function.resource_attributes: condition = function.resource_attributes[CONDITION] editor.add_lambda_integration(self.Path, self.Method, uri, self.Auth, api.get('Auth'), condition=condition) if self.Auth: method_authorizer = self.Auth.get('Authorizer') api_auth = api.get('Auth') if method_authorizer: api_authorizers = api_auth and api_auth.get('Authorizers') if method_authorizer != 'AWS_IAM': if method_authorizer != 'NONE' and not api_authorizers: raise InvalidEventException( self.relative_id, 'Unable to set Authorizer [{authorizer}] on API method [{method}] for path [{path}] ' 'because the related API does not define any Authorizers.'.format( authorizer=method_authorizer, method=self.Method, path=self.Path)) if method_authorizer != 'NONE' and not api_authorizers.get(method_authorizer): raise InvalidEventException( self.relative_id, 'Unable to set Authorizer [{authorizer}] on API method [{method}] for path [{path}] ' 'because it wasn\'t defined in the API\'s Authorizers.'.format( authorizer=method_authorizer, method=self.Method, path=self.Path)) if method_authorizer == 'NONE' and not api_auth.get('DefaultAuthorizer'): raise InvalidEventException( self.relative_id, 'Unable to set Authorizer on API method [{method}] for path [{path}] because \'NONE\' ' 'is only a valid value when a DefaultAuthorizer on the API is specified.'.format( method=self.Method, path=self.Path)) apikey_required_setting = self.Auth.get('ApiKeyRequired') apikey_required_setting_is_false = apikey_required_setting is not None and not apikey_required_setting if apikey_required_setting_is_false and not api_auth.get('ApiKeyRequired'): raise InvalidEventException( self.relative_id, 'Unable to set ApiKeyRequired [False] on API method [{method}] for path [{path}] ' 'because the related API does not specify any ApiKeyRequired.'.format( method=self.Method, path=self.Path)) if method_authorizer or apikey_required_setting is not None: editor.add_auth_to_method(api=api, path=self.Path, method_name=self.Method, auth=self.Auth) if self.Auth.get('ResourcePolicy'): resource_policy = self.Auth.get('ResourcePolicy') editor.add_resource_policy(resource_policy=resource_policy, path=self.Path, api_id=self.RestApiId.get('Ref'), stage=self.Stage) if self.RequestModel: method_model = self.RequestModel.get('Model') if method_model: api_models = api.get('Models') if not api_models: raise InvalidEventException( self.relative_id, 'Unable to set RequestModel [{model}] on API method [{method}] for path [{path}] ' 'because the related API does not define any Models.'.format( model=method_model, method=self.Method, path=self.Path)) if not api_models.get(method_model): raise InvalidEventException( self.relative_id, 'Unable to set RequestModel [{model}] on API method [{method}] for path [{path}] ' 'because it wasn\'t defined in the API\'s Models.'.format( model=method_model, method=self.Method, path=self.Path)) editor.add_request_model_to_method(path=self.Path, method_name=self.Method, request_model=self.RequestModel) if self.RequestParameters: default_value = { 'Required': False, 'Caching': False } parameters = [] for parameter in self.RequestParameters: if isinstance(parameter, dict): parameter_name, parameter_value = next(iter(parameter.items())) if not re.match('method\.request\.(querystring|path|header)\.', parameter_name): raise InvalidEventException( self.relative_id, "Invalid value for 'RequestParameters' property. Keys must be in the format " "'method.request.[querystring|path|header].{value}', " "e.g 'method.request.header.Authorization'.") if not isinstance(parameter_value, dict) or not all(key in REQUEST_PARAMETER_PROPERTIES for key in parameter_value.keys()): raise InvalidEventException( self.relative_id, "Invalid value for 'RequestParameters' property. Values must be an object, " "e.g { Required: true, Caching: false }") settings = default_value.copy() settings.update(parameter_value) settings.update({'Name': parameter_name}) parameters.append(settings) elif isinstance(parameter, string_types): if not re.match('method\.request\.(querystring|path|header)\.', parameter): raise InvalidEventException( self.relative_id, "Invalid value for 'RequestParameters' property. Keys must be in the format " "'method.request.[querystring|path|header].{value}', " "e.g 'method.request.header.Authorization'.") settings = default_value.copy() settings.update({'Name': parameter}) parameters.append(settings) else: raise InvalidEventException( self.relative_id, "Invalid value for 'RequestParameters' property. Property must be either a string or an object") editor.add_request_parameters_to_method(path=self.Path, method_name=self.Method, request_parameters=parameters) api["DefinitionBody"] = editor.swagger
def _add_swagger_integration(self, api, function): """Adds the path and method for this Api event source to the Swagger body for the provided RestApi. :param model.apigateway.ApiGatewayRestApi rest_api: the RestApi to which the path and method should be added. """ swagger_body = api.get("DefinitionBody") if swagger_body is None: return function_arn = function.get_runtime_attr('arn') partition = ArnGenerator.get_partition_name() uri = fnSub( 'arn:' + partition + ':apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/' + make_shorthand(function_arn) + '/invocations') editor = SwaggerEditor(swagger_body) if editor.has_integration(self.Path, self.Method): # Cannot add the Lambda Integration, if it is already present raise InvalidEventException( self.relative_id, 'API method "{method}" defined multiple times for path "{path}".' .format(method=self.Method, path=self.Path)) condition = None if CONDITION in function.resource_attributes: condition = function.resource_attributes[CONDITION] editor.add_lambda_integration(self.Path, self.Method, uri, self.Auth, api.get('Auth'), condition=condition) if self.Auth: method_authorizer = self.Auth.get('Authorizer') api_auth = api.get('Auth') if method_authorizer: api_authorizers = api_auth and api_auth.get('Authorizers') if method_authorizer != 'AWS_IAM': if not api_authorizers: raise InvalidEventException( self.relative_id, 'Unable to set Authorizer [{authorizer}] on API method [{method}] for path [{path}] ' 'because the related API does not define any Authorizers.' .format(authorizer=method_authorizer, method=self.Method, path=self.Path)) if method_authorizer != 'NONE' and not api_authorizers.get( method_authorizer): raise InvalidEventException( self.relative_id, 'Unable to set Authorizer [{authorizer}] on API method [{method}] for path [{path}] ' 'because it wasn\'t defined in the API\'s Authorizers.' .format(authorizer=method_authorizer, method=self.Method, path=self.Path)) if method_authorizer == 'NONE' and not api_auth.get( 'DefaultAuthorizer'): raise InvalidEventException( self.relative_id, 'Unable to set Authorizer on API method [{method}] for path [{path}] because \'NONE\' ' 'is only a valid value when a DefaultAuthorizer on the API is specified.' .format(method=self.Method, path=self.Path)) apikey_required_setting = self.Auth.get('ApiKeyRequired') apikey_required_setting_is_false = apikey_required_setting is not None and not apikey_required_setting if apikey_required_setting_is_false and not api_auth.get( 'ApiKeyRequired'): raise InvalidEventException( self.relative_id, 'Unable to set ApiKeyRequired [False] on API method [{method}] for path [{path}] ' 'because the related API does not specify any ApiKeyRequired.' .format(method=self.Method, path=self.Path)) if method_authorizer or apikey_required_setting is not None: editor.add_auth_to_method(api=api, path=self.Path, method_name=self.Method, auth=self.Auth) if self.RequestModel: method_model = self.RequestModel.get('Model') if method_model: api_models = api.get('Models') if not api_models: raise InvalidEventException( self.relative_id, 'Unable to set RequestModel [{model}] on API method [{method}] for path [{path}] ' 'because the related API does not define any Models.'. format(model=method_model, method=self.Method, path=self.Path)) if not api_models.get(method_model): raise InvalidEventException( self.relative_id, 'Unable to set RequestModel [{model}] on API method [{method}] for path [{path}] ' 'because it wasn\'t defined in the API\'s Models.'. format(model=method_model, method=self.Method, path=self.Path)) editor.add_request_model_to_method( path=self.Path, method_name=self.Method, request_model=self.RequestModel) api["DefinitionBody"] = editor.swagger
def to_cloudformation(self, **kwargs): """Returns the Lambda Permission resource allowing SNS to invoke the function this event source triggers. :param dict kwargs: no existing resources need to be modified :returns: a list of vanilla CloudFormation Resources, to which this SNS event expands :rtype: list """ function = kwargs.get("function") role = kwargs.get("role") if not function: raise TypeError("Missing required keyword argument: function") # SNS -> Lambda if not self.SqsSubscription: subscription = self._inject_subscription( "lambda", function.get_runtime_attr("arn"), self.Topic, self.Region, self.FilterPolicy, function.resource_attributes, ) return [self._construct_permission(function, source_arn=self.Topic), subscription] # SNS -> SQS(Create New) -> Lambda if isinstance(self.SqsSubscription, bool): resources = [] queue = self._inject_sqs_queue() queue_arn = queue.get_runtime_attr("arn") queue_url = queue.get_runtime_attr("queue_url") queue_policy = self._inject_sqs_queue_policy(self.Topic, queue_arn, queue_url) subscription = self._inject_subscription( "sqs", queue_arn, self.Topic, self.Region, self.FilterPolicy, function.resource_attributes ) event_source = self._inject_sqs_event_source_mapping(function, role, queue_arn) resources = resources + event_source resources.append(queue) resources.append(queue_policy) resources.append(subscription) return resources # SNS -> SQS(Existing) -> Lambda resources = [] queue_arn = self.SqsSubscription.get("QueueArn", None) queue_url = self.SqsSubscription.get("QueueUrl", None) if not queue_arn or not queue_url: raise InvalidEventException(self.relative_id, "No QueueARN or QueueURL provided.") queue_policy_logical_id = self.SqsSubscription.get("QueuePolicyLogicalId", None) batch_size = self.SqsSubscription.get("BatchSize", None) enabled = self.SqsSubscription.get("Enabled", None) queue_policy = self._inject_sqs_queue_policy(self.Topic, queue_arn, queue_url, queue_policy_logical_id) subscription = self._inject_subscription( "sqs", queue_arn, self.Topic, self.Region, self.FilterPolicy, function.resource_attributes ) event_source = self._inject_sqs_event_source_mapping(function, role, queue_arn, batch_size, enabled) resources = resources + event_source resources.append(queue_policy) resources.append(subscription) return resources
def _add_swagger_integration(self, api, function): """Adds the path and method for this Api event source to the Swagger body for the provided RestApi. :param model.apigateway.ApiGatewayRestApi rest_api: the RestApi to which the path and method should be added. """ swagger_body = api.get("DefinitionBody") if swagger_body is None: return function_arn = function.get_runtime_attr('arn') partition = ArnGenerator.get_partition_name() uri = fnSub( 'arn:' + partition + ':apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/' + make_shorthand(function_arn) + '/invocations') editor = SwaggerEditor(swagger_body) if editor.has_integration(self.Path, self.Method): # Cannot add the Lambda Integration, if it is already present raise InvalidEventException( self.relative_id, 'API method "{method}" defined multiple times for path "{path}".' .format(method=self.Method, path=self.Path)) editor.add_lambda_integration(self.Path, self.Method, uri) if self.Auth: method_authorizer = self.Auth.get('Authorizer') if method_authorizer: api_auth = api.get('Auth') api_authorizers = api_auth and api_auth.get('Authorizers') if not api_authorizers: raise InvalidEventException( self.relative_id, 'Unable to set Authorizer [{authorizer}] on API method [{method}] for path [{path}] because ' 'the related API does not define any Authorizers.'. format(authorizer=method_authorizer, method=self.Method, path=self.Path)) if method_authorizer != 'NONE' and not api_authorizers.get( method_authorizer): raise InvalidEventException( self.relative_id, 'Unable to set Authorizer [{authorizer}] on API method [{method}] for path [{path}] because it ' 'wasn\'t defined in the API\'s Authorizers.'.format( authorizer=method_authorizer, method=self.Method, path=self.Path)) if method_authorizer == 'NONE' and not api_auth.get( 'DefaultAuthorizer'): raise InvalidEventException( self.relative_id, 'Unable to set Authorizer on API method [{method}] for path [{path}] because \'NONE\' ' 'is only a valid value when a DefaultAuthorizer on the API is specified.' .format(method=self.Method, path=self.Path)) editor.add_auth_to_method(api=api, path=self.Path, method_name=self.Method, auth=self.Auth) api["DefinitionBody"] = editor.swagger
def get_policy_statements(self): if not self.SourceAccessConfigurations: raise InvalidEventException( self.relative_id, "No SourceAccessConfigurations for Amazon MQ event provided.", ) if not type(self.SourceAccessConfigurations) is list: raise InvalidEventException( self.relative_id, "Provided SourceAccessConfigurations cannot be parsed into a list.", ) basic_auth_uri = None for conf in self.SourceAccessConfigurations: event_type = conf.get("Type") if event_type not in ("BASIC_AUTH", "VIRTUAL_HOST"): raise InvalidEventException( self.relative_id, "Invalid property specified in SourceAccessConfigurations for Amazon MQ event.", ) if event_type == "BASIC_AUTH": if basic_auth_uri: raise InvalidEventException( self.relative_id, "Multiple BASIC_AUTH properties specified in SourceAccessConfigurations for Amazon MQ event.", ) basic_auth_uri = conf.get("URI") if not basic_auth_uri: raise InvalidEventException( self.relative_id, "No BASIC_AUTH URI property specified in SourceAccessConfigurations for Amazon MQ event.", ) if not basic_auth_uri: raise InvalidEventException( self.relative_id, "No BASIC_AUTH property specified in SourceAccessConfigurations for Amazon MQ event.", ) document = { "PolicyName": "SamAutoGeneratedAMQPolicy", "PolicyDocument": { "Statement": [ { "Action": [ "secretsmanager:GetSecretValue", ], "Effect": "Allow", "Resource": basic_auth_uri, }, { "Action": [ "mq:DescribeBroker", ], "Effect": "Allow", "Resource": self.Broker, }, ] }, } if self.SecretsManagerKmsKeyId: kms_policy = { "Action": "kms:Decrypt", "Effect": "Allow", "Resource": { "Fn::Sub": "arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/" + self.SecretsManagerKmsKeyId }, } document["PolicyDocument"]["Statement"].append(kms_policy) return [document]