0
votes

I have built a .NET serverless application and have the following serverless.template

{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Transform": "AWS::Serverless-2016-10-31",
    "Description": "API Gateway to access InSite data-store.",
    "Resources": {
        "Get": {
            "Type": "AWS::Serverless::Function",
            "Properties": {
                "VpcConfig": {
                    "SecurityGroupIds": [
                        "sg-111a1476"
                    ],
                    "SubnetIds": [
                        "subnet-3029a769",
                        "subnet-5ec0b928"
                    ]
                },
                "Handler": "AWSServerlessInSiteDataGw::AWSServerlessInSiteDataGw.Functions::Get",
                "Runtime": "dotnetcore2.1",
                "CodeUri": "",
                "MemorySize": 256,
                "Timeout": 30,
                "Role": null,
                "Policies": [
                    "AWSLambdaBasicExecutionRole",
                    "AWSLambdaVPCAccessExecutionRole",
                    "AmazonSSMFullAccess"
                ],
                "Environment": {
                    "Variables":{
                        "LOG_LEVEL":"2",
                        "CONNECTION_STRING":"insite-api-connectionstring"
                    }
                },
                "Events": {
                    "PutResource": {
                        "Type": "Api",
                        "Properties": {
                            "Path": "/",
                            "Method": "GET"
                        }
                    }
                }
            }
        },
        "GetTableBasic": {
            "Type": "AWS::Serverless::Function",
            "Properties": {
                "VpcConfig": {
                    "SecurityGroupIds": [
                        "sg-111a1476"
                    ],
                    "SubnetIds": [
                        "subnet-3029a769",
                        "subnet-5ec0b928"
                    ]
                },
                "Handler": "AWSServerlessInSiteDataGw::AWSServerlessInSiteDataGw.Functions::GetTableBasic",
                "Runtime": "dotnetcore2.1",
                "CodeUri": "",
                "MemorySize": 256,
                "Timeout": 30,
                "Role": null,
                "Policies": [
                    "AWSLambdaBasicExecutionRole",
                    "AWSLambdaVPCAccessExecutionRole",
                    "AmazonSSMFullAccess"
                ],
                "Environment": {
                    "Variables":{
                        "LOG_LEVEL":"2",
                        "CONNECTION_STRING":"insite-api-connectionstring"
                    }
                },
                "Events": {
                    "PutResource": {
                        "Type": "Api",
                        "Properties": {
                            "Path": "/tables/{tableid}/{columnid}",
                            "Method": "GET"
                        }
                    }
                }
            }
        },
        "PostClickCollectNotification": {
            "Type": "AWS::Serverless::Function",
            "Properties": {
                "VpcConfig": {
                    "SecurityGroupIds": [
                        "sg-111a1476"
                    ],
                    "SubnetIds": [
                        "subnet-3029a769",
                        "subnet-5ec0b928"
                    ]
                },
                "Handler": "AWSServerlessInSiteDataGw::AWSServerlessInSiteDataGw.Functions::PostClickCollectNotification",
                "Runtime": "dotnetcore2.1",
                "CodeUri": "",
                "MemorySize": 256,
                "Timeout": 30,
                "Role": null,
                "Policies": [
                    "AWSLambdaBasicExecutionRole",
                    "AWSLambdaVPCAccessExecutionRole",
                    "AmazonSSMFullAccess"
                ],
                "Environment": {
                    "Variables":{
                        "LOG_LEVEL":"2",
                        "CONNECTION_STRING":"insite-api-connectionstring"
                    }
                },
                "Events": {
                    "PutResource": {
                        "Type": "Api",
                        "Properties": {
                            "Path": "/order/notify",
                            "Method": "POST"
                        }
                    }
                }
            }
        },
        "PostClickCollectStockUpdate": {
            "Type": "AWS::Serverless::Function",
            "Properties": {
                "VpcConfig": {
                    "SecurityGroupIds": [
                        "sg-111a1476"
                    ],
                    "SubnetIds": [
                        "subnet-3029a769",
                        "subnet-5ec0b928"
                    ]
                },
                "Handler": "AWSServerlessInSiteDataGw::AWSServerlessInSiteDataGw.Functions::PostClickCollectStockUpdate",
                "Runtime": "dotnetcore2.1",
                "CodeUri": "",
                "MemorySize": 256,
                "Timeout": 30,
                "Role": null,
                "Policies": [
                    "AWSLambdaBasicExecutionRole",
                    "AWSLambdaVPCAccessExecutionRole",
                    "AmazonSSMFullAccess"
                ],
                "Environment": {
                    "Variables":{
                        "LOG_LEVEL":"2",
                        "TARGET_TABLE":"InSiteClickCollect",
                        "CONNECTION_STRING":"insite-api-connectionstring"
                    }
                },
                "Events": {
                    "PutResource": {
                        "Type": "Api",
                        "Properties": {
                            "Path": "/order/stock/update/",
                            "Method": "POST"
                        }
                    }
                }
            }
        },
        "GetTableResponse": {
            "Type": "AWS::Serverless::Function",
            "Properties": {
                "VpcConfig": {
                    "SecurityGroupIds": [
                        "sg-111a1476"
                    ],
                    "SubnetIds": [
                        "subnet-3029a769",
                        "subnet-5ec0b928"
                    ]
                },
                "Handler": "AWSServerlessInSiteDataGw::AWSServerlessInSiteDataGw.Functions::GetTableResponse",
                "Runtime": "dotnetcore2.1",
                "CodeUri": "",
                "MemorySize": 256,
                "Timeout": 30,
                "Role": null,
                "Policies": [
                    "AWSLambdaBasicExecutionRole",
                    "AWSLambdaVPCAccessExecutionRole",
                    "AmazonSSMFullAccess"
                ],
                "Environment": {
                    "Variables":{
                        "LOG_LEVEL":"2",
                        "CONNECTION_STRING":"insite-api-connectionstring"
                    }
                },
                "Events": {
                    "PutResource": {
                        "Type": "Api",
                        "Properties": {
                            "Path": "tables/query/{tableid}",
                            "Method": "GET"
                        }
                    }
                }
            }
        },
        "ServerlessRestApi": {
            "Type": "AWS::ApiGateway::RestApi",
            "Properties": {
                "Description":"InSite Web API Version 2.0.0.0",
                "Body": {
                    "swagger": "2.0",
                    "info": {
                        "version": "1.0",
                        "title": {
                            "Ref": "AWS::StackName"
                        }
                    },
                    "x-amazon-apigateway-api-key-source" : "HEADER",
                    "schemes":["https"],
                    "paths": {
                        "tables/query/{tableid}": {
                            "get": {
                                "x-amazon-apigateway-integration": {
                                    "httpMethod": "GET",
                                    "type": "aws_proxy",
                                    "uri": {
                                        "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetTableResponse.Arn}/invocations"
                                    }
                                },
                                "responses": {},
                                    "security": [
                                    {
                                        "api_key": []
                                    }
                                ]
                            }
                        },
                        "/products/update/": {
                            "post": {
                                "x-amazon-apigateway-integration": {
                                    "httpMethod": "POST",
                                    "type": "aws_proxy",
                                    "uri": {
                                        "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${PostClickCollectStockUpdate.Arn}/invocations"
                                    }
                                },
                                "responses": {}
                            }
                        },
                        "/": {
                            "get": {
                                "x-amazon-apigateway-integration": {
                                    "httpMethod": "GET",
                                    "type": "aws_proxy",
                                    "uri": {
                                        "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Get.Arn}/invocations"
                                    }
                                },
                                "responses": {}
                            }
                        },
                        "/tables/{tableid}/{columnid}": {
                            "get": {
                                "x-amazon-apigateway-integration": {
                                    "httpMethod": "GET",
                                    "type": "aws_proxy",
                                    "uri": {
                                        "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetTableBasic.Arn}/invocations"
                                    }
                                },
                                "responses": {}
                            }
                        }
                    },
                    "securityDefinitions": {
                        "api_key": {
                            "type": "apiKey",
                            "name": "x-api-key",
                            "in": "header"
                        }
                    }
                }
            }
        },
        "InternalUsagePlan": {
            "Type": "AWS::ApiGateway::UsagePlan",
            "Properties": {
                "ApiStages": [
                    {
                        "ApiId": {
                            "Ref": "ServerlessRestApi"
                        },
                        "Stage": {
                            "Ref": "ServerlessRestApiProdStage"
                        }
                    }
                ],
                "Description": "Internal Apps Usage Plan",
                "UsagePlanName": "Insite-datagw-InternalAppPlan-new"
            }
        },
        "ExternalUsagePlan": {
            "Type": "AWS::ApiGateway::UsagePlan",
            "Properties": {
                "ApiStages": [
                    {
                        "ApiId": {
                            "Ref": "ServerlessRestApi"
                        },
                        "Stage": {
                            "Ref": "ServerlessRestApiProdStage"
                        }
                    }
                ],
                "Description": "External Apps Usage Plan",
                "UsagePlanName": "InSite-datagw-ExternalAppPlan-new"
            }
        },
        "KeyHeartInHand": {
            "Type": "AWS::ApiGateway::ApiKey",
            "DependsOn": [
                "ServerlessRestApi",
                "ServerlessRestApiProdStage"
            ],
            "Properties": {
                "Name": "apikeyHeartInHand-new",
                "Description": "Api Key for Heart In Hand",
                "Enabled": true,
                "GenerateDistinctId": true,
                "StageKeys": [
                    {
                        "RestApiId": {
                            "Ref": "ServerlessRestApi"
                        },
                        "StageName": {
                            "Ref": "ServerlessRestApiProdStage"
                        }
                    }
                ]
            }
        },
        "LinkHeartInHandKey": { 
            "Type": "AWS::ApiGateway::UsagePlanKey",
            "Properties": {
                "KeyId": {
                    "Ref": "KeyHeartInHand"
                },
                "KeyType": "API_KEY",
                "UsagePlanId": {
                    "Ref": "InternalUsagePlan"
                }
            }
        },
        "KeyUmbraco": {
            "Type": "AWS::ApiGateway::ApiKey",
            "DependsOn": [
                "ServerlessRestApi",
                "ServerlessRestApiProdStage"
            ],
            "Properties": {
                "Name": "KeyUmbraco-new",
                "Description": "Api Key for Umbraco Website",
                "Enabled": true,
                "GenerateDistinctId": true,
                "StageKeys": [
                    {
                        "RestApiId": {
                            "Ref": "ServerlessRestApi"
                        },
                        "StageName": {
                            "Ref": "ServerlessRestApiProdStage"
                        }
                    }
                ]
            }
        },
        "LinkPricelineSiteKey": {
            "Type": "AWS::ApiGateway::UsagePlanKey",
            "Properties": {
                "KeyId": {
                    "Ref": "KeyUmbraco"
                },
                "KeyType": "API_KEY",
                "UsagePlanId": {
                    "Ref": "InternalUsagePlan"
                }
            }
        },
        "KeyPenTest": {
            "Type": "AWS::ApiGateway::ApiKey",
            "DependsOn": [
                "ServerlessRestApi",
                "ServerlessRestApiProdStage"
            ],
            "Properties": {
                "Name": "apiKeyPenTesting-new",
                "Description": "Api Key for pen testing",
                "Enabled": true,
                "GenerateDistinctId": true,
                "StageKeys": [
                    {
                        "RestApiId": {
                            "Ref": "ServerlessRestApi"
                        },
                        "StageName": {
                            "Ref": "ServerlessRestApiProdStage"
                        }
                    }
                ]
            }
        },
        "LinkPenTestKey": {
            "Type": "AWS::ApiGateway::UsagePlanKey",
            "Properties": {
                "KeyId": {
                    "Ref": "KeyPenTest"
                },
                "KeyType": "API_KEY",
                "UsagePlanId": {
                    "Ref": "InternalUsagePlan"
                }
            }
        }
    },
    "Outputs": {
        "ApiURL": {
            "Description": "API endpoint URL for Prod environment",
            "Value": {
                "Fn::Sub": "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
            }
        }
    }
}

