aws / serverless-application-model

The AWS Serverless Application Model (AWS SAM) transform is a AWS CloudFormation macro that transforms SAM templates into CloudFormation templates.
https://aws.amazon.com/serverless/sam
Apache License 2.0
9.33k stars 2.38k forks source link

Support Request Validator to the method on API Event #1403

Closed scarlier closed 2 years ago

scarlier commented 4 years ago

Description: Defining api model to required=true will not add the Request Validator to the method.

with following sam template and sam cli version 0.40

AWSTemplateFormatVersion : "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"

Globals:
  Function:
    AutoPublishAlias: live
    Runtime: python3.8
    Handler: lambda_handler.handler

Resources:

  restGateway:
    Type: "AWS::Serverless::Api"
    Properties:
      StageName: default
      Models:
        SomeModel:
          type: object
          properties:
            keyname:
              type: string
          required:
            - keyname

  byIamcToken:
    Type: "AWS::Serverless::Function"
    Properties:
      CodeUri: src.zip
      Events:
        HttpGet:
          Type: Api
          Properties:
            Path: '/request-path'
            Method: post
            RestApiId: !Ref restGateway
            RequestModel:
              Model: SomeModel
              Required: true

Observed result: Request Validator in method settings has value "NONE"

Expected result: Request Validator in method settings has value "Validate body..."

praneetap commented 4 years ago

Thanks for the feature request! We have passed this along to our product team for possible prioritization. Please +1 on the feature to help with prioritization.

brysontyrrell commented 4 years ago

@praneetap this has been raised before. I opened issue #1232 and it was quickly closed in favor of #931

asyba commented 4 years ago

+1

g-pelletier commented 4 years ago

A temporary solution could be to add a partial DefinitionBody in your ApiGateway resource :

   DefinitionBody:
        swagger: 2.0
        x-amazon-apigateway-request-validators:
          basic:
            validateRequestBody: true
            validateRequestParameters: true
        x-amazon-apigateway-request-validator: basic
        paths:
          /request-path:
            post:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri:
                  Fn::Sub: 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${byIamcToken.Arn}/invocations'
         parameters:
              - required: true
                in: body
                name: somemodel
                schema:
                  $ref: '#/definitions/somemodel'

This will add a validator named basic and will add it to every function in your apigateway, thus will enforce validation of the body and parameters (tweak it as you need)

brysontyrrell commented 4 years ago

~@g-pelletier as I understand you can't do a partial swagger doc. You'll need to define the full API and the links to the Lambda functions as SAM will either generate the swagger doc or it will rely on the one provided if you've set DefinitionBody.~

~I believe in the case of the new HttpApi resource they have designed that to be able to merge source and auto-generated definitions. @praneetap can you verify?~

g-pelletier commented 4 years ago

You can generate partial swagger doc. I just did in our templates that needed request body verification. What I posted is exactly what I used combined with something similiar as what is posted in the original post.

brysontyrrell commented 4 years ago

@g-pelletier disregard that last comment. I wasn't thinking. What you posted is what we've had to do for similar workarounds as well. Though I don't think you could really call that a partial swagger doc.

g-pelletier commented 4 years ago

Sorry, by partial, I meant that you don't need to write the full API code to have it working. SAM will append the other parts, such as Models and Authorizers.

agostbiro commented 4 years ago

@praneetap @keetonian could you provide some info on how this relates to #931 and #1232? Those issues seem to suggest that there is work underway yet this issue seems to be still in triage? I'd avoid the swagger workaround if validation support for AWS::Serverless::Api-Models is in the pipeline.

paolorechia commented 4 years ago

Hi!

I was a little bit annoyed with the lack of this feature, so I started hacking away a possible solution. I ended up with a version that works locally using cloud formation packaging/deploy, but I am not sure if it would be eligible for a Pull Request. I would appreciate if anyone would give me a feedback whether this would be useful to the general development of the specification.

Basically, I've added properties to the SAM specification

The first one basically injects the following on the generated template:

          "x-amazon-apigateway-request-validators": {
            "BODY": {
              "validateRequestParameters": false, 
              "validateRequestBody": true
            }, 
            "FULL": {
              "validateRequestParameters": true, 
              "validateRequestBody": false
            }, 
            "PARAMS": {
              "validateRequestParameters": true, 
              "validateRequestBody": false
            }
          }

Then the second would inject this to the API Method:

                "x-amazon-apigateway-request-validator": "BODY",

