예제 #1
0
    def _construct_basepath_mappings(self, basepaths, http_api):
        basepath_resource_list = []

        if basepaths is None:
            basepath_mapping = ApiGatewayV2ApiMapping(
                self.logical_id + "ApiMapping", attributes=self.passthrough_resource_attributes
            )
            basepath_mapping.DomainName = ref(self.domain.get("ApiDomainName"))
            basepath_mapping.ApiId = ref(http_api.logical_id)
            basepath_mapping.Stage = ref(http_api.logical_id + ".Stage")
            basepath_resource_list.extend([basepath_mapping])
        else:
            for path in basepaths:
                # search for invalid characters in the path and raise error if there are
                invalid_regex = r"[^0-9a-zA-Z\/\-\_]+"
                if re.search(invalid_regex, path) is not None:
                    raise InvalidResourceException(self.logical_id, "Invalid Basepath name provided.")
                # ignore leading and trailing `/` in the path name
                m = re.search(r"[a-zA-Z0-9]+[\-\_]?[a-zA-Z0-9]+", path)
                path = m.string[m.start(0) : m.end(0)]
                if path is None:
                    raise InvalidResourceException(self.logical_id, "Invalid Basepath name provided.")
                logical_id = "{}{}{}".format(self.logical_id, re.sub(r"[\-\_]+", "", path), "ApiMapping")
                basepath_mapping = ApiGatewayV2ApiMapping(logical_id, attributes=self.passthrough_resource_attributes)
                basepath_mapping.DomainName = ref(self.domain.get("ApiDomainName"))
                basepath_mapping.ApiId = ref(http_api.logical_id)
                basepath_mapping.Stage = ref(http_api.logical_id + ".Stage")
                basepath_mapping.ApiMappingKey = path
                basepath_resource_list.extend([basepath_mapping])
        return basepath_resource_list
    def _set_default_authorizer(self, open_api_editor, authorizers,
                                default_authorizer, api_authorizers):
        """
        Sets the default authorizer if one is given in the template
        :param open_api_editor: editor object that contains the OpenApi definition
        :param authorizers: authorizer definitions converted from the API auth section
        :param default_authorizer: name of the default authorizer
        :param api_authorizers: API auth section authorizer defintions
        """
        if not default_authorizer:
            return

        if is_intrinsic_no_value(default_authorizer):
            return

        if is_intrinsic(default_authorizer):
            raise InvalidResourceException(
                self.logical_id,
                "Unable to set DefaultAuthorizer because intrinsic functions are not supported for this field.",
            )

        if not authorizers.get(default_authorizer):
            raise InvalidResourceException(
                self.logical_id,
                "Unable to set DefaultAuthorizer because '" +
                default_authorizer + "' was not defined in 'Authorizers'.",
            )

        for path in open_api_editor.iter_on_path():
            open_api_editor.set_path_default_authorizer(
                path,
                default_authorizer,
                authorizers=authorizers,
                api_authorizers=api_authorizers)
예제 #3
0
    def _set_default_authorizer(self,
                                swagger_editor,
                                authorizers,
                                default_authorizer,
                                add_default_auth_to_preflight=True,
                                api_authorizers=None):
        if not default_authorizer:
            return

        if not isinstance(default_authorizer, string_types):
            raise InvalidResourceException(
                self.logical_id,
                "DefaultAuthorizer is not a string.",
            )

        if not authorizers.get(
                default_authorizer) and default_authorizer != "AWS_IAM":
            raise InvalidResourceException(
                self.logical_id,
                "Unable to set DefaultAuthorizer because '" +
                default_authorizer + "' was not defined in 'Authorizers'.",
            )

        for path in swagger_editor.iter_on_path():
            swagger_editor.set_path_default_authorizer(
                path,
                default_authorizer,
                authorizers=authorizers,
                add_default_auth_to_preflight=add_default_auth_to_preflight,
                api_authorizers=api_authorizers,
            )
예제 #4
0
    def _get_retention_policy_value(self, intrinsics_resolver):
        """
        Sets the deletion policy on this resource. The default is 'Retain'.

        :return: value for the DeletionPolicy attribute.
        """
        if isinstance(self.RetentionPolicy, dict):
            self.RetentionPolicy = intrinsics_resolver.resolve_parameter_refs(
                self.RetentionPolicy)
            # If it's still not a string, throw an exception
            if not isinstance(self.RetentionPolicy, string_types):
                raise InvalidResourceException(
                    self.logical_id,
                    "Could not resolve parameter for '{}' or parameter is not a String."
                    .format('RetentionPolicy'))

        if self.RetentionPolicy is None or self.RetentionPolicy.lower(
        ) == self.RETAIN.lower():
            return self.RETAIN
        elif self.RetentionPolicy.lower() == self.DELETE.lower():
            return self.DELETE
        elif self.RetentionPolicy.lower() not in self.retention_policy_options:
            raise InvalidResourceException(
                self.logical_id,
                "'{}' must be one of the following options: {}.".format(
                    'RetentionPolicy', [self.RETAIN, self.DELETE]))
