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

Function specific ApiKeyRequired Auth property does not override the global API default #1514

Open piyushjaware opened 4 years ago

piyushjaware commented 4 years ago

The documentation states that if you have specified ApiKeyRequired: true globally on the API and want to make a specific Function public, you can override it with the following in the Function's event source:

  ApiKeyRequired: false

However, this does not seem to function as stated.

Steps to reproduce the issue: Here's the template I used.

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-app

  Sample SAM Template for sam-app

Globals:
  Function:
    Timeout: 3
    Runtime: nodejs12.x

Resources:
  MyAPI:
    Type: AWS::Serverless::Api
    Properties:
      Name: MyAPI
      StageName: Default
      EndpointConfiguration: REGIONAL
      Auth:
        ApiKeyRequired: true
      DefinitionBody:
        openapi: 3.0.0
        x-amazon-apigateway-api-key-source: "HEADER"
        paths:
          /public:
            get:
              x-amazon-apigateway-integration:
                type: aws_proxy
                httpMethod: POST
                uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${PublicFunction.Arn}/invocations
          /private:
            get:
              x-amazon-apigateway-integration:
                type: aws_proxy
                httpMethod: POST
                uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${PrivateFunction.Arn}/invocations

  PublicFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: PublicFunction
      Role: !GetAtt LambdaRole.Arn
      Handler: src/public.handler
      Events:
        API:
          Type: Api
          Properties:
            Path: /public
            Method: ANY
            RestApiId:
              Ref: MyAPI
            Auth:
              ApiKeyRequired: false

  PrivateFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: PrivateFunction
      Role: !GetAtt LambdaRole.Arn
      Handler: src/private.handler
      Events:
        API:
          Type: Api
          Properties:
            Path: /private
            Method: ANY
            RestApiId:
              Ref: MyAPI

  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

  ApiKey:
    Type: AWS::ApiGateway::ApiKey
    Properties:
      Name: !Join [ "-", [ MyAPI, "ApiKey" ]]
      Description: "APIkey"
      Enabled: true
      GenerateDistinctId: false 

  ApiUsagePlan:
    Type: AWS::ApiGateway::UsagePlan
    DependsOn: MyAPIDefaultStage
    Properties:
      ApiStages:
        - ApiId: !Ref MyAPI
          Stage: Default
      Description: !Join [ "-", [ MyAPI, "UsagePlan" ] ]
      UsagePlanName: !Join [ "-", [ MyAPI, "UsagePlan" ] ]

  ApiUsagePlanKey:
    Type: AWS::ApiGateway::UsagePlanKey
    Properties:
      KeyId: !Ref ApiKey
      KeyType: API_KEY
      UsagePlanId: !Ref ApiUsagePlan

Outputs:

  APi:
    Description: "API Gateway endpoint URL"
    Value: !Sub "https://${MyAPI}.execute-api.${AWS::Region}.amazonaws.com/Default/public"

# Observed result:

API Key Still required

# Expected result: The /public route should have API Key Required = False.

gaurang171 commented 4 years ago

I am having the same issue. did you find a solution? the only other solution I am seeing is having auth at method level and security and use of securityDefinitions for definationBody instead of global Auth

piyushjaware commented 4 years ago

@gaurang171 I went with the swagger route for now. Note: Here's the detailed solution if you are interested.

qingchm commented 3 years ago

Hi, @piyushjaware I can confirm that I was able to reproduce this, after deployment the API gateway does show "API Key Required" for the public GET, will investigate on the cause. Thanks for reporting this!

qingchm commented 3 years ago

Hey @piyushjaware and @gaurang171 here's what is going on: SAM do support overriding Function specific ApiKeyRequired Auth property over the global API default, but this only happens when you do not have an explicit definition for the API's definition body. When you do not have a definition body provided, SAM will generate it for you, which will support the overriding that you want. But we do not touch the settings that customers define in their template's API definition so far. So a template like this actually achieves what you want:

Resources:
  MyAPI:
    Type: AWS::Serverless::Api
    Properties:
      Name: MyAPI
      StageName: Default
      EndpointConfiguration: REGIONAL
      Auth:
        ApiKeyRequired: true

  PublicFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: PublicFunction
      Role: !GetAtt LambdaRole.Arn
      InlineCode: "code"
      Handler: index.handler
      Events:
        API:
          Type: Api
          Properties:
            Path: /public
            Method: get
            RestApiId:
              Ref: MyAPI
            Auth:
              ApiKeyRequired: false

  PrivateFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: PrivateFunction
      Role: !GetAtt LambdaRole.Arn
      InlineCode: "code"
      Handler: index.handler
      Events:
        API:
          Type: Api
          Properties:
            Path: /private
            Method: get
            RestApiId:
              Ref: MyAPI

I have verified that deployments with this actually works with ApiGateway's ApiKeyRequired field correctly set up. Currently SAM does not support overriding if you have a definition body defined. Let me know if you want SAM to override over your own defined definition body, I will view this as a feature request. Right now the workaround would be to not input your definition body and let SAM do it for you. If this does not satisfy your need, I would recommend after the translation use the translated template to modify the swagger yourself (just change

                "security": [
                  {
                    "api_key": []
                  }
                ],  

to

                "security": [],

I hope this helps!

mrsan22 commented 2 years ago

@qingchm Your answer helped me in the scenario where I wanted API Key required for HTTP (GET, POST) methods except OPTIONS (for CORS support.)

bogartlisa commented 1 year ago

I have been struggling with this issue for a couple days now. It seems that no matter how I try to set the api key config - either in the definition body or via the sam template, I cannot get the appropriate settings to deploy. I have tried removing the API Key config from my template and only setting in the definition doc:

x-amazon-apigateway-api-key-source: "HEADER" paths: : security:

securitySchemes: api_key: type: apiKey name: x-api-key in: header x-amazon-apigateway-api-key-source : HEADER

I have tried only setting the API key via the template: MyApi: Name: MyApi Type: AWS::Serverless::Api Properties: : Auth: ApiKeyRequired: false # turns off API Key for all methods Authorizers: : ResourcePolicy: :

MyFunction: Type: AWS::Serverless::Function Properties: CodeUri: lambdas/src/handlers/junk/ Handler: handler.lambdaHandler Events: MyFunctionEvent: Type: Api Properties: Path: /mypath Method: put RestApiId: !Ref MyApi Auth: ApiKeyRequired: true # overrides and turns on API Key for just this method

I have tried various combinations of the above and so far no magic. It seems whatever I set as the value for the API trumps any further configuration. I have also tried not setting the ApiKeyRequired at all for the API and that doesn't work either.

I can manually adjust the configuration in the console and get things working the way I want but I cannot for the life of me get the sam deploy to deploy that configuration. I have even tried creating the configuration that I want, exporting the definition and then deploying that. Still doesn't work. Really don't want to build 2 separate APIs just because I can't get the deployment to work as intended.

What am I doing wrong??