A sample template would be:

  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      GenerateValidators: true  # New property
      StageName: Dev
      Models:
        Book:
          required:
          - name
          - author
          type: object
          properties:
            name:
              type: string
            author:
              type: string
              description: author

  CreateBookFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: dynamo/
      Handler: handlers.put_item_handler
      Events:
        HelloWorld:
          Type: Api 
          Properties:
            RestApiId: !Ref MyApi
            Path: /book
            Method: post
            RequestModel:
              Model: Book
              Required: true
            GeneratedValidator: BODY  # New property
AntonUspishnyi commented 4 years ago

Still need "Request Validator" parameter in SAM.

ifurs commented 4 years ago

+1 for "Request Validator"

Bigsamovar commented 4 years ago

Still need "Request Validator" parameter in SAM.

+1

Doroschuk commented 4 years ago

Still need "Request Validator" parameter in SAM.

+++

Lifeformwp commented 4 years ago

Still need "Request Validator" parameter in SAM.

+1

ShytN1k commented 4 years ago

Still need "Request Validator" parameter in SAM.

+1

AlexOliinyk commented 4 years ago

++

PashaKoval commented 4 years ago

+1

wgfilho commented 4 years ago

+1

AntonUspishnyi commented 4 years ago

Hi! Is there any ETA?

yuryks commented 4 years ago

+1

johangu commented 4 years ago

+1

revolutionisme commented 4 years ago

This is really needed.

AntonUspishnyi commented 4 years ago

Up

MaiKaY commented 4 years ago

+1

YegresOm commented 4 years ago

+

Bigsamovar commented 4 years ago

+1

jetchopper commented 4 years ago

+1

edreanernst commented 4 years ago

+1

karthikvadla commented 4 years ago

++++++1

ifurs commented 4 years ago

+1

joyacv2 commented 4 years ago

+1

KeynesYouDigIt commented 4 years ago

+1

sasankmukkamala commented 4 years ago

+1

ifurs commented 4 years ago

Any updates?

gham-khaled commented 4 years ago

+1

bataysyk commented 4 years ago

+1

AntonUspishnyi commented 4 years ago

+1

atmarges commented 4 years ago

+1

ahhelmy commented 4 years ago

+1

niloy369 commented 4 years ago

+1

EnzoAliatis commented 4 years ago

+1

cb-hariharasudan commented 4 years ago

+1

clrkco commented 4 years ago

+1

EnzoAliatis commented 4 years ago

++++++1

vanjacatak4 commented 4 years ago

+1 !

karthikvadla commented 4 years ago

A temporary solution could be to add a partial DefinitionBody in your ApiGateway resource :

   DefinitionBody:
        swagger: 2.0
        x-amazon-apigateway-request-validators:
          basic:
            validateRequestBody: true
            validateRequestParameters: true
        x-amazon-apigateway-request-validator: basic
        paths:
          /request-path:
            post:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri:
                  Fn::Sub: 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${byIamcToken.Arn}/invocations'
         parameters:
              - required: true
                in: body
                name: somemodel
                schema:
                  $ref: '#/definitions/somemodel'

This will add a validator named basic and will add it to every function in your apigateway, thus will enforce validation of the body and parameters (tweak it as you need)

Hi @g-pelletier, I did try as you mentioned with openAPI-3.0.0. API deployed successfully but i get this error

Execution failed due to configuration error: Invalid permissions on Lambda function

My CFN looks as this DefinitionBoyd