예제 #5
0
    def _validate_deployment_preference_and_add_update_policy(
            self, deployment_preference_collection, lambda_alias,
            intrinsics_resolver):
        if 'Enabled' in self.DeploymentPreference:
            self.DeploymentPreference[
                'Enabled'] = intrinsics_resolver.resolve_parameter_refs(
                    self.DeploymentPreference['Enabled'])
            if isinstance(self.DeploymentPreference['Enabled'], dict):
                raise InvalidResourceException(
                    self.logical_id, "'Enabled' must be a boolean value")

        if deployment_preference_collection is None:
            raise ValueError(
                'deployment_preference_collection required for parsing the deployment preference'
            )

        deployment_preference_collection.add(self.logical_id,
                                             self.DeploymentPreference)

        if deployment_preference_collection.get(self.logical_id).enabled:
            if self.AutoPublishAlias is None:
                raise InvalidResourceException(
                    self.logical_id,
                    "'DeploymentPreference' requires AutoPublishAlias property to be specified"
                )
            if lambda_alias is None:
                raise ValueError(
                    'lambda_alias expected for updating it with the appropriate update policy'
                )

            lambda_alias.set_resource_attribute(
                "UpdatePolicy",
                deployment_preference_collection.update_policy(
                    self.logical_id).to_dict())
예제 #6
0
    def from_dict(cls, logical_id, deployment_preference_dict):
        """
        :param logical_id: the logical_id of the resource that owns this deployment preference
        :param deployment_preference_dict: the dict object taken from the SAM template
        :return:
        """
        enabled = deployment_preference_dict.get('Enabled', True)
        if not enabled:
            return DeploymentPreference(None, None, None, None, False, None)

        if 'Type' not in deployment_preference_dict:
            raise InvalidResourceException(
                logical_id,
                "'DeploymentPreference' is missing required Property 'Type'")

        deployment_type = deployment_preference_dict['Type']
        hooks = deployment_preference_dict.get('Hooks', dict())
        if not isinstance(hooks, dict):
            raise InvalidResourceException(
                logical_id,
                "'Hooks' property of 'DeploymentPreference' must be a dictionary"
            )

        pre_traffic_hook = hooks.get('PreTraffic', None)
        post_traffic_hook = hooks.get('PostTraffic', None)
        alarms = deployment_preference_dict.get('Alarms', None)
        role = deployment_preference_dict.get('Role', None)
        return DeploymentPreference(deployment_type, pre_traffic_hook,
                                    post_traffic_hook, alarms, enabled, role)
def construct_s3_location_object(location_uri, logical_id, property_name):
    """Constructs a Lambda `Code` or `Content` property, from the SAM `CodeUri` or `ContentUri` property.
    This follows the current scheme for Lambda Functions and LayerVersions.

    :param dict or string location_uri: s3 location dict or string
    :param string logical_id: logical_id of the resource calling this function
    :param string property_name: name of the property which is used as an input to this function.
    :returns: a Code dict, containing the S3 Bucket, Key, and Version of the Lambda layer code
    :rtype: dict
    """
    if isinstance(location_uri, dict):
        if not location_uri.get("Bucket") or not location_uri.get("Key"):
            # location_uri is a dictionary but does not contain Bucket or Key property
            raise InvalidResourceException(
                logical_id, "'{}' requires Bucket and Key properties to be "
                "specified".format(property_name))

        s3_pointer = location_uri

    else:
        # location_uri is NOT a dictionary. Parse it as a string
        s3_pointer = parse_s3_uri(location_uri)

        if s3_pointer is None:
            raise InvalidResourceException(
                logical_id,
                "'{}' is not a valid S3 Uri of the form "
                '"s3://bucket/key" with optional versionId query '
                "parameter.".format(property_name),
            )

    code = {"S3Bucket": s3_pointer["Bucket"], "S3Key": s3_pointer["Key"]}
    if "Version" in s3_pointer:
        code["S3ObjectVersion"] = s3_pointer["Version"]
    return code
    def _add_auth(self):
        """
        Add Auth configuration to the OAS file, if necessary
        """
        if not self.auth:
            return

        if self.auth and not self.definition_body:
            raise InvalidResourceException(
                self.logical_id,
                "Auth works only with inline OpenApi specified in the 'DefinitionBody' property."
            )

        # Make sure keys in the dict are recognized
        if not all(key in AuthProperties._fields for key in self.auth.keys()):
            raise InvalidResourceException(
                self.logical_id, "Invalid value for 'Auth' property")

        if not OpenApiEditor.is_valid(self.definition_body):
            raise InvalidResourceException(
                self.logical_id,
                "Unable to add Auth configuration because 'DefinitionBody' does not contain a valid OpenApi definition.",
            )
        open_api_editor = OpenApiEditor(self.definition_body)
        auth_properties = AuthProperties(**self.auth)
        authorizers = self._get_authorizers(auth_properties.Authorizers,
                                            auth_properties.DefaultAuthorizer)

        # authorizers is guaranteed to return a value or raise an exception
        open_api_editor.add_authorizers_security_definitions(authorizers)
        self._set_default_authorizer(open_api_editor, authorizers,
                                     auth_properties.DefaultAuthorizer,
                                     auth_properties.Authorizers)
        self.definition_body = open_api_editor.openapi
예제 #9
0
    def __init__(
        self,
        api_logical_id=None,
        name=None,
        authorization_scopes=[],
        jwt_configuration={},
        id_source=None,
    ):
        """
        Creates an authorizer for use in V2 Http Apis
        """
        # Currently only one type of auth
        self.auth_type = "oauth2"

        self.api_logical_id = api_logical_id
        self.name = name
        self.authorization_scopes = authorization_scopes

        # Validate necessary parameters exist
        if not jwt_configuration:
            raise InvalidResourceException(
                api_logical_id,
                name + " Authorizer must define 'JwtConfiguration'.")
        self.jwt_configuration = jwt_configuration
        if not id_source:
            raise InvalidResourceException(
                api_logical_id,
                name + " Authorizer must define 'IdentitySource'.")
        self.id_source = id_source
    def _construct_http_api(self):
        """Constructs and returns the ApiGatewayV2 HttpApi.

        :returns: the HttpApi to which this SAM Api corresponds
        :rtype: model.apigatewayv2.ApiGatewayHttpApi
        """
        http_api = ApiGatewayV2HttpApi(self.logical_id, depends_on=self.depends_on, attributes=self.resource_attributes)

        if self.definition_uri and self.definition_body:
            raise InvalidResourceException(
                self.logical_id, "Specify either 'DefinitionUri' or 'DefinitionBody' property and not both"
            )

        self._add_auth()

        if self.definition_uri:
            http_api.BodyS3Location = self._construct_body_s3_dict()
        elif self.definition_body:
            http_api.Body = self.definition_body
        else:
            raise InvalidResourceException(
                self.logical_id,
                "'DefinitionUri' or 'DefinitionBody' are required properties of an "
                "'AWS::Serverless::HttpApi'. Add a value for one of these properties or "
                "add a 'HttpApi' event to an 'AWS::Serverless::Function'",
            )

        if self.tags is not None:
            http_api.Tags = get_tag_list(self.tags)

        return http_api
예제 #11
0
    def _construct_definition_uri(self):
        """
        Constructs the State Machine's `DefinitionS3 property`_, from the SAM State Machines's DefinitionUri property.

        :returns: a DefinitionUri dict, containing the S3 Bucket, Key, and Version of the State Machine definition.
        :rtype: dict
        """
        if isinstance(self.definition_uri, dict):
            if not self.definition_uri.get(
                    "Bucket", None) or not self.definition_uri.get(
                        "Key", None):
                # DefinitionUri is a dictionary but does not contain Bucket or Key property
                raise InvalidResourceException(
                    self.logical_id,
                    "'DefinitionUri' requires Bucket and Key properties to be specified."
                )
            s3_pointer = self.definition_uri
        else:
            # DefinitionUri is a string
            s3_pointer = parse_s3_uri(self.definition_uri)
            if s3_pointer is None:
                raise InvalidResourceException(
                    self.logical_id,
                    "'DefinitionUri' is not a valid S3 Uri of the form "
                    "'s3://bucket/key' with optional versionId query parameter.",
                )

        definition_s3 = {
            "Bucket": s3_pointer["Bucket"],
            "Key": s3_pointer["Key"]
        }
        if "Version" in s3_pointer:
            definition_s3["Version"] = s3_pointer["Version"]
        return definition_s3
    def _get_authorizers(self, authorizers_config, default_authorizer=None):
        authorizers = {}
        if default_authorizer == "AWS_IAM":
            authorizers[default_authorizer] = ApiGatewayAuthorizer(
                api_logical_id=self.logical_id, name=default_authorizer, is_aws_iam_authorizer=True
            )

        if not authorizers_config:
            if "AWS_IAM" in authorizers:
                return authorizers
            return None

        if not isinstance(authorizers_config, dict):
            raise InvalidResourceException(self.logical_id, "Authorizers must be a dictionary.")

        for authorizer_name, authorizer in authorizers_config.items():
            if not isinstance(authorizer, dict):
                raise InvalidResourceException(
                    self.logical_id, "Authorizer %s must be a dictionary." % (authorizer_name)
                )

            authorizers[authorizer_name] = ApiGatewayAuthorizer(
                api_logical_id=self.logical_id,
                name=authorizer_name,
                user_pool_arn=authorizer.get("UserPoolArn"),
                function_arn=authorizer.get("FunctionArn"),
                identity=authorizer.get("Identity"),
                function_payload_type=authorizer.get("FunctionPayloadType"),
                function_invoke_role=authorizer.get("FunctionInvokeRole"),
                authorization_scopes=authorizer.get("AuthorizationScopes"),
            )
        return authorizers
    def _add_tags(self):
        """
        Adds tags to the Http Api, including a default SAM tag.
        """
        if self.tags and not self.definition_body:
            raise InvalidResourceException(
                self.logical_id,
                "Tags works only with inline OpenApi specified in the 'DefinitionBody' property."
            )

        if not self.definition_body:
            return

        if self.tags and not OpenApiEditor.is_valid(self.definition_body):
            raise InvalidResourceException(
                self.logical_id,
                "Unable to add `Tags` because 'DefinitionBody' does not contain a valid OpenApi definition.",
            )
        elif not OpenApiEditor.is_valid(self.definition_body):
            return

        if not self.tags:
            self.tags = {}
        self.tags[HttpApiTagName] = "SAM"

        open_api_editor = OpenApiEditor(self.definition_body)

        # authorizers is guaranteed to return a value or raise an exception
        open_api_editor.add_tags(self.tags)
        self.definition_body = open_api_editor.openapi
예제 #14
0
    def validate_properties(self):
        """Validates that the required properties for this Resource have been populated, and that all properties have
        valid values.

        :returns: True if all properties are valid
        :rtype: bool
        :raises TypeError: if any properties are invalid
        """
        for name, property_type in self.property_types.items():
            value = getattr(self, name)

            # If the property value is an intrinsic function, any remaining validation has to be left to CloudFormation
            if property_type.supports_intrinsics and self._is_intrinsic_function(value):
                continue

            # If the property value has not been set, verify that the property is not required.
            if value is None:
                if property_type.required:
                    raise InvalidResourceException(
                        self.logical_id,
                        "Missing required property '{property_name}'.".format(property_name=name))
            # Otherwise, validate the value of the property.
            elif not property_type.validate(value, should_raise=False):
                raise InvalidResourceException(
                    self.logical_id,
                    "Type of property '{property_name}' is invalid.".format(property_name=name))
    def _add_models(self):
        """
        Add Model definitions to the Swagger file, if necessary
        :return:
        """

        if not self.models:
            return

        if self.models and not self.definition_body:
            raise InvalidResourceException(
                self.logical_id, "Models works only with inline Swagger specified in " "'DefinitionBody' property."
            )

        if not SwaggerEditor.is_valid(self.definition_body):
            raise InvalidResourceException(
                self.logical_id,
                "Unable to add Models definitions because "
                "'DefinitionBody' does not contain a valid Swagger definition.",
            )

        if not all(isinstance(model, dict) for model in self.models.values()):
            raise InvalidResourceException(self.logical_id, "Invalid value for 'Models' property")

        swagger_editor = SwaggerEditor(self.definition_body)
        swagger_editor.add_models(self.models)

        # Assign the Swagger back to template

        self.definition_body = self._openapi_postprocess(swagger_editor.swagger)
    def _construct_code_dict_code_uri(self):
        """Constructs the Lambda function's `Code property`_, from the SAM function's CodeUri property.

        .. _Code property: \
        http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html

        :returns: a Code dict, containing the S3 Bucket, Key, and Version of the Lambda function code
        :rtype: dict
        """
        if isinstance(self.CodeUri, dict):
            if not self.CodeUri.get("Bucket", None) or not self.CodeUri.get("Key", None):
                # CodeUri is a dictionary but does not contain Bucket or Key property
                raise InvalidResourceException(self.logical_id,
                                               "'CodeUri' requires Bucket and Key properties to be specified")

            s3_pointer = self.CodeUri

        else:
            # CodeUri is NOT a dictionary. Parse it as a string
            s3_pointer = parse_s3_uri(self.CodeUri)

            if s3_pointer is None:
                raise InvalidResourceException(self.logical_id,
                                               '\'CodeUri\' is not a valid S3 Uri of the form '
                                               '"s3://bucket/key" with optional versionId query parameter.')

        code = {
            'S3Bucket': s3_pointer['Bucket'],
            'S3Key': s3_pointer['Key']
        }
        if 'Version' in s3_pointer:
            code['S3ObjectVersion'] = s3_pointer['Version']
        return code
