diff --git a/samtranslator/__init__.py b/samtranslator/__init__.py index 9813fabb0..b3f408df1 100644 --- a/samtranslator/__init__.py +++ b/samtranslator/__init__.py @@ -1 +1 @@ -__version__ = "1.27.0" +__version__ = "1.28.0" diff --git a/samtranslator/model/api/http_api_generator.py b/samtranslator/model/api/http_api_generator.py index afd9c4073..76ccecf3f 100644 --- a/samtranslator/model/api/http_api_generator.py +++ b/samtranslator/model/api/http_api_generator.py @@ -436,6 +436,11 @@ def _get_authorizers(self, authorizers_config, default_authorizer=None): authorization_scopes=authorizer.get("AuthorizationScopes"), jwt_configuration=authorizer.get("JwtConfiguration"), id_source=authorizer.get("IdentitySource"), + function_arn=authorizer.get("FunctionArn"), + function_invoke_role=authorizer.get("FunctionInvokeRole"), + identity=authorizer.get("Identity"), + authorizer_payload_format_version=authorizer.get("AuthorizerPayloadFormatVersion"), + enable_simple_responses=authorizer.get("EnableSimpleResponses"), ) return authorizers diff --git a/samtranslator/model/apigatewayv2.py b/samtranslator/model/apigatewayv2.py index ecce846df..ea4675eec 100644 --- a/samtranslator/model/apigatewayv2.py +++ b/samtranslator/model/apigatewayv2.py @@ -1,7 +1,10 @@ from samtranslator.model import PropertyType, Resource from samtranslator.model.types import is_type, one_of, is_str, list_of -from samtranslator.model.intrinsics import ref +from samtranslator.model.intrinsics import ref, fnSub from samtranslator.model.exceptions import InvalidResourceException +from samtranslator.translator.arn_generator import ArnGenerator + +APIGATEWAY_AUTHORIZER_KEY = "x-amazon-apigateway-authorizer" class ApiGatewayV2HttpApi(Resource): @@ -57,39 +60,209 @@ class ApiGatewayV2ApiMapping(Resource): class ApiGatewayV2Authorizer(object): def __init__( - self, api_logical_id=None, name=None, authorization_scopes=[], jwt_configuration={}, id_source=None, + self, + api_logical_id=None, + name=None, + authorization_scopes=None, + jwt_configuration=None, + id_source=None, + function_arn=None, + function_invoke_role=None, + identity=None, + authorizer_payload_format_version=None, + enable_simple_responses=None, ): """ Creates an authorizer for use in V2 Http Apis """ - if authorization_scopes is not None and not isinstance(authorization_scopes, list): - raise InvalidResourceException(api_logical_id, "AuthorizationScopes must be a list.") - - # 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 + self.function_arn = function_arn + self.function_invoke_role = function_invoke_role + self.identity = identity + self.authorizer_payload_format_version = authorizer_payload_format_version + self.enable_simple_responses = enable_simple_responses + + self._validate_input_parameters() + + authorizer_type = self._get_auth_type() + + # Validate necessary parameters exist + if authorizer_type == "JWT": + self._validate_jwt_authorizer() + + if authorizer_type == "REQUEST": + self._validate_lambda_authorizer() + + def _get_auth_type(self): + if self.jwt_configuration: + return "JWT" + return "REQUEST" + + def _validate_input_parameters(self): + authorizer_type = self._get_auth_type() + + if self.authorization_scopes is not None and not isinstance(self.authorization_scopes, list): + raise InvalidResourceException(self.api_logical_id, "AuthorizationScopes must be a list.") + + if self.authorization_scopes is not None and not authorizer_type == "JWT": + raise InvalidResourceException( + self.api_logical_id, "AuthorizationScopes must be defined only for OAuth2 Authorizer." + ) + + if self.jwt_configuration is not None and not authorizer_type == "JWT": + raise InvalidResourceException( + self.api_logical_id, "JwtConfiguration must be defined only for OAuth2 Authorizer." + ) + + if self.id_source is not None and not authorizer_type == "JWT": + raise InvalidResourceException( + self.api_logical_id, "IdentitySource must be defined only for OAuth2 Authorizer." + ) + + if self.function_arn is not None and not authorizer_type == "REQUEST": + raise InvalidResourceException( + self.api_logical_id, "FunctionArn must be defined only for Lambda Authorizer." + ) + + if self.function_invoke_role is not None and not authorizer_type == "REQUEST": + raise InvalidResourceException( + self.api_logical_id, "FunctionInvokeRole must be defined only for Lambda Authorizer." + ) + + if self.identity is not None and not authorizer_type == "REQUEST": + raise InvalidResourceException(self.api_logical_id, "Identity must be defined only for Lambda Authorizer.") + + if self.authorizer_payload_format_version is not None and not authorizer_type == "REQUEST": + raise InvalidResourceException( + self.api_logical_id, "AuthorizerPayloadFormatVersion must be defined only for Lambda Authorizer." + ) + + if self.enable_simple_responses is not None and not authorizer_type == "REQUEST": + raise InvalidResourceException( + self.api_logical_id, "EnableSimpleResponses must be defined only for Lambda Authorizer." + ) + + 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'." + ) + + 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'." + ) def generate_openapi(self): """ Generates OAS for the securitySchemes section """ - openapi = { - "type": self.auth_type, - "x-amazon-apigateway-authorizer": { + 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 + + def _get_function_invoke_role(self): + if not self.function_invoke_role or self.function_invoke_role == "NONE": + return None + + return self.function_invoke_role + + def _get_identity_source(self): + identity_source_headers = [] + identity_source_query_strings = [] + identity_source_stage_variables = [] + identity_source_context = [] + + if self.identity.get("Headers"): + identity_source_headers = list(map(lambda h: "$request.header." + h, self.identity.get("Headers"))) + + if self.identity.get("QueryStrings"): + identity_source_query_strings = list( + map(lambda qs: "$request.querystring." + qs, self.identity.get("QueryStrings")) + ) + + if self.identity.get("StageVariables"): + identity_source_stage_variables = list( + map(lambda sv: "$stageVariables." + sv, self.identity.get("StageVariables")) + ) + + if self.identity.get("Context"): + identity_source_context = list(map(lambda c: "$context." + c, self.identity.get("Context"))) + + identity_source = ( + identity_source_headers + + identity_source_query_strings + + identity_source_stage_variables + + identity_source_context + ) + + return identity_source + + def _get_reauthorize_every(self): + if not self.identity: + return None + + return self.identity.get("ReauthorizeEvery") diff --git a/samtranslator/model/stepfunctions/events.py b/samtranslator/model/stepfunctions/events.py index 22d2e7e27..290fd75f0 100644 --- a/samtranslator/model/stepfunctions/events.py +++ b/samtranslator/model/stepfunctions/events.py @@ -43,7 +43,7 @@ def _generate_logical_id(self, prefix, suffix, resource_type): return logical_id def _construct_role(self, resource, prefix=None, suffix=""): - """Constructs the IAM Role resource allowing the event service to invoke + """Constructs the IAM Role resource allowing the event service to invoke the StartExecution API of the state machine resource it is associated with. :param model.stepfunctions.StepFunctionsStateMachine resource: The state machine resource associated with the event @@ -135,7 +135,7 @@ class CloudWatchEvent(EventSource): } def to_cloudformation(self, resource, **kwargs): - """Returns the CloudWatch Events/EventBridge Rule and IAM Role to which this + """Returns the CloudWatch Events/EventBridge Rule and IAM Role to which this CloudWatch Events/EventBridge event source corresponds. :param dict kwargs: no existing resources need to be modified @@ -379,10 +379,10 @@ def _add_swagger_integration(self, api, resource, role, intrinsics_resolver): api["DefinitionBody"] = editor.swagger def _generate_request_template(self, resource): - """Generates the Body mapping request template for the Api. This allows for the input + """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 + :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 diff --git a/samtranslator/model/stepfunctions/generators.py b/samtranslator/model/stepfunctions/generators.py index 427e2d6f5..1170cb18b 100644 --- a/samtranslator/model/stepfunctions/generators.py +++ b/samtranslator/model/stepfunctions/generators.py @@ -54,7 +54,7 @@ def __init__( :param logical_id: Logical id of the SAM State Machine Resource :param depends_on: Any resources that need to be depended on :param managed_policy_map: Map of managed policy names to the ARNs - :param intrinsics_resolver: Instance of the resolver that knows how to resolve parameter references + :param intrinsics_resolver: Instance of the resolver that knows how to resolve parameter references :param definition: State Machine definition :param definition_uri: URI to State Machine definition :param logging: Logging configuration for the State Machine diff --git a/samtranslator/open_api/open_api.py b/samtranslator/open_api/open_api.py index 68b652cfb..40bea55f9 100644 --- a/samtranslator/open_api/open_api.py +++ b/samtranslator/open_api/open_api.py @@ -246,7 +246,7 @@ def iter_on_path(self): def add_timeout_to_method(self, api, path, method_name, timeout): """ Adds a timeout to this path/method. - + :param dict api: Reference to the related Api's properties as defined in the template. :param string path: Path name :param string method_name: Method name @@ -260,7 +260,7 @@ def add_timeout_to_method(self, api, path, method_name, timeout): def add_path_parameters_to_method(self, api, path, method_name, path_parameters): """ Adds path parameters to this path + method - + :param dict api: Reference to the related Api's properties as defined in the template. :param string path: Path name :param string method_name: Method name diff --git a/tests/model/test_api_v2.py b/tests/model/test_api_v2.py index d895067c3..eb32e711c 100644 --- a/tests/model/test_api_v2.py +++ b/tests/model/test_api_v2.py @@ -1,8 +1,9 @@ from unittest import TestCase import pytest +import mock from samtranslator.model import InvalidResourceException -from samtranslator.model.apigatewayv2 import ApiGatewayV2Authorizer, ApiGatewayV2HttpApi, ApiGatewayV2Stage +from samtranslator.model.apigatewayv2 import ApiGatewayV2Authorizer class TestApiGatewayV2Authorizer(TestCase): @@ -14,23 +15,185 @@ def test_create_oauth2_auth(self): id_source="https://example.com", authorization_scopes=["scope1", "scope2"], ) - self.assertEquals(auth.auth_type, "oauth2") + self.assertEquals(auth.api_logical_id, "logicalId") + self.assertEquals(auth.name, "authName") + self.assertEquals(auth.jwt_configuration, {"config": "value"}) + self.assertEquals(auth.id_source, "https://example.com") + self.assertEquals(auth.authorization_scopes, ["scope1", "scope2"]) - def test_create_authorizer_no_id_source(self): - with pytest.raises(InvalidResourceException): - auth = ApiGatewayV2Authorizer( - api_logical_id="logicalId", name="authName", jwt_configuration={"config": "value"} - ) - - def test_create_authorizer_no_jwt_config(self): - with pytest.raises(InvalidResourceException): - auth = ApiGatewayV2Authorizer(api_logical_id="logicalId", name="authName", id_source="https://example.com") + def test_create_lambda_auth(self): + auth = ApiGatewayV2Authorizer( + api_logical_id="logicalId", + name="lambdaAuth", + function_arn="lambdaArn", + function_invoke_role="iamRole", + identity={"Headers": ["Authorization"], "ReauthorizeEvery": 42}, + authorizer_payload_format_version="2.0", + enable_simple_responses=True, + ) + self.assertEquals(auth.api_logical_id, "logicalId") + self.assertEquals(auth.name, "lambdaAuth") + self.assertEquals(auth.function_arn, "lambdaArn") + self.assertEquals(auth.identity, {"Headers": ["Authorization"], "ReauthorizeEvery": 42}) + self.assertEquals(auth.authorizer_payload_format_version, "2.0") + self.assertEquals(auth.enable_simple_responses, True) def test_create_authorizer_fails_with_string_authorization_scopes(self): - with pytest.raises(InvalidResourceException): - auth = ApiGatewayV2Authorizer( + with pytest.raises(InvalidResourceException) as e: + ApiGatewayV2Authorizer( api_logical_id="logicalId", name="authName", jwt_configuration={"config": "value"}, authorization_scopes="invalid_scope", ) + self.assertEqual( + e.value.message, "Resource with id [logicalId] is invalid. " + "AuthorizationScopes must be a list.", + ) + + def test_create_authorizer_fails_with_authorization_scopes_non_oauth2(self): + with pytest.raises(InvalidResourceException) as e: + ApiGatewayV2Authorizer( + api_logical_id="logicalId", name="authName", authorization_scopes=["scope1", "scope2"], + ) + self.assertEqual( + e.value.message, + "Resource with id [logicalId] is invalid. " + + "AuthorizationScopes must be defined only for OAuth2 Authorizer.", + ) + + @mock.patch( + "samtranslator.model.apigatewayv2.ApiGatewayV2Authorizer._get_auth_type", mock.MagicMock(return_value="INVALID") + ) + def test_create_authorizer_fails_with_jtw_configuration_non_oauth2(self): + with pytest.raises(InvalidResourceException) as e: + ApiGatewayV2Authorizer( + api_logical_id="logicalId", name="authName", jwt_configuration={"config": "value"}, + ) + self.assertEqual( + e.value.message, + "Resource with id [logicalId] is invalid. " + + "JwtConfiguration must be defined only for OAuth2 Authorizer.", + ) + + def test_create_authorizer_fails_with_id_source_non_oauth2(self): + with pytest.raises(InvalidResourceException) as e: + ApiGatewayV2Authorizer( + api_logical_id="logicalId", name="authName", id_source="https://example.com", + ) + self.assertEqual( + e.value.message, + "Resource with id [logicalId] is invalid. " + "IdentitySource must be defined only for OAuth2 Authorizer.", + ) + + def test_create_authorizer_fails_with_function_arn_non_lambda(self): + with pytest.raises(InvalidResourceException) as e: + ApiGatewayV2Authorizer( + api_logical_id="logicalId", + name="authName", + jwt_configuration={"config": "value"}, + authorization_scopes=["scope1", "scope2"], + function_arn="lambdaArn", + ) + self.assertEqual( + e.value.message, + "Resource with id [logicalId] is invalid. " + "FunctionArn must be defined only for Lambda Authorizer.", + ) + + def test_create_authorizer_fails_with_function_invoke_role_non_lambda(self): + with pytest.raises(InvalidResourceException) as e: + ApiGatewayV2Authorizer( + api_logical_id="logicalId", + name="authName", + jwt_configuration={"config": "value"}, + authorization_scopes=["scope1", "scope2"], + function_invoke_role="iamRole", + ) + self.assertEqual( + e.value.message, + "Resource with id [logicalId] is invalid. " + + "FunctionInvokeRole must be defined only for Lambda Authorizer.", + ) + + def test_create_authorizer_fails_with_identity_non_lambda(self): + with pytest.raises(InvalidResourceException) as e: + ApiGatewayV2Authorizer( + api_logical_id="logicalId", + name="authName", + jwt_configuration={"config": "value"}, + authorization_scopes=["scope1", "scope2"], + identity={"Headers": ["Authorization"], "ReauthorizeEvery": 42}, + ) + self.assertEqual( + e.value.message, + "Resource with id [logicalId] is invalid. " + "Identity must be defined only for Lambda Authorizer.", + ) + + def test_create_authorizer_fails_with_authorizer_payload_format_version_non_lambda(self): + with pytest.raises(InvalidResourceException) as e: + ApiGatewayV2Authorizer( + api_logical_id="logicalId", + name="authName", + jwt_configuration={"config": "value"}, + authorization_scopes=["scope1", "scope2"], + authorizer_payload_format_version="2.0", + ) + self.assertEqual( + e.value.message, + "Resource with id [logicalId] is invalid. " + + "AuthorizerPayloadFormatVersion must be defined only for Lambda Authorizer.", + ) + + def test_create_authorizer_fails_with_enable_simple_responses_non_lambda(self): + with pytest.raises(InvalidResourceException) as e: + ApiGatewayV2Authorizer( + api_logical_id="logicalId", + name="authName", + jwt_configuration={"config": "value"}, + authorization_scopes=["scope1", "scope2"], + enable_simple_responses=True, + ) + self.assertEqual( + e.value.message, + "Resource with id [logicalId] is invalid. " + + "EnableSimpleResponses must be defined only for Lambda Authorizer.", + ) + + @mock.patch( + "samtranslator.model.apigatewayv2.ApiGatewayV2Authorizer._get_auth_type", mock.MagicMock(return_value="JWT") + ) + def test_create_jwt_authorizer_no_jwt_configuration(self): + with pytest.raises(InvalidResourceException) as e: + ApiGatewayV2Authorizer(api_logical_id="logicalId", name="authName") + self.assertEqual( + e.value.message, + "Resource with id [logicalId] is invalid. " + "authName OAuth2 Authorizer must define 'JwtConfiguration'.", + ) + + def test_create_jwt_authorizer_no_id_source(self): + with pytest.raises(InvalidResourceException) as e: + ApiGatewayV2Authorizer(api_logical_id="logicalId", name="authName", jwt_configuration={"config": "value"}) + self.assertEqual( + e.value.message, + "Resource with id [logicalId] is invalid. " + "authName OAuth2 Authorizer must define 'IdentitySource'.", + ) + + def test_create_lambda_auth_no_function_arn(self): + with pytest.raises(InvalidResourceException) as e: + ApiGatewayV2Authorizer( + api_logical_id="logicalId", name="lambdaAuth", + ) + self.assertEqual( + e.value.message, + "Resource with id [logicalId] is invalid. " + "lambdaAuth Lambda Authorizer must define 'FunctionArn'.", + ) + + def test_create_lambda_auth_no_authorizer_payload_format_version(self): + with pytest.raises(InvalidResourceException) as e: + ApiGatewayV2Authorizer( + api_logical_id="logicalId", name="lambdaAuth", function_arn="lambdaArn", + ) + self.assertEqual( + e.value.message, + "Resource with id [logicalId] is invalid. " + + "lambdaAuth Lambda Authorizer must define 'AuthorizerPayloadFormatVersion'.", + ) diff --git a/tests/translator/input/error_http_api_invalid_lambda_auth.yaml b/tests/translator/input/error_http_api_invalid_lambda_auth.yaml new file mode 100644 index 000000000..679a0f325 --- /dev/null +++ b/tests/translator/input/error_http_api_invalid_lambda_auth.yaml @@ -0,0 +1,79 @@ +Resources: + Function1: + Type: AWS::Serverless::Function + Properties: + Runtime: python3.7 + Handler: index.handler + CodeUri: s3://bucket/key + Events: + Api: + Type: HttpApi + Properties: + ApiId: !Ref MyApi1 + Auth: + Authorizer: LambdaAuth + Function2: + Type: AWS::Serverless::Function + Properties: + Runtime: python3.7 + Handler: index.handler + CodeUri: s3://bucket/key + Events: + Api2: + Type: HttpApi + Properties: + ApiId: !Ref MyApi2 + Auth: + Authorizer: LambdaAuth + + MyAuthFn: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://bucket/key + Handler: index.handler + Runtime: nodejs12.x + + MyApi1: + Type: AWS::Serverless::HttpApi + Properties: + Tags: + Tag1: value1 + Tag2: value2 + Auth: + Authorizers: + LambdaAuth: + FunctionArn: !GetAtt MyAuthFn.Arn + FunctionInvokeRole: !GetAtt MyAuthFnRole.Arn + Identity: + Context: + - contextVar + Headers: + - Authorization + QueryStrings: + - petId + StageVariables: + - stageVar + ReauthorizeEvery: 60 + EnableSimpleResponses: true + DefaultAuthorizer: LambdaAuth + + MyApi2: + Type: AWS::Serverless::HttpApi + Properties: + Auth: + Authorizers: + LambdaAuth: + FunctionInvokeRole: !GetAtt MyAuthFnRole.Arn + Identity: + Context: + - contextVar + Headers: + - Authorization + QueryStrings: + - petId + StageVariables: + - stageVar + ReauthorizeEvery: 60 + AuthorizerPayloadFormatVersion: 2.0 + EnableSimpleResponses: true + DefaultAuthorizer: LambdaAuth \ No newline at end of file diff --git a/tests/translator/input/http_api_lambda_auth.yaml b/tests/translator/input/http_api_lambda_auth.yaml new file mode 100644 index 000000000..04bd02d60 --- /dev/null +++ b/tests/translator/input/http_api_lambda_auth.yaml @@ -0,0 +1,45 @@ +Resources: + HttpApiFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/todo_list.zip + Handler: index.restapi + Runtime: python3.7 + Events: + Basic: # integration exists + Type: HttpApi + Properties: + PayloadFormatVersion: "2.0" + Path: /basic + Method: post + ApiId: !Ref MyApi + Basic2: # integration exists, auth doesn't + Type: HttpApi + Properties: + Path: /basic + Method: get + ApiId: !Ref MyApi + SimpleCase: # path exists, integration doesn't + Type: HttpApi + Properties: + ApiId: !Ref MyApi + + MyAuthFn: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://bucket/key + Handler: index.handler + Runtime: nodejs12.x + + MyApi: + Type: AWS::Serverless::HttpApi + Properties: + Tags: + Tag1: value1 + Tag2: value2 + Auth: + Authorizers: + LambdaAuth: + FunctionArn: !GetAtt MyAuthFn.Arn + AuthorizerPayloadFormatVersion: 1.0 + DefaultAuthorizer: LambdaAuth \ No newline at end of file diff --git a/tests/translator/input/http_api_lambda_auth_full.yaml b/tests/translator/input/http_api_lambda_auth_full.yaml new file mode 100644 index 000000000..5a5b976fa --- /dev/null +++ b/tests/translator/input/http_api_lambda_auth_full.yaml @@ -0,0 +1,57 @@ +Resources: + HttpApiFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/todo_list.zip + Handler: index.restapi + Runtime: python3.7 + Events: + Basic: # integration exists + Type: HttpApi + Properties: + PayloadFormatVersion: "2.0" + Path: /basic + Method: post + ApiId: !Ref MyApi + Basic2: # integration exists, auth doesn't + Type: HttpApi + Properties: + Path: /basic + Method: get + ApiId: !Ref MyApi + SimpleCase: # path exists, integration doesn't + Type: HttpApi + Properties: + ApiId: !Ref MyApi + + MyAuthFn: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://bucket/key + Handler: index.handler + Runtime: nodejs12.x + + MyApi: + Type: AWS::Serverless::HttpApi + Properties: + Tags: + Tag1: value1 + Tag2: value2 + Auth: + Authorizers: + LambdaAuth: + FunctionArn: !GetAtt MyAuthFn.Arn + FunctionInvokeRole: !GetAtt MyAuthFnRole.Arn + Identity: + Context: + - contextVar + Headers: + - Authorization + QueryStrings: + - petId + StageVariables: + - stageVar + ReauthorizeEvery: 60 + AuthorizerPayloadFormatVersion: 2.0 + EnableSimpleResponses: true + DefaultAuthorizer: LambdaAuth \ No newline at end of file diff --git a/tests/translator/output/aws-cn/http_api_lambda_auth.json b/tests/translator/output/aws-cn/http_api_lambda_auth.json new file mode 100644 index 000000000..9f40ee94f --- /dev/null +++ b/tests/translator/output/aws-cn/http_api_lambda_auth.json @@ -0,0 +1,255 @@ +{ + "Resources": { + "HttpApiFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Handler": "index.restapi", + "Role": { + "Fn::GetAtt": [ + "HttpApiFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "HttpApiFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "HttpApiFunctionSimpleCasePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "HttpApiFunction" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + { + "__ApiId__": { + "Ref": "MyApi" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyAuthFn": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyAuthFnRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyAuthFnRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "openapi": "3.0.1", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/basic": { + "post": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "LambdaAuth": [] + } + ] + }, + "get": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "LambdaAuth": [] + } + ] + } + }, + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + }, + "isDefaultRoute": true, + "responses": {}, + "security": [ + { + "LambdaAuth": [] + } + ] + } + } + }, + "components": { + "securitySchemes": { + "LambdaAuth": { + "type": "apiKey", + "name": "Unused", + "in": "header", + "x-amazon-apigateway-authorizer": { + "type": "request", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "authorizerPayloadFormatVersion": 1.0 + } + } + } + }, + "tags": [ + { + "name": "Tag1", + "x-amazon-apigateway-tag-value": "value1" + }, + { + "name": "Tag2", + "x-amazon-apigateway-tag-value": "value2" + }, + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + }, + "MyApiApiGatewayDefaultStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "StageName": "$default", + "Tags": { + "Tag1": "value1", + "Tag2": "value2", + "httpapi:createdBy": "SAM" + }, + "AutoDeploy": true + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/http_api_lambda_auth_full.json b/tests/translator/output/aws-cn/http_api_lambda_auth_full.json new file mode 100644 index 000000000..7fe416f92 --- /dev/null +++ b/tests/translator/output/aws-cn/http_api_lambda_auth_full.json @@ -0,0 +1,269 @@ +{ + "Resources": { + "HttpApiFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Handler": "index.restapi", + "Role": { + "Fn::GetAtt": [ + "HttpApiFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "HttpApiFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "HttpApiFunctionSimpleCasePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "HttpApiFunction" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + { + "__ApiId__": { + "Ref": "MyApi" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyAuthFn": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyAuthFnRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyAuthFnRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "openapi": "3.0.1", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/basic": { + "post": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "LambdaAuth": [] + } + ] + }, + "get": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "LambdaAuth": [] + } + ] + } + }, + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + }, + "isDefaultRoute": true, + "responses": {}, + "security": [ + { + "LambdaAuth": [] + } + ] + } + } + }, + "components": { + "securitySchemes": { + "LambdaAuth": { + "type": "apiKey", + "name": "Unused", + "in": "header", + "x-amazon-apigateway-authorizer": { + "type": "request", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "authorizerCredentials": { + "Fn::GetAtt": [ + "MyAuthFnRole", + "Arn" + ] + }, + "identitySource": [ + "$context.contextVar", + "$request.header.Authorization", + "$request.querystring.petId", + "$stageVariables.stageVar" + ], + "authorizerResultTtlInSeconds": 60, + "authorizerPayloadFormatVersion": 2.0, + "enableSimpleResponses": true + } + } + } + }, + "tags": [ + { + "name": "Tag1", + "x-amazon-apigateway-tag-value": "value1" + }, + { + "name": "Tag2", + "x-amazon-apigateway-tag-value": "value2" + }, + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + }, + "MyApiApiGatewayDefaultStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "StageName": "$default", + "Tags": { + "Tag1": "value1", + "Tag2": "value2", + "httpapi:createdBy": "SAM" + }, + "AutoDeploy": true + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/http_api_lambda_auth.json b/tests/translator/output/aws-us-gov/http_api_lambda_auth.json new file mode 100644 index 000000000..bf4184c64 --- /dev/null +++ b/tests/translator/output/aws-us-gov/http_api_lambda_auth.json @@ -0,0 +1,255 @@ +{ + "Resources": { + "HttpApiFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Handler": "index.restapi", + "Role": { + "Fn::GetAtt": [ + "HttpApiFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "HttpApiFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "HttpApiFunctionSimpleCasePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "HttpApiFunction" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + { + "__ApiId__": { + "Ref": "MyApi" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyAuthFn": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyAuthFnRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyAuthFnRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "openapi": "3.0.1", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/basic": { + "post": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "LambdaAuth": [] + } + ] + }, + "get": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "LambdaAuth": [] + } + ] + } + }, + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + }, + "isDefaultRoute": true, + "responses": {}, + "security": [ + { + "LambdaAuth": [] + } + ] + } + } + }, + "components": { + "securitySchemes": { + "LambdaAuth": { + "type": "apiKey", + "name": "Unused", + "in": "header", + "x-amazon-apigateway-authorizer": { + "type": "request", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "authorizerPayloadFormatVersion": 1.0 + } + } + } + }, + "tags": [ + { + "name": "Tag1", + "x-amazon-apigateway-tag-value": "value1" + }, + { + "name": "Tag2", + "x-amazon-apigateway-tag-value": "value2" + }, + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + }, + "MyApiApiGatewayDefaultStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "StageName": "$default", + "Tags": { + "Tag1": "value1", + "Tag2": "value2", + "httpapi:createdBy": "SAM" + }, + "AutoDeploy": true + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/http_api_lambda_auth_full.json b/tests/translator/output/aws-us-gov/http_api_lambda_auth_full.json new file mode 100644 index 000000000..3fe74a218 --- /dev/null +++ b/tests/translator/output/aws-us-gov/http_api_lambda_auth_full.json @@ -0,0 +1,269 @@ +{ + "Resources": { + "HttpApiFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Handler": "index.restapi", + "Role": { + "Fn::GetAtt": [ + "HttpApiFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "HttpApiFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "HttpApiFunctionSimpleCasePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "HttpApiFunction" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + { + "__ApiId__": { + "Ref": "MyApi" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyAuthFn": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyAuthFnRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyAuthFnRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "openapi": "3.0.1", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/basic": { + "post": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "LambdaAuth": [] + } + ] + }, + "get": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "LambdaAuth": [] + } + ] + } + }, + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + }, + "isDefaultRoute": true, + "responses": {}, + "security": [ + { + "LambdaAuth": [] + } + ] + } + } + }, + "components": { + "securitySchemes": { + "LambdaAuth": { + "type": "apiKey", + "name": "Unused", + "in": "header", + "x-amazon-apigateway-authorizer": { + "type": "request", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "authorizerCredentials": { + "Fn::GetAtt": [ + "MyAuthFnRole", + "Arn" + ] + }, + "identitySource": [ + "$context.contextVar", + "$request.header.Authorization", + "$request.querystring.petId", + "$stageVariables.stageVar" + ], + "authorizerResultTtlInSeconds": 60, + "authorizerPayloadFormatVersion": 2.0, + "enableSimpleResponses": true + } + } + } + }, + "tags": [ + { + "name": "Tag1", + "x-amazon-apigateway-tag-value": "value1" + }, + { + "name": "Tag2", + "x-amazon-apigateway-tag-value": "value2" + }, + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + }, + "MyApiApiGatewayDefaultStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "StageName": "$default", + "Tags": { + "Tag1": "value1", + "Tag2": "value2", + "httpapi:createdBy": "SAM" + }, + "AutoDeploy": true + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/error_http_api_invalid_lambda_auth.json b/tests/translator/output/error_http_api_invalid_lambda_auth.json new file mode 100644 index 000000000..298d91738 --- /dev/null +++ b/tests/translator/output/error_http_api_invalid_lambda_auth.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Resource with id [MyApi1] is invalid. LambdaAuth Lambda Authorizer must define 'AuthorizerPayloadFormatVersion'. Resource with id [MyApi2] is invalid. LambdaAuth Lambda Authorizer must define 'FunctionArn'." + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 2. Resource with id [MyApi1] is invalid. LambdaAuth Lambda Authorizer must define 'AuthorizerPayloadFormatVersion'. Resource with id [MyApi2] is invalid. LambdaAuth Lambda Authorizer must define 'FunctionArn'." +} \ No newline at end of file diff --git a/tests/translator/output/http_api_lambda_auth.json b/tests/translator/output/http_api_lambda_auth.json new file mode 100644 index 000000000..ac6d3b4fb --- /dev/null +++ b/tests/translator/output/http_api_lambda_auth.json @@ -0,0 +1,255 @@ +{ + "Resources": { + "HttpApiFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Handler": "index.restapi", + "Role": { + "Fn::GetAtt": [ + "HttpApiFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "HttpApiFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "HttpApiFunctionSimpleCasePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "HttpApiFunction" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + { + "__ApiId__": { + "Ref": "MyApi" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyAuthFn": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyAuthFnRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyAuthFnRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "openapi": "3.0.1", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/basic": { + "post": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "LambdaAuth": [] + } + ] + }, + "get": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "LambdaAuth": [] + } + ] + } + }, + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + }, + "isDefaultRoute": true, + "responses": {}, + "security": [ + { + "LambdaAuth": [] + } + ] + } + } + }, + "components": { + "securitySchemes": { + "LambdaAuth": { + "type": "apiKey", + "name": "Unused", + "in": "header", + "x-amazon-apigateway-authorizer": { + "type": "request", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "authorizerPayloadFormatVersion": 1.0 + } + } + } + }, + "tags": [ + { + "name": "Tag1", + "x-amazon-apigateway-tag-value": "value1" + }, + { + "name": "Tag2", + "x-amazon-apigateway-tag-value": "value2" + }, + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + }, + "MyApiApiGatewayDefaultStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "StageName": "$default", + "Tags": { + "Tag1": "value1", + "Tag2": "value2", + "httpapi:createdBy": "SAM" + }, + "AutoDeploy": true + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/http_api_lambda_auth_full.json b/tests/translator/output/http_api_lambda_auth_full.json new file mode 100644 index 000000000..20b14e636 --- /dev/null +++ b/tests/translator/output/http_api_lambda_auth_full.json @@ -0,0 +1,269 @@ +{ + "Resources": { + "HttpApiFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Handler": "index.restapi", + "Role": { + "Fn::GetAtt": [ + "HttpApiFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "HttpApiFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "HttpApiFunctionSimpleCasePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "HttpApiFunction" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + { + "__ApiId__": { + "Ref": "MyApi" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyAuthFn": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyAuthFnRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyAuthFnRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "openapi": "3.0.1", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/basic": { + "post": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "LambdaAuth": [] + } + ] + }, + "get": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "LambdaAuth": [] + } + ] + } + }, + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + }, + "isDefaultRoute": true, + "responses": {}, + "security": [ + { + "LambdaAuth": [] + } + ] + } + } + }, + "components": { + "securitySchemes": { + "LambdaAuth": { + "type": "apiKey", + "name": "Unused", + "in": "header", + "x-amazon-apigateway-authorizer": { + "type": "request", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "authorizerCredentials": { + "Fn::GetAtt": [ + "MyAuthFnRole", + "Arn" + ] + }, + "identitySource": [ + "$context.contextVar", + "$request.header.Authorization", + "$request.querystring.petId", + "$stageVariables.stageVar" + ], + "authorizerResultTtlInSeconds": 60, + "authorizerPayloadFormatVersion": 2.0, + "enableSimpleResponses": true + } + } + } + }, + "tags": [ + { + "name": "Tag1", + "x-amazon-apigateway-tag-value": "value1" + }, + { + "name": "Tag2", + "x-amazon-apigateway-tag-value": "value2" + }, + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + }, + "MyApiApiGatewayDefaultStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "StageName": "$default", + "Tags": { + "Tag1": "value1", + "Tag2": "value2", + "httpapi:createdBy": "SAM" + }, + "AutoDeploy": true + } + } + } +} \ No newline at end of file diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index 72ecdac57..2ee086381 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -379,6 +379,8 @@ def test_transform_success(self, testcase, partition_with_region): "http_api_def_uri", "explicit_http_api", "http_api_with_cors", + "http_api_lambda_auth", + "http_api_lambda_auth_full", ], [ ("aws", "ap-southeast-1"), @@ -650,6 +652,7 @@ def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_sw "error_function_with_api_key_false", "error_api_with_usage_plan_invalid_parameter", "error_http_api_with_cors_def_uri", + "error_http_api_invalid_lambda_auth", ], ) @patch("boto3.session.Session.region_name", "ap-southeast-1")