Exemplo n.º 1
0
    def to_cloudformation(self, **kwargs):
        function = kwargs.get('function')

        if not function:
            raise TypeError("Missing required keyword argument: function")

        resources = []

        resource = 'rule/${RuleName}'

        partition = ArnGenerator.get_partition_name()
        source_arn = fnSub(
            ArnGenerator.generate_arn(partition=partition,
                                      service='iot',
                                      resource=resource),
            {'RuleName': ref(self.logical_id)})
        source_account = fnSub('${AWS::AccountId}')

        resources.append(
            self._construct_permission(function,
                                       source_arn=source_arn,
                                       source_account=source_account))
        resources.append(self._construct_iot_rule(function))

        return resources
Exemplo n.º 2
0
    def generate_swagger(self):
        authorizer_type = self._get_type()
        APIGATEWAY_AUTHORIZER_KEY = "x-amazon-apigateway-authorizer"
        swagger = {
            "type": "apiKey",
            "name": self._get_swagger_header_name(),
            "in": "header",
            "x-amazon-apigateway-authtype": self._get_swagger_authtype(),
        }

        if authorizer_type == "COGNITO_USER_POOLS":
            swagger[APIGATEWAY_AUTHORIZER_KEY] = {
                "type": self._get_swagger_authorizer_type(),
                "providerARNs": self._get_user_pool_arn_array(),
            }

        elif authorizer_type == "LAMBDA":
            swagger[APIGATEWAY_AUTHORIZER_KEY] = {
                "type": self._get_swagger_authorizer_type()
            }
            partition = ArnGenerator.get_partition_name()
            resource = "lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations"
            authorizer_uri = fnSub(
                ArnGenerator.generate_arn(partition=partition,
                                          service="apigateway",
                                          resource=resource,
                                          include_account_id=False),
                {"__FunctionArn__": self.function_arn},
            )

            swagger[APIGATEWAY_AUTHORIZER_KEY][
                "authorizerUri"] = authorizer_uri
            reauthorize_every = self._get_reauthorize_every()
            function_invoke_role = self._get_function_invoke_role()

            if reauthorize_every is not None:
                swagger[APIGATEWAY_AUTHORIZER_KEY][
                    "authorizerResultTtlInSeconds"] = reauthorize_every

            if function_invoke_role:
                swagger[APIGATEWAY_AUTHORIZER_KEY][
                    "authorizerCredentials"] = function_invoke_role

            if self._get_function_payload_type() == "REQUEST":
                swagger[APIGATEWAY_AUTHORIZER_KEY][
                    "identitySource"] = self._get_identity_source()

        # Authorizer Validation Expression is only allowed on COGNITO_USER_POOLS and LAMBDA_TOKEN
        is_lambda_token_authorizer = authorizer_type == "LAMBDA" and self._get_function_payload_type(
        ) == "TOKEN"

        if authorizer_type == "COGNITO_USER_POOLS" or is_lambda_token_authorizer:
            identity_validation_expression = self._get_identity_validation_expression(
            )

            if identity_validation_expression:
                swagger[APIGATEWAY_AUTHORIZER_KEY][
                    "identityValidationExpression"] = identity_validation_expression

        return swagger