예제 #17
0
    def from_dict(cls, logical_id, deployment_preference_dict):
        """
        :param logical_id: the logical_id of the resource that owns this deployment preference
        :param deployment_preference_dict: the dict object taken from the SAM template
        :return:
        """
        enabled = deployment_preference_dict.get("Enabled", True)
        if not enabled:
            return DeploymentPreference(None, None, None, None, False, None,
                                        None)

        if "Type" not in deployment_preference_dict:
            raise InvalidResourceException(
                logical_id,
                "'DeploymentPreference' is missing required Property 'Type'")

        deployment_type = deployment_preference_dict["Type"]
        hooks = deployment_preference_dict.get("Hooks", dict())
        if not isinstance(hooks, dict):
            raise InvalidResourceException(
                logical_id,
                "'Hooks' property of 'DeploymentPreference' must be a dictionary"
            )

        pre_traffic_hook = hooks.get("PreTraffic", None)
        post_traffic_hook = hooks.get("PostTraffic", None)
        alarms = deployment_preference_dict.get("Alarms", None)
        role = deployment_preference_dict.get("Role", None)
        trigger_configurations = deployment_preference_dict.get(
            "TriggerConfigurations", None)
        return DeploymentPreference(deployment_type, pre_traffic_hook,
                                    post_traffic_hook, alarms, enabled, role,
                                    trigger_configurations)
예제 #18
0
    def _add_auth(self):
        """
        Add Auth configuration to the Swagger file, if necessary
        """

        if not self.auth:
            return

        if self.auth and not self.definition_body:
            raise InvalidResourceException(
                self.logical_id,
                "Auth works only with inline Swagger specified in "
                "'DefinitionBody' property")

        # Make sure keys in the dict are recognized
        if not all(key in AuthProperties._fields for key in self.auth.keys()):
            raise InvalidResourceException(
                self.logical_id, "Invalid value for 'Auth' property")

        if not SwaggerEditor.is_valid(self.definition_body):
            raise InvalidResourceException(
                self.logical_id, "Unable to add Auth configuration because "
                "'DefinitionBody' does not contain a valid Swagger")
        swagger_editor = SwaggerEditor(self.definition_body)
        auth_properties = AuthProperties(**self.auth)
        authorizers = self._get_authorizers(auth_properties.Authorizers)

        if authorizers:
            swagger_editor.add_authorizers(authorizers)
            self._set_default_authorizer(swagger_editor, authorizers,
                                         auth_properties.DefaultAuthorizer)

        # Assign the Swagger back to template
        self.definition_body = swagger_editor.swagger
    def _get_authorizers(self, authorizers_config, default_authorizer=None):
        """
        Returns all authorizers for an API as an ApiGatewayV2Authorizer object
        :param authorizers_config: authorizer configuration from the API Auth section
        :param default_authorizer: name of the default authorizer
        """
        authorizers = {}

        if not isinstance(authorizers_config, dict):
            raise InvalidResourceException(self.logical_id, "Authorizers must be a dictionary")

        for authorizer_name, authorizer in authorizers_config.items():
            if not isinstance(authorizer, dict):
                raise InvalidResourceException(
                    self.logical_id, "Authorizer %s must be a dictionary." % (authorizer_name)
                )

            authorizers[authorizer_name] = ApiGatewayV2Authorizer(
                api_logical_id=self.logical_id,
                name=authorizer_name,
                open_id_connect_url=authorizer.get("OpenIdConnectUrl"),
                authorization_scopes=authorizer.get("AuthorizationScopes"),
                jwt_configuration=authorizer.get("JwtConfiguration"),
                id_source=authorizer.get("IdentitySource"),
            )
        return authorizers
예제 #20
0
    def _construct_body_s3_dict(self):
        """Constructs the RestApi's `BodyS3Location property`_, from the SAM Api's DefinitionUri property.

        :returns: a BodyS3Location dict, containing the S3 Bucket, Key, and Version of the Swagger definition
        :rtype: dict
        """
        if isinstance(self.definition_uri, dict):
            if not self.definition_uri.get(
                    "Bucket", None) or not self.definition_uri.get(
                        "Key", None):
                # DefinitionUri is a dictionary but does not contain Bucket or Key property
                raise InvalidResourceException(
                    self.logical_id,
                    "'DefinitionUri' requires Bucket and Key properties to be specified"
                )
            s3_pointer = self.definition_uri

        else:

            # DefinitionUri is a string
            s3_pointer = parse_s3_uri(self.definition_uri)
            if s3_pointer is None:
                raise InvalidResourceException(
                    self.logical_id,
                    '\'DefinitionUri\' is not a valid S3 Uri of the form '
                    '"s3://bucket/key" with optional versionId query parameter.'
                )

        body_s3 = {'Bucket': s3_pointer['Bucket'], 'Key': s3_pointer['Key']}
        if 'Version' in s3_pointer:
            body_s3['Version'] = s3_pointer['Version']
        return body_s3
    def __init__(
        self,
        api_logical_id=None,
        name=None,
        open_id_connect_url=None,
        authorization_scopes=[],
        jwt_configuration={},
        id_source=None,
    ):
        """
        Creates an authorizer for use in V2 Http Apis
        """
        # OIDC uses a connect url, oauth2 doesn't
        self.auth_type = "openIdConnect"
        if open_id_connect_url is None:
            self.auth_type = "oauth2"

        self.api_logical_id = api_logical_id
        self.name = name
        self.open_id_connect_url = open_id_connect_url
        self.authorization_scopes = authorization_scopes

        # Validate necessary parameters exist
        if not jwt_configuration:
            raise InvalidResourceException(
                api_logical_id,
                name + " Authorizer must define 'JwtConfiguration'")
        self.jwt_configuration = jwt_configuration
        if not id_source:
            raise InvalidResourceException(
                api_logical_id,
                name + " Authorizer must define 'IdentitySource'")
        self.id_source = id_source