TestServiceApi:
    Type: AWS::Serverless::Api
    Properties:
      Name: TestServiceApi
      EndpointConfiguration: EDGE
      StageName: Prod
      TracingEnabled: true
      Auth:
        DefaultAuthorizer: CustomAuthorizer
        AddDefaultAuthorizerToCorsPreflight: false
        Authorizers:
          CustomAuthorizer:
            FunctionPayloadType: TOKEN
            FunctionArn: !GetAtt TestServiceCustomAuthorizerLambdaFunction.Arn
            Identity:
              ValidationExpression: Bearer.*
              ReauthorizeEvery: 0
      GatewayResponses:
        DEFAULT_4xx:
          ResponseParameters:
            Headers:
              Access-Control-Allow-Origin: "'*'"
        DEFAULT_5XX:
          ResponseParameters:
            Headers:
              Access-Control-Allow-Origin: "'*'"
      Cors:
        AllowMethods: "'*'"
        AllowHeaders: "'*'"
        AllowOrigin:
          Fn::Join:
            - ''
            - - "'https://"
              - Fn::ImportValue: !Sub "${ProjectPrefix}-${Stage}-DomainName"
              - "'"
      DefinitionBody:
        openapi: "3.0.0"
        info:
          description: "This is a Test management service API specification."
          version: "1.0.0"
          title: "Test Management Service API"
          termsOfService: "http://aws.com/terms/"
          contact:
            email: "aws-bots-grimsby@amazon.com"
          license:
            name: "Amazon"
            url: "http://www.amazon.com"

        servers:
          - url: "https://{stage}.aws.com/test-management"
            variables:
              stage:
                default: api    # Production server
                enum:
                  - api         # Production server
                  - api.gamma   # Pre-Prod server
                  - api.beta    # Beta server
        x-amazon-apigateway-request-validators:
          basic:
            validateRequestBody: true
            validateRequestParameters: true
        x-amazon-apigateway-request-validator: basic
        paths:
          /instructors:
            get:
              summary: "Finds Instructors"
              operationId: "GetInstructorsLambdaFunction"
              x-amazon-apigateway-integration:
                uri:
                  Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetInstructorsLambdaFunction.Arn}/invocations"
                httpMethod: POST  # Keep "POST" when the API definition method is not POST. This "httpMethod" is used to call Lambda.
                type: aws_proxy
              parameters:
                - name: "search_text"
                  in: "query"
                  description: "Filter instructors based on partial search"
                  required: false
                  schema:
                    type: "string"
                    example: "pra"
                - name: "from"
                  in: "query"
                  description: "from field for pagination"
                  required: false
                  schema:
                    type: "integer"
                    example: 0
                - name: "size"
                  in: "query"
                  description: "size field for pagination"
                  required: false
                  schema:
                    type: "integer"
                    example: 10
                - name: "sort_fields"
                  in: "query"
                  description: "Sort instructors based on given fields"
                  required: false
                  schema:
                    type: "array"
                    items:
                      type: "object"
                    example: [{"email": {"order": "asc"}},{"instrctor_status": {"order": "desc"}}]
                - name: "source_fields"
                  in: "query"
                  description: "Source fields to return in reponse"
                  required: false
                  schema:
                    type: "array"
                    items:
                      type: "string"
                      example: ["first_name", "last_name", "programs", "regions"]
                - name: "saved_filter"
                  in: "query"
                  description: "Addtional filters to apply"
                  required: false
                  schema:
                    type: "object"
                    example: {"postcal_code": "97124", "active": True}
              responses:
                "200":
                  description: "successful operation"
                  content:
                    application/json:
                      schema:
                        type: "array"
                        items:
                          type: "object"

Any help on this please.

@paolorechia any thoughts from your end too??

paolorechia commented 4 years ago

Hi, @karthikvadla.

As far as I remember, you really just need these two pieces in the generated CFN.

"x-amazon-apigateway-request-validators": {
            "SomeValidatorName": {
              "validateRequestParameters": true, 
              "validateRequestBody": true
            }, 

And

x-amazon-apigateway-request-validator: SomeValidatorName

I am not sure why your CFN did not work with OpenAPI 3.0. What I can do to help you is clone my old forked repo and try to generate a CFN that works so we can compare.

Also, for the final the solution, I believe it would be ideal if we could automatically generate the boilerplate by simply defining:

  1. a model
  2. the request model below as required

For instance:

(...)
 Models:
        Book:
          required:
          - name
          - author
          type: object
          properties:
            name:
              type: string
            author:
              type: string
              description: author

(...)
            RequestModel:
              Model: Book
              Required: true

I think the developer using SAM CLI would not really care about which actual validator is used, as long as it actually validates both body and query params.

paolorechia commented 4 years ago

It seems like I added the validator to each API Method and not to the root level, like your CFN. I'll confirm this once I have the develop environment installed again.

Here's the full diff from a stale version of develop to my local branch, so you can inspect what I did:

diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py
index 1dc2caa..73c05ae 100644
--- a/samtranslator/model/api/api_generator.py
+++ b/samtranslator/model/api/api_generator.py
@@ -85,6 +85,7 @@ class ApiGenerator(object):
         open_api_version=None,
         models=None,
         domain=None,
+        generate_validators=False
     ):
         """Constructs an API Generator class that generates API Gateway resources

@@ -104,6 +105,7 @@ class ApiGenerator(object):
         :param resource_attributes: Resource attributes to add to API resources
         :param passthrough_resource_attributes: Attributes such as `Condition` that are added to derived resources
         :param models: Model definitions to be used by API methods
+        :param generate_validators: Uses auto generated validators
         """
         self.logical_id = logical_id
         self.cache_cluster_enabled = cache_cluster_enabled
@@ -131,6 +133,7 @@ class ApiGenerator(object):
         self.remove_extra_stage = open_api_version
         self.models = models
         self.domain = domain