Exemplo n.º 3
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
Exemplo n.º 4
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
Exemplo n.º 5
0
    def _get_permission(self, resources_to_link, stage, suffix):
        # It turns out that APIGW doesn't like trailing slashes in paths (#665)
        # and removes as a part of their behaviour, but this isn't documented.
        # The regex removes the tailing slash to ensure the permission works as intended
        path = re.sub(r'^(.+)/$', r'\1', self.Path)

        if not stage or not suffix:
            raise RuntimeError("Could not add permission to lambda function.")

        path = re.sub(r'{([a-zA-Z0-9._-]+|proxy\+)}', '*', path)
        method = '*' if self.Method.lower() == 'any' else self.Method.upper()

        api_id = self.RestApiId

        # RestApiId can be a simple string or intrinsic function like !Ref. Using Fn::Sub will handle both cases
        resource = '${__ApiId__}/' + '${__Stage__}/' + method + path
        partition = ArnGenerator.get_partition_name()
        source_arn = fnSub(
            ArnGenerator.generate_arn(partition=partition,
                                      service='execute-api',
                                      resource=resource), {
                                          "__ApiId__": api_id,
                                          "__Stage__": stage
                                      })

        return self._construct_permission(resources_to_link['function'],
                                          source_arn=source_arn,
                                          suffix=suffix)
    def deployment_group(self, function_logical_id):
        """
        :param function_logical_id: logical_id of the function this deployment group belongs to
        :return: CodeDeployDeploymentGroup resource
        """
        deployment_preference = self.get(function_logical_id)

        deployment_group = CodeDeployDeploymentGroup(self.deployment_group_logical_id(function_logical_id))

        if deployment_preference.alarms is not None:
            deployment_group.AlarmConfiguration = {'Enabled': True,
                                                   'Alarms': [{'Name': alarm} for alarm in
                                                              deployment_preference.alarms]}

        deployment_group.ApplicationName = self.codedeploy_application.get_runtime_attr('name')
        deployment_group.AutoRollbackConfiguration = {'Enabled': True,
                                                      'Events': ['DEPLOYMENT_FAILURE',
                                                                 'DEPLOYMENT_STOP_ON_ALARM',
                                                                 'DEPLOYMENT_STOP_ON_REQUEST']}
        deployment_group.DeploymentConfigName = fnSub("CodeDeployDefault.Lambda${ConfigName}",
                                                      {"ConfigName":  deployment_preference.deployment_type})
        deployment_group.DeploymentStyle = {'DeploymentType': 'BLUE_GREEN',
                                            'DeploymentOption': 'WITH_TRAFFIC_CONTROL'}

        deployment_group.ServiceRoleArn = self.codedeploy_iam_role.get_runtime_attr("arn")
        if deployment_preference.role:
            deployment_group.ServiceRoleArn = deployment_preference.role

        return deployment_group
    def _get_permission(self, authorizer_name, authorizer_lambda_function_arn):
        """Constructs and returns the Lambda Permission resource allowing the Authorizer to invoke the function.

        :returns: the permission resource
        :rtype: model.lambda_.LambdaPermission
        """
        rest_api = ApiGatewayRestApi(self.logical_id, depends_on=self.depends_on, attributes=self.resource_attributes)
        api_id = rest_api.get_runtime_attr("rest_api_id")

        partition = ArnGenerator.get_partition_name()
        resource = "${__ApiId__}/authorizers/*"
        source_arn = fnSub(
            ArnGenerator.generate_arn(partition=partition, service="execute-api", resource=resource),
            {"__ApiId__": api_id},
        )

        lambda_permission = LambdaPermission(
            self.logical_id + authorizer_name + "AuthorizerPermission", attributes=self.passthrough_resource_attributes
        )
        lambda_permission.Action = "lambda:InvokeFunction"
        lambda_permission.FunctionName = authorizer_lambda_function_arn
        lambda_permission.Principal = "apigateway.amazonaws.com"
        lambda_permission.SourceArn = source_arn

        return lambda_permission