예제 #22
0
    def __init__(self,
                 api_logical_id=None,
                 name=None,
                 user_pool_arn=None,
                 function_arn=None,
                 identity=None,
                 function_payload_type=None,
                 function_invoke_role=None,
                 is_aws_iam_authorizer=False):
        if function_payload_type not in ApiGatewayAuthorizer._VALID_FUNCTION_PAYLOAD_TYPES:
            raise InvalidResourceException(
                api_logical_id, name + " Authorizer has invalid "
                "'FunctionPayloadType': " + function_payload_type)

        if function_payload_type == 'REQUEST' and self._is_missing_identity_source(
                identity):
            raise InvalidResourceException(
                api_logical_id,
                name + " Authorizer must specify Identity with at least one "
                "of Headers, QueryStrings, StageVariables, or Context.")

        self.api_logical_id = api_logical_id
        self.name = name
        self.user_pool_arn = user_pool_arn
        self.function_arn = function_arn
        self.identity = identity
        self.function_payload_type = function_payload_type
        self.function_invoke_role = function_invoke_role
        self.is_aws_iam_authorizer = is_aws_iam_authorizer
예제 #23
0
    def _get_authorizers(self, authorizers_config, default_authorizer=None):
        """
        Returns all authorizers for an API as an ApiGatewayV2Authorizer object
        :param authorizers_config: authorizer configuration from the API Auth section
        :param default_authorizer: name of the default authorizer
        """
        authorizers = {}

        if not isinstance(authorizers_config, dict):
            raise InvalidResourceException(
                self.logical_id, "Authorizers must be a dictionary.")

        for authorizer_name, authorizer in authorizers_config.items():
            if not isinstance(authorizer, dict):
                raise InvalidResourceException(
                    self.logical_id,
                    "Authorizer %s must be a dictionary." % (authorizer_name))

            if "OpenIdConnectUrl" in authorizer:
                raise InvalidResourceException(
                    self.logical_id,
                    "'OpenIdConnectUrl' is no longer a supported property for authorizer '%s'. Please refer to the AWS SAM documentation."
                    % (authorizer_name),
                )
            authorizers[authorizer_name] = ApiGatewayV2Authorizer(
                api_logical_id=self.logical_id,
                name=authorizer_name,
                authorization_scopes=authorizer.get("AuthorizationScopes"),
                jwt_configuration=authorizer.get("JwtConfiguration"),
                id_source=authorizer.get("IdentitySource"),
            )
        return authorizers
    def _add_cors(self):
        """
        Add CORS configuration to the Swagger file, if necessary
        """

        INVALID_ERROR = "Invalid value for 'Cors' property"

        if not self.cors:
            return

        if self.cors and not self.definition_body:
            raise InvalidResourceException(
                self.logical_id,
                "Cors works only with inline Swagger specified in "
                "'DefinitionBody' property")

        if isinstance(self.cors, string_types) or is_instrinsic(self.cors):
            # Just set Origin property. Others will be defaults
            properties = CorsProperties(AllowOrigin=self.cors)
        elif isinstance(self.cors, dict):

            # Make sure keys in the dict are recognized
            if not all(key in CorsProperties._fields
                       for key in self.cors.keys()):
                raise InvalidResourceException(self.logical_id, INVALID_ERROR)

            properties = CorsProperties(**self.cors)

        else:
            raise InvalidResourceException(self.logical_id, INVALID_ERROR)

        if not SwaggerEditor.is_valid(self.definition_body):
            raise InvalidResourceException(
                self.logical_id,
                "Unable to add Cors configuration because "
                "'DefinitionBody' does not contain a valid Swagger",
            )

        if properties.AllowCredentials is True and properties.AllowOrigin == _CORS_WILDCARD:
            raise InvalidResourceException(
                self.logical_id,
                "Unable to add Cors configuration because "
                "'AllowCredentials' can not be true when "
                "'AllowOrigin' is \"'*'\" or not set",
            )

        editor = SwaggerEditor(self.definition_body)
        for path in editor.iter_on_path():
            editor.add_cors(
                path,
                properties.AllowOrigin,
                properties.AllowHeaders,
                properties.AllowMethods,
                max_age=properties.MaxAge,
                allow_credentials=properties.AllowCredentials,
            )

        # Assign the Swagger back to template
        self.definition_body = editor.swagger