The application deploys OK via AWS Toolkit for Visual Studio, but the processed cloudformation appears to be missing most if not all of the updates I've made to the AWS::ApiGateway::RestApi section. The processed CF template looks as the following when I check in the AWS console:

   "ServerlessRestApi": {
      "Type": "AWS::ApiGateway::RestApi",
      "Properties": {
        "Body": {
          "info": {
            "version": "1.0",
            "title": {
              "Ref": "AWS::StackName"
            }
          },
          "paths": {
            "/order/stock/update/": {
              "post": {
                "x-amazon-apigateway-integration": {
                  "httpMethod": "POST",
                  "type": "aws_proxy",
                  "uri": {
                    "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${PostClickCollectStockUpdate.Arn}/invocations"
                  }
                },
                "responses": {}
              }
            },
            "/order/notify": {
              "post": {
                "x-amazon-apigateway-integration": {
                  "httpMethod": "POST",
                  "type": "aws_proxy",
                  "uri": {
                    "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${PostClickCollectNotification.Arn}/invocations"
                  }
                },
                "responses": {}
              }
            },
            "/tables/{tableid}/{columnid}": {
              "get": {
                "x-amazon-apigateway-integration": {
                  "httpMethod": "POST",
                  "type": "aws_proxy",
                  "uri": {
                    "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetTableBasic.Arn}/invocations"
                  }
                },
                "responses": {}
              }
            },
            "tables/query/{tableid}": {
              "get": {
                "x-amazon-apigateway-integration": {
                  "httpMethod": "POST",
                  "type": "aws_proxy",
                  "uri": {
                    "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetTableResponse.Arn}/invocations"
                  }
                },
                "responses": {}
              }
            },
            "/": {
              "get": {
                "x-amazon-apigateway-integration": {
                  "httpMethod": "POST",
                  "type": "aws_proxy",
                  "uri": {
                    "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Get.Arn}/invocations"
                  }
                },
                "responses": {}
              }
            }
          },
          "swagger": "2.0"
        }
      }
    },