Exemplo n.º 8
0
    def _get_permission(self, authorizer_name, authorizer_lambda_function_arn):
        """Constructs and returns the Lambda Permission resource allowing the Authorizer to invoke the function.

        :returns: the permission resource
        :rtype: model.lambda_.LambdaPermission
        """
        rest_api = ApiGatewayRestApi(self.logical_id,
                                     depends_on=self.depends_on)
        api_id = rest_api.get_runtime_attr('rest_api_id')

        partition = ArnGenerator.get_partition_name()
        resource = '${__ApiId__}/authorizers/*'
        source_arn = fnSub(
            ArnGenerator.generate_arn(partition=partition,
                                      service='execute-api',
                                      resource=resource),
            {"__ApiId__": api_id})

        lambda_permission = LambdaPermission(self.logical_id +
                                             authorizer_name +
                                             'AuthorizerPermission')
        lambda_permission.Action = 'lambda:invokeFunction'
        lambda_permission.FunctionName = authorizer_lambda_function_arn
        lambda_permission.Principal = 'apigateway.amazonaws.com'
        lambda_permission.SourceArn = source_arn

        return lambda_permission
    def get_source_arn(self):
        resource = "log-group:${__LogGroupName__}:*"
        partition = ArnGenerator.get_partition_name()

        return fnSub(
            ArnGenerator.generate_arn(partition=partition, service="logs", resource=resource),
            {"__LogGroupName__": self.LogGroupName},
        )
    def generate_swagger(self):
        authorizer_type = self._get_type()
        APIGATEWAY_AUTHORIZER_KEY = 'x-amazon-apigateway-authorizer'
        swagger = {
            "type": "apiKey",
            "name": self._get_swagger_header_name(),
            "in": "header",
            "x-amazon-apigateway-authtype": self._get_swagger_authtype(),
            "x-amazon-apigateway-authorizer": {
                "type": self._get_swagger_authorizer_type()
            }
        }

        if authorizer_type == 'COGNITO_USER_POOLS':
            swagger[APIGATEWAY_AUTHORIZER_KEY][
                'providerARNs'] = self._get_user_pool_arn_array()

        elif authorizer_type == 'LAMBDA':
            partition = ArnGenerator.get_partition_name()
            resource = 'lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations'
            authorizer_uri = fnSub(
                ArnGenerator.generate_arn(partition=partition,
                                          service='apigateway',
                                          resource=resource,
                                          include_account_id=False),
                {'__FunctionArn__': self.function_arn})

            swagger[APIGATEWAY_AUTHORIZER_KEY][
                'authorizerUri'] = authorizer_uri
            reauthorize_every = self._get_reauthorize_every()
            function_invoke_role = self._get_function_invoke_role()

            if reauthorize_every is not None:
                swagger[APIGATEWAY_AUTHORIZER_KEY][
                    'authorizerResultTtlInSeconds'] = reauthorize_every

            if function_invoke_role:
                swagger[APIGATEWAY_AUTHORIZER_KEY][
                    'authorizerCredentials'] = function_invoke_role

            if self._get_function_payload_type() == 'REQUEST':
                swagger[APIGATEWAY_AUTHORIZER_KEY][
                    'identitySource'] = self._get_identity_source()

        # Authorizer Validation Expression is only allowed on COGNITO_USER_POOLS and LAMBDA_TOKEN
        is_lambda_token_authorizer = authorizer_type == 'LAMBDA' and self._get_function_payload_type(
        ) == 'TOKEN'

        if authorizer_type == 'COGNITO_USER_POOLS' or is_lambda_token_authorizer:
            identity_validation_expression = self._get_identity_validation_expression(
            )

            if identity_validation_expression:
                swagger[APIGATEWAY_AUTHORIZER_KEY][
                    'identityValidationExpression'] = identity_validation_expression

        return swagger
Exemplo n.º 11
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
Exemplo n.º 12
0
    def to_cloudformation(self, **kwargs):
        function = kwargs.get('function')

        if not function:
            raise TypeError("Missing required keyword argument: function")

        resources = []

        resource = 'rule/${RuleName}'

        partition = ArnGenerator.get_partition_name()
        source_arn = fnSub(ArnGenerator.generate_arn(partition=partition, service='iot', resource=resource),
                           {'RuleName': ref(self.logical_id)})
        source_account = fnSub('${AWS::AccountId}')

        resources.append(self._construct_permission(function, source_arn=source_arn,
                                                    source_account=source_account))
        resources.append(self._construct_iot_rule(function))

        return resources