예제 #25
0
    def to_cloudformation(self):
        """
        Constructs and returns the State Machine resource and any additional resources associated with it.

        :returns: a list of resources including the State Machine resource.
        :rtype: list
        """
        resources = [self.state_machine]

        # Defaulting to {} will add the DefinitionSubstitutions field on the transform output even when it is not relevant
        if self.definition_substitutions:
            self.state_machine.DefinitionSubstitutions = self.definition_substitutions

        if self.definition and self.definition_uri:
            raise InvalidResourceException(
                self.logical_id, "Specify either 'Definition' or 'DefinitionUri' property and not both."
            )
        elif self.definition:
            processed_definition = deepcopy(self.definition)
            substitutions = self._replace_dynamic_values_with_substitutions(processed_definition)
            if len(substitutions) > 0:
                if self.state_machine.DefinitionSubstitutions:
                    self.state_machine.DefinitionSubstitutions.update(substitutions)
                else:
                    self.state_machine.DefinitionSubstitutions = substitutions
            self.state_machine.DefinitionString = self._build_definition_string(processed_definition)
        elif self.definition_uri:
            self.state_machine.DefinitionS3Location = self._construct_definition_uri()
        else:
            raise InvalidResourceException(
                self.logical_id, "Either 'Definition' or 'DefinitionUri' property must be specified."
            )

        if self.role and self.policies:
            raise InvalidResourceException(
                self.logical_id, "Specify either 'Role' or 'Policies' property and not both."
            )
        elif self.role:
            self.state_machine.RoleArn = self.role
        elif self.policies:
            if not self.managed_policy_map:
                raise Exception("Managed policy map is empty, but should not be.")

            execution_role = self._construct_role()
            self.state_machine.RoleArn = execution_role.get_runtime_attr("arn")
            resources.append(execution_role)
        else:
            raise InvalidResourceException(self.logical_id, "Either 'Role' or 'Policies' property must be specified.")

        self.state_machine.StateMachineName = self.name
        self.state_machine.StateMachineType = self.type
        self.state_machine.LoggingConfiguration = self.logging
        self.state_machine.TracingConfiguration = self.tracing
        self.state_machine.Tags = self._construct_tag_list()

        event_resources = self._generate_event_resources()
        resources.extend(event_resources)

        return resources
예제 #26
0
    def _construct_api_domain(self, http_api):
        """
        Constructs and returns the ApiGateway Domain and BasepathMapping
        """
        if self.domain is None:
            return None, None, None

        if self.domain.get("DomainName") is None or self.domain.get(
                "CertificateArn") is None:
            raise InvalidResourceException(
                self.logical_id,
                "Custom Domains only works if both DomainName and CertificateArn"
                " are provided.")

        self.domain["ApiDomainName"] = "{}{}".format(
            "ApiGatewayDomainNameV2",
            logical_id_generator.LogicalIdGenerator(
                "", self.domain.get("DomainName")).gen())

        domain = ApiGatewayV2DomainName(
            self.domain.get("ApiDomainName"),
            attributes=self.passthrough_resource_attributes)
        domain_config = dict()
        domain.DomainName = self.domain.get("DomainName")
        domain.Tags = self.tags
        endpoint = self.domain.get("EndpointConfiguration")

        if endpoint is None:
            endpoint = "REGIONAL"
            # to make sure that default is always REGIONAL
            self.domain["EndpointConfiguration"] = "REGIONAL"
        elif endpoint not in ["REGIONAL"]:
            raise InvalidResourceException(
                self.logical_id,
                "EndpointConfiguration for Custom Domains must be one of {}.".
                format(["REGIONAL"]),
            )
        domain_config["EndpointType"] = endpoint
        domain_config["CertificateArn"] = self.domain.get("CertificateArn")

        domain.DomainNameConfigurations = [domain_config]

        # Create BasepathMappings
        if self.domain.get("BasePath") and isinstance(
                self.domain.get("BasePath"), string_types):
            basepaths = [self.domain.get("BasePath")]
        elif self.domain.get("BasePath") and isinstance(
                self.domain.get("BasePath"), list):
            basepaths = self.domain.get("BasePath")
        else:
            basepaths = None
        basepath_resource_list = self._construct_basepath_mappings(
            basepaths, http_api)

        # Create the Route53 RecordSetGroup resource
        record_set_group = self._construct_route53_recordsetgroup()

        return domain, basepath_resource_list, record_set_group
