aws / chalice

Python Serverless Microframework for AWS
Apache License 2.0
10.58k stars 1.01k forks source link

api keys missing usage plans? #420

Open mjdunn opened 7 years ago

mjdunn commented 7 years ago

I understand how to require an api key, but I don't see how to get the AWS::ApiGateway::ApiKey, AWS::ApiGateway::UsagePlan & AWS::ApiGateway::UsagePlanKey generated into the sam.json (I'm targeting a CloudFormation deployment). Isn't requiring an api key kind of pointless if one can't be configured? I realize I can use the awscli to create these resources post-CloudFormation, but that kind of defeats the purpose of using CloudFormation in the first place. Am I missing something?

achautha commented 7 years ago

Hi, @mjdunn/ @stealthycoin
I tried a quick POC on extending sam.json with AWS::ApiGateway::UsagePlan & AWS::ApiGateway::UsagePlanKey resources. However, when I deploy cf template, it gives circular dependency error. Do you have any working sample CF template ?

Generated sam.json

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Outputs": {
    "RestAPIId": {
      "Value": {
        "Ref": "RestAPI"
      }
    },
    "APIHandlerName": {
      "Value": {
        "Ref": "APIHandler"
      }
    },
    "APIHandlerArn": {
      "Value": {
        "Fn::GetAtt": [
          "APIHandler",
          "Arn"
        ]
      }
    },
    "EndpointURL": {
      "Value": {
        "Fn::Sub": "https://${RestAPI}.execute-api.${AWS::Region}.amazonaws.com/api/"
      }
    }
  },
  "Transform": "AWS::Serverless-2016-10-31",
  "Resources": {
    "RESTApiUsagePlanKey": {
      "Type": "AWS::ApiGateway::UsagePlanKey",
      "Properties": {
        "KeyType": "API_KEY",
        "KeyId": {
          "Ref": "RESTApiKey"
        },
        "UsagePlanId": {
          "Ref": "RESTApiUsagePlan"
        }
      }
    },
    "RestAPI": {
      "Type": "AWS::Serverless::Api",
      "Properties": {
        "DefinitionBody": {
          "info": {
            "version": "1.0",
            "title": "sdktest"
          },
          "paths": {
            "/": {
              "get": {
                "responses": {
                  "200": {
                    "description": "200 response",
                    "schema": {
                      "$ref": "#/definitions/Empty"
                    }
                  }
                },
                "security": [
                  {
                    "api_key": []
                  }
                ],
                "x-amazon-apigateway-integration": {
                  "contentHandling": "CONVERT_TO_TEXT",
                  "responses": {
                    "default": {
                      "statusCode": "200"
                    }
                  },
                  "uri": {
                    "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${APIHandler.Arn}/invocations"
                  },
                  "httpMethod": "POST",
                  "passthroughBehavior": "when_no_match",
                  "type": "aws_proxy"
                },
                "consumes": [
                  "application/json"
                ],
                "produces": [
                  "application/json"
                ]
              }
            }
          },
          "schemes": [
            "https"
          ],
          "securityDefinitions": {
            "api_key": {
              "type": "apiKey",
              "name": "x-api-key",
              "in": "header"
            }
          },
          "x-amazon-apigateway-binary-media-types": [
            "application/octet-stream",
            "application/x-tar",
            "application/zip",
            "audio/basic",
            "audio/ogg",
            "audio/mp4",
            "audio/mpeg",
            "audio/wav",
            "audio/webm",
            "image/png",
            "image/jpg",
            "image/gif",
            "video/ogg",
            "video/mpeg",
            "video/webm"
          ],
          "definitions": {
            "Empty": {
              "type": "object",
              "title": "Empty Schema"
            }
          },
          "swagger": "2.0"
        },
        "StageName": "api"
      }
    },
    "RESTApiUsagePlan": {
      "Type": "AWS::ApiGateway::UsagePlan",
      "Properties": {
        "Throttle": {
          "RateLimit": 100,
          "BurstLimit": 200
        },
        "ApiStages": [
          {
            "ApiId": {
              "Ref": "RestAPI"
            },
            "Stage": {
              "Ref": "api"
            }
          }
        ],
        "Description": "Customer ABCs usage plan",
        "Quota": {
          "Limit": 5000,
          "Period": "MONTH"
        }
      }
    },
    "APIHandler": {
      "Type": "AWS::Serverless::Function",
      "Properties": {
        "Policies": [
          {
            "Version": "2012-10-17",
            "Statement": [
              {
                "Action": [
                  "logs:CreateLogGroup",
                  "logs:CreateLogStream",
                  "logs:PutLogEvents"
                ],
                "Resource": "arn:aws:logs:*:*:*",
                "Effect": "Allow"
              }
            ]
          }
        ],
        "Tags": {
          "aws-chalice": "version=1.0.0:stage=dev:app=usage_ex"
        },
        "MemorySize": 128,
        "Handler": "app.app",
        "Timeout": 60,
        "CodeUri": "./deployment.zip",
        "Runtime": "python2.7",
        "Events": {
          "indexget6a99": {
            "Type": "Api",
            "Properties": {
              "Path": "/",
              "RestApiId": {
                "Ref": "RestAPI"
              },
              "Method": "get"
            }
          }
        }
      }
    },
    "RESTApiKey": {
      "Type": "AWS::ApiGateway::ApiKey",
      "Properties": {
        "Enabled": "true",
        "Description": "CloudFormation API Key V1",
        "Name": "NewRESTAPIKey"
      }
    }
  }
}