Exemplo n.º 13
0
 def _replace_deployment_types(self, value):
     if isinstance(value, list):
         for i in range(len(value)):
             value[i] = self._replace_deployment_types(value[i])
         return value
     elif is_instrinsic(value):
         for (k, v) in value.items():
             value[k] = self._replace_deployment_types(v)
         return value
     else:
         if value in CODEDEPLOY_PREDEFINED_CONFIGURATIONS_LIST:
             return fnSub("CodeDeployDefault.Lambda${ConfigName}",
                          {"ConfigName": value})
         return value
    def generate_swagger(self):
        authorizer_type = self._get_type()
        APIGATEWAY_AUTHORIZER_KEY = 'x-amazon-apigateway-authorizer'
        swagger = {
            "type": "apiKey",
            "name": self._get_swagger_header_name(),
            "in": "header",
            "x-amazon-apigateway-authtype": self._get_swagger_authtype()
        }

        if authorizer_type == 'COGNITO_USER_POOLS':
            swagger[APIGATEWAY_AUTHORIZER_KEY] = {
                'type': self._get_swagger_authorizer_type(),
                'providerARNs': self._get_user_pool_arn_array()
            }

        elif authorizer_type == 'LAMBDA':
            swagger[APIGATEWAY_AUTHORIZER_KEY] = {
                'type': self._get_swagger_authorizer_type()
            }
            partition = ArnGenerator.get_partition_name()
            resource = 'lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations'
            authorizer_uri = fnSub(ArnGenerator.generate_arn(partition=partition, service='apigateway',
                                   resource=resource, include_account_id=False),
                                   {'__FunctionArn__': self.function_arn})

            swagger[APIGATEWAY_AUTHORIZER_KEY]['authorizerUri'] = authorizer_uri
            reauthorize_every = self._get_reauthorize_every()
            function_invoke_role = self._get_function_invoke_role()

            if reauthorize_every is not None:
                swagger[APIGATEWAY_AUTHORIZER_KEY]['authorizerResultTtlInSeconds'] = reauthorize_every

            if function_invoke_role:
                swagger[APIGATEWAY_AUTHORIZER_KEY]['authorizerCredentials'] = function_invoke_role

            if self._get_function_payload_type() == 'REQUEST':
                swagger[APIGATEWAY_AUTHORIZER_KEY]['identitySource'] = self._get_identity_source()

        # Authorizer Validation Expression is only allowed on COGNITO_USER_POOLS and LAMBDA_TOKEN
        is_lambda_token_authorizer = authorizer_type == 'LAMBDA' and self._get_function_payload_type() == 'TOKEN'

        if authorizer_type == 'COGNITO_USER_POOLS' or is_lambda_token_authorizer:
            identity_validation_expression = self._get_identity_validation_expression()

            if identity_validation_expression:
                swagger[APIGATEWAY_AUTHORIZER_KEY]['identityValidationExpression'] = identity_validation_expression

        return swagger
Exemplo n.º 15
0
    def _get_permission(self, resources_to_link, stage, suffix):

        if not stage or not suffix:
            raise RuntimeError("Could not add permission to lambda function.")

        path = self.Path.replace('{proxy+}', '*')

        api_id = self.RestApiId

        # RestApiId can be a simple string or intrinsic function like !Ref. Using Fn::Sub will handle both cases
        resource = '${__ApiId__}/' + '${__Stage__}/' + self.Method.upper() + path
        partition = ArnGenerator.get_partition_name()
        source_arn = fnSub(ArnGenerator.generate_arn(partition=partition, service='execute-api', resource=resource),
                           {"__ApiId__": api_id, "__Stage__": stage})

        return self._construct_permission(resources_to_link['function'], source_arn=source_arn, suffix=suffix)
Exemplo n.º 16
0
    def _get_method_path_uri_list(self, path, api_id, stage):
        """
        It turns out that APIGW doesn't like trailing slashes in paths (#665)
        and removes as a part of their behavior, but this isn't documented.
        The regex removes the trailing slash to ensure the permission works as intended
        """
        methods = list(self.get_path(path).keys())

        uri_list = []
        path = SwaggerEditor.get_path_without_trailing_slash(path)

        for m in methods:
            method = '*' if (m.lower() == self._X_ANY_METHOD or m.lower() == 'any') else m.upper()
            resource = "execute-api:/${__Stage__}/" + method + path
            resource = fnSub(resource, {"__Stage__": stage})
            uri_list.extend([resource])
        return uri_list
Exemplo n.º 17
0
    def _get_permission(self, resources_to_link, stage, suffix):

        if not stage or not suffix:
            raise RuntimeError("Could not add permission to lambda function.")

        path = self.Path.replace('{proxy+}', '*')
        method = '*' if self.Method.lower() == 'any' else self.Method.upper()

        api_id = self.RestApiId

        # RestApiId can be a simple string or intrinsic function like !Ref. Using Fn::Sub will handle both cases
        resource = '${__ApiId__}/' + '${__Stage__}/' + method + path
        partition = ArnGenerator.get_partition_name()
        source_arn = fnSub(ArnGenerator.generate_arn(partition=partition, service='execute-api', resource=resource),
                           {"__ApiId__": api_id, "__Stage__": stage})

        return self._construct_permission(resources_to_link['function'], source_arn=source_arn, suffix=suffix)
    def deployment_group(self, function_logical_id):
        """
        :param function_logical_id: logical_id of the function this deployment group belongs to
        :return: CodeDeployDeploymentGroup resource
        """
        deployment_preference = self.get(function_logical_id)

        deployment_group = CodeDeployDeploymentGroup(
            self.deployment_group_logical_id(function_logical_id))

        if deployment_preference.alarms is not None:
            deployment_group.AlarmConfiguration = {
                'Enabled':
                True,
                'Alarms': [{
                    'Name': alarm
                } for alarm in deployment_preference.alarms]
            }

        deployment_group.ApplicationName = self.codedeploy_application.get_runtime_attr(
            'name')
        deployment_group.AutoRollbackConfiguration = {
            'Enabled':
            True,
            'Events': [
                'DEPLOYMENT_FAILURE', 'DEPLOYMENT_STOP_ON_ALARM',
                'DEPLOYMENT_STOP_ON_REQUEST'
            ]
        }
        deployment_group.DeploymentConfigName = fnSub(
            "CodeDeployDefault.Lambda${ConfigName}",
            {"ConfigName": deployment_preference.deployment_type})
        deployment_group.DeploymentStyle = {
            'DeploymentType': 'BLUE_GREEN',
            'DeploymentOption': 'WITH_TRAFFIC_CONTROL'
        }

        deployment_group.ServiceRoleArn = self.codedeploy_iam_role.get_runtime_attr(
            "arn")
        if deployment_preference.role:
            deployment_group.ServiceRoleArn = deployment_preference.role

        return deployment_group
Exemplo n.º 19
0
    def _generate_request_template(self, resource):
        """Generates the Body mapping request template for the Api. This allows for the input 
        request to the Api to be passed as the execution input to the associated state machine resource.

        :param model.stepfunctions.resources.StepFunctionsStateMachine resource; the state machine 
                resource to which the Api event source must be associated

        :returns: a body mapping request which passes the Api input to the state machine execution
        :rtype: dict
        """
        request_templates = {
            "application/json":
            fnSub(
                json.dumps({
                    "input": "$util.escapeJavaScript($input.json('$'))",
                    "stateMachineArn": "${" + resource.logical_id + "}",
                }))
        }
        return request_templates
Exemplo n.º 20
0
    def _get_permission(self, resources_to_link, stage):
        # It turns out that APIGW doesn't like trailing slashes in paths (#665)
        # and removes as a part of their behaviour, but this isn't documented.
        # The regex removes the tailing slash to ensure the permission works as intended
        path = re.sub(r"^(.+)/$", r"\1", self.Path)

        editor = None
        if resources_to_link["explicit_api"].get("DefinitionBody"):
            try:
                editor = OpenApiEditor(resources_to_link["explicit_api"].get("DefinitionBody"))
            except ValueError as e:
                api_logical_id = self.ApiId.get("Ref") if isinstance(self.ApiId, dict) else self.ApiId
                raise InvalidResourceException(api_logical_id, e)

        # If this is using the new $default path, keep path blank and add a * permission
        if path == OpenApiEditor._DEFAULT_PATH:
            path = ""
        elif editor and resources_to_link.get("function").logical_id == editor.get_integration_function_logical_id(
            OpenApiEditor._DEFAULT_PATH, OpenApiEditor._X_ANY_METHOD
        ):
            # Case where default exists for this function, and so the permissions for that will apply here as well
            # This can save us several CFN resources (not duplicating permissions)
            return
        else:
            path = OpenApiEditor.get_path_without_trailing_slash(path)

        # Handle case where Method is already the ANY ApiGateway extension
        if self.Method.lower() == "any" or self.Method.lower() == OpenApiEditor._X_ANY_METHOD:
            method = "*"
        else:
            method = self.Method.upper()

        api_id = self.ApiId

        # ApiId can be a simple string or intrinsic function like !Ref. Using Fn::Sub will handle both cases
        resource = "${__ApiId__}/" + "${__Stage__}/" + method + path
        source_arn = fnSub(
            ArnGenerator.generate_arn(partition="${AWS::Partition}", service="execute-api", resource=resource),
            {"__ApiId__": api_id, "__Stage__": stage},
        )

        return self._construct_permission(resources_to_link["function"], source_arn=source_arn)
    def _get_permission(self, resources_to_link, stage, suffix):
        # It turns out that APIGW doesn't like trailing slashes in paths (#665)
        # and removes as a part of their behaviour, but this isn't documented.
        # The regex removes the tailing slash to ensure the permission works as intended
        path = re.sub(r'^(.+)/$', r'\1', self.Path)

        if not stage or not suffix:
            raise RuntimeError("Could not add permission to lambda function.")

        path = path.replace('{proxy+}', '*')
        method = '*' if self.Method.lower() == 'any' else self.Method.upper()

        api_id = self.RestApiId

        # RestApiId can be a simple string or intrinsic function like !Ref. Using Fn::Sub will handle both cases
        resource = '${__ApiId__}/' + '${__Stage__}/' + method + path
        partition = ArnGenerator.get_partition_name()
        source_arn = fnSub(ArnGenerator.generate_arn(partition=partition, service='execute-api', resource=resource),
                           {"__ApiId__": api_id, "__Stage__": stage})

        return self._construct_permission(resources_to_link['function'], source_arn=source_arn, suffix=suffix)
    def _get_permission(self, authorizer_name, authorizer_lambda_function_arn):
        """Constructs and returns the Lambda Permission resource allowing the Authorizer to invoke the function.

        :returns: the permission resource
        :rtype: model.lambda_.LambdaPermission
        """
        rest_api = ApiGatewayRestApi(self.logical_id, depends_on=self.depends_on)
        api_id = rest_api.get_runtime_attr('rest_api_id')

        partition = ArnGenerator.get_partition_name()
        resource = '${__ApiId__}/authorizers/*'
        source_arn = fnSub(ArnGenerator.generate_arn(partition=partition, service='execute-api', resource=resource),
                           {"__ApiId__": api_id})

        lambda_permission = LambdaPermission(self.logical_id + authorizer_name + 'AuthorizerPermission')
        lambda_permission.Action = 'lambda:invokeFunction'
        lambda_permission.FunctionName = authorizer_lambda_function_arn
        lambda_permission.Principal = 'apigateway.amazonaws.com'
        lambda_permission.SourceArn = source_arn

        return lambda_permission
Exemplo n.º 23
0
    def _get_permission(self, resources_to_link, stage, suffix):
        # It turns out that APIGW doesn't like trailing slashes in paths (#665)
        # and removes as a part of their behaviour, but this isn't documented.
        # The regex removes the tailing slash to ensure the permission works as intended
        path = re.sub(r"^(.+)/$", r"\1", self.Path)

        if not stage or not suffix:
            raise RuntimeError("Could not add permission to lambda function.")

        path = SwaggerEditor.get_path_without_trailing_slash(path)
        method = "*" if self.Method.lower() == "any" else self.Method.upper()

        api_id = self.RestApiId

        # RestApiId can be a simple string or intrinsic function like !Ref. Using Fn::Sub will handle both cases
        resource = "${__ApiId__}/" + "${__Stage__}/" + method + path
        partition = ArnGenerator.get_partition_name()
        source_arn = fnSub(
            ArnGenerator.generate_arn(partition=partition, service="execute-api", resource=resource),
            {"__ApiId__": api_id, "__Stage__": stage},
        )

        return self._construct_permission(resources_to_link["function"], source_arn=source_arn, suffix=suffix)
 def get_source_arn(self):
     resource = "log-group:${__LogGroupName__}:*"
     partition = ArnGenerator.get_partition_name()
     
     return fnSub(ArnGenerator.generate_arn(partition=partition, service='logs', resource=resource),
                        {'__LogGroupName__': self.LogGroupName})
Exemplo n.º 25
0
    def generate_openapi(self):
        """
        Generates OAS for the securitySchemes section
        """
        authorizer_type = self._get_auth_type()

        if authorizer_type == "JWT":
            openapi = {"type": "oauth2"}
            openapi[APIGATEWAY_AUTHORIZER_KEY] = {
                "jwtConfiguration": self.jwt_configuration,
                "identitySource": self.id_source,
                "type": "jwt",
            }

        if authorizer_type == "REQUEST":
            openapi = {
                "type": "apiKey",
                "name": "Unused",
                "in": "header",
            }
            openapi[APIGATEWAY_AUTHORIZER_KEY] = {"type": "request"}

            # Generate the lambda arn
            partition = ArnGenerator.get_partition_name()
            resource = "lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations"
            authorizer_uri = fnSub(
                ArnGenerator.generate_arn(partition=partition,
                                          service="apigateway",
                                          resource=resource,
                                          include_account_id=False),
                {"__FunctionArn__": self.function_arn},
            )
            openapi[APIGATEWAY_AUTHORIZER_KEY][
                "authorizerUri"] = authorizer_uri

            # Set authorizerCredentials if present
            function_invoke_role = self._get_function_invoke_role()
            if function_invoke_role:
                openapi[APIGATEWAY_AUTHORIZER_KEY][
                    "authorizerCredentials"] = function_invoke_role

            # Set authorizerResultTtlInSeconds if present
            reauthorize_every = self._get_reauthorize_every()
            if reauthorize_every is not None:
                openapi[APIGATEWAY_AUTHORIZER_KEY][
                    "authorizerResultTtlInSeconds"] = reauthorize_every

            # Set identitySource if present
            if self.identity:
                openapi[APIGATEWAY_AUTHORIZER_KEY][
                    "identitySource"] = self._get_identity_source()

            # Set authorizerPayloadFormatVersion. It's a required parameter
            openapi[APIGATEWAY_AUTHORIZER_KEY][
                "authorizerPayloadFormatVersion"] = self.authorizer_payload_format_version

            # Set authorizerPayloadFormatVersion. It's a required parameter
            if self.enable_simple_responses:
                openapi[APIGATEWAY_AUTHORIZER_KEY][
                    "enableSimpleResponses"] = self.enable_simple_responses

        return openapi
Exemplo n.º 26
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
Exemplo n.º 27
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
Exemplo n.º 28
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
Exemplo n.º 29
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