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..."

karthikvadla commented 4 years ago

I need to explicitly add permissions to API gateway to invoke Lambda function. Adding this resource helped to fix permissions issue.

 TestApiGatewayInvokePermissions:
    Type: AWS::Lambda::Permission
    DependsOn:
      - TestLambdaFunction
      - TestServiceApi
    Properties:
      Action: lambda:invokeFunction
      FunctionName:
        Ref: TestLambdaFunction
      SourceArn:
        Fn::Sub: arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${TestServiceApi}/*
      Principal: apigateway.amazonaws.com

Thanks a lot again @paolorechia

S4lem commented 4 years ago

+1

eda-cpu commented 4 years ago

+1

chrisj-au commented 4 years ago

Well that's it. I am giving up on SAM.

central182 commented 4 years ago

I ended up setting up a two-phase deployment with my CD pipeline (GitHub Actions)

  1. Run sam deploy
  2. Create a request validator with aws apigateway create-request-validator and note the id down
  3. Attach the validator id to PATCH, POST, PUT methods with aws apigateway update-method
  4. Commit those changes with aws apigateway create-deployment

    So far so good

AntonUspishnyi commented 4 years ago

My workaround as a step in CI/CD (after successful Cloudfromation deploy):

#!/usr/bin/env python3

import os
import boto3
from typing import Optional
from argparse import ArgumentParser
from utils.config import VaultConfig

client = boto3.client("apigateway", region_name=os.environ.get("AWS_REGION"))
validator_name = "validate-all"

def get_api_id(api_name: str) -> str:
    api_ids = []
    for api_conf in client.get_rest_apis(limit=500).get("items"):
        if api_conf.get("name") == api_name:
            api_ids.append(api_conf.get("id"))
    if len(api_ids) != 1:
        raise Exception(f"Could not define Api ID by Api Name - {api_ids}")
    return api_ids[0]

def get_resources(api_id: str) -> dict:
    resources = {}
    paginator = client.get_paginator("get_resources")
    response_iterator = paginator.paginate(
        restApiId=api_id, PaginationConfig={"MaxItems": 100}
    )
    for page in response_iterator:
        for each in page.get("items"):
            _id = each["id"]
            _methods = each.get("resourceMethods") or []
            for _method in _methods:
                if _method not in ("OPTIONS", "ANY"):
                    resources.setdefault(_id, []).append(_method)
    return resources

def get_validator_id(api_id: str) -> str:
    validators = client.get_request_validators(restApiId=api_id, limit=500).get("items")
    if not validators:
        print("Creating new RequestValidator...")
        return create_validator(api_id)
    for _validator in validators:
        if _validator.get("name") == validator_name:
            print(f"Using RequestValidator {validator_name} that already exists")
            return _validator.get("id")

def create_validator(api_id: str) -> str:
    r = client.create_request_validator(
        restApiId=api_id,
        name=validator_name,
        validateRequestBody=True,
        validateRequestParameters=True,
    )
    return r.get("id")

def set_validator(api_id: str, resource_id: str, method: str, validator: str) -> None:
    client.update_method(
        restApiId=api_id,
        resourceId=resource_id,
        httpMethod=method,
        patchOperations=[
            {"op": "replace", "path": "/requestValidatorId", "value": validator},
        ],
    )

def deploy_api(api_id: str, env: str, desc: str) -> None:
    client.create_deployment(restApiId=api_id, stageName=env, description=desc)

def remove_validator(api_id: str, validator_id: str) -> None:
    client.delete_request_validator(restApiId=api_id, requestValidatorId=validator_id)

def skip(api_set_request_validator: bool) -> None:
    print(f"########################################\n"
          f"RequestValidator processing was skipped:\n"
          f"API_SET_REQUEST_VALIDATOR = {api_set_request_validator}\n"
          f"########################################")
    exit(0)

def run(config: VaultConfig, remove_request_validator: Optional[bool]) -> None:
    api = get_api_id(config.api_name)
    validator_id = get_validator_id(api)

    if remove_request_validator:
        remove_validator(api_id=api, validator_id=validator_id)
        print(f"RequestValidator {validator_id} was removed")
    else:
        for res, methods in get_resources(api).items():
            for method_name in methods:
                set_validator(api, res, method_name, validator_id)
                print(
                    f"Successfully set Validator {validator_id} for Resource {res}/{method_name}"
                )
        deploy_api(api, config.env, config.ci_pipeline_url)
        print(f"Successfully deploy {config.api_name}:{config.env} ({api})")

if __name__ == "__main__":
    parser = ArgumentParser()
    parser.add_argument("--remove", action="store_true")
    args = parser.parse_args()

    conf = VaultConfig()
    if not conf.api_set_request_validator:
        skip(conf.api_set_request_validator)

    run(config=conf, remove_request_validator=args.remove)
naveenkumardot25 commented 4 years ago

Well that's it. I am giving up on SAM.

@chrisj-au Don't give-up

I'm able to validate incoming and outgoing request using Use DefinitionBody or DefinitionUri

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

+1

AntonUspishnyi commented 3 years ago

@praneetap any updates?

RamVadranam commented 3 years ago

+1

BohdanKrasko commented 3 years ago

+1

ifurs commented 3 years ago

any ETA?

smoothie99 commented 3 years ago

+1

alex-all3dp commented 3 years ago

+1

PeppyDays commented 3 years ago

+1 totally not understandable why request validator is not included in SAM

husain-amjhera commented 3 years ago

+1

husseinkohy commented 3 years ago

+1

me2resh commented 3 years ago

+1

ferencdev commented 3 years ago

Yes please!

erashdan commented 3 years ago

+1

ElasticMobile-joakimbulow commented 3 years ago

This feature would make so much sense..

jolane commented 3 years ago

+1

AllanOricil commented 3 years ago

+1

bakosa commented 3 years ago

+1

codepay4999 commented 3 years ago

+1

huiliuPaf commented 3 years ago

+1

drwxmrrs commented 3 years ago

+1

hirro commented 3 years ago

+1

mixalis-ombashis commented 3 years ago

+1

nicodewet commented 3 years ago

+1 (and it's somewhat annoying to have to waste time on this topic)

Rondineli commented 3 years ago

I have added a PR to try fix this: https://github.com/aws/serverless-application-model/pull/2026 I also need when deploy a model set the validation to the route/method.

If someone could review and give a +1.

jdonboch commented 3 years ago

+1, we need this!

dominicfarr commented 3 years ago

Ok, so I'm not going mad: I've spent several days trying to get SAM to add a Request Validator. Looks like the PR above will make this simple to do. +1

claranetpetecocoon commented 3 years ago

+1

AllanOricil commented 3 years ago

Now that the solution was implemented. Can somebody explain to me what is the main advantage of having validation as a Request Validator instead of validating in the code?

My guess it that AWS won't even charge me if the request validation failed, while if I do the validation in the code AWS will charge me for the seconds the code spent validating. Is this guess accurate?

AntonUspishnyi commented 3 years ago

@AllanOricil I mean, why do something by yourself when you can outsource it to AWS? :) Also, it's a win-win for overall request latency.

petecocoon commented 3 years ago

Has this feature been released?

AllanOricil commented 3 years ago

@AllanOricil I mean, why do something by yourself when you can outsource it to AWS? :) Also, it's a win-win for overall request latency.

@AntonUspehov but does AWS charge me for the time the Request Validator validation takes or per validation/request?

AntonUspishnyi commented 3 years ago

@AllanOricil I mean, why do something by yourself when you can outsource it to AWS? :) Also, it's a win-win for overall request latency.

@AntonUspehov, but does AWS charge me for the time the Request Validator validation takes or per validation/request?

@AllanOricil No, only for the API Gateway request, as I know. Lambda won't even be trigger.

AllanOricil commented 3 years ago

@AllanOricil I mean, why do something by yourself when you can outsource it to AWS? :) Also, it's a win-win for overall request latency.

@AntonUspehov, but does AWS charge me for the time the Request Validator validation takes or per validation/request?

@AllanOricil No, only for the API Gateway request, as I know. Lambda won't even be trigger.

Cool. I will start testing it :D Amazing job guys. If anybody here needs a serverless app developer, you can count on me as well.

moelasmar commented 2 years ago

This feature has been released.

santiperone commented 2 years ago

I have a question regarding this feature @moelasmar. Typically, one would want to validate for request parametes only. But in order to do this you need to define a RequestModel, would it be better being able to enable parameterValidation from requestParameters instead?

Rondineli commented 2 years ago

@santiperone I created another PR for this scenario: https://github.com/aws/serverless-application-model/pull/2450

A thumbs up at the PR would be appreciated!

antoineaws commented 8 months ago

Team, currently there seems to be a missing piece, when adding request parameters to the SAM template as below, the request parameters get added properly, however the "Request validator" flag doesn't get set and remain None. Our SAM docs for EventSource doesn't seem to have a property to set the Request Validator. Am I missing something? Screenshot 2023-12-30 at 1 09 49 PM

      Events:
        ServerlessRestApiGETMethod:
          Type: Api
          Properties:
            RestApiId: !Ref Api
            Path: /my-path
            Method: GET
            RequestParameters:
              - method.request.querystring.param1:
                  Required: true
              - method.request.querystring.param2:
                  Required: true

cc @moelasmar

Cheers

Rondineli commented 8 months ago

I had created this PR to address it, but it was not accepted, it is something I would like really implemented, but never got clear directive of better implementation of it. I would be happy to cut a new PR to do it if I have clear direction on it. cc: @moelasmar My PR: https://github.com/aws/serverless-application-model/pull/2450

AllanOricil commented 8 months ago

Just switch to cdk or pollumi. Use SAM for testing your lambdas locally. Much easier than write these templates by hand

jasonterando commented 23 hours ago

Is this topic dead?