When I run cf deploy

 aws cloudformation deploy --template-file /tmp/packaged-app/packaged.yaml --stack-name mystack --capabilities CAPABILITY_IAM

Error message:

Waiting for changeset to be created..

Failed to create the changeset: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state Status: FAILED. Reason: Circular dependency between resources: [RESTApiUsagePlan, RESTApiUsagePlanKey]
thijsdev commented 5 years ago

Any news on this?

I was trying to merge generated sam with this json:

{
    "Resources": {
        "ApiKey": {
            "Type": "AWS::ApiGateway::ApiKey",
            "DependsOn": [
                "RestAPI"
            ],
            "Properties": {
                "Name": "trial-api-key",
                "Description": "CloudFormation API Key V1",
                "Enabled": "true",
                "StageKeys": [
                    {
                        "RestApiId": {
                            "Ref": "RestAPI"
                        },
                        "StageName": "api"
                    }
                ]
            }
        },
        "UsagePlan": {
            "DependsOn": [
                "RestAPI"
            ],
            "Type": "AWS::ApiGateway::UsagePlan",
            "Properties": {
                "ApiStages": [
                    {
                        "ApiId": {
                            "Ref": "RestAPI"
                        },
                        "Stage": "api"
                    }
                ],
                "Description": "Production"
            }
        },
        "UsagePlanKey": {
            "Type": "AWS::ApiGateway::UsagePlanKey",
            "Properties": {
                "KeyId": {
                    "Ref": "ApiKey"
                },
                "KeyType": "API_KEY",
                "UsagePlanId": {
                    "Ref": "UsagePlan"
                }
            }
        }
    }
}

using this little script:

import json

our_filepath = "api_usage_plan.json"
sam_filepath = "builds/sam.json"

print("about to merge {our_filepath} into {sam_filepath}")
with open(our_filepath, "r") as f:
    local_template = json.load(f)
    print("{our_filepath} loaded")

with open(sam_filepath, "r") as f:
    sam_template = json.load(f)
    print("{sam_filepath} loaded")

for key in local_template['Resources'].keys():
    print("adressing resource {key}")
    sam_template['Resources'][key] = local_template['Resources'][key]

with open(sam_filepath, "w") as f:
    json.dump(sam_template, f, indent=4, sort_keys=True)
print("{sam_filepath} has been replaced.")

But I keep running in dependency problems because the API Stage isn't explicitly specified. Any suggestions on how to go about this?

brno32 commented 2 years ago

Is this still in the works? I'm using the cdk integration and for a given endpoint, I'd like to require an API key