From c5690d50b60acf5991db4c98e30e063722534429 Mon Sep 17 00:00:00 2001 From: Aayush thapa <84202325+aaythapa@users.noreply.github.com> Date: Mon, 13 Mar 2023 10:59:12 -0700 Subject: [PATCH] feat: Add EnableFunctionDefaultPermissions property to HTTP Api (#3001) Co-authored-by: Christoffer Rehn <1280602+hoffa@users.noreply.github.com> --- .../schema_source/aws_serverless_httpapi.py | 1 + samtranslator/model/api/http_api_generator.py | 63 +++- samtranslator/model/apigatewayv2.py | 14 + samtranslator/model/sam_resources.py | 3 + samtranslator/schema/schema.json | 4 + schema_source/sam.schema.json | 4 + tests/model/test_api_v2.py | 15 + .../error_http_api_invalid_lambda_auth.yaml | 23 ++ ...lambda_auth_with_permissions_resource.yaml | 62 ++++ ...lambda_auth_with_permissions_resource.json | 346 ++++++++++++++++++ ...lambda_auth_with_permissions_resource.json | 346 ++++++++++++++++++ .../error_http_api_invalid_lambda_auth.json | 15 +- ...lambda_auth_with_permissions_resource.json | 346 ++++++++++++++++++ 13 files changed, 1232 insertions(+), 10 deletions(-) create mode 100644 tests/translator/input/http_api_lambda_auth_with_permissions_resource.yaml create mode 100644 tests/translator/output/aws-cn/http_api_lambda_auth_with_permissions_resource.json create mode 100644 tests/translator/output/aws-us-gov/http_api_lambda_auth_with_permissions_resource.json create mode 100644 tests/translator/output/http_api_lambda_auth_with_permissions_resource.json diff --git a/samtranslator/internal/schema_source/aws_serverless_httpapi.py b/samtranslator/internal/schema_source/aws_serverless_httpapi.py index 86c35c7ee..9a64cccfd 100644 --- a/samtranslator/internal/schema_source/aws_serverless_httpapi.py +++ b/samtranslator/internal/schema_source/aws_serverless_httpapi.py @@ -47,6 +47,7 @@ class LambdaAuthorizer(BaseModel): EnableSimpleResponses: Optional[bool] = lambdaauthorizer("EnableSimpleResponses") FunctionArn: SamIntrinsicable[str] = lambdaauthorizer("FunctionArn") FunctionInvokeRole: Optional[SamIntrinsicable[str]] = lambdaauthorizer("FunctionInvokeRole") + EnableFunctionDefaultPermissions: Optional[bool] # TODO: add docs Identity: Optional[LambdaAuthorizerIdentity] = lambdaauthorizer("Identity") diff --git a/samtranslator/model/api/http_api_generator.py b/samtranslator/model/api/http_api_generator.py index 9a6a87824..cbbac3324 100644 --- a/samtranslator/model/api/http_api_generator.py +++ b/samtranslator/model/api/http_api_generator.py @@ -11,10 +11,12 @@ ApiGatewayV2Stage, ) from samtranslator.model.exceptions import InvalidResourceException -from samtranslator.model.intrinsics import fnGetAtt, is_intrinsic, is_intrinsic_no_value, ref +from samtranslator.model.intrinsics import fnGetAtt, fnSub, is_intrinsic, is_intrinsic_no_value, ref +from samtranslator.model.lambda_ import LambdaPermission from samtranslator.model.route53 import Route53RecordSetGroup from samtranslator.model.s3_utils.uri_parser import parse_s3_uri from samtranslator.open_api.open_api import OpenApiEditor +from samtranslator.translator.arn_generator import ArnGenerator from samtranslator.translator.logical_id_generator import LogicalIdGenerator from samtranslator.utils.types import Intrinsicable from samtranslator.utils.utils import InvalidValueType, dict_deep_get @@ -511,6 +513,60 @@ def _add_tags(self) -> None: open_api_editor.add_tags(self.tags) self.definition_body = open_api_editor.openapi + def _get_permission( + self, authorizer_name: str, authorizer_lambda_function_arn: str, api_arn: str + ) -> LambdaPermission: + """Constructs and returns the Lambda Permission resource allowing the Authorizer to invoke the function. + + :returns: the permission resource + :rtype: model.lambda_.LambdaPermission + """ + + resource = "${__ApiId__}/authorizers/*" + source_arn = fnSub( + ArnGenerator.generate_arn(partition="${AWS::Partition}", service="execute-api", resource=resource), + {"__ApiId__": api_arn}, + ) + + lambda_permission = LambdaPermission( + self.logical_id + authorizer_name + "AuthorizerPermission", attributes=self.passthrough_resource_attributes + ) + lambda_permission.Action = "lambda:InvokeFunction" + lambda_permission.FunctionName = authorizer_lambda_function_arn + lambda_permission.Principal = "apigateway.amazonaws.com" + lambda_permission.SourceArn = source_arn + + return lambda_permission + + def _construct_authorizer_lambda_permission(self, http_api: ApiGatewayV2HttpApi) -> List[LambdaPermission]: + if not self.auth: + return [] + + auth_properties = AuthProperties(**self.auth) + authorizers = self._get_authorizers(auth_properties.Authorizers, auth_properties.EnableIamAuthorizer) + + if not authorizers: + return [] + + permissions: List[LambdaPermission] = [] + + for authorizer_name, authorizer in authorizers.items(): + # Construct permissions for Lambda Authorizers only + # Http Api shouldn't create the permissions by default (when its none) + if ( + not authorizer.function_arn + or authorizer.enable_function_default_permissions is None + or not authorizer.enable_function_default_permissions + ): + continue + + permission = self._get_permission( + authorizer_name, authorizer.function_arn, http_api.get_runtime_attr("http_api_id") + ) + permissions.append(permission) + + return permissions + def _set_default_authorizer( self, open_api_editor: OpenApiEditor, @@ -582,6 +638,7 @@ def _get_authorizers( identity=authorizer.get("Identity"), authorizer_payload_format_version=authorizer.get("AuthorizerPayloadFormatVersion"), enable_simple_responses=authorizer.get("EnableSimpleResponses"), + enable_function_default_permissions=authorizer.get("EnableFunctionDefaultPermissions"), ) return authorizers @@ -719,6 +776,7 @@ def to_cloudformation( Optional[ApiGatewayV2DomainName], Optional[List[ApiGatewayV2ApiMapping]], Optional[Route53RecordSetGroup], + Optional[List[LambdaPermission]], ]: """Generates CloudFormation resources from a SAM HTTP API resource @@ -727,6 +785,7 @@ def to_cloudformation( """ http_api = self._construct_http_api() domain, basepath_mapping, route53 = self._construct_api_domain(http_api, route53_record_set_groups) + permissions = self._construct_authorizer_lambda_permission(http_api) stage = self._construct_stage() - return http_api, stage, domain, basepath_mapping, route53 + return http_api, stage, domain, basepath_mapping, route53, permissions diff --git a/samtranslator/model/apigatewayv2.py b/samtranslator/model/apigatewayv2.py index d45b41819..188cdc726 100644 --- a/samtranslator/model/apigatewayv2.py +++ b/samtranslator/model/apigatewayv2.py @@ -88,6 +88,7 @@ def __init__( # type: ignore[no-untyped-def] # noqa: too-many-arguments authorizer_payload_format_version=None, enable_simple_responses=None, is_aws_iam_authorizer=False, + enable_function_default_permissions=None, ): """ Creates an authorizer for use in V2 Http Apis @@ -103,6 +104,7 @@ def __init__( # type: ignore[no-untyped-def] # noqa: too-many-arguments self.authorizer_payload_format_version = authorizer_payload_format_version self.enable_simple_responses = enable_simple_responses self.is_aws_iam_authorizer = is_aws_iam_authorizer + self.enable_function_default_permissions = enable_function_default_permissions self._validate_input_parameters() @@ -115,6 +117,13 @@ def __init__( # type: ignore[no-untyped-def] # noqa: too-many-arguments if authorizer_type == "REQUEST": self._validate_lambda_authorizer() + if enable_function_default_permissions is not None: + sam_expect( + enable_function_default_permissions, + api_logical_id, + f"Authorizers.{name}.EnableFunctionDefaultPermissions", + ).to_be_a_bool() + def _get_auth_type(self) -> str: if self.is_aws_iam_authorizer: return "AWS_IAM" @@ -166,6 +175,11 @@ def _validate_input_parameters(self) -> None: self.api_logical_id, "EnableSimpleResponses must be defined only for Lambda Authorizer." ) + if self.enable_function_default_permissions is not None and authorizer_type != "REQUEST": + raise InvalidResourceException( + self.api_logical_id, "EnableFunctionDefaultPermissions must be defined only for Lambda Authorizer." + ) + def _validate_jwt_authorizer(self) -> None: if not self.jwt_configuration: raise InvalidResourceException( diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 696834c5c..63ce47f77 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -1392,9 +1392,12 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def] domain, basepath_mapping, route53, + permissions, ) = api_generator.to_cloudformation(kwargs.get("route53_record_set_groups", {})) resources.append(http_api) + if permissions: + resources.extend(permissions) if domain: resources.append(domain) if basepath_mapping: diff --git a/samtranslator/schema/schema.json b/samtranslator/schema/schema.json index 89f9d7030..387ea6902 100644 --- a/samtranslator/schema/schema.json +++ b/samtranslator/schema/schema.json @@ -195743,6 +195743,10 @@ "markdownDescription": "Specifies the format of the payload sent to an HTTP API Lambda authorizer\\. Required for HTTP API Lambda authorizers\\. \nThis is passed through to the `authorizerPayloadFormatVersion` section of an `x-amazon-apigateway-authorizer` in the `securitySchemes` section of an OpenAPI definition\\. \n*Valid values*: `1.0` or `2.0` \n*Type*: String \n*Required*: Yes \n*AWS CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent\\.", "title": "AuthorizerPayloadFormatVersion" }, + "EnableFunctionDefaultPermissions": { + "title": "Enablefunctiondefaultpermissions", + "type": "boolean" + }, "EnableSimpleResponses": { "description": "Specifies whether a Lambda authorizer returns a response in a simple format\\. By default, a Lambda authorizer must return an AWS Identity and Access Management \\(IAM\\) policy\\. If enabled, the Lambda authorizer can return a boolean value instead of an IAM policy\\. \nThis is passed through to the `enableSimpleResponses` section of an `x-amazon-apigateway-authorizer` in the `securitySchemes` section of an OpenAPI definition\\. \n*Type*: Boolean \n*Required*: No \n*AWS CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent\\.", "markdownDescription": "Specifies whether a Lambda authorizer returns a response in a simple format\\. By default, a Lambda authorizer must return an AWS Identity and Access Management \\(IAM\\) policy\\. If enabled, the Lambda authorizer can return a boolean value instead of an IAM policy\\. \nThis is passed through to the `enableSimpleResponses` section of an `x-amazon-apigateway-authorizer` in the `securitySchemes` section of an OpenAPI definition\\. \n*Type*: Boolean \n*Required*: No \n*AWS CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent\\.", diff --git a/schema_source/sam.schema.json b/schema_source/sam.schema.json index e5e0e8cfc..75a1669e6 100644 --- a/schema_source/sam.schema.json +++ b/schema_source/sam.schema.json @@ -1561,6 +1561,10 @@ "markdownDescription": "Specifies the format of the payload sent to an HTTP API Lambda authorizer\\. Required for HTTP API Lambda authorizers\\. \nThis is passed through to the `authorizerPayloadFormatVersion` section of an `x-amazon-apigateway-authorizer` in the `securitySchemes` section of an OpenAPI definition\\. \n*Valid values*: `1.0` or `2.0` \n*Type*: String \n*Required*: Yes \n*AWS CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent\\.", "title": "AuthorizerPayloadFormatVersion" }, + "EnableFunctionDefaultPermissions": { + "title": "Enablefunctiondefaultpermissions", + "type": "boolean" + }, "EnableSimpleResponses": { "description": "Specifies whether a Lambda authorizer returns a response in a simple format\\. By default, a Lambda authorizer must return an AWS Identity and Access Management \\(IAM\\) policy\\. If enabled, the Lambda authorizer can return a boolean value instead of an IAM policy\\. \nThis is passed through to the `enableSimpleResponses` section of an `x-amazon-apigateway-authorizer` in the `securitySchemes` section of an OpenAPI definition\\. \n*Type*: Boolean \n*Required*: No \n*AWS CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent\\.", "markdownDescription": "Specifies whether a Lambda authorizer returns a response in a simple format\\. By default, a Lambda authorizer must return an AWS Identity and Access Management \\(IAM\\) policy\\. If enabled, the Lambda authorizer can return a boolean value instead of an IAM policy\\. \nThis is passed through to the `enableSimpleResponses` section of an `x-amazon-apigateway-authorizer` in the `securitySchemes` section of an OpenAPI definition\\. \n*Type*: Boolean \n*Required*: No \n*AWS CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent\\.", diff --git a/tests/model/test_api_v2.py b/tests/model/test_api_v2.py index e5de8d872..9820986af 100644 --- a/tests/model/test_api_v2.py +++ b/tests/model/test_api_v2.py @@ -168,6 +168,21 @@ def test_create_authorizer_fails_with_enable_simple_responses_non_lambda(self): + "EnableSimpleResponses must be defined only for Lambda Authorizer.", ) + def test_create_authorizer_fails_with_enable_function_default_permissions_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_function_default_permissions=True, + ) + self.assertEqual( + e.value.message, + "Resource with id [logicalId] is invalid. " + + "EnableFunctionDefaultPermissions must be defined only for Lambda Authorizer.", + ) + @mock.patch( "samtranslator.model.apigatewayv2.ApiGatewayV2Authorizer._get_auth_type", mock.MagicMock(return_value="JWT") ) diff --git a/tests/translator/input/error_http_api_invalid_lambda_auth.yaml b/tests/translator/input/error_http_api_invalid_lambda_auth.yaml index 1237f209b..4274e9752 100644 --- a/tests/translator/input/error_http_api_invalid_lambda_auth.yaml +++ b/tests/translator/input/error_http_api_invalid_lambda_auth.yaml @@ -77,3 +77,26 @@ Resources: AuthorizerPayloadFormatVersion: 2.0 EnableSimpleResponses: true DefaultAuthorizer: LambdaAuth + + MyApi3: + Type: AWS::Serverless::HttpApi + Properties: + Auth: + Authorizers: + LambdaAuth: + FunctionArn: !GetAtt MyAuthFn.Arn + EnableFunctionDefaultPermissions: foo + AuthorizerPayloadFormatVersion: 2.0 + DefaultAuthorizer: LambdaAuth + + MyApi4: + Type: AWS::Serverless::HttpApi + Properties: + Auth: + Authorizers: + NonLambdaAuth: + JwtConfiguration: + audience: https://test-sam.com + issuer: https://test-sam.com + EnableFunctionDefaultPermissions: foo + DefaultAuthorizer: LambdaAuth diff --git a/tests/translator/input/http_api_lambda_auth_with_permissions_resource.yaml b/tests/translator/input/http_api_lambda_auth_with_permissions_resource.yaml new file mode 100644 index 000000000..cb7155d8a --- /dev/null +++ b/tests/translator/input/http_api_lambda_auth_with_permissions_resource.yaml @@ -0,0 +1,62 @@ +Resources: + HttpApiFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/todo_list.zip + Handler: index.restapi + Runtime: python3.7 + Events: + Basic: + Type: HttpApi + Properties: + Path: /basic + Method: GET + 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: + LambdaAuthWithEnablePropertyTrue: + # should create permissions resource for this auth + FunctionArn: !GetAtt MyAuthFn.Arn + EnableFunctionDefaultPermissions: true + AuthorizerPayloadFormatVersion: 1.0 + LambdaAuthNoEnableProperty: + # should not create permissions resource for this auth as http api doesn't create the resource by default + FunctionArn: !GetAtt MyAuthFn.Arn + AuthorizerPayloadFormatVersion: 1.0 + LambdaAuthWithEnablePropertySetFalse: + # should not create permissions resource for this auth + FunctionArn: !GetAtt MyAuthFn.Arn + AuthorizerPayloadFormatVersion: 1.0 + EnableFunctionDefaultPermissions: false + LambdaAuthFull: + # should create permissions resource for this auth + FunctionArn: !GetAtt MyAuthFn.Arn + FunctionInvokeRole: !GetAtt MyAuthFnRole.Arn + EnableFunctionDefaultPermissions: true + Identity: + Context: + - contextVar + Headers: + - Authorization + QueryStrings: + - petId + StageVariables: + - stageVar + ReauthorizeEvery: 60 + AuthorizerPayloadFormatVersion: 2.0 + EnableSimpleResponses: true + DefaultAuthorizer: LambdaAuthWithEnablePropertyTrue diff --git a/tests/translator/output/aws-cn/http_api_lambda_auth_with_permissions_resource.json b/tests/translator/output/aws-cn/http_api_lambda_auth_with_permissions_resource.json new file mode 100644 index 000000000..bb2f70920 --- /dev/null +++ b/tests/translator/output/aws-cn/http_api_lambda_auth_with_permissions_resource.json @@ -0,0 +1,346 @@ +{ + "Resources": { + "HttpApiFunction": { + "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" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "HttpApiFunctionBasicPermission": { + "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__}/GET/basic", + { + "__ApiId__": { + "Ref": "MyApi" + }, + "__Stage__": "*" + } + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "HttpApiFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "MyApi": { + "Properties": { + "Body": { + "components": { + "securitySchemes": { + "LambdaAuthFull": { + "in": "header", + "name": "Unused", + "type": "apiKey", + "x-amazon-apigateway-authorizer": { + "authorizerCredentials": { + "Fn::GetAtt": [ + "MyAuthFnRole", + "Arn" + ] + }, + "authorizerPayloadFormatVersion": 2.0, + "authorizerResultTtlInSeconds": 60, + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "enableSimpleResponses": true, + "identitySource": [ + "$request.header.Authorization", + "$request.querystring.petId", + "$stageVariables.stageVar", + "$context.contextVar" + ], + "type": "request" + } + }, + "LambdaAuthNoEnableProperty": { + "in": "header", + "name": "Unused", + "type": "apiKey", + "x-amazon-apigateway-authorizer": { + "authorizerPayloadFormatVersion": 1.0, + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "type": "request" + } + }, + "LambdaAuthWithEnablePropertySetFalse": { + "in": "header", + "name": "Unused", + "type": "apiKey", + "x-amazon-apigateway-authorizer": { + "authorizerPayloadFormatVersion": 1.0, + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "type": "request" + } + }, + "LambdaAuthWithEnablePropertyTrue": { + "in": "header", + "name": "Unused", + "type": "apiKey", + "x-amazon-apigateway-authorizer": { + "authorizerPayloadFormatVersion": 1.0, + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "type": "request" + } + } + } + }, + "info": { + "title": { + "Ref": "AWS::StackName" + }, + "version": "1.0" + }, + "openapi": "3.0.1", + "paths": { + "/basic": { + "get": { + "responses": {}, + "security": [ + { + "LambdaAuthWithEnablePropertyTrue": [] + } + ], + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + } + } + } + }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + }, + { + "name": "Tag1", + "x-amazon-apigateway-tag-value": "value1" + }, + { + "name": "Tag2", + "x-amazon-apigateway-tag-value": "value2" + } + ] + } + }, + "Type": "AWS::ApiGatewayV2::Api" + }, + "MyApiApiGatewayDefaultStage": { + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "AutoDeploy": true, + "StageName": "$default", + "Tags": { + "Tag1": "value1", + "Tag2": "value2", + "httpapi:createdBy": "SAM" + } + }, + "Type": "AWS::ApiGatewayV2::Stage" + }, + "MyApiLambdaAuthFullAuthorizerPermission": { + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "MyApiLambdaAuthWithEnablePropertyTrueAuthorizerPermission": { + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "MyAuthFn": { + "Properties": { + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyAuthFnRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MyAuthFnRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +} diff --git a/tests/translator/output/aws-us-gov/http_api_lambda_auth_with_permissions_resource.json b/tests/translator/output/aws-us-gov/http_api_lambda_auth_with_permissions_resource.json new file mode 100644 index 000000000..3d9d870b7 --- /dev/null +++ b/tests/translator/output/aws-us-gov/http_api_lambda_auth_with_permissions_resource.json @@ -0,0 +1,346 @@ +{ + "Resources": { + "HttpApiFunction": { + "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" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "HttpApiFunctionBasicPermission": { + "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__}/GET/basic", + { + "__ApiId__": { + "Ref": "MyApi" + }, + "__Stage__": "*" + } + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "HttpApiFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "MyApi": { + "Properties": { + "Body": { + "components": { + "securitySchemes": { + "LambdaAuthFull": { + "in": "header", + "name": "Unused", + "type": "apiKey", + "x-amazon-apigateway-authorizer": { + "authorizerCredentials": { + "Fn::GetAtt": [ + "MyAuthFnRole", + "Arn" + ] + }, + "authorizerPayloadFormatVersion": 2.0, + "authorizerResultTtlInSeconds": 60, + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "enableSimpleResponses": true, + "identitySource": [ + "$request.header.Authorization", + "$request.querystring.petId", + "$stageVariables.stageVar", + "$context.contextVar" + ], + "type": "request" + } + }, + "LambdaAuthNoEnableProperty": { + "in": "header", + "name": "Unused", + "type": "apiKey", + "x-amazon-apigateway-authorizer": { + "authorizerPayloadFormatVersion": 1.0, + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "type": "request" + } + }, + "LambdaAuthWithEnablePropertySetFalse": { + "in": "header", + "name": "Unused", + "type": "apiKey", + "x-amazon-apigateway-authorizer": { + "authorizerPayloadFormatVersion": 1.0, + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "type": "request" + } + }, + "LambdaAuthWithEnablePropertyTrue": { + "in": "header", + "name": "Unused", + "type": "apiKey", + "x-amazon-apigateway-authorizer": { + "authorizerPayloadFormatVersion": 1.0, + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "type": "request" + } + } + } + }, + "info": { + "title": { + "Ref": "AWS::StackName" + }, + "version": "1.0" + }, + "openapi": "3.0.1", + "paths": { + "/basic": { + "get": { + "responses": {}, + "security": [ + { + "LambdaAuthWithEnablePropertyTrue": [] + } + ], + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + } + } + } + }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + }, + { + "name": "Tag1", + "x-amazon-apigateway-tag-value": "value1" + }, + { + "name": "Tag2", + "x-amazon-apigateway-tag-value": "value2" + } + ] + } + }, + "Type": "AWS::ApiGatewayV2::Api" + }, + "MyApiApiGatewayDefaultStage": { + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "AutoDeploy": true, + "StageName": "$default", + "Tags": { + "Tag1": "value1", + "Tag2": "value2", + "httpapi:createdBy": "SAM" + } + }, + "Type": "AWS::ApiGatewayV2::Stage" + }, + "MyApiLambdaAuthFullAuthorizerPermission": { + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "MyApiLambdaAuthWithEnablePropertyTrueAuthorizerPermission": { + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "MyAuthFn": { + "Properties": { + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyAuthFnRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MyAuthFnRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +} diff --git a/tests/translator/output/error_http_api_invalid_lambda_auth.json b/tests/translator/output/error_http_api_invalid_lambda_auth.json index dd7196172..d4069a892 100644 --- a/tests/translator/output/error_http_api_invalid_lambda_auth.json +++ b/tests/translator/output/error_http_api_invalid_lambda_auth.json @@ -1,16 +1,15 @@ { "_autoGeneratedBreakdownErrorMessage": [ "Invalid Serverless Application Specification document. ", - "Number of errors found: 2. ", + "Number of errors found: 4. ", "Resource with id [MyApi1] is invalid. ", "LambdaAuth Lambda Authorizer must define 'AuthorizerPayloadFormatVersion'. ", "Resource with id [MyApi2] is invalid. ", - "LambdaAuth Lambda Authorizer must define 'FunctionArn'." + "LambdaAuth Lambda Authorizer must define 'FunctionArn'. ", + "Resource with id [MyApi3] is invalid. ", + "Property 'Authorizers.LambdaAuth.EnableFunctionDefaultPermissions' should be a boolean. ", + "Resource with id [MyApi4] is invalid. ", + "EnableFunctionDefaultPermissions must be defined only for Lambda Authorizer." ], - "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'.", - "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: 4. Resource with id [MyApi1] is invalid. LambdaAuth Lambda Authorizer must define 'AuthorizerPayloadFormatVersion'. Resource with id [MyApi2] is invalid. LambdaAuth Lambda Authorizer must define 'FunctionArn'. Resource with id [MyApi3] is invalid. Property 'Authorizers.LambdaAuth.EnableFunctionDefaultPermissions' should be a boolean. Resource with id [MyApi4] is invalid. EnableFunctionDefaultPermissions must be defined only for Lambda Authorizer." } diff --git a/tests/translator/output/http_api_lambda_auth_with_permissions_resource.json b/tests/translator/output/http_api_lambda_auth_with_permissions_resource.json new file mode 100644 index 000000000..4816dcc01 --- /dev/null +++ b/tests/translator/output/http_api_lambda_auth_with_permissions_resource.json @@ -0,0 +1,346 @@ +{ + "Resources": { + "HttpApiFunction": { + "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" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "HttpApiFunctionBasicPermission": { + "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__}/GET/basic", + { + "__ApiId__": { + "Ref": "MyApi" + }, + "__Stage__": "*" + } + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "HttpApiFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "MyApi": { + "Properties": { + "Body": { + "components": { + "securitySchemes": { + "LambdaAuthFull": { + "in": "header", + "name": "Unused", + "type": "apiKey", + "x-amazon-apigateway-authorizer": { + "authorizerCredentials": { + "Fn::GetAtt": [ + "MyAuthFnRole", + "Arn" + ] + }, + "authorizerPayloadFormatVersion": 2.0, + "authorizerResultTtlInSeconds": 60, + "authorizerUri": { + "Fn::Sub": [ + "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "enableSimpleResponses": true, + "identitySource": [ + "$request.header.Authorization", + "$request.querystring.petId", + "$stageVariables.stageVar", + "$context.contextVar" + ], + "type": "request" + } + }, + "LambdaAuthNoEnableProperty": { + "in": "header", + "name": "Unused", + "type": "apiKey", + "x-amazon-apigateway-authorizer": { + "authorizerPayloadFormatVersion": 1.0, + "authorizerUri": { + "Fn::Sub": [ + "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "type": "request" + } + }, + "LambdaAuthWithEnablePropertySetFalse": { + "in": "header", + "name": "Unused", + "type": "apiKey", + "x-amazon-apigateway-authorizer": { + "authorizerPayloadFormatVersion": 1.0, + "authorizerUri": { + "Fn::Sub": [ + "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "type": "request" + } + }, + "LambdaAuthWithEnablePropertyTrue": { + "in": "header", + "name": "Unused", + "type": "apiKey", + "x-amazon-apigateway-authorizer": { + "authorizerPayloadFormatVersion": 1.0, + "authorizerUri": { + "Fn::Sub": [ + "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "type": "request" + } + } + } + }, + "info": { + "title": { + "Ref": "AWS::StackName" + }, + "version": "1.0" + }, + "openapi": "3.0.1", + "paths": { + "/basic": { + "get": { + "responses": {}, + "security": [ + { + "LambdaAuthWithEnablePropertyTrue": [] + } + ], + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + } + } + } + } + }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + }, + { + "name": "Tag1", + "x-amazon-apigateway-tag-value": "value1" + }, + { + "name": "Tag2", + "x-amazon-apigateway-tag-value": "value2" + } + ] + } + }, + "Type": "AWS::ApiGatewayV2::Api" + }, + "MyApiApiGatewayDefaultStage": { + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "AutoDeploy": true, + "StageName": "$default", + "Tags": { + "Tag1": "value1", + "Tag2": "value2", + "httpapi:createdBy": "SAM" + } + }, + "Type": "AWS::ApiGatewayV2::Stage" + }, + "MyApiLambdaAuthFullAuthorizerPermission": { + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "MyApiLambdaAuthWithEnablePropertyTrueAuthorizerPermission": { + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "MyAuthFn": { + "Properties": { + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyAuthFnRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MyAuthFnRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +}