Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions .github/workflows/package.yml

This file was deleted.

2 changes: 1 addition & 1 deletion .github/workflows/tfsec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ jobs:
tfsec:
uses: skyleague/node-standards/.github/workflows/reusable-tfsec.yml@main
with:
terraform-version: "1.2.7"
terraform-version: "1.4.6"
working-directory: "./"
tfsec-var-files: '["test/default.tfvars", "test/a.tfvars"]'
21 changes: 0 additions & 21 deletions .vscode/aws-rest-api.code-workspace

This file was deleted.

13 changes: 6 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ This module simplifies the deployment of AWS API Gateway REST API (v1) by consol

In addition, it simplifies the integration of AWS Lambda by providing a standardized syntax to integrate AWS Lambda using the `AWS_PROXY` integration, as well as creating all the neccesary permissions for the API to invoke the Lambda functionss that are integrated with it.

## Dependencies

In order to deploy this Terraform module, you need a working `node` installation available during the deployment, with accompanying `npx` executable (usually present when `node` is installed). `node` is used in order to dynamically generate the OpenAPI `body` that defines the integrations with the REST API. The `ajv` package is a required dependency for validating the input of the generation script. This can be installed in the project `node_modules` (it likely is if you're using Javascript/Typescript in your toolchain), or as a global `npm` package.

## Usage

```terraform
Expand All @@ -21,8 +17,9 @@ module "api" {
definition = jsonencode({
"/v1/hello-world" = {
"GET" = {
# This also supports the aws_lambda_alias type
lambda = aws_lambda_function.hello_world
lambda = {
function_name = "prefix-hello-world"
}
}
}
})
Expand Down Expand Up @@ -55,7 +52,9 @@ module "api" {
"x-amazon-apigateway-integration" = {
cacheKeyParameters = ["method.request.querystring.name"]
}
lambda = aws_lambda_function.hello_world
lambda = {
function_name = "prefix-hello-world"
}
}
}
})
Expand Down
71 changes: 35 additions & 36 deletions iam.tf
Original file line number Diff line number Diff line change
@@ -1,39 +1,38 @@
resource "aws_cloudformation_stack" "lambda_permissions" {
name = "${var.name}-lambda-permissions-${aws_api_gateway_rest_api.this.id}"
template_body = jsonencode({
Resources = merge(
merge([
for urlPath, config in local.definition : {
for httpMethod, definition in config : "AllowExecutionFromAPIGateway${substr(sha256("${upper(httpMethod)} ${urlPath}"), 0, 8)}" => {
Type = "AWS::Lambda::Permission"
Properties = {
FunctionName = definition.lambda.function_name
Action = "lambda:InvokeFunction"
Principal = "apigateway.amazonaws.com"
resource "aws_lambda_permission" "api_invoke" {
for_each = merge([
for http_path, path_items in var.definition : {
for http_method, path_item in path_items : "${upper(http_method)} ${http_path}" => {
function_name = path_item.lambda.function_name
http_path = http_path
http_method = http_method
} if try(path_item.lambda, null) != null
}
]...)

action = "lambda:InvokeFunction"
function_name = each.value.function_name
principal = "apigateway.amazonaws.com"

# # More: http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-control-access-using-iam-policies-to-invoke-api.html
SourceArn = "arn:aws:execute-api:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:${aws_api_gateway_rest_api.this.id}/*/${upper(httpMethod)}${urlPath}"
}
} if can(definition.lambda.function_name)
}
]...),
merge([
for urlPath, config in local.definition : {
for httpMethod, definition in config : "AllowExecutionFromAPIGatewayAuthorizer${substr(sha256(definition.authorizer.name), 0, 8)}" => {
Type = "AWS::Lambda::Permission"
Properties = {
FunctionName = definition.authorizer.lambda.function_name
Action = "lambda:InvokeFunction"
Principal = "apigateway.amazonaws.com"
# More: http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-control-access-using-iam-policies-to-invoke-api.html
source_arn = "arn:aws:execute-api:${local.region}:${local.account_id}:${aws_api_gateway_rest_api.this.id}/*/${upper(each.value.http_method)}${each.value.http_path}"
statement_id = "AllowExecutionFromAPIGateway${substr(sha256("${aws_api_gateway_rest_api.this.id} ${each.key}"), 0, 8)}"
}

resource "aws_lambda_permission" "authorizer_invoke" {
for_each = merge([
for http_path, path_items in var.definition : {
for http_method, path_item in path_items : path_item.authorizer.name => {
function_name = path_item.authorizer.lambda.function_name
} if try(path_item.authorizer.lambda, null) != null
}
]...)

# # More: http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-control-access-using-iam-policies-to-invoke-api.html
SourceArn = "arn:aws:execute-api:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:${aws_api_gateway_rest_api.this.id}/authorizers/*"
}
} if can(definition.authorizer.lambda.function_name)
}
]...)
)
})
action = "lambda:InvokeFunction"
function_name = each.value.function_name
principal = "apigateway.amazonaws.com"
# More: http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-control-access-using-iam-policies-to-invoke-api.html
source_arn = "arn:aws:execute-api:${local.region}:${local.account_id}:${aws_api_gateway_rest_api.this.id}/authorizers/*"
statement_id = "AllowExecutionFromAPIGatewayAuthorizer${substr(sha256("${aws_api_gateway_rest_api.this.id} ${each.key}"), 0, 8)}"
}

data "aws_iam_policy_document" "vpc_invoke" {
Expand All @@ -48,7 +47,7 @@ data "aws_iam_policy_document" "vpc_invoke" {
identifiers = ["*"]
}
actions = ["execute-api:Invoke"]
resources = ["arn:aws:execute-api:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:${aws_api_gateway_rest_api.this.id}/${statement.value}/*/*"]
resources = ["arn:aws:execute-api:${local.region}:${local.account_id}:${aws_api_gateway_rest_api.this.id}/${statement.value}/*/*"]
condition {
test = "StringNotEquals"
variable = "aws:sourceVpc"
Expand All @@ -66,7 +65,7 @@ data "aws_iam_policy_document" "vpc_invoke" {
identifiers = ["*"]
}
actions = ["execute-api:Invoke"]
resources = ["arn:aws:execute-api:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:${aws_api_gateway_rest_api.this.id}/${statement.value}/*/*"]
resources = ["arn:aws:execute-api:${local.region}:${local.account_id}:${aws_api_gateway_rest_api.this.id}/${statement.value}/*/*"]
}
}
}
Expand Down
14 changes: 3 additions & 11 deletions main.tf
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
data "external" "spec" {
program = ["npx", "-y", "ts-node", "--esm", "-T", "--skip-project", "${path.module}/scripts/src/index.ts"]

query = {
definition = jsonencode(local.definition)
extensions = var.extensions
}
}
resource "aws_api_gateway_rest_api" "this" {
name = var.name
description = coalesce(var.description, "API for ${var.name}")
Expand All @@ -16,7 +8,7 @@ resource "aws_api_gateway_rest_api" "this" {
vpc_endpoint_ids = var.endpoint_type == "PRIVATE" ? var.vpc_endpoint_ids : null
}

body = jsonencode(jsondecode(data.external.spec.result.spec))
body = jsonencode(local.compiled_definition)

lifecycle {
precondition {
Expand Down Expand Up @@ -53,7 +45,6 @@ resource "aws_api_gateway_deployment" "this" {

depends_on = [
aws_api_gateway_rest_api_policy.vpc_invoke,
aws_cloudformation_stack.lambda_permissions,
]
}

Expand All @@ -75,6 +66,7 @@ resource "aws_api_gateway_stage" "this" {

depends_on = [
aws_cloudwatch_log_group.execution,
aws_cloudformation_stack.lambda_permissions,
aws_lambda_permission.api_invoke,
aws_lambda_permission.authorizer_invoke,
]
}
15 changes: 0 additions & 15 deletions scripts/.eslintignore

This file was deleted.

1 change: 0 additions & 1 deletion scripts/.eslintrc.cjs

This file was deleted.

16 changes: 0 additions & 16 deletions scripts/.gitignore

This file was deleted.

2 changes: 0 additions & 2 deletions scripts/.npmrc

This file was deleted.

15 changes: 0 additions & 15 deletions scripts/.prettierignore

This file was deleted.

7 changes: 0 additions & 7 deletions scripts/LICENSE.md

This file was deleted.

23 changes: 0 additions & 23 deletions scripts/build.config.ts

This file was deleted.

1 change: 0 additions & 1 deletion scripts/commitlint.config.cjs

This file was deleted.

28 changes: 0 additions & 28 deletions scripts/jest.config.cjs

This file was deleted.

1 change: 0 additions & 1 deletion scripts/lint-staged.config.cjs

This file was deleted.

Loading