+        self.generate_validators = generate_validators

     def _construct_rest_api(self):
         """Constructs and returns the ApiGateway RestApi.
@@ -144,6 +147,8 @@ class ApiGenerator(object):
         rest_api.BinaryMediaTypes = self.binary_media
         rest_api.MinimumCompressionSize = self.minimum_compression_size

+        # Defines default request validators for the API
+
         if self.endpoint_configuration:
             self._set_endpoint_configuration(rest_api, self.endpoint_configuration)

@@ -181,6 +186,17 @@ class ApiGenerator(object):
         if self.name:
             rest_api.Name = self.name

+        print(self.generate_validators)
+        if self.generate_validators:
+            print( "Generating...")
+            self._set_generated_validators()
+            _X_APIGW_REQUEST_VALIDATORS = "x-amazon-apigateway-request-validators"
+            rest_api.Body[_X_APIGW_REQUEST_VALIDATORS] = {
+                "BODY": {"validateRequestBody": True, "validateRequestParameters": False},
+                "PARAMS": {"validateRequestBody": False, "validateRequestParameters": True},
+                "FULL": {"validateRequestBody": False, "validateRequestParameters": True}
+            }
+
         return rest_api

     def _construct_body_s3_dict(self):
@@ -723,6 +739,17 @@ class ApiGenerator(object):
         # Assign the Swagger back to template
         self.definition_body = swagger_editor.swagger

+    def _set_generated_validators(self):
+        swagger_editor = SwaggerEditor(self.definition_body)
+        swagger_editor.set_request_validators({
+            "BODY": {"validateRequestBody": True, "validateRequestParameters": False},
+            "PARAMS": {"validateRequestBody": False, "validateRequestParameters": True},
+            "FULL": {"validateRequestBody": False, "validateRequestParameters": True}
+        })
+
+        self.definition_body = swagger_editor.swagger
+        # print(self.definition_body)
+
     def _add_models(self):
         """
         Add Model definitions to the Swagger file, if necessary
diff --git a/samtranslator/model/eventsources/push.py b/samtranslator/model/eventsources/push.py
index 03ec6a8..258d037 100644
--- a/samtranslator/model/eventsources/push.py
+++ b/samtranslator/model/eventsources/push.py
@@ -483,6 +483,8 @@ class Api(PushEventSource):
         "Auth": PropertyType(False, is_type(dict)),
         "RequestModel": PropertyType(False, is_type(dict)),
         "RequestParameters": PropertyType(False, is_type(list)),
+        "GeneratedValidator": PropertyType(False, is_str()),
+        "x-amazon-apigateway-request-validator": PropertyType(False, is_str())
     }

     def resources_to_link(self, resources):