예제 #27
0
    def _construct_rest_api(self):
        """Constructs and returns the ApiGateway RestApi.

        :returns: the RestApi to which this SAM Api corresponds
        :rtype: model.apigateway.ApiGatewayRestApi
        """
        rest_api = ApiGatewayRestApi(self.logical_id,
                                     depends_on=self.depends_on,
                                     attributes=self.resource_attributes)
        # NOTE: For backwards compatibility we need to retain BinaryMediaTypes on the CloudFormation Property
        # Removing this and only setting x-amazon-apigateway-binary-media-types results in other issues.
        rest_api.BinaryMediaTypes = self.binary_media
        rest_api.MinimumCompressionSize = self.minimum_compression_size

        if self.endpoint_configuration:
            self._set_endpoint_configuration(rest_api,
                                             self.endpoint_configuration)

        elif not RegionConfiguration.is_apigw_edge_configuration_supported():
            # Since this region does not support EDGE configuration, we explicitly set the endpoint type
            # to Regional which is the only supported config.
            self._set_endpoint_configuration(rest_api, "REGIONAL")

        if self.definition_uri and self.definition_body:
            raise InvalidResourceException(
                self.logical_id,
                "Specify either 'DefinitionUri' or 'DefinitionBody' property and not both."
            )

        if self.open_api_version:
            if not SwaggerEditor.safe_compare_regex_with_string(
                    SwaggerEditor.get_openapi_versions_supported_regex(),
                    self.open_api_version):
                raise InvalidResourceException(
                    self.logical_id,
                    "The OpenApiVersion value must be of the format '3.0.0'.")

        self._add_cors()
        self._add_auth()
        self._add_gateway_responses()
        self._add_binary_media_types()
        self._add_models()

        if self.definition_uri:
            rest_api.BodyS3Location = self._construct_body_s3_dict()
        elif self.definition_body:
            # # Post Process OpenApi Auth Settings
            self.definition_body = self._openapi_postprocess(
                self.definition_body)
            rest_api.Body = self.definition_body

        if self.name:
            rest_api.Name = self.name

        if self.description:
            rest_api.Description = self.description

        return rest_api
예제 #28
0
 def _validate_jwt_authorizer(self):
     if not self.jwt_configuration:
         raise InvalidResourceException(
             self.api_logical_id, self.name +
             " OAuth2 Authorizer must define 'JwtConfiguration'.")
     if not self.id_source:
         raise InvalidResourceException(
             self.api_logical_id,
             self.name + " OAuth2 Authorizer must define 'IdentitySource'.")
예제 #29
0
 def _validate_lambda_authorizer(self):
     if not self.function_arn:
         raise InvalidResourceException(
             self.api_logical_id,
             self.name + " Lambda Authorizer must define 'FunctionArn'.")
     if not self.authorizer_payload_format_version:
         raise InvalidResourceException(
             self.api_logical_id, self.name +
             " Lambda Authorizer must define 'AuthorizerPayloadFormatVersion'."
         )
예제 #30
0
    def _add_auth(self):
        """
        Add Auth configuration to the Swagger file, if necessary
        """

        if not self.auth:
            return

        if self.auth and not self.definition_body:
            raise InvalidResourceException(
                self.logical_id,
                "Auth works only with inline Swagger specified in "
                "'DefinitionBody' property.")

        # Make sure keys in the dict are recognized
        if not all(key in AuthProperties._fields for key in self.auth.keys()):
            raise InvalidResourceException(
                self.logical_id, "Invalid value for 'Auth' property")

        if not SwaggerEditor.is_valid(self.definition_body):
            raise InvalidResourceException(
                self.logical_id,
                "Unable to add Auth configuration because "
                "'DefinitionBody' does not contain a valid Swagger definition.",
            )
        swagger_editor = SwaggerEditor(self.definition_body)
        auth_properties = AuthProperties(**self.auth)
        authorizers = self._get_authorizers(auth_properties.Authorizers,
                                            auth_properties.DefaultAuthorizer)

        if authorizers:
            swagger_editor.add_authorizers_security_definitions(authorizers)
            self._set_default_authorizer(
                swagger_editor,
                authorizers,
                auth_properties.DefaultAuthorizer,
                auth_properties.AddDefaultAuthorizerToCorsPreflight,
                auth_properties.Authorizers,
            )

        if auth_properties.ApiKeyRequired:
            swagger_editor.add_apikey_security_definition()
            self._set_default_apikey_required(swagger_editor)

        if auth_properties.ResourcePolicy:
            for path in swagger_editor.iter_on_path():
                swagger_editor.add_resource_policy(
                    auth_properties.ResourcePolicy, path, self.logical_id,
                    self.stage_name)
            if auth_properties.ResourcePolicy.get("CustomStatements"):
                swagger_editor.add_custom_statements(
                    auth_properties.ResourcePolicy.get("CustomStatements"))

        self.definition_body = self._openapi_postprocess(
            swagger_editor.swagger)