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.32k stars 2.38k forks source link

When using an inline swagger, api+method+path authorizers do not get added #650

Closed georgealton closed 1 year ago

georgealton commented 5 years ago

Description: My expectation was that when using the Auth functionality in SAM against an API and Function, when no default authorizer is configured then the Authorizer would be assigned against the Functions path

I've also noticed that when using a Default Authorizer, all methods on the API get the Authorizer assigned, configuring NONE authorizer against an API Event does not prevent the authorizer getting assigned.

Steps to reproduce the issue:

Given a SAM Template that includes a an inline swagger document, an Authorizer and Auth configured on the DELETE and POST events

template.yaml

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

Globals:
  Function:
    Runtime: python3.6
    MemorySize: 128
    Timeout: 15

Resources:
  Create:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: example
      Handler: service.example
      Events:
        Create:
          Type: Api
          Properties:
            Method: post
            Path: /
            RestApiId: !Ref API
            Auth:
              Authorizer: ExampleAuth

  List:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: example
      Handler: service.example
      Events:
        Create:
          Type: Api
          Properties:
            Method: get
            Path: /
            RestApiId: !Ref API

  Delete:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: example
      Handler: service.example
      Events:
        AddTenant:
          Type: Api
          Properties:
            Method: delete
            Path: /{id}
            RestApiId: !Ref API
            Auth:
              Authorizer: ExampleAuth

  Retrieve:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: example
      Handler: service.example
      Events:
        AddTenant:
          Type: Api
          Properties:
            Method: post
            Path: /{id}
            RestApiId: !Ref API

  Auth:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: example
      Handler: service.example

  API:
    Type: AWS::Serverless::Api
    Properties:
      Name: ExampleAPI
      EndpointConfiguration: REGIONAL
      StageName: v1
      Auth:
        Authorizers:
          ExampleAuth:
            FunctionPayloadType: TOKEN
            FunctionArn: !GetAtt Auth.Arn
            Identity:
              Header: Authorization
              ValidationExpression: Bearer.*
              ReauthorizeEvery: 0
      DefinitionBody:
        swagger: "2.0"
        info:
          title: Example
          version: "1"
        parameters:
          id:
            name: id
            in: path
            required: true
            type: string
        paths:
          /:
            post:
              parameters:
                - in: body
                  name: item
                  schema:
                    type: object
              responses:
                "201":
                  description: Created
              x-amazon-apigateway-integration:
                type: aws_proxy
                httpMethod: POST
                passthroughBehavior: when_no_match
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Create.Arn}/invocations
            get:
              responses:
                "200":
                  description: Got
              x-amazon-apigateway-integration:
                type: aws_proxy
                httpMethod: POST
                passthroughBehavior: when_no_match
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${List.Arn}/invocations
          /{id}:
            get:
              parameters:
                - $ref: '#/parameters/id'
              responses:
                "200":
                  description: Got
              x-amazon-apigateway-integration:
                type: aws_proxy
                httpMethod: POST
                passthroughBehavior: when_no_match
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Retrieve.Arn}/invocations
            delete:
              parameters:
                - $ref: '#/parameters/id'
              responses:
                "200":
                  description: Deleted
              x-amazon-apigateway-integration:
                type: aws_proxy
                httpMethod: POST
                passthroughBehavior: when_no_match
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Delete.Arn}/invocations

example/service.py

def example(event, context):
    print(event)
    return 200

example/__init__.py

__version__ = "1.0.0"

Deploy this SAM Template

sam package --template-file template.yaml --s3-bucket "${S3_BUCKET}" > template-export.yaml
sam deploy --template-file template-export.yaml --stack-name "${STACK_NAME}" --capabilities CAPABILITY_IAM

Observed result: No Authorizers are assigned to any of the methods

image image

Expected result: My expectation was that the GET Methods on the path / and /{id} would not have an authorizer assigned, but POST / and DELETE /{id} do not have authorizer assigned

brettstack commented 5 years ago

We plan on adding a feature to merge your defined Swagger with the generated Swagger from SAM. This should resolve this issue also.

bonybrown commented 5 years ago

A workaround exists where if the name of the Authorizer defined in the SAM template matches the name of a securityDefinitions in the swagger document, AND the endpoint refers to that named security definition, then the correct authorizer will be assigned to the endpoint.

This is often what is desired, because if you have security requirements for the endpoint, you probably want to define them (as best you can) in the swagger doc.

SAM snippet below:

  myApi:
    Type: AWS::Serverless::Api
    Properties:
      EndpointConfiguration: REGIONAL
      StageName: master
      DefinitionBody:
        swagger: '2.0'
        info:
          title: Test
          version: 1.0.0
          description: Only a test
        schemes:
        - https
        paths:
          "/sayHello":
            get:
              operationId: hello
              description: Says "Hello"
              produces:
              - text/html
              responses:
                '200':
                  description: OK
                default:
                  description: Error
              security:
              - keyAuth: [] # refers to security definition in this swagger doc
              x-amazon-apigateway-integration:
                uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations
                httpMethod: POST
                type: aws_proxy
        securityDefinitions:
          keyAuth: #matches name of Auth/Authorizer defined on the Api
            description: Authentication will be performed using a custom authenticator
            type: apiKey
            name: Authorization
            in: header
      Auth:
        Authorizers:
          keyAuth: # Name of this authorizer; becomes the name of the authorizer in API Gateway console
            Identity:
              Headers:
              - Authorization
              ReauthorizeEvery: 600
            FunctionPayloadType: REQUEST
            FunctionArn: !Ref CustomAuthArn

The result of the above is that the authorizer is created with expected properties in the API, and the endpoint is configured to use that authorizer.

Untested are the effects of setting the Serverless::Function/Events/Auth/Authorizer to NONE or setting the DefaultAuthorizer property on the Serverless::Api/Auth/Authorizers

ngalchemist commented 3 years ago

A workaround exists where if the name of the Authorizer defined in the SAM template matches the name of a securityDefinitions in the swagger document, AND the endpoint refers to that named security definition, then the correct authorizer will be assigned to the endpoint.

This is often what is desired, because if you have security requirements for the endpoint, you probably want to define them (as best you can) in the swagger doc.

SAM snippet below:

  myApi:
    Type: AWS::Serverless::Api
    Properties:
      EndpointConfiguration: REGIONAL
      StageName: master
      DefinitionBody:
        swagger: '2.0'
        info:
          title: Test
          version: 1.0.0
          description: Only a test
        schemes:
        - https
        paths:
          "/sayHello":
            get:
              operationId: hello
              description: Says "Hello"
              produces:
              - text/html
              responses:
                '200':
                  description: OK
                default:
                  description: Error
              security:
              - keyAuth: [] # refers to security definition in this swagger doc
              x-amazon-apigateway-integration:
                uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations
                httpMethod: POST
                type: aws_proxy
        securityDefinitions:
          keyAuth: #matches name of Auth/Authorizer defined on the Api
            description: Authentication will be performed using a custom authenticator
            type: apiKey
            name: Authorization
            in: header
      Auth:
        Authorizers:
          keyAuth: # Name of this authorizer; becomes the name of the authorizer in API Gateway console
            Identity:
              Headers:
              - Authorization
              ReauthorizeEvery: 600
            FunctionPayloadType: REQUEST
            FunctionArn: !Ref CustomAuthArn

The result of the above is that the authorizer is created with expected properties in the API, and the endpoint is configured to use that authorizer.

Untested are the effects of setting the Serverless::Function/Events/Auth/Authorizer to NONE or setting the DefaultAuthorizer property on the Serverless::Api/Auth/Authorizers

This solution worked perfectly for me. Thank you!

GavinZZ commented 1 year ago

Consider the following example, when custom swagger definition is commented out, the API resource has custom authorizer configured. When the comments are removed, it seems that we ignore the generated swagger definition and use custom swagger definition, which caused this problem.

Transform: AWS::Serverless-2016-10-31

Resources:
  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Prod
      # DefinitionBody:
      #   swagger: "2.0"
      #   info:
      #     title: Example
      #     version: "1"
      #   paths:
      #     /test:
      #       get:
      #         x-amazon-apigateway-integration:
      #           httpMethod: "GET"
      #           type: "aws_proxy"
      #           uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations"
      Auth:
        Authorizers:
          MyAuthorizer:
            UserPoolArn: !GetAtt  MyCognitoUserPool.Arn

  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: nodejs14.x
      InlineCode: |
        exports.handler = async (event, context, callback) => {
          return {
            statusCode: 200,
            body: 'Success'
          }
        }
      Events:
        MyEventV1:
          Type: Api
          Properties:
            RestApiId: !Ref MyApi
            Path: /test
            Method: get
            Auth:
              Authorizer: MyAuthorizer

  MyCognitoUserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      UserPoolName: MyCognitoUserPoolRandomName

  MyCognitoUserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      UserPoolId:
        Ref: MyCognitoUserPool
      ClientName: MyCognitoUserPoolClient
      GenerateSecret: false

Will bring this up and discuss with the team.

GavinZZ commented 1 year ago

Introducing MergeDefinitions property in AWS::Serverless::Api. This property is default to False. Setting this property to True will allow the following:

Merge SAM generated swagger definition into inline swagger definition if a specific API+Path+Method is defined both in SAM API and API event source:
        - for a conflicting key, use SAM generated value
        - otherwise include key-value pairs from both definitions

https://github.com/aws/serverless-application-model/pull/2943 Changes are pending release. It should take around a week for the changes to roll out to production. Marking this issue closed now. Feel free to re-open it if you have any additional questions.