diff --git a/integration/combination/test_api_with_cors.py b/integration/combination/test_api_with_cors.py index 55fab2777..e6babcc8f 100644 --- a/integration/combination/test_api_with_cors.py +++ b/integration/combination/test_api_with_cors.py @@ -15,6 +15,7 @@ class TestApiWithCors(BaseTest): [ "combination/api_with_cors", "combination/api_with_cors_openapi", + "combination/api_with_cors_and_apikey", ] ) def test_cors(self, file_name): diff --git a/integration/resources/expected/combination/api_with_cors_and_apikey.json b/integration/resources/expected/combination/api_with_cors_and_apikey.json new file mode 100644 index 000000000..503bbd9b7 --- /dev/null +++ b/integration/resources/expected/combination/api_with_cors_and_apikey.json @@ -0,0 +1,26 @@ +[ + { + "LogicalResourceId": "MyApi", + "ResourceType": "AWS::ApiGateway::RestApi" + }, + { + "LogicalResourceId": "MyApiDeployment", + "ResourceType": "AWS::ApiGateway::Deployment" + }, + { + "LogicalResourceId": "MyApidevStage", + "ResourceType": "AWS::ApiGateway::Stage" + }, + { + "LogicalResourceId": "ApiGatewayLambdaRole", + "ResourceType": "AWS::IAM::Role" + }, + { + "LogicalResourceId": "MyFunction", + "ResourceType": "AWS::Lambda::Function" + }, + { + "LogicalResourceId": "MyFunctionRole", + "ResourceType": "AWS::IAM::Role" + } +] diff --git a/integration/resources/templates/combination/api_with_cors_and_apikey.yaml b/integration/resources/templates/combination/api_with_cors_and_apikey.yaml new file mode 100644 index 000000000..d005a1429 --- /dev/null +++ b/integration/resources/templates/combination/api_with_cors_and_apikey.yaml @@ -0,0 +1,87 @@ +AWSTemplateFormatVersion: '2010-09-09' + +Transform: +- AWS::Serverless-2016-10-31 + +Globals: + Api: + Auth: + ApiKeyRequired: true + AddApiKeyRequiredToCorsPreflight: false + +Resources: + + MyFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + InlineCode: | + exports.handler = async function (event) { + return { + statusCode: 200, + body: JSON.stringify({ message: "Hello, SAM!" }), + } + } + Runtime: nodejs16.x + + ApiGatewayLambdaRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: {Service: apigateway.amazonaws.com} + Action: sts:AssumeRole + Policies: + - PolicyName: AllowInvokeLambdaFunctions + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: lambda:InvokeFunction + Resource: '*' + + MyApi: + Type: AWS::Serverless::Api + Properties: + Cors: + AllowMethods: "'methods'" + AllowHeaders: "'headers'" + AllowOrigin: "'origins'" + MaxAge: "'600'" + Auth: + ApiKeyRequired: true + StageName: dev + DefinitionBody: + openapi: 3.0.1 + paths: + /apione: + get: + x-amazon-apigateway-integration: + credentials: + Fn::Sub: ${ApiGatewayLambdaRole.Arn} + uri: + Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations + passthroughBehavior: when_no_match + httpMethod: POST + type: aws_proxy + /apitwo: + get: + x-amazon-apigateway-integration: + credentials: + Fn::Sub: ${ApiGatewayLambdaRole.Arn} + uri: + Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations + passthroughBehavior: when_no_match + httpMethod: POST + type: aws_proxy + + + +Outputs: + ApiUrl: + Description: URL of your API endpoint + Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/dev/" +Metadata: + SamTransformTest: true diff --git a/samtranslator/internal/schema_source/aws_serverless_api.py b/samtranslator/internal/schema_source/aws_serverless_api.py index e43e9242d..6509f5c8a 100644 --- a/samtranslator/internal/schema_source/aws_serverless_api.py +++ b/samtranslator/internal/schema_source/aws_serverless_api.py @@ -100,6 +100,7 @@ class UsagePlan(BaseModel): class Auth(BaseModel): AddDefaultAuthorizerToCorsPreflight: Optional[bool] = auth("AddDefaultAuthorizerToCorsPreflight") + AddApiKeyRequiredToCorsPreflight: Optional[bool] # TODO Add Docs ApiKeyRequired: Optional[bool] = auth("ApiKeyRequired") Authorizers: Optional[ Dict[ diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 5c84da483..bb03a827f 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -52,12 +52,13 @@ "DefaultAuthorizer", "InvokeRole", "AddDefaultAuthorizerToCorsPreflight", + "AddApiKeyRequiredToCorsPreflight", "ApiKeyRequired", "ResourcePolicy", "UsagePlan", ], ) -AuthProperties.__new__.__defaults__ = (None, None, None, True, None, None, None) +AuthProperties.__new__.__defaults__ = (None, None, None, True, True, None, None, None) UsagePlanProperties = namedtuple( "UsagePlanProperties", ["CreateUsagePlan", "Description", "Quota", "Tags", "Throttle", "UsagePlanName"] ) @@ -752,7 +753,7 @@ def _add_auth(self) -> None: if auth_properties.ApiKeyRequired: swagger_editor.add_apikey_security_definition() - self._set_default_apikey_required(swagger_editor) + self._set_default_apikey_required(swagger_editor, auth_properties.AddApiKeyRequiredToCorsPreflight) if auth_properties.ResourcePolicy: SwaggerEditor.validate_is_dict( @@ -1224,9 +1225,9 @@ def _set_default_authorizer( add_default_auth_to_preflight=add_default_auth_to_preflight, ) - def _set_default_apikey_required(self, swagger_editor: SwaggerEditor) -> None: + def _set_default_apikey_required(self, swagger_editor: SwaggerEditor, required_options_api_key: bool) -> None: for path in swagger_editor.iter_on_path(): - swagger_editor.set_path_default_apikey_required(path) + swagger_editor.set_path_default_apikey_required(path, required_options_api_key) def _set_endpoint_configuration(self, rest_api: ApiGatewayRestApi, value: Union[str, Dict[str, Any]]) -> None: """ diff --git a/samtranslator/schema/schema.json b/samtranslator/schema/schema.json index a36f90e39..25992893e 100644 --- a/samtranslator/schema/schema.json +++ b/samtranslator/schema/schema.json @@ -197274,6 +197274,10 @@ "samtranslator__internal__schema_source__aws_serverless_api__Auth": { "additionalProperties": false, "properties": { + "AddApiKeyRequiredToCorsPreflight": { + "title": "Addapikeyrequiredtocorspreflight", + "type": "boolean" + }, "AddDefaultAuthorizerToCorsPreflight": { "description": "If the `DefaultAuthorizer` and `Cors` properties are set, then setting `AddDefaultAuthorizerToCorsPreflight` will cause the default authorizer to be added to the `Options` property in the OpenAPI section\\. \n*Type*: Boolean \n*Required*: No \n*Default*: True \n*AWS CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent\\.", "markdownDescription": "If the `DefaultAuthorizer` and `Cors` properties are set, then setting `AddDefaultAuthorizerToCorsPreflight` will cause the default authorizer to be added to the `Options` property in the OpenAPI section\\. \n*Type*: Boolean \n*Required*: No \n*Default*: True \n*AWS CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent\\.", diff --git a/samtranslator/swagger/swagger.py b/samtranslator/swagger/swagger.py index d5a74909d..96e57d53c 100644 --- a/samtranslator/swagger/swagger.py +++ b/samtranslator/swagger/swagger.py @@ -612,7 +612,7 @@ def set_path_default_authorizer( # noqa: too-many-branches if "AWS_IAM" in method_definition["security"][0]: self.add_awsiam_security_definition() - def set_path_default_apikey_required(self, path: str) -> None: + def set_path_default_apikey_required(self, path: str, required_options_api_key: bool = True) -> None: """ Add the ApiKey security as required for each method on this path unless ApiKeyRequired was defined at the Function/Path/Method level. This is intended to be used to set the @@ -620,6 +620,8 @@ def set_path_default_apikey_required(self, path: str) -> None: Serverless API. :param string path: Path name + :param bool required_options_api_key: Bool of whether to add the ApiKeyRequired + to OPTIONS preflight requests. """ for method_name, method_definition in self.iter_on_all_methods_for_path(path): # type: ignore[no-untyped-call] @@ -673,6 +675,9 @@ def set_path_default_apikey_required(self, path: str) -> None: security = existing_non_apikey_security + apikey_security + if method_name == "options" and not required_options_api_key: + security = existing_non_apikey_security + if security != existing_security: method_definition["security"] = security @@ -691,10 +696,12 @@ def add_auth_to_method(self, path: str, method_name: str, auth: Dict[str, Any], method_scopes = auth and auth.get("AuthorizationScopes") api_auth = api and api.get("Auth") authorizers = api_auth and api_auth.get("Authorizers") + if method_authorizer: self._set_method_authorizer(path, method_name, method_authorizer, authorizers, method_scopes) # type: ignore[no-untyped-call] method_apikey_required = auth and auth.get("ApiKeyRequired") + if method_apikey_required is not None: self._set_method_apikey_handling(path, method_name, method_apikey_required) # type: ignore[no-untyped-call] diff --git a/schema_source/sam.schema.json b/schema_source/sam.schema.json index b5982d11c..60ad7ea81 100644 --- a/schema_source/sam.schema.json +++ b/schema_source/sam.schema.json @@ -3015,6 +3015,10 @@ "samtranslator__internal__schema_source__aws_serverless_api__Auth": { "additionalProperties": false, "properties": { + "AddApiKeyRequiredToCorsPreflight": { + "title": "Addapikeyrequiredtocorspreflight", + "type": "boolean" + }, "AddDefaultAuthorizerToCorsPreflight": { "description": "If the `DefaultAuthorizer` and `Cors` properties are set, then setting `AddDefaultAuthorizerToCorsPreflight` will cause the default authorizer to be added to the `Options` property in the OpenAPI section\\. \n*Type*: Boolean \n*Required*: No \n*Default*: True \n*AWS CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent\\.", "markdownDescription": "If the `DefaultAuthorizer` and `Cors` properties are set, then setting `AddDefaultAuthorizerToCorsPreflight` will cause the default authorizer to be added to the `Options` property in the OpenAPI section\\. \n*Type*: Boolean \n*Required*: No \n*Default*: True \n*AWS CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent\\.", diff --git a/tests/translator/input/api_with_cors_and_apikey.yaml b/tests/translator/input/api_with_cors_and_apikey.yaml new file mode 100644 index 000000000..d005a1429 --- /dev/null +++ b/tests/translator/input/api_with_cors_and_apikey.yaml @@ -0,0 +1,87 @@ +AWSTemplateFormatVersion: '2010-09-09' + +Transform: +- AWS::Serverless-2016-10-31 + +Globals: + Api: + Auth: + ApiKeyRequired: true + AddApiKeyRequiredToCorsPreflight: false + +Resources: + + MyFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + InlineCode: | + exports.handler = async function (event) { + return { + statusCode: 200, + body: JSON.stringify({ message: "Hello, SAM!" }), + } + } + Runtime: nodejs16.x + + ApiGatewayLambdaRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: {Service: apigateway.amazonaws.com} + Action: sts:AssumeRole + Policies: + - PolicyName: AllowInvokeLambdaFunctions + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: lambda:InvokeFunction + Resource: '*' + + MyApi: + Type: AWS::Serverless::Api + Properties: + Cors: + AllowMethods: "'methods'" + AllowHeaders: "'headers'" + AllowOrigin: "'origins'" + MaxAge: "'600'" + Auth: + ApiKeyRequired: true + StageName: dev + DefinitionBody: + openapi: 3.0.1 + paths: + /apione: + get: + x-amazon-apigateway-integration: + credentials: + Fn::Sub: ${ApiGatewayLambdaRole.Arn} + uri: + Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations + passthroughBehavior: when_no_match + httpMethod: POST + type: aws_proxy + /apitwo: + get: + x-amazon-apigateway-integration: + credentials: + Fn::Sub: ${ApiGatewayLambdaRole.Arn} + uri: + Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations + passthroughBehavior: when_no_match + httpMethod: POST + type: aws_proxy + + + +Outputs: + ApiUrl: + Description: URL of your API endpoint + Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/dev/" +Metadata: + SamTransformTest: true diff --git a/tests/translator/input/api_with_cors_and_apikey_defined_at_api_level.yaml b/tests/translator/input/api_with_cors_and_apikey_defined_at_api_level.yaml new file mode 100644 index 000000000..dcabe6bfb --- /dev/null +++ b/tests/translator/input/api_with_cors_and_apikey_defined_at_api_level.yaml @@ -0,0 +1,66 @@ +AWSTemplateFormatVersion: '2010-09-09' + +Transform: +- AWS::Serverless-2016-10-31 + +Resources: + + MyFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + InlineCode: | + exports.handler = async function (event) { + return { + statusCode: 200, + body: JSON.stringify({ message: "Hello, SAM!" }), + } + } + Runtime: nodejs16.x + + ApiGatewayLambdaRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: {Service: apigateway.amazonaws.com} + Action: sts:AssumeRole + Policies: + - PolicyName: AllowInvokeLambdaFunctions + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: lambda:InvokeFunction + Resource: '*' + + MyApi: + Type: AWS::Serverless::Api + Properties: + Cors: "'*'" + Auth: + ApiKeyRequired: true + AddApiKeyRequiredToCorsPreflight: false + StageName: dev + DefinitionBody: + openapi: 3.0.1 + paths: + /hello: + get: + x-amazon-apigateway-integration: + credentials: + Fn::Sub: ${ApiGatewayLambdaRole.Arn} + uri: + Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations + passthroughBehavior: when_no_match + httpMethod: POST + type: aws_proxy + + + +Outputs: + WebEndpoint: + Description: API Gateway endpoint URL + Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/dev/hello" diff --git a/tests/translator/output/api_with_cors_and_apikey.json b/tests/translator/output/api_with_cors_and_apikey.json new file mode 100644 index 000000000..5f878d55c --- /dev/null +++ b/tests/translator/output/api_with_cors_and_apikey.json @@ -0,0 +1,297 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Metadata": { + "SamTransformTest": true + }, + "Outputs": { + "ApiUrl": { + "Description": "URL of your API endpoint", + "Value": { + "Fn::Sub": "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/dev/" + } + } + }, + "Resources": { + "ApiGatewayLambdaRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AllowInvokeLambdaFunctions" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "MyApi": { + "Properties": { + "Body": { + "components": { + "securitySchemes": { + "api_key": { + "in": "header", + "name": "x-api-key", + "type": "apiKey" + } + } + }, + "openapi": "3.0.1", + "paths": { + "/apione": { + "get": { + "security": [ + { + "api_key": [] + } + ], + "x-amazon-apigateway-integration": { + "credentials": { + "Fn::Sub": "${ApiGatewayLambdaRole.Arn}" + }, + "httpMethod": "POST", + "passthroughBehavior": "when_no_match", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + } + }, + "options": { + "responses": { + "200": { + "description": "Default response for CORS method", + "headers": { + "Access-Control-Allow-Headers": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Allow-Methods": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Allow-Origin": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Max-Age": { + "schema": { + "schema": { + "type": "integer" + } + } + } + } + } + }, + "summary": "CORS support", + "x-amazon-apigateway-integration": { + "requestTemplates": { + "application/json": "{\n \"statusCode\" : 200\n}\n" + }, + "responses": { + "default": { + "responseParameters": { + "method.response.header.Access-Control-Allow-Headers": "'headers'", + "method.response.header.Access-Control-Allow-Methods": "'methods'", + "method.response.header.Access-Control-Allow-Origin": "'origins'", + "method.response.header.Access-Control-Max-Age": "'600'" + }, + "responseTemplates": { + "application/json": "{}\n" + }, + "statusCode": "200" + } + }, + "type": "mock" + } + } + }, + "/apitwo": { + "get": { + "security": [ + { + "api_key": [] + } + ], + "x-amazon-apigateway-integration": { + "credentials": { + "Fn::Sub": "${ApiGatewayLambdaRole.Arn}" + }, + "httpMethod": "POST", + "passthroughBehavior": "when_no_match", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + } + }, + "options": { + "responses": { + "200": { + "description": "Default response for CORS method", + "headers": { + "Access-Control-Allow-Headers": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Allow-Methods": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Allow-Origin": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Max-Age": { + "schema": { + "schema": { + "type": "integer" + } + } + } + } + } + }, + "summary": "CORS support", + "x-amazon-apigateway-integration": { + "requestTemplates": { + "application/json": "{\n \"statusCode\" : 200\n}\n" + }, + "responses": { + "default": { + "responseParameters": { + "method.response.header.Access-Control-Allow-Headers": "'headers'", + "method.response.header.Access-Control-Allow-Methods": "'methods'", + "method.response.header.Access-Control-Allow-Origin": "'origins'", + "method.response.header.Access-Control-Max-Age": "'600'" + }, + "responseTemplates": { + "application/json": "{}\n" + }, + "statusCode": "200" + } + }, + "type": "mock" + } + } + } + } + } + }, + "Type": "AWS::ApiGateway::RestApi" + }, + "MyApiDeployment996e54a322": { + "Properties": { + "Description": "RestApi deployment id: 996e54a3224f4396bddbafc7ca104cb3685eccdd", + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "Stage" + }, + "Type": "AWS::ApiGateway::Deployment" + }, + "MyApidevStage": { + "Properties": { + "DeploymentId": { + "Ref": "MyApiDeployment996e54a322" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "dev" + }, + "Type": "AWS::ApiGateway::Stage" + }, + "MyFunction": { + "Properties": { + "Code": { + "ZipFile": "exports.handler = async function (event) {\n return {\n statusCode: 200,\n body: JSON.stringify({ message: \"Hello, SAM!\" }),\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs16.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MyFunctionRole": { + "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" + } + } +} diff --git a/tests/translator/output/api_with_cors_and_apikey_defined_at_api_level.json b/tests/translator/output/api_with_cors_and_apikey_defined_at_api_level.json new file mode 100644 index 000000000..eca1c0bdb --- /dev/null +++ b/tests/translator/output/api_with_cors_and_apikey_defined_at_api_level.json @@ -0,0 +1,200 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Outputs": { + "WebEndpoint": { + "Description": "API Gateway endpoint URL", + "Value": { + "Fn::Sub": "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/dev/hello" + } + } + }, + "Resources": { + "ApiGatewayLambdaRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AllowInvokeLambdaFunctions" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "MyApi": { + "Properties": { + "Body": { + "components": { + "securitySchemes": { + "api_key": { + "in": "header", + "name": "x-api-key", + "type": "apiKey" + } + } + }, + "openapi": "3.0.1", + "paths": { + "/hello": { + "get": { + "security": [ + { + "api_key": [] + } + ], + "x-amazon-apigateway-integration": { + "credentials": { + "Fn::Sub": "${ApiGatewayLambdaRole.Arn}" + }, + "httpMethod": "POST", + "passthroughBehavior": "when_no_match", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + } + }, + "options": { + "responses": { + "200": { + "description": "Default response for CORS method", + "headers": { + "Access-Control-Allow-Methods": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Allow-Origin": { + "schema": { + "schema": { + "type": "string" + } + } + } + } + } + }, + "summary": "CORS support", + "x-amazon-apigateway-integration": { + "requestTemplates": { + "application/json": "{\n \"statusCode\" : 200\n}\n" + }, + "responses": { + "default": { + "responseParameters": { + "method.response.header.Access-Control-Allow-Methods": "'GET,OPTIONS'", + "method.response.header.Access-Control-Allow-Origin": "'*'" + }, + "responseTemplates": { + "application/json": "{}\n" + }, + "statusCode": "200" + } + }, + "type": "mock" + } + } + } + } + } + }, + "Type": "AWS::ApiGateway::RestApi" + }, + "MyApiDeploymentdb7bc48ce9": { + "Properties": { + "Description": "RestApi deployment id: db7bc48ce9273479c81f6c8c0b91862e71dbbef8", + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "Stage" + }, + "Type": "AWS::ApiGateway::Deployment" + }, + "MyApidevStage": { + "Properties": { + "DeploymentId": { + "Ref": "MyApiDeploymentdb7bc48ce9" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "dev" + }, + "Type": "AWS::ApiGateway::Stage" + }, + "MyFunction": { + "Properties": { + "Code": { + "ZipFile": "exports.handler = async function (event) {\n return {\n statusCode: 200,\n body: JSON.stringify({ message: \"Hello, SAM!\" }),\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs16.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MyFunctionRole": { + "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" + } + } +} diff --git a/tests/translator/output/aws-cn/api_with_cors_and_apikey.json b/tests/translator/output/aws-cn/api_with_cors_and_apikey.json new file mode 100644 index 000000000..6050364a1 --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_cors_and_apikey.json @@ -0,0 +1,305 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Metadata": { + "SamTransformTest": true + }, + "Outputs": { + "ApiUrl": { + "Description": "URL of your API endpoint", + "Value": { + "Fn::Sub": "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/dev/" + } + } + }, + "Resources": { + "ApiGatewayLambdaRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AllowInvokeLambdaFunctions" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "MyApi": { + "Properties": { + "Body": { + "components": { + "securitySchemes": { + "api_key": { + "in": "header", + "name": "x-api-key", + "type": "apiKey" + } + } + }, + "openapi": "3.0.1", + "paths": { + "/apione": { + "get": { + "security": [ + { + "api_key": [] + } + ], + "x-amazon-apigateway-integration": { + "credentials": { + "Fn::Sub": "${ApiGatewayLambdaRole.Arn}" + }, + "httpMethod": "POST", + "passthroughBehavior": "when_no_match", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + } + }, + "options": { + "responses": { + "200": { + "description": "Default response for CORS method", + "headers": { + "Access-Control-Allow-Headers": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Allow-Methods": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Allow-Origin": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Max-Age": { + "schema": { + "schema": { + "type": "integer" + } + } + } + } + } + }, + "summary": "CORS support", + "x-amazon-apigateway-integration": { + "requestTemplates": { + "application/json": "{\n \"statusCode\" : 200\n}\n" + }, + "responses": { + "default": { + "responseParameters": { + "method.response.header.Access-Control-Allow-Headers": "'headers'", + "method.response.header.Access-Control-Allow-Methods": "'methods'", + "method.response.header.Access-Control-Allow-Origin": "'origins'", + "method.response.header.Access-Control-Max-Age": "'600'" + }, + "responseTemplates": { + "application/json": "{}\n" + }, + "statusCode": "200" + } + }, + "type": "mock" + } + } + }, + "/apitwo": { + "get": { + "security": [ + { + "api_key": [] + } + ], + "x-amazon-apigateway-integration": { + "credentials": { + "Fn::Sub": "${ApiGatewayLambdaRole.Arn}" + }, + "httpMethod": "POST", + "passthroughBehavior": "when_no_match", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + } + }, + "options": { + "responses": { + "200": { + "description": "Default response for CORS method", + "headers": { + "Access-Control-Allow-Headers": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Allow-Methods": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Allow-Origin": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Max-Age": { + "schema": { + "schema": { + "type": "integer" + } + } + } + } + } + }, + "summary": "CORS support", + "x-amazon-apigateway-integration": { + "requestTemplates": { + "application/json": "{\n \"statusCode\" : 200\n}\n" + }, + "responses": { + "default": { + "responseParameters": { + "method.response.header.Access-Control-Allow-Headers": "'headers'", + "method.response.header.Access-Control-Allow-Methods": "'methods'", + "method.response.header.Access-Control-Allow-Origin": "'origins'", + "method.response.header.Access-Control-Max-Age": "'600'" + }, + "responseTemplates": { + "application/json": "{}\n" + }, + "statusCode": "200" + } + }, + "type": "mock" + } + } + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + }, + "Type": "AWS::ApiGateway::RestApi" + }, + "MyApiDeployment996e54a322": { + "Properties": { + "Description": "RestApi deployment id: 996e54a3224f4396bddbafc7ca104cb3685eccdd", + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "Stage" + }, + "Type": "AWS::ApiGateway::Deployment" + }, + "MyApidevStage": { + "Properties": { + "DeploymentId": { + "Ref": "MyApiDeployment996e54a322" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "dev" + }, + "Type": "AWS::ApiGateway::Stage" + }, + "MyFunction": { + "Properties": { + "Code": { + "ZipFile": "exports.handler = async function (event) {\n return {\n statusCode: 200,\n body: JSON.stringify({ message: \"Hello, SAM!\" }),\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs16.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MyFunctionRole": { + "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-cn/api_with_cors_and_apikey_defined_at_api_level.json b/tests/translator/output/aws-cn/api_with_cors_and_apikey_defined_at_api_level.json new file mode 100644 index 000000000..372a3d807 --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_cors_and_apikey_defined_at_api_level.json @@ -0,0 +1,208 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Outputs": { + "WebEndpoint": { + "Description": "API Gateway endpoint URL", + "Value": { + "Fn::Sub": "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/dev/hello" + } + } + }, + "Resources": { + "ApiGatewayLambdaRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AllowInvokeLambdaFunctions" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "MyApi": { + "Properties": { + "Body": { + "components": { + "securitySchemes": { + "api_key": { + "in": "header", + "name": "x-api-key", + "type": "apiKey" + } + } + }, + "openapi": "3.0.1", + "paths": { + "/hello": { + "get": { + "security": [ + { + "api_key": [] + } + ], + "x-amazon-apigateway-integration": { + "credentials": { + "Fn::Sub": "${ApiGatewayLambdaRole.Arn}" + }, + "httpMethod": "POST", + "passthroughBehavior": "when_no_match", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + } + }, + "options": { + "responses": { + "200": { + "description": "Default response for CORS method", + "headers": { + "Access-Control-Allow-Methods": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Allow-Origin": { + "schema": { + "schema": { + "type": "string" + } + } + } + } + } + }, + "summary": "CORS support", + "x-amazon-apigateway-integration": { + "requestTemplates": { + "application/json": "{\n \"statusCode\" : 200\n}\n" + }, + "responses": { + "default": { + "responseParameters": { + "method.response.header.Access-Control-Allow-Methods": "'GET,OPTIONS'", + "method.response.header.Access-Control-Allow-Origin": "'*'" + }, + "responseTemplates": { + "application/json": "{}\n" + }, + "statusCode": "200" + } + }, + "type": "mock" + } + } + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + }, + "Type": "AWS::ApiGateway::RestApi" + }, + "MyApiDeploymentdb7bc48ce9": { + "Properties": { + "Description": "RestApi deployment id: db7bc48ce9273479c81f6c8c0b91862e71dbbef8", + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "Stage" + }, + "Type": "AWS::ApiGateway::Deployment" + }, + "MyApidevStage": { + "Properties": { + "DeploymentId": { + "Ref": "MyApiDeploymentdb7bc48ce9" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "dev" + }, + "Type": "AWS::ApiGateway::Stage" + }, + "MyFunction": { + "Properties": { + "Code": { + "ZipFile": "exports.handler = async function (event) {\n return {\n statusCode: 200,\n body: JSON.stringify({ message: \"Hello, SAM!\" }),\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs16.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MyFunctionRole": { + "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/api_with_cors_and_apikey.json b/tests/translator/output/aws-us-gov/api_with_cors_and_apikey.json new file mode 100644 index 000000000..78e299269 --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_cors_and_apikey.json @@ -0,0 +1,305 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Metadata": { + "SamTransformTest": true + }, + "Outputs": { + "ApiUrl": { + "Description": "URL of your API endpoint", + "Value": { + "Fn::Sub": "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/dev/" + } + } + }, + "Resources": { + "ApiGatewayLambdaRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AllowInvokeLambdaFunctions" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "MyApi": { + "Properties": { + "Body": { + "components": { + "securitySchemes": { + "api_key": { + "in": "header", + "name": "x-api-key", + "type": "apiKey" + } + } + }, + "openapi": "3.0.1", + "paths": { + "/apione": { + "get": { + "security": [ + { + "api_key": [] + } + ], + "x-amazon-apigateway-integration": { + "credentials": { + "Fn::Sub": "${ApiGatewayLambdaRole.Arn}" + }, + "httpMethod": "POST", + "passthroughBehavior": "when_no_match", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + } + }, + "options": { + "responses": { + "200": { + "description": "Default response for CORS method", + "headers": { + "Access-Control-Allow-Headers": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Allow-Methods": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Allow-Origin": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Max-Age": { + "schema": { + "schema": { + "type": "integer" + } + } + } + } + } + }, + "summary": "CORS support", + "x-amazon-apigateway-integration": { + "requestTemplates": { + "application/json": "{\n \"statusCode\" : 200\n}\n" + }, + "responses": { + "default": { + "responseParameters": { + "method.response.header.Access-Control-Allow-Headers": "'headers'", + "method.response.header.Access-Control-Allow-Methods": "'methods'", + "method.response.header.Access-Control-Allow-Origin": "'origins'", + "method.response.header.Access-Control-Max-Age": "'600'" + }, + "responseTemplates": { + "application/json": "{}\n" + }, + "statusCode": "200" + } + }, + "type": "mock" + } + } + }, + "/apitwo": { + "get": { + "security": [ + { + "api_key": [] + } + ], + "x-amazon-apigateway-integration": { + "credentials": { + "Fn::Sub": "${ApiGatewayLambdaRole.Arn}" + }, + "httpMethod": "POST", + "passthroughBehavior": "when_no_match", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + } + }, + "options": { + "responses": { + "200": { + "description": "Default response for CORS method", + "headers": { + "Access-Control-Allow-Headers": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Allow-Methods": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Allow-Origin": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Max-Age": { + "schema": { + "schema": { + "type": "integer" + } + } + } + } + } + }, + "summary": "CORS support", + "x-amazon-apigateway-integration": { + "requestTemplates": { + "application/json": "{\n \"statusCode\" : 200\n}\n" + }, + "responses": { + "default": { + "responseParameters": { + "method.response.header.Access-Control-Allow-Headers": "'headers'", + "method.response.header.Access-Control-Allow-Methods": "'methods'", + "method.response.header.Access-Control-Allow-Origin": "'origins'", + "method.response.header.Access-Control-Max-Age": "'600'" + }, + "responseTemplates": { + "application/json": "{}\n" + }, + "statusCode": "200" + } + }, + "type": "mock" + } + } + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + }, + "Type": "AWS::ApiGateway::RestApi" + }, + "MyApiDeployment996e54a322": { + "Properties": { + "Description": "RestApi deployment id: 996e54a3224f4396bddbafc7ca104cb3685eccdd", + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "Stage" + }, + "Type": "AWS::ApiGateway::Deployment" + }, + "MyApidevStage": { + "Properties": { + "DeploymentId": { + "Ref": "MyApiDeployment996e54a322" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "dev" + }, + "Type": "AWS::ApiGateway::Stage" + }, + "MyFunction": { + "Properties": { + "Code": { + "ZipFile": "exports.handler = async function (event) {\n return {\n statusCode: 200,\n body: JSON.stringify({ message: \"Hello, SAM!\" }),\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs16.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MyFunctionRole": { + "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/aws-us-gov/api_with_cors_and_apikey_defined_at_api_level.json b/tests/translator/output/aws-us-gov/api_with_cors_and_apikey_defined_at_api_level.json new file mode 100644 index 000000000..f6fe0d9ec --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_cors_and_apikey_defined_at_api_level.json @@ -0,0 +1,208 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Outputs": { + "WebEndpoint": { + "Description": "API Gateway endpoint URL", + "Value": { + "Fn::Sub": "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/dev/hello" + } + } + }, + "Resources": { + "ApiGatewayLambdaRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AllowInvokeLambdaFunctions" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "MyApi": { + "Properties": { + "Body": { + "components": { + "securitySchemes": { + "api_key": { + "in": "header", + "name": "x-api-key", + "type": "apiKey" + } + } + }, + "openapi": "3.0.1", + "paths": { + "/hello": { + "get": { + "security": [ + { + "api_key": [] + } + ], + "x-amazon-apigateway-integration": { + "credentials": { + "Fn::Sub": "${ApiGatewayLambdaRole.Arn}" + }, + "httpMethod": "POST", + "passthroughBehavior": "when_no_match", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + } + }, + "options": { + "responses": { + "200": { + "description": "Default response for CORS method", + "headers": { + "Access-Control-Allow-Methods": { + "schema": { + "schema": { + "type": "string" + } + } + }, + "Access-Control-Allow-Origin": { + "schema": { + "schema": { + "type": "string" + } + } + } + } + } + }, + "summary": "CORS support", + "x-amazon-apigateway-integration": { + "requestTemplates": { + "application/json": "{\n \"statusCode\" : 200\n}\n" + }, + "responses": { + "default": { + "responseParameters": { + "method.response.header.Access-Control-Allow-Methods": "'GET,OPTIONS'", + "method.response.header.Access-Control-Allow-Origin": "'*'" + }, + "responseTemplates": { + "application/json": "{}\n" + }, + "statusCode": "200" + } + }, + "type": "mock" + } + } + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + }, + "Type": "AWS::ApiGateway::RestApi" + }, + "MyApiDeploymentdb7bc48ce9": { + "Properties": { + "Description": "RestApi deployment id: db7bc48ce9273479c81f6c8c0b91862e71dbbef8", + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "Stage" + }, + "Type": "AWS::ApiGateway::Deployment" + }, + "MyApidevStage": { + "Properties": { + "DeploymentId": { + "Ref": "MyApiDeploymentdb7bc48ce9" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "dev" + }, + "Type": "AWS::ApiGateway::Stage" + }, + "MyFunction": { + "Properties": { + "Code": { + "ZipFile": "exports.handler = async function (event) {\n return {\n statusCode: 200,\n body: JSON.stringify({ message: \"Hello, SAM!\" }),\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs16.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MyFunctionRole": { + "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" + } + } +}