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)
예제 #4
0
    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
예제 #5
0
    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
예제 #6
0
    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
예제 #7
0
    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
예제 #9
0
 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.")
예제 #10
0
 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
예제 #13
0
    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
            }
        }
예제 #14
0
    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
예제 #15
0
    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
예제 #16
0
    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
예제 #17
0
    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
예제 #18
0
    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
예제 #19
0
    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]