├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── ga ├── .gitignore ├── README.md ├── api_gateway_v1 │ ├── events │ │ └── auth.json │ ├── src │ │ ├── auth │ │ │ ├── app.py │ │ │ └── requirements.txt │ │ └── responder │ │ │ ├── app.py │ │ │ └── requirements.txt │ └── tf-resources │ │ ├── api.tf │ │ ├── functions.tf │ │ ├── main.tf │ │ ├── samconfig.yaml │ │ └── variables.tf ├── api_gateway_v2 │ ├── events │ │ └── auth.json │ ├── src │ │ ├── auth │ │ │ ├── app.py │ │ │ └── requirements.txt │ │ └── responder │ │ │ ├── app.py │ │ │ └── requirements.txt │ └── tf-resources │ │ ├── api.tf │ │ ├── functions.tf │ │ ├── main.tf │ │ ├── samconfig.yaml │ │ └── variables.tf └── api_gateway_v2_tf_cloud │ ├── api.tf │ ├── custom-plan.json │ ├── events │ └── auth.json │ ├── functions.tf │ ├── main.tf │ ├── samconfig.yaml │ └── variables.tf ├── serverless_tf_sample └── api-lambda-dynamodb-example │ ├── README.md │ ├── events │ ├── bad-review-score.json │ ├── lambda-input.json │ └── new-review.json │ ├── locals.json │ ├── main.tf │ ├── outputs.tf │ ├── src │ ├── index.py │ └── requirements.txt │ └── variables.tf └── zip_based_lambda_functions ├── api-lambda-dynamodb-example ├── PyBuild.ps1 ├── events │ ├── bad-review-score.json │ ├── lambda-input.json │ └── new-review.json ├── locals.json ├── main.tf ├── outputs.tf ├── py_build.sh ├── src │ ├── index.py │ └── requirements.txt └── variables.tf └── lambda-example ├── PyBuild.ps1 ├── events └── sample_event.json ├── main.tf ├── py_build.sh ├── src └── index.py └── variables.tf /.gitignore: -------------------------------------------------------------------------------- 1 | *.zip 2 | .aws-sam 3 | .terraform 4 | .aws-sam-iacs/ 5 | .DS_Store 6 | .terraform.lock.hcl 7 | terraform.tfstate 8 | terraform.tfstate.backup -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS SAM and Terraform 2 | 3 | This repository contains a sample book reviews serverless application written in Terraform. Full instructions on how to use this repository are contained in blog post [Better Together: AWS SAM CLI and Hashicorp Terraform](https://aws.amazon.com/blogs/compute/better-together-aws-sam-cli-and-hashicorp-terraform/) 4 | 5 | # SAM support for Terraform GA examples 6 | 7 | The GA folder of the repository contains the demo applications for the GA blog. See the [README.md](./ga/README.md) for more. -------------------------------------------------------------------------------- /ga/.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Crash log files 9 | crash.log 10 | crash.*.log 11 | 12 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as 13 | # password, private keys, and other secrets. These should not be part of version 14 | # control as they are data points which are potentially sensitive and subject 15 | # to change depending on the environment. 16 | *.tfvars 17 | *.tfvars.json 18 | 19 | # Ignore override files as they are usually used to override resources locally and so 20 | # are not checked in 21 | override.tf 22 | override.tf.json 23 | *_override.tf 24 | *_override.tf.json 25 | 26 | # Include override files you do wish to add to version control using negated pattern 27 | # !example_override.tf 28 | 29 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 30 | # example: *tfplan* 31 | 32 | # Ignore CLI configuration files 33 | .terraformrc 34 | terraform.rc 35 | 36 | # VSCode 37 | .vscode 38 | 39 | # Serverless.tf 40 | builds 41 | .terraform 42 | .aws-sam 43 | .aws-sam-iacs 44 | .terraform.lock.hcl -------------------------------------------------------------------------------- /ga/README.md: -------------------------------------------------------------------------------- 1 | # Terraform and SAM example applications 2 | 3 | This repository contains sample applications showing how to use the AWS SAM CLI with Hashicorp's Terraform. 4 | 5 | ## Requirements 6 | * [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-getting-started.html]) (Be sure to follow the instructions for the SAM CLI requirements) 7 | * [Docker](docker.com) or Docker equivelant 8 | * [Terraform](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) by HashiCorp 9 | 10 | ## Local testing 11 | The following instructions work for the Amazon API Gateway REST (v1) and HTTP (v2) APIs. 12 | 13 | Choose the api you would like to test and change to that directory: 14 | * REST (v1) 15 | ``` 16 | cd api_gateway_v1/tf-resources 17 | ``` 18 | * HTTP (v2) 19 | ``` 20 | cd api_gateway_v2/tf-resources 21 | ``` 22 | ### Preparing the application 23 | To ensure all Terraform dependencies are installed run the following: 24 | ``` 25 | terraform init 26 | ``` 27 | 28 | ### Function invocation 29 | Each example contains two Lambda functions. One function is a simple application responder that returns a simple `hello TF World` message, the other is an authorizer Lambda function used by API Gateway to authorize a user. 30 | 31 | 1. Test the responder function: 32 | ``` 33 | sam local invoke 'module.lambda_function_responder.aws_lambda_function.this[0]' 34 | ``` 35 | 2. Test the authorizer function 36 | ``` 37 | sam local invoke 'module.lambda_function_auth.aws_lambda_function.this[0]' -e events/auth.json 38 | ``` 39 | ### Function invocation through *start-api* 40 | Each project has two endpoints. One is an open endpoint and the other is secured by a Lambda authorizer for API Gateway. To test the endpoints start the local API with the following command: 41 | ``` 42 | sam local start-api 43 | ``` 44 | 45 | **To test the open endpoint:** 46 | ``` 47 | curl --location 'http://localhost:3000/open' 48 | ``` 49 | **To test the secure endpoint as authorized:** 50 | ``` 51 | curl --location 'http://localhost:3000/secure' --header 'myheader: 123456789' 52 | ``` 53 | **To test the secure endpoint as unauthorized:** 54 | ``` 55 | curl --location 'http://localhost:3000/secure' --header 'myheader: 123' 56 | ``` 57 | ## Deploy the application 58 | 1. Create a file in the folder of the version you would like to deploy called *terraform.tfvars* and add the following items if needed. 59 | ``` 60 | aws_region = "us-west-2" 61 | ``` 62 | Optionally add the following if needed: 63 | ``` 64 | profile = "your AWS profile" (defaults to 'default') 65 | config_location = "location of your AWS config file" (defaults to '~/.aws/config') 66 | creds_location = "location of your AWS creds file" (defaults to '~/.aws/credentials') 67 | ``` 68 | 2. Run the following to see what it will do: 69 | ``` 70 | terraform plan 71 | ``` 72 | 3. Run the following to deploy: 73 | ``` 74 | terraform apply 75 | ``` 76 | 77 | ## Tear down 78 | To destroy the application run the following command: 79 | ``` 80 | terraform destroy 81 | ``` -------------------------------------------------------------------------------- /ga/api_gateway_v1/events/auth.json: -------------------------------------------------------------------------------- 1 | { 2 | "authorizationToken": "123456789", 3 | "methodArn": "arn:aws:execute-api:us-east-1:123456789012:1234567890/prod/GET/secure", 4 | "type": "TOKEN" 5 | } -------------------------------------------------------------------------------- /ga/api_gateway_v1/src/auth/app.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | 4 | def handler(event, context): 5 | principalId = "user|a1b2c3d4" 6 | arn = event.get("methodArn") or event.get("routeArn") 7 | tmp = arn.split(':') 8 | apiGatewayArnTmp = tmp[5].split('/') 9 | awsAccountId = tmp[4] 10 | 11 | policy = AuthPolicy(principalId, awsAccountId) 12 | policy.restApiId = apiGatewayArnTmp[0] 13 | policy.region = tmp[3] 14 | policy.stage = apiGatewayArnTmp[1] 15 | if event["authorizationToken"] == "123456789": 16 | policy.allowAllMethods() 17 | else: 18 | policy.denyAllMethods() 19 | # policy.allowMethod(HttpVerb.GET, "/hello/world/*") 20 | 21 | # Finally, build the policy 22 | authResponse = policy.build() 23 | 24 | # new! -- add additional key-value pairs associated with the authenticated principal 25 | # these are made available by APIGW like so: $context.authorizer. 26 | # additional context is cached 27 | context = { 28 | # "key": str(auth_source), # $context.authorizer.key -> value 29 | 'number' : 1, 30 | 'bool' : True, 31 | 'passed': 'from authorizer' 32 | } 33 | # context['arr'] = ['foo'] <- this is invalid, APIGW will not accept it 34 | # context['obj'] = {'foo':'bar'} <- also invalid 35 | 36 | authResponse['context'] = context 37 | return authResponse 38 | 39 | 40 | class HttpVerb: 41 | GET = "GET" 42 | POST = "POST" 43 | PUT = "PUT" 44 | PATCH = "PATCH" 45 | HEAD = "HEAD" 46 | DELETE = "DELETE" 47 | OPTIONS = "OPTIONS" 48 | ALL = "*" 49 | 50 | 51 | class AuthPolicy(object): 52 | awsAccountId = "" 53 | """The AWS account id the policy will be generated for. This is used to create the method ARNs.""" 54 | principalId = "" 55 | """The principal used for the policy, this should be a unique identifier for the end user.""" 56 | version = "2012-10-17" 57 | """The policy version used for the evaluation. This should always be '2012-10-17'""" 58 | pathRegex = "^[/.a-zA-Z0-9-\*]+$" 59 | """The regular expression used to validate resource paths for the policy""" 60 | 61 | """these are the internal lists of allowed and denied methods. These are lists 62 | of objects and each object has 2 properties: A resource ARN and a nullable 63 | conditions statement. 64 | the build method processes these lists and generates the approriate 65 | statements for the final policy""" 66 | allowMethods = [] 67 | denyMethods = [] 68 | 69 | 70 | restApiId = "<>" 71 | """ Replace the placeholder value with a default API Gateway API id to be used in the policy. 72 | Beware of using '*' since it will not simply mean any API Gateway API id, because stars will greedily expand over '/' or other separators. 73 | See https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_resource.html for more details. """ 74 | 75 | region = "<>" 76 | """ Replace the placeholder value with a default region to be used in the policy. 77 | Beware of using '*' since it will not simply mean any region, because stars will greedily expand over '/' or other separators. 78 | See https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_resource.html for more details. """ 79 | 80 | stage = "<>" 81 | """ Replace the placeholder value with a default stage to be used in the policy. 82 | Beware of using '*' since it will not simply mean any stage, because stars will greedily expand over '/' or other separators. 83 | See https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_resource.html for more details. """ 84 | 85 | def __init__(self, principal, awsAccountId): 86 | self.awsAccountId = awsAccountId 87 | self.principalId = principal 88 | self.allowMethods = [] 89 | self.denyMethods = [] 90 | 91 | def _addMethod(self, effect, verb, resource, conditions): 92 | """Adds a method to the internal lists of allowed or denied methods. Each object in 93 | the internal list contains a resource ARN and a condition statement. The condition 94 | statement can be null.""" 95 | if verb != "*" and not hasattr(HttpVerb, verb): 96 | raise NameError("Invalid HTTP verb " + verb + ". Allowed verbs in HttpVerb class") 97 | resourcePattern = re.compile(self.pathRegex) 98 | if not resourcePattern.match(resource): 99 | raise NameError("Invalid resource path: " + resource + ". Path should match " + self.pathRegex) 100 | 101 | if resource[:1] == "/": 102 | resource = resource[1:] 103 | 104 | resourceArn = ("arn:aws:execute-api:" + 105 | self.region + ":" + 106 | self.awsAccountId + ":" + 107 | self.restApiId + "/" + 108 | self.stage + "/" + 109 | verb + "/" + 110 | resource) 111 | 112 | if effect.lower() == "allow": 113 | self.allowMethods.append({ 114 | 'resourceArn' : resourceArn, 115 | 'conditions' : conditions 116 | }) 117 | elif effect.lower() == "deny": 118 | self.denyMethods.append({ 119 | 'resourceArn' : resourceArn, 120 | 'conditions' : conditions 121 | }) 122 | 123 | def _getEmptyStatement(self, effect): 124 | """Returns an empty statement object prepopulated with the correct action and the 125 | desired effect.""" 126 | statement = { 127 | 'Action': 'execute-api:Invoke', 128 | 'Effect': effect[:1].upper() + effect[1:].lower(), 129 | 'Resource': [] 130 | } 131 | 132 | return statement 133 | 134 | def _getStatementForEffect(self, effect, methods): 135 | """This function loops over an array of objects containing a resourceArn and 136 | conditions statement and generates the array of statements for the policy.""" 137 | statements = [] 138 | 139 | if len(methods) > 0: 140 | statement = self._getEmptyStatement(effect) 141 | 142 | for curMethod in methods: 143 | if curMethod['conditions'] is None or len(curMethod['conditions']) == 0: 144 | statement['Resource'].append(curMethod['resourceArn']) 145 | else: 146 | conditionalStatement = self._getEmptyStatement(effect) 147 | conditionalStatement['Resource'].append(curMethod['resourceArn']) 148 | conditionalStatement['Condition'] = curMethod['conditions'] 149 | statements.append(conditionalStatement) 150 | 151 | statements.append(statement) 152 | 153 | return statements 154 | 155 | def allowAllMethods(self): 156 | """Adds a '*' allow to the policy to authorize access to all methods of an API""" 157 | self._addMethod("Allow", HttpVerb.ALL, "*", []) 158 | 159 | def denyAllMethods(self): 160 | """Adds a '*' allow to the policy to deny access to all methods of an API""" 161 | self._addMethod("Deny", HttpVerb.ALL, "*", []) 162 | 163 | def allowMethod(self, verb, resource): 164 | """Adds an API Gateway method (Http verb + Resource path) to the list of allowed 165 | methods for the policy""" 166 | self._addMethod("Allow", verb, resource, []) 167 | 168 | def denyMethod(self, verb, resource): 169 | """Adds an API Gateway method (Http verb + Resource path) to the list of denied 170 | methods for the policy""" 171 | self._addMethod("Deny", verb, resource, []) 172 | 173 | def allowMethodWithConditions(self, verb, resource, conditions): 174 | """Adds an API Gateway method (Http verb + Resource path) to the list of allowed 175 | methods and includes a condition for the policy statement. More on AWS policy 176 | conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition""" 177 | self._addMethod("Allow", verb, resource, conditions) 178 | 179 | def denyMethodWithConditions(self, verb, resource, conditions): 180 | """Adds an API Gateway method (Http verb + Resource path) to the list of denied 181 | methods and includes a condition for the policy statement. More on AWS policy 182 | conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition""" 183 | self._addMethod("Deny", verb, resource, conditions) 184 | 185 | def build(self): 186 | """Generates the policy document based on the internal lists of allowed and denied 187 | conditions. This will generate a policy with two main statements for the effect: 188 | one statement for Allow and one statement for Deny. 189 | Methods that includes conditions will have their own statement in the policy.""" 190 | if ((self.allowMethods is None or len(self.allowMethods) == 0) and 191 | (self.denyMethods is None or len(self.denyMethods) == 0)): 192 | raise NameError("No statements defined for the policy") 193 | 194 | policy = { 195 | 'principalId' : self.principalId, 196 | 'policyDocument' : { 197 | 'Version' : self.version, 198 | 'Statement' : [] 199 | } 200 | } 201 | 202 | policy['policyDocument']['Statement'].extend(self._getStatementForEffect("Allow", self.allowMethods)) 203 | policy['policyDocument']['Statement'].extend(self._getStatementForEffect("Deny", self.denyMethods)) 204 | 205 | return policy -------------------------------------------------------------------------------- /ga/api_gateway_v1/src/auth/requirements.txt: -------------------------------------------------------------------------------- 1 | boto3 -------------------------------------------------------------------------------- /ga/api_gateway_v1/src/responder/app.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | 4 | def open_handler(event, context): 5 | 6 | try: 7 | ip = requests.get("http://checkip.amazonaws.com/") 8 | except requests.RequestException as e: 9 | # Send some context about this error to Lambda Logs 10 | print(e) 11 | 12 | raise e 13 | 14 | return { 15 | "statusCode": 200, 16 | "body": json.dumps({ 17 | "message": "Hello TF World", 18 | "location": ip.text.replace("\n", "") 19 | }), 20 | } 21 | -------------------------------------------------------------------------------- /ga/api_gateway_v1/src/responder/requirements.txt: -------------------------------------------------------------------------------- 1 | boto3 2 | requests -------------------------------------------------------------------------------- /ga/api_gateway_v1/tf-resources/api.tf: -------------------------------------------------------------------------------- 1 | resource "aws_api_gateway_rest_api" "api" { 2 | name = "Terraform REST Example" 3 | } 4 | 5 | resource "aws_api_gateway_deployment" "deployment" { 6 | rest_api_id = aws_api_gateway_rest_api.api.id 7 | triggers = { 8 | redeployment = sha1(jsonencode([ 9 | aws_api_gateway_resource.open_resource.id, 10 | aws_api_gateway_method.open_get_method.id, 11 | aws_api_gateway_integration.open_integration.id, 12 | aws_api_gateway_resource.secure_resource.id, 13 | aws_api_gateway_method.secure_get_method.id, 14 | aws_api_gateway_integration.secure_integration.id 15 | ])) 16 | } 17 | lifecycle { 18 | create_before_destroy = true 19 | } 20 | } 21 | 22 | resource "aws_cloudwatch_log_group" "logs" { 23 | name = "/aws/vendedlogs/tf_rest_logs" 24 | } 25 | 26 | resource "aws_api_gateway_stage" "stage" { 27 | deployment_id = aws_api_gateway_deployment.deployment.id 28 | rest_api_id = aws_api_gateway_rest_api.api.id 29 | stage_name = "prod" 30 | access_log_settings { 31 | destination_arn = aws_cloudwatch_log_group.logs.arn 32 | format = jsonencode({"requestId":"$context.requestId", "ip":"$context.identity.sourceIp", "requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","routeKey":"$context.routeKey", "status":"$context.status","protocol":"$context.protocol", "responseLength":"$context.responseLength", "integrationError":"$context.integrationErrorMessage" }) 33 | } 34 | } 35 | 36 | ####################################### 37 | ## Open endpoint ## 38 | ####################################### 39 | 40 | resource "aws_api_gateway_resource" "open_resource" { 41 | rest_api_id = aws_api_gateway_rest_api.api.id 42 | parent_id = aws_api_gateway_rest_api.api.root_resource_id 43 | path_part = "open" 44 | } 45 | 46 | resource "aws_api_gateway_method" "open_get_method" { 47 | rest_api_id = aws_api_gateway_rest_api.api.id 48 | resource_id = aws_api_gateway_resource.open_resource.id 49 | http_method = "GET" 50 | authorization = "NONE" 51 | } 52 | 53 | resource "aws_api_gateway_integration" "open_integration" { 54 | rest_api_id = aws_api_gateway_rest_api.api.id 55 | resource_id = aws_api_gateway_resource.open_resource.id 56 | http_method = aws_api_gateway_method.open_get_method.http_method 57 | integration_http_method = "POST" 58 | type = "AWS_PROXY" 59 | content_handling = "CONVERT_TO_TEXT" 60 | uri = module.lambda_function_responder.lambda_function_invoke_arn 61 | } 62 | 63 | ####################################### 64 | ## Secure endpoint ## 65 | ####################################### 66 | 67 | resource "aws_api_gateway_resource" "secure_resource" { 68 | rest_api_id = aws_api_gateway_rest_api.api.id 69 | parent_id = aws_api_gateway_rest_api.api.root_resource_id 70 | path_part = "secure" 71 | } 72 | 73 | resource "aws_api_gateway_method" "secure_get_method" { 74 | rest_api_id = aws_api_gateway_rest_api.api.id 75 | resource_id = aws_api_gateway_resource.secure_resource.id 76 | http_method = "GET" 77 | authorization = "CUSTOM" 78 | authorizer_id = aws_api_gateway_authorizer.token_authorizer.id 79 | } 80 | 81 | resource "aws_api_gateway_integration" "secure_integration" { 82 | rest_api_id = aws_api_gateway_rest_api.api.id 83 | resource_id = aws_api_gateway_resource.secure_resource.id 84 | http_method = aws_api_gateway_method.secure_get_method.http_method 85 | integration_http_method = "POST" 86 | type = "AWS_PROXY" 87 | content_handling = "CONVERT_TO_TEXT" 88 | uri = module.lambda_function_responder.lambda_function_invoke_arn 89 | } 90 | 91 | resource "aws_api_gateway_authorizer" "token_authorizer" { 92 | name = "token_authorizer" 93 | rest_api_id = aws_api_gateway_rest_api.api.id 94 | authorizer_uri = module.lambda_function_auth.lambda_function_invoke_arn 95 | authorizer_credentials = aws_iam_role.invocation_role.arn 96 | identity_source = "method.request.header.myheader" 97 | authorizer_result_ttl_in_seconds = 0 # for testing only, caching should be added for production 98 | identity_validation_expression = "^[0-9]+$" 99 | } 100 | 101 | data "aws_iam_policy_document" "invocation_assume_role" { 102 | statement { 103 | effect = "Allow" 104 | 105 | principals { 106 | type = "Service" 107 | identifiers = ["apigateway.amazonaws.com"] 108 | } 109 | 110 | actions = ["sts:AssumeRole"] 111 | } 112 | } 113 | 114 | resource "aws_iam_role" "invocation_role" { 115 | name = "api_gateway_auth_invocation" 116 | path = "/" 117 | assume_role_policy = data.aws_iam_policy_document.invocation_assume_role.json 118 | } 119 | 120 | data "aws_iam_policy_document" "invocation_policy" { 121 | statement { 122 | effect = "Allow" 123 | actions = ["lambda:InvokeFunction"] 124 | resources = [module.lambda_function_auth.lambda_function_arn] 125 | } 126 | } 127 | 128 | resource "aws_iam_role_policy" "invocation_policy" { 129 | name = "default" 130 | role = aws_iam_role.invocation_role.id 131 | policy = data.aws_iam_policy_document.invocation_policy.json 132 | } 133 | -------------------------------------------------------------------------------- /ga/api_gateway_v1/tf-resources/functions.tf: -------------------------------------------------------------------------------- 1 | module "lambda_function_responder" { 2 | source = "terraform-aws-modules/lambda/aws" 3 | version = "~> 6.0" 4 | timeout = 300 5 | source_path = "../src/responder/" 6 | function_name = "responder" 7 | handler = "app.open_handler" 8 | runtime = "python3.9" 9 | create_sam_metadata = true 10 | publish = true 11 | allowed_triggers = { 12 | APIGatewayAny = { 13 | service = "apigateway" 14 | source_arn = "${aws_api_gateway_rest_api.api.execution_arn}/*/*" 15 | } 16 | } 17 | } 18 | 19 | module "lambda_function_auth" { 20 | source = "terraform-aws-modules/lambda/aws" 21 | version = "~> 6.0" 22 | timeout = 300 23 | source_path = "../src/auth/" 24 | function_name = "authorizer" 25 | handler = "app.handler" 26 | runtime = "python3.9" 27 | create_sam_metadata = true 28 | } 29 | -------------------------------------------------------------------------------- /ga/api_gateway_v1/tf-resources/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~> 4.16" 6 | } 7 | } 8 | required_version = ">= 1.2.0" 9 | } 10 | 11 | provider "aws" { 12 | region = var.aws_region 13 | shared_config_files = [var.config_location] 14 | shared_credentials_files = [var.creds_location] 15 | profile = var.profile 16 | } 17 | 18 | ################################### 19 | ## Outputs ## 20 | ################################### 21 | 22 | output "api_endpoint" { 23 | description = "Base url for api" 24 | value = aws_api_gateway_stage.stage.invoke_url 25 | } 26 | 27 | output "http_api_logs_command" { 28 | description = "Command to view http api logs with sam" 29 | value = "sam logs --cw-log-group ${aws_cloudwatch_log_group.logs.name} -t" 30 | } 31 | 32 | output "authorizer_logs_command" { 33 | description = "Command to view authorizer function logs with sam" 34 | value = "sam logs --cw-log-group ${module.lambda_function_auth.lambda_cloudwatch_log_group_name} -t" 35 | } 36 | 37 | output "responder_logs_command" { 38 | description = "Command to view responder function logs with sam" 39 | value = "sam logs --cw-log-group ${module.lambda_function_responder.lambda_cloudwatch_log_group_name} -t" 40 | } 41 | 42 | output "all_logs" { 43 | description = "Command to view an aggragate of all logs with sam" 44 | value = "sam logs --cw-log-group ${aws_cloudwatch_log_group.logs.name} --cw-log-group ${module.lambda_function_auth.lambda_cloudwatch_log_group_name} --cw-log-group ${module.lambda_function_responder.lambda_cloudwatch_log_group_name} -t" 45 | } 46 | -------------------------------------------------------------------------------- /ga/api_gateway_v1/tf-resources/samconfig.yaml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | default: 3 | global: 4 | parameters: 5 | hook_name: terraform 6 | skip_prepare_infra: true 7 | build: 8 | parameters: 9 | terraform_project_root_path: ../ -------------------------------------------------------------------------------- /ga/api_gateway_v1/tf-resources/variables.tf: -------------------------------------------------------------------------------- 1 | variable "aws_region" { 2 | description = "AWS region" 3 | type = string 4 | } 5 | 6 | variable "profile" { 7 | description = "AWS profile" 8 | type = string 9 | default = "default" 10 | } 11 | 12 | variable "config_location" { 13 | description = "AWS configuration file" 14 | type = string 15 | default = "~/.aws/config" 16 | } 17 | 18 | variable "creds_location" { 19 | description = "AWS credentials file" 20 | type = string 21 | default = "~/.aws/credentials" 22 | } -------------------------------------------------------------------------------- /ga/api_gateway_v2/events/auth.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": "", 3 | "cookies": [], 4 | "headers": { 5 | "Accept": "*/*", 6 | "Accept-Encoding": "gzip, deflate, br", 7 | "Connection": "keep-alive", 8 | "Host": "localhost:3000", 9 | "Myheader": "123456789", 10 | "Postman-Token": "6108430a-79dd-4dc4-bbd8-82214d4cca37", 11 | "User-Agent": "PostmanRuntime/7.32.3", 12 | "X-Forwarded-Port": "3000", 13 | "X-Forwarded-Proto": "http" 14 | }, 15 | "identitySource": [ 16 | "123456789" 17 | ], 18 | "isBase64Encoded": false, 19 | "pathParameters": {}, 20 | "rawPath": "/secure", 21 | "rawQueryString": "", 22 | "requestContext": { 23 | "accountId": "123456789012", 24 | "apiId": "1234567890", 25 | "domainName": "localhost", 26 | "domainPrefix": "localhost", 27 | "http": { 28 | "method": "GET", 29 | "path": "/secure", 30 | "protocol": "HTTP/1.1", 31 | "sourceIp": "127.0.0.1", 32 | "userAgent": "Custom User Agent String" 33 | }, 34 | "requestId": "0e148f13-ec52-465f-af27-f80cf5be2153", 35 | "routeKey": "GET /secure", 36 | "stage": "$default", 37 | "time": "16/Aug/2023:05:16:17 +0000", 38 | "timeEpoch": 1692162977 39 | }, 40 | "routeArn": "arn:aws:execute-api:us-east-1:123456789012:1234567890/$default/GET/secure", 41 | "routeKey": "GET /secure", 42 | "stageVariables": "None", 43 | "type": "REQUEST", 44 | "version": "2.0" 45 | } -------------------------------------------------------------------------------- /ga/api_gateway_v2/src/auth/app.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | def handler(event, context): 4 | response = {"isAuthorized": False} 5 | 6 | if event["headers"]["Myheader"] == "123456789": 7 | response["isAuthorized"] = True 8 | 9 | return response -------------------------------------------------------------------------------- /ga/api_gateway_v2/src/auth/requirements.txt: -------------------------------------------------------------------------------- 1 | boto3 -------------------------------------------------------------------------------- /ga/api_gateway_v2/src/responder/app.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | 4 | def open_handler(event, context): 5 | 6 | try: 7 | ip = requests.get("http://checkip.amazonaws.com/") 8 | except requests.RequestException as e: 9 | # Send some context about this error to Lambda Logs 10 | print(e) 11 | 12 | raise e 13 | 14 | return json.dumps({ 15 | "message": "Hello TF World", 16 | "location": ip.text.replace("\n", "") 17 | }) -------------------------------------------------------------------------------- /ga/api_gateway_v2/src/responder/requirements.txt: -------------------------------------------------------------------------------- 1 | boto3 2 | requests -------------------------------------------------------------------------------- /ga/api_gateway_v2/tf-resources/api.tf: -------------------------------------------------------------------------------- 1 | resource "aws_apigatewayv2_api" "api" { 2 | name = "Terraform HTTP API Example" 3 | protocol_type = "HTTP" 4 | } 5 | 6 | resource "aws_cloudwatch_log_group" "logs" { 7 | name = "/aws/vendedlogs/tf_http_logs" 8 | } 9 | 10 | resource "aws_apigatewayv2_stage" "stage" { 11 | api_id = aws_apigatewayv2_api.api.id 12 | auto_deploy = true 13 | name = "$default" 14 | access_log_settings { 15 | destination_arn = aws_cloudwatch_log_group.logs.arn 16 | format = jsonencode({"requestId":"$context.requestId", "ip":"$context.identity.sourceIp", "requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","routeKey":"$context.routeKey", "status":"$context.status","protocol":"$context.protocol", "responseLength":"$context.responseLength", "integrationError":"$context.integrationErrorMessage" }) 17 | } 18 | } 19 | 20 | # ####################################### 21 | # ## Open endpoint ## 22 | # ####################################### 23 | 24 | resource "aws_apigatewayv2_integration" "open_integration" { 25 | api_id = aws_apigatewayv2_api.api.id 26 | integration_type = "AWS_PROXY" 27 | integration_method = "POST" 28 | integration_uri = module.lambda_function_responder.lambda_function_invoke_arn 29 | payload_format_version = "2.0" 30 | } 31 | 32 | resource "aws_apigatewayv2_route" "get_open" { 33 | api_id = aws_apigatewayv2_api.api.id 34 | target = "integrations/${aws_apigatewayv2_integration.open_integration.id}" 35 | route_key = "GET /open" 36 | operation_name = "get_open_operation" 37 | authorization_type = "NONE" 38 | } 39 | 40 | # ####################################### 41 | # ## Secure endpoint ## 42 | # ####################################### 43 | 44 | resource "aws_apigatewayv2_integration" "secure_integration" { 45 | api_id = aws_apigatewayv2_api.api.id 46 | integration_type = "AWS_PROXY" 47 | integration_method = "POST" 48 | integration_uri = module.lambda_function_responder.lambda_function_invoke_arn 49 | payload_format_version = "2.0" 50 | } 51 | 52 | resource "aws_apigatewayv2_route" "get_secure" { 53 | api_id = aws_apigatewayv2_api.api.id 54 | target = "integrations/${aws_apigatewayv2_integration.secure_integration.id}" 55 | route_key = "GET /secure" 56 | operation_name = "get_secure_operation" 57 | authorization_type = "CUSTOM" 58 | authorizer_id = aws_apigatewayv2_authorizer.request_authorizer.id 59 | } 60 | 61 | resource "aws_apigatewayv2_authorizer" "request_authorizer" { 62 | api_id = aws_apigatewayv2_api.api.id 63 | authorizer_type = "REQUEST" 64 | authorizer_uri = module.lambda_function_auth.lambda_function_invoke_arn 65 | authorizer_credentials_arn = aws_iam_role.invocation_role.arn 66 | authorizer_payload_format_version = "2.0" 67 | identity_sources = ["$request.header.myheader"] 68 | name = "header_authorizer" 69 | enable_simple_responses = true 70 | authorizer_result_ttl_in_seconds = 0 # for testing only, caching should be added for production 71 | } 72 | 73 | data "aws_iam_policy_document" "invocation_assume_role" { 74 | statement { 75 | effect = "Allow" 76 | 77 | principals { 78 | type = "Service" 79 | identifiers = ["apigateway.amazonaws.com"] 80 | } 81 | 82 | actions = ["sts:AssumeRole"] 83 | } 84 | } 85 | 86 | resource "aws_iam_role" "invocation_role" { 87 | name = "api_gateway_auth_http_invocation" 88 | path = "/" 89 | assume_role_policy = data.aws_iam_policy_document.invocation_assume_role.json 90 | } 91 | 92 | data "aws_iam_policy_document" "invocation_policy" { 93 | statement { 94 | effect = "Allow" 95 | actions = ["lambda:InvokeFunction"] 96 | resources = [module.lambda_function_auth.lambda_function_arn] 97 | } 98 | } 99 | 100 | resource "aws_iam_role_policy" "invocation_policy" { 101 | name = "default" 102 | role = aws_iam_role.invocation_role.id 103 | policy = data.aws_iam_policy_document.invocation_policy.json 104 | } 105 | -------------------------------------------------------------------------------- /ga/api_gateway_v2/tf-resources/functions.tf: -------------------------------------------------------------------------------- 1 | module "lambda_function_responder" { 2 | source = "terraform-aws-modules/lambda/aws" 3 | version = "~> 6.0" 4 | timeout = 300 5 | source_path = "../src/responder/" 6 | function_name = "http_responder" 7 | handler = "app.open_handler" 8 | runtime = "python3.9" 9 | create_sam_metadata = true 10 | publish = true 11 | allowed_triggers = { 12 | APIGatewayAny = { 13 | service = "apigateway" 14 | source_arn = "${aws_apigatewayv2_api.api.execution_arn}/*/*" 15 | } 16 | } 17 | } 18 | 19 | module "lambda_function_auth" { 20 | source = "terraform-aws-modules/lambda/aws" 21 | version = "~> 6.0" 22 | timeout = 300 23 | source_path = "../src/auth/" 24 | function_name = "http_authorizer" 25 | handler = "app.handler" 26 | runtime = "python3.9" 27 | create_sam_metadata = true 28 | } 29 | -------------------------------------------------------------------------------- /ga/api_gateway_v2/tf-resources/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~> 4.16" 6 | } 7 | } 8 | required_version = ">= 1.2.0" 9 | } 10 | 11 | provider "aws" { 12 | region = var.aws_region 13 | shared_config_files = [var.config_location] 14 | shared_credentials_files = [var.creds_location] 15 | profile = var.profile 16 | } 17 | 18 | ################################### 19 | ## Outputs ## 20 | ################################### 21 | 22 | output "api_endpoint" { 23 | description = "Base url for api" 24 | value = aws_apigatewayv2_api.api.api_endpoint 25 | } 26 | 27 | output "http_api_logs_command" { 28 | description = "Command to view http api logs with sam" 29 | value = "sam logs --cw-log-group ${aws_cloudwatch_log_group.logs.name} -t" 30 | } 31 | 32 | output "authorizer_logs_command" { 33 | description = "Command to view authorizer function logs with sam" 34 | value = "sam logs --cw-log-group ${module.lambda_function_auth.lambda_cloudwatch_log_group_name} -t" 35 | } 36 | 37 | output "responder_logs_command" { 38 | description = "Command to view responder function logs with sam" 39 | value = "sam logs --cw-log-group ${module.lambda_function_responder.lambda_cloudwatch_log_group_name} -t" 40 | } 41 | 42 | output "all_logs" { 43 | description = "Command to view an aggragate of all logs with sam" 44 | value = "sam logs --cw-log-group ${aws_cloudwatch_log_group.logs.name} --cw-log-group ${module.lambda_function_auth.lambda_cloudwatch_log_group_name} --cw-log-group ${module.lambda_function_responder.lambda_cloudwatch_log_group_name} -t" 45 | } 46 | -------------------------------------------------------------------------------- /ga/api_gateway_v2/tf-resources/samconfig.yaml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | default: 3 | global: 4 | parameters: 5 | hook_name: terraform 6 | skip_prepare_infra: true 7 | build: 8 | parameters: 9 | terraform_project_root_path: ../ -------------------------------------------------------------------------------- /ga/api_gateway_v2/tf-resources/variables.tf: -------------------------------------------------------------------------------- 1 | variable "aws_region" { 2 | description = "AWS region" 3 | type = string 4 | } 5 | 6 | variable "profile" { 7 | description = "AWS profile" 8 | type = string 9 | default = "default" 10 | } 11 | 12 | variable "config_location" { 13 | description = "AWS configuration file" 14 | type = string 15 | default = "~/.aws/config" 16 | } 17 | 18 | variable "creds_location" { 19 | description = "AWS credentials file" 20 | type = string 21 | default = "~/.aws/credentials" 22 | } -------------------------------------------------------------------------------- /ga/api_gateway_v2_tf_cloud/api.tf: -------------------------------------------------------------------------------- 1 | resource "aws_apigatewayv2_api" "api" { 2 | name = "Terraform HTTP API Example" 3 | protocol_type = "HTTP" 4 | } 5 | 6 | resource "aws_cloudwatch_log_group" "logs" { 7 | name = "/aws/vendedlogs/tf_http_logs" 8 | } 9 | 10 | resource "aws_apigatewayv2_stage" "stage" { 11 | api_id = aws_apigatewayv2_api.api.id 12 | auto_deploy = true 13 | name = "$default" 14 | access_log_settings { 15 | destination_arn = aws_cloudwatch_log_group.logs.arn 16 | format = jsonencode({"requestId":"$context.requestId", "ip":"$context.identity.sourceIp", "requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","routeKey":"$context.routeKey", "status":"$context.status","protocol":"$context.protocol", "responseLength":"$context.responseLength", "integrationError":"$context.integrationErrorMessage" }) 17 | } 18 | } 19 | 20 | # ####################################### 21 | # ## Open endpoint ## 22 | # ####################################### 23 | 24 | resource "aws_apigatewayv2_integration" "open_integration" { 25 | api_id = aws_apigatewayv2_api.api.id 26 | integration_type = "AWS_PROXY" 27 | integration_method = "POST" 28 | integration_uri = module.lambda_function_responder.lambda_function_invoke_arn 29 | payload_format_version = "2.0" 30 | } 31 | 32 | resource "aws_apigatewayv2_route" "get_open" { 33 | api_id = aws_apigatewayv2_api.api.id 34 | target = "integrations/${aws_apigatewayv2_integration.open_integration.id}" 35 | route_key = "GET /open" 36 | operation_name = "get_open_operation" 37 | authorization_type = "NONE" 38 | } 39 | 40 | # ####################################### 41 | # ## Secure endpoint ## 42 | # ####################################### 43 | 44 | resource "aws_apigatewayv2_integration" "secure_integration" { 45 | api_id = aws_apigatewayv2_api.api.id 46 | integration_type = "AWS_PROXY" 47 | integration_method = "POST" 48 | integration_uri = module.lambda_function_responder.lambda_function_invoke_arn 49 | payload_format_version = "2.0" 50 | } 51 | 52 | resource "aws_apigatewayv2_route" "get_secure" { 53 | api_id = aws_apigatewayv2_api.api.id 54 | target = "integrations/${aws_apigatewayv2_integration.secure_integration.id}" 55 | route_key = "GET /secure" 56 | operation_name = "get_secure_operation" 57 | authorization_type = "CUSTOM" 58 | authorizer_id = aws_apigatewayv2_authorizer.request_authorizer.id 59 | } 60 | 61 | resource "aws_apigatewayv2_authorizer" "request_authorizer" { 62 | api_id = aws_apigatewayv2_api.api.id 63 | authorizer_type = "REQUEST" 64 | authorizer_uri = module.lambda_function_auth.lambda_function_invoke_arn 65 | authorizer_credentials_arn = aws_iam_role.invocation_role.arn 66 | authorizer_payload_format_version = "2.0" 67 | identity_sources = ["$request.header.myheader"] 68 | name = "header_authorizer" 69 | enable_simple_responses = true 70 | authorizer_result_ttl_in_seconds = 0 # for testing only, caching should be added for production 71 | } 72 | 73 | data "aws_iam_policy_document" "invocation_assume_role" { 74 | statement { 75 | effect = "Allow" 76 | 77 | principals { 78 | type = "Service" 79 | identifiers = ["apigateway.amazonaws.com"] 80 | } 81 | 82 | actions = ["sts:AssumeRole"] 83 | } 84 | } 85 | 86 | resource "aws_iam_role" "invocation_role" { 87 | name = "api_gateway_auth_http_invocation" 88 | path = "/" 89 | assume_role_policy = data.aws_iam_policy_document.invocation_assume_role.json 90 | } 91 | 92 | data "aws_iam_policy_document" "invocation_policy" { 93 | statement { 94 | effect = "Allow" 95 | actions = ["lambda:InvokeFunction"] 96 | resources = [module.lambda_function_auth.lambda_function_arn] 97 | } 98 | } 99 | 100 | resource "aws_iam_role_policy" "invocation_policy" { 101 | name = "default" 102 | role = aws_iam_role.invocation_role.id 103 | policy = data.aws_iam_policy_document.invocation_policy.json 104 | } 105 | -------------------------------------------------------------------------------- /ga/api_gateway_v2_tf_cloud/events/auth.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": "", 3 | "cookies": [], 4 | "headers": { 5 | "Accept": "*/*", 6 | "Accept-Encoding": "gzip, deflate, br", 7 | "Connection": "keep-alive", 8 | "Host": "localhost:3000", 9 | "Myheader": "123456789", 10 | "Postman-Token": "6108430a-79dd-4dc4-bbd8-82214d4cca37", 11 | "User-Agent": "PostmanRuntime/7.32.3", 12 | "X-Forwarded-Port": "3000", 13 | "X-Forwarded-Proto": "http" 14 | }, 15 | "identitySource": [ 16 | "123456789" 17 | ], 18 | "isBase64Encoded": false, 19 | "pathParameters": {}, 20 | "rawPath": "/secure", 21 | "rawQueryString": "", 22 | "requestContext": { 23 | "accountId": "123456789012", 24 | "apiId": "1234567890", 25 | "domainName": "localhost", 26 | "domainPrefix": "localhost", 27 | "http": { 28 | "method": "GET", 29 | "path": "/secure", 30 | "protocol": "HTTP/1.1", 31 | "sourceIp": "127.0.0.1", 32 | "userAgent": "Custom User Agent String" 33 | }, 34 | "requestId": "0e148f13-ec52-465f-af27-f80cf5be2153", 35 | "routeKey": "GET /secure", 36 | "stage": "$default", 37 | "time": "16/Aug/2023:05:16:17 +0000", 38 | "timeEpoch": 1692162977 39 | }, 40 | "routeArn": "arn:aws:execute-api:us-east-1:123456789012:1234567890/$default/GET/secure", 41 | "routeKey": "GET /secure", 42 | "stageVariables": "None", 43 | "type": "REQUEST", 44 | "version": "2.0" 45 | } -------------------------------------------------------------------------------- /ga/api_gateway_v2_tf_cloud/functions.tf: -------------------------------------------------------------------------------- 1 | module "lambda_function_responder" { 2 | source = "terraform-aws-modules/lambda/aws" 3 | version = "~> 6.0" 4 | timeout = 300 5 | local_existing_package = "./src/responder/function.zip" 6 | function_name = "http_responder" 7 | handler = "app.open_handler" 8 | runtime = "python3.9" 9 | create_package = false 10 | publish = true 11 | allowed_triggers = { 12 | APIGatewayAny = { 13 | service = "apigateway" 14 | source_arn = "${aws_apigatewayv2_api.api.execution_arn}/*/*" 15 | } 16 | } 17 | } 18 | 19 | module "lambda_function_auth" { 20 | source = "terraform-aws-modules/lambda/aws" 21 | version = "~> 6.0" 22 | timeout = 300 23 | local_existing_package = "./src/auth/function.zip" 24 | create_package = false 25 | function_name = "http_authorizer" 26 | handler = "app.handler" 27 | runtime = "python3.9" 28 | } 29 | -------------------------------------------------------------------------------- /ga/api_gateway_v2_tf_cloud/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | } 6 | } 7 | 8 | cloud { 9 | organization = "" 10 | 11 | workspaces { 12 | name = "" 13 | } 14 | } 15 | } 16 | 17 | provider "aws" { 18 | region = var.aws_region 19 | shared_config_files = [var.config_location] 20 | shared_credentials_files = [var.creds_location] 21 | profile = var.profile 22 | } 23 | 24 | ################################### 25 | ## Outputs ## 26 | ################################### 27 | 28 | output "api_endpoint" { 29 | description = "Base url for api" 30 | value = aws_apigatewayv2_api.api.api_endpoint 31 | } 32 | 33 | output "http_api_logs_command" { 34 | description = "Command to view http api logs with sam" 35 | value = "sam logs --cw-log-group ${aws_cloudwatch_log_group.logs.name} -t" 36 | } 37 | 38 | output "authorizer_logs_command" { 39 | description = "Command to view authorizer function logs with sam" 40 | value = "sam logs --cw-log-group ${module.lambda_function_auth.lambda_cloudwatch_log_group_name} -t" 41 | } 42 | 43 | output "responder_logs_command" { 44 | description = "Command to view responder function logs with sam" 45 | value = "sam logs --cw-log-group ${module.lambda_function_responder.lambda_cloudwatch_log_group_name} -t" 46 | } 47 | 48 | output "all_logs" { 49 | description = "Command to view an aggragate of all logs with sam" 50 | value = "sam logs --cw-log-group ${aws_cloudwatch_log_group.logs.name} --cw-log-group ${module.lambda_function_auth.lambda_cloudwatch_log_group_name} --cw-log-group ${module.lambda_function_responder.lambda_cloudwatch_log_group_name} -t" 51 | } 52 | -------------------------------------------------------------------------------- /ga/api_gateway_v2_tf_cloud/samconfig.yaml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | default: 3 | global: 4 | parameters: 5 | hook_name: terraform 6 | skip_prepare_infra: true -------------------------------------------------------------------------------- /ga/api_gateway_v2_tf_cloud/variables.tf: -------------------------------------------------------------------------------- 1 | variable "aws_region" { 2 | description = "AWS region" 3 | type = string 4 | default = "us-east-1" 5 | } 6 | 7 | variable "profile" { 8 | description = "AWS profile" 9 | type = string 10 | default = "default" 11 | } 12 | 13 | variable "config_location" { 14 | description = "AWS configuration file" 15 | type = string 16 | default = "~/.aws/config" 17 | } 18 | 19 | variable "creds_location" { 20 | description = "AWS credentials file" 21 | type = string 22 | default = "~/.aws/credentials" 23 | } -------------------------------------------------------------------------------- /serverless_tf_sample/api-lambda-dynamodb-example/README.md: -------------------------------------------------------------------------------- 1 | ## Deploying the example application 2 | This Lambda function interacts with DynamoDB. For the example to work, it requires an existing DynamoDB table in an AWS account. Deploying this creates all the required resources for local testing and debugging of the Lambda function. 3 | 4 | To deploy: 5 | 6 | 1- Initialize a working directory containing Terraform configuration files: 7 | ``` 8 | terraform init 9 | ``` 10 | 2- Deploy the application using Terraform CLI. 11 | ``` 12 | terraform apply -auto-approve 13 | ``` 14 | ## Local testing 15 | With the backend services now deployed, run local tests to see if everything is working. The locally running sample Lambda function interacts with the services deployed in the AWS account. Run the sam build to reflect the local sam testing environment with changes after each code update. 16 | 17 | 1- Local Build: To create a local build of the Lambda function for testing, use the sam build command: 18 | ``` 19 | sam build --hook-name terraform --beta-features 20 | ``` 21 | 22 | 2- Local invoke: The first test is to invoke the Lambda function with a mocked event payload from the API Gateway. These events are in the events directory. Run this command, passing in a mocked event: 23 | ``` 24 | AWS_DEFAULT_REGION= 25 | sam local invoke module.publish_book_review.aws_lambda_function.this[0] -e events/new-review.json --beta-features 26 | ``` -------------------------------------------------------------------------------- /serverless_tf_sample/api-lambda-dynamodb-example/events/bad-review-score.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "routeKey": "POST /", 4 | "rawPath": "/", 5 | "rawQueryString": "", 6 | "headers": { 7 | "accept": "*/*", 8 | "accept-encoding": "gzip, deflate, br", 9 | "cache-control": "no-cache", 10 | "content-length": "67", 11 | "content-type": "application/json", 12 | "host": "5gduqjk6dg.execute-api.ap-southeast-2.amazonaws.com", 13 | "postman-token": "e1e29d9d-389f-45da-b7f3-c5bdd873c916", 14 | "user-agent": "PostmanRuntime/7.26.10", 15 | "x-amzn-trace-id": "Root=1-6087880a-1c8111a52205e65b2c2e5e1a", 16 | "x-forwarded-for": "69.21.110.171", 17 | "x-forwarded-port": "443", 18 | "x-forwarded-proto": "https" 19 | }, 20 | "requestContext": { 21 | "accountId": "088xxxx489", 22 | "apiId": "5gduqjk6dg", 23 | "domainName": "5gduqjk6dg.execute-api.ap-southeast.amazonaws.com", 24 | "domainPrefix": "5gduqjk6dg", 25 | "http": { 26 | "method": "POST", 27 | "path": "/", 28 | "protocol": "HTTP/1.1", 29 | "sourceIp": "69.21.110.171", 30 | "userAgent": "PostmanRuntime/7.26.10" 31 | }, 32 | "requestId": "12345", 33 | "routeKey": "POST /", 34 | "stage": "$default", 35 | "time": "27/Apr/2021:03:42:02 +0000", 36 | "timeEpoch": 1619494922394 37 | }, 38 | "body": "{\"BookTitle\":\"My fave book\", \"ReviewScore\": \"17\", \"ReviewText\":\"Epic read! Definitely recommended!\"}", 39 | "isBase64Encoded": false 40 | } -------------------------------------------------------------------------------- /serverless_tf_sample/api-lambda-dynamodb-example/events/lambda-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "BookTitle": "My fave book", 3 | "ReviewScore": "8", 4 | "ReviewText": "Epic read! Definitely recommended!" 5 | } -------------------------------------------------------------------------------- /serverless_tf_sample/api-lambda-dynamodb-example/events/new-review.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "routeKey": "POST /", 4 | "rawPath": "/", 5 | "rawQueryString": "", 6 | "headers": { 7 | "accept": "*/*", 8 | "accept-encoding": "gzip, deflate, br", 9 | "cache-control": "no-cache", 10 | "content-length": "67", 11 | "content-type": "application/json", 12 | "host": "5gduqjk6dg.execute-api.ap-southeast-2.amazonaws.com", 13 | "postman-token": "e1e29d9d-389f-45da-b7f3-c5bdd873c916", 14 | "user-agent": "PostmanRuntime/7.26.10", 15 | "x-amzn-trace-id": "Root=1-6087880a-1c8111a52205e65b2c2e5e1a", 16 | "x-forwarded-for": "69.21.110.171", 17 | "x-forwarded-port": "443", 18 | "x-forwarded-proto": "https" 19 | }, 20 | "requestContext": { 21 | "accountId": "088xxxx489", 22 | "apiId": "5gduqjk6dg", 23 | "domainName": "5gduqjk6dg.execute-api.ap-southeast.amazonaws.com", 24 | "domainPrefix": "5gduqjk6dg", 25 | "http": { 26 | "method": "POST", 27 | "path": "/", 28 | "protocol": "HTTP/1.1", 29 | "sourceIp": "69.21.110.171", 30 | "userAgent": "PostmanRuntime/7.26.10" 31 | }, 32 | "requestId": "12345", 33 | "routeKey": "POST /", 34 | "stage": "$default", 35 | "time": "27/Apr/2021:03:42:02 +0000", 36 | "timeEpoch": 1619494922394 37 | }, 38 | "body": "{\"BookTitle\":\"My fave book\", \"ReviewScore\": \"8\", \"ReviewText\":\"Epic read! Definitely recommended!\"}", 39 | "isBase64Encoded": false 40 | } -------------------------------------------------------------------------------- /serverless_tf_sample/api-lambda-dynamodb-example/locals.json: -------------------------------------------------------------------------------- 1 | { 2 | "aws_lambda_function.publish_book_review": { 3 | "DYNAMODB_TABLE_NAME": "GameScores2" 4 | } 5 | } -------------------------------------------------------------------------------- /serverless_tf_sample/api-lambda-dynamodb-example/main.tf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | terraform { 5 | required_version = ">= 0.13.1" 6 | 7 | required_providers { 8 | aws = { 9 | source = "hashicorp/aws" 10 | version = ">= 3.19" 11 | } 12 | random = { 13 | source = "hashicorp/random" 14 | version = ">= 2.0" 15 | } 16 | } 17 | } 18 | 19 | provider "aws" { 20 | region = "us-west-1" 21 | } 22 | 23 | module "publish_book_review" { 24 | source = "terraform-aws-modules/lambda/aws" 25 | version = "4.6.0" 26 | create_role = false 27 | timeout = 30 28 | source_path = local.lambda_src_path 29 | function_name = "publish-book-review" 30 | handler = "index.lambda_handler" 31 | runtime = "python3.8" 32 | lambda_role = aws_iam_role.iam_for_lambda.arn 33 | environment_variables = { 34 | DYNAMODB_TABLE_NAME = "${aws_dynamodb_table.book-reviews-ddb-table.id}" 35 | } 36 | } 37 | 38 | resource "aws_iam_role" "iam_for_lambda" { 39 | name = "iam_for_lambda_usage" 40 | 41 | assume_role_policy = <