@@ -491,6 +493,7 @@ class Api(PushEventSource):
         necessary data from the explicit API
         """

+
         rest_api_id = self.RestApiId
         if isinstance(rest_api_id, dict) and "Ref" in rest_api_id:
             rest_api_id = rest_api_id["Ref"]
@@ -504,6 +507,10 @@ class Api(PushEventSource):
         permitted_stage = "*"
         stage_suffix = "AllStages"
         explicit_api = None
+
+        if self.GeneratedValidator:
+            setattr(self, 'x-amazon-apigateway-request-validator', self.GeneratedValidator)
+
         if isinstance(rest_api_id, string_types):

             if (
@@ -788,6 +795,10 @@ class Api(PushEventSource):
                 path=self.Path, method_name=self.Method, request_parameters=parameters
             )

+        if self.GeneratedValidator:
+            editor.add_request_validator_to_method(
+                path=self.Path, method_name=self.Method, request_validator=self.GeneratedValidator)
+
         api["DefinitionBody"] = editor.swagger

diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py
index b1ed7f6..c78f076 100644
--- a/samtranslator/model/sam_resources.py
+++ b/samtranslator/model/sam_resources.py
@@ -760,6 +760,7 @@ class SamApi(SamResourceMacro):
         "Tags": PropertyType(False, is_type(dict)),
         "DefinitionBody": PropertyType(False, is_type(dict)),
         "DefinitionUri": PropertyType(False, one_of(is_str(), is_type(dict))),
+        "GenerateValidators": PropertyType(False, is_type(bool)),
         "CacheClusterEnabled": PropertyType(False, is_type(bool)),
         "CacheClusterSize": PropertyType(False, is_str()),
         "Variables": PropertyType(False, is_type(dict)),
@@ -829,6 +830,7 @@ class SamApi(SamResourceMacro):
             open_api_version=self.OpenApiVersion,
             models=self.Models,
             domain=self.Domain,
+            generate_validators=self.GenerateValidators
         )

         (
diff --git a/samtranslator/swagger/swagger.py b/samtranslator/swagger/swagger.py
index abd15b9..6aa4db5 100644
--- a/samtranslator/swagger/swagger.py
+++ b/samtranslator/swagger/swagger.py
@@ -23,6 +23,7 @@ class SwaggerEditor(object):
     _X_APIGW_GATEWAY_RESPONSES = "x-amazon-apigateway-gateway-responses"
     _X_APIGW_POLICY = "x-amazon-apigateway-policy"
     _X_ANY_METHOD = "x-amazon-apigateway-any-method"
+    _X_APIGW_REQUEST_VALIDATORS = "x-amazon-apigateway-request-validators"
     _CACHE_KEY_PARAMETERS = "cacheKeyParameters"
     # https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
     _ALL_HTTP_METHODS = ["OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "PATCH"]
@@ -44,6 +45,7 @@ class SwaggerEditor(object):

         self._doc = copy.deepcopy(doc)
         self.paths = self._doc["paths"]
+        self._X_APIGW_REQUEST_VALIDATORS = {}
         self.security_definitions = self._doc.get("securityDefinitions", {})
         self.gateway_responses = self._doc.get(self._X_APIGW_GATEWAY_RESPONSES, {})
         self.resource_policy = self._doc.get(self._X_APIGW_POLICY, {})
@@ -152,6 +154,12 @@ class SwaggerEditor(object):

         path_dict.setdefault(method, {})

+
+    def set_request_validators(self, validators):
+        self._X_APIGW_REQUEST_VALIDATORS = validators
+        print(self._X_APIGW_REQUEST_VALIDATORS)
+
+
     def add_lambda_integration(
         self, path, method, integration_uri, method_auth_config=None, api_auth_config=None, condition=None
     ):
@@ -1004,6 +1012,19 @@ class SwaggerEditor(object):
                     statement.append(s)
             self.resource_policy["Statement"] = statement

+
+    def add_request_validator_to_method(self, path, method_name, request_validator):
+        normalized_method_name = self._normalize_method_name(method_name)
+
+        for method_definition in self.get_method_contents(self.get_path(path)[normalized_method_name]):
+
+            # If no integration given, then we don't need to process this definition (could be AWS::NoValue)
+            if not self.method_definition_has_integration(method_definition):
+                continue
+
+            method_definition["x-amazon-apigateway-request-validator"] = request_validator
+
+
     def add_request_parameters_to_method(self, path, method_name, request_parameters):
         """
         Add Parameters to Swagger.
diff --git a/samtranslator/validator/sam_schema/schema.json b/samtranslator/validator/sam_schema/schema.json
index c18f106..af54c10 100644
--- a/samtranslator/validator/sam_schema/schema.json
+++ b/samtranslator/validator/sam_schema/schema.json
@@ -5,6 +5,9 @@
     "AWS::Serverless::Api": {
       "additionalProperties": false,
       "properties": {
+        "GenerateValidators": {
+          "type": "boolean"
+        },
         "DeletionPolicy": {
           "enum": [
             "Delete",
@@ -86,6 +89,9 @@
                 }
               },
               "type": "object"
+            },
+            "x-amazon-apigateway-request-validators": {
+              "type:": "object"
             }
           },
           "required": [
@@ -310,6 +316,9 @@
     "AWS::Serverless::Function.ApiEvent": {
       "additionalProperties": false,
       "properties": {
+        "GeneratedValidator": {
+          "type": "string"
+        },
         "Method": {
           "type": "string"
         },
paolorechia commented 4 years ago

I've found an old transformed template lying around (swagger 2.0) in my local file system. You can find the full file here

In API Method:

 "/book": {
              "post": {
                "x-amazon-apigateway-integration": {
                  "httpMethod": "POST", 
                  "type": "aws_proxy", 
                  "uri": {
                    "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${CreateBookFunction.Arn}/invocations"
                  }
                }, 
                "x-amazon-apigateway-request-validator": "BODY", 
                "security": [
                  {
                    "MyCognitoAuthorizer": [
                      "api/generic"
                    ]
                  }
                ], 
                "parameters": [
                  {
                    "required": true, 
                    "in": "body", 
                    "name": "book", 
                    "schema": {
                      "$ref": "#/definitions/book"
                    }
                  }
                ], 
                "responses": {}
              }, 

Somewhere in the top level:

          "swagger": "2.0", 
          "x-amazon-apigateway-request-validators": {
            "BODY": {
              "validateRequestParameters": false, 
              "validateRequestBody": true
            }, 
            "FULL": {
              "validateRequestParameters": true, 
              "validateRequestBody": false
            }, 
            "PARAMS": {
              "validateRequestParameters": true, 
              "validateRequestBody": false
            }
          }