As a result of the above issue, the apiKey required = true is not set in my API Gateway. Everything else creates fine, the api keys are created, usage plans created, the link between the key and plan is created, however the api key being set to required = true does not happen. I'm at a loss here. I have tried deploying the stack with a new name to essentially create a new API, but the same thing occurs.

2

2 Answers

2
votes

It seems there is a few issues with your template.

Firstly, you are using AWS::ApiGateway::RestApi resource to define your API instead of the AWS SAM AWS::Serverless::Api resource. I'd recommend changing to use AWS SAM resources as much as possible unless there is a specific reason not to use them.

Secondly, you do not seem to be using the available Auth properties on the Serverless functions. You should be able to get the functionality you desire by adding "Auth": { "ApiKeyRequired": true } to your functions (docs).

Lastly, I see reference to a ServerlessRestApiProdStage resource but no definition for it, this may be due to your use of AWS::ApiGateway::RestApi and mixing it with AWS SAM. If you change to use AWS::Serverless::Api you can access the autogenerated stage by using { "Fn::GetAtt" : [ "ServerlessRestApi", "Stage" ] }

Hope this helps!

0
votes

It seems like this is a limitation of the SAM framework at this time. According to this, using a required API key is at the moment only possible by using an external swagger file, which in turn limits the use of the AWS::Serverless::Function event configuration.

According to this issue in the SAM repo there is now an RFC in order to prioritize adding support for API keys to SAM.