Yu Chen
Yu Chen

Reputation: 7490

Disabling security for one method resource endpoint in API Gateway via AWS SAM template

I'm using AWS Serverless to create an API Gateway backed with Lambda functions.

I have the following resources and methods defined:

/projects
   -> GET (should require API key)
   -> OPTIONS (should not, since it is used for CORS preflight)

I'm having issues with CORS and requiring an API key. The frontend client code is getting a 403 Forbidden error when it initiates the preflight CORS OPTIONS request, since the API Key Required in the AWS Management console is set to True for the OPTIONS method.

I want to disable security specifically for the OPTIONS request, but keep it for all other methods (GET, POST, etc.). Here are my resource definitions (you can see I have set a default ApiKeyRequired: true in my Auth object:

  MyApi:
    Type: 'AWS::Serverless::Api'
    Name: MyApi
    Properties:
      Auth:
        AddDefaultAuthorizerToCorsPreflight: true
        ApiKeyRequired: true # sets for all methods
      Cors:
        AllowCredentials: true
        AllowHeaders: '"Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token"'
        AllowMethods: '"POST,GET,OPTION"'
        AllowOrigin: '"*"'
        MaxAge: '"600"'
      StageName: !Ref StageName
      DefinitionBody:
        swagger: 2.0
        info:
          title: !Sub API-Lambda-${StageName}
          description: "API for MyApi"
          version: "1.0.0"
        paths:
          /projects:
            get:
              produces:
                - application/json
              responses:
                "200":
                  description: OK
              x-amazon-apigateway-any-method:
                produces:
                  - application/json
              x-amazon-apigateway-integration:
                httpMethod: post
                type: aws_proxy
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetAllProjectsFunction.Arn}/invocations
            options:
              consumes:
                - application/json
              produces:
                - application/json
              responses:
                '200':
                  description: 200 response
                  headers:
                    Access-Control-Allow-Origin:
                      type: string
                    Access-Control-Allow-Methods:
                      type: string
                    Access-Control-Allow-Headers:
                      type: string
              x-amazon-apigateway-integration:
                responses:
                  default:
                    statusCode: 200
                    responseParameters:
                      method.response.header.Access-Control-Allow-Methods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'"
                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,mode,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                passthroughBehavior: when_no_match
                requestTemplates:
                  application/json: "{\"statusCode\": 200}"
                type: mock
          /projects/{userId}:
            get:
              responses:
                "200":
                  description: OK
              x-amazon-apigateway-any-method:
                produces:
                  - application/json
              x-amazon-apigateway-integration:
                httpMethod: post
                type: aws_proxy
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetProjectsForUserFunction.Arn}/invocations
            options:
              consumes:
                - application/json
              responses:
                '200':
                  description: 200 response
                  headers:
                    Access-Control-Allow-Origin:
                      type: string
                    Access-Control-Allow-Methods:
                      type: string
                    Access-Control-Allow-Headers:
                      type: string
              x-amazon-apigateway-integration:
                responses:
                  default:
                    statusCode: 200
                    responseParameters:
                      method.response.header.Access-Control-Allow-Methods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'"
                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,mode,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                passthroughBehavior: when_no_match
                requestTemplates:
                  application/json: "{\"statusCode\": 200}"
                type: mock

I know that the Swagger documentation says I can override security by adding a security object for each resource method. This SO post also suggests I can disable security by making the security object an empty list.

However, I tried the following approaches:

        options:
          consumes:
            - application/json
          produces:
            - application/json
          security:
            -
          responses: ...

And also simply making security a None object:

        options:
          consumes:
            - application/json
          produces:
            - application/json
          security:
          responses: ...

In both cases, I get the following error when attempting to deploy with aws sam deploy:

Waiting for changeset to be created.. Error: Failed to create changeset for the stack: my-app, ex: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state Status: FAILED. Reason: Transform AWS::Serverless-2016-10-31 failed with: Internal transform failure.

Which seems to that my security definition is wrong. How do I disable security for one method of a resource (namely the OPTIONS method)?

UPDATE:

I got the template to deploy by using the following syntax:

    options:
      consumes:
        - application/json
      produces:
        - application/json
      security:
        - {}
      responses:

However, even after deploying, I still have this in my console:

enter image description here

I'm honestly at a loss right now because this is so easy to do with a regular AWS::ApiGateway::Method resource (just set ApiKeyRequired to true).

Upvotes: 7

Views: 4007

Answers (3)

chillbird
chillbird

Reputation: 1

AddDefaultAuthorizerToCorsPreflight: false does omit the default Authorizer, which can be one of CognitoAuthorizer | LambdaTokenAuthorizer | LambdaRequestAuthorizer, but unfortunately it does not omit the ApiKeyRequired. ApiKey would still be required on preflight requests. However, browsers do not attach X-API-Key Header to OPTIONS request.

Skipping ApiKey requirement manually for each preflight request seems to be the only option. Unfortunately I have no idea how to do that other than through the console manually after each deployment.

I have opened an issue on github here: https://github.com/aws/aws-sam-cli/issues/3735

Upvotes: 0

iaforek
iaforek

Reputation: 3108

You can simply set AddDefaultAuthorizerToCorsPreflight: false that will cause OPTIONS requests to be unsecured as you wished.

See this part of documentation:

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.

Ref: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-api-apiauth.html

Upvotes: 9

okigan
okigan

Reputation: 1629

Not great but I think you'll have to disable api_key on every OPTIONS method -- provide definition of the method with openapi and skip/omit 'security' key in it

Upvotes: 1

Related Questions