feat: initial POC
Initial POC of an api gateway with cognito authorizer deployed by terraformmain
parent
4b5da1ccc9
commit
ff6bd7bed9
|
@ -0,0 +1 @@
|
|||
terraform_api_gateway
|
|
@ -0,0 +1 @@
|
|||
a4f344a1cb3c3e0b077542f62f288858
|
|
@ -0,0 +1 @@
|
|||
a4f344a1cb3c3e0b077542f62f288858
|
|
@ -0,0 +1 @@
|
|||
d53f07fbdb97d1fbe6dd58682cad241a
|
|
@ -0,0 +1 @@
|
|||
f7efe372f54c13c2418d38d14dd5b970
|
31
README.md
31
README.md
|
@ -1,3 +1,32 @@
|
|||
# terraform_api_gateway
|
||||
|
||||
API Gateway with terraform for learning!
|
||||
API Gateway with terraform for learning!
|
||||
|
||||
## deployment tasks
|
||||
⚠️ require [`go-task`](https://taskfile.dev/installation/) and [`jq`](https://jqlang.github.io/jq/)
|
||||
|
||||
`task terraform_plan ENV=...` => plan the terraform deployment + create a `tfplan.json` that resume the modifications.
|
||||
`task terraform_apply ENV=...` => deploy the stack.
|
||||
`task terraform_destroy ENV=...` => destroy the stack.
|
||||
|
||||
ℹ️ configured environments:
|
||||
- dev
|
||||
## test commmands
|
||||
### login to cognito from cli to get IdToken
|
||||
Run:
|
||||
`aws cognito-idp initiate-auth --region <...> --auth-flow USER_PASSWORD_AUTH --client-id <...> --auth-parameters USERNAME=<...>,PASSWORD=<...>`
|
||||
Then copy the `"IdToken"` from response.
|
||||
|
||||
### test an api endpoint with cognito authorizer
|
||||
`curl --header "Authorization: <IdToken>" https://<...>.execute-api.eu-west-1.amazonaws.com/<stage>/<route>`
|
||||
|
||||
## TODO
|
||||
- [ ] create a post route
|
||||
- [ ] body validation
|
||||
- [ ] reponse transformation
|
||||
- [ ] routes module
|
||||
- [ ] swagger documentation
|
||||
- [ ] use aws lambda module
|
||||
|
||||
## critique du code
|
||||
Il n'y as pas vraiment de plus value d'avoir une conf de backend par env avec les workspaces.
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
version: '3'
|
||||
|
||||
env:
|
||||
ENV: "{{.ENV}}"
|
||||
SERVICES_DIRECTORY: "{{.ROOT_DIR}}/services"
|
||||
TERRAFORM_DIRECTORY: "{{.ROOT_DIR}}/terraform/code"
|
||||
CONFIG_DIRECTORY: "../configs"
|
||||
TFVARS: "{{.CONFIG_DIRECTORY}}/{{.ENV}}/terraform.tfvars"
|
||||
BACKEND: "{{.CONFIG_DIRECTORY}}/{{.ENV}}/backend.conf"
|
||||
DEFAULT_VARS: "{{.CONFIG_DIRECTORY}}/_default_values/terraform.tfvars"
|
||||
TF_IN_AUTOMATION: true
|
||||
PLAN: plan.tfplan
|
||||
JSON_PLAN_FILE: tfplan.json
|
||||
|
||||
tasks:
|
||||
check_env:
|
||||
preconditions:
|
||||
- sh: "[ $ENV != '' ]"
|
||||
msg: "Variable ENV is not set"
|
||||
cmds:
|
||||
- cmd: 'echo "Using environment: $ENV"'
|
||||
silent: true
|
||||
|
||||
check_jq:
|
||||
preconditions:
|
||||
- sh: "command -v jq >/dev/null 2>&1 || { echo >&2 \"jq is not installed. Aborting.\"; exit 1; }"
|
||||
msg: "jq is not installed"
|
||||
|
||||
terraform_init:
|
||||
deps: [check_env]
|
||||
dir: "{{.TERRAFORM_DIRECTORY}}"
|
||||
cmds:
|
||||
- terraform init -backend-config=${BACKEND} -input=false
|
||||
- terraform workspace new $ENV || terraform workspace select $ENV
|
||||
|
||||
build_services:
|
||||
dir: "{{.SERVICES_DIRECTORY}}"
|
||||
cmds:
|
||||
- zip hello_world.zip hello_world.py
|
||||
sources:
|
||||
- hello_world.py
|
||||
generates:
|
||||
- hello_world.zip
|
||||
|
||||
terraform_plan:
|
||||
deps: [terraform_init, build_services]
|
||||
dir: "{{.TERRAFORM_DIRECTORY}}"
|
||||
sources:
|
||||
- '*.tf'
|
||||
- '{{.CONFIG_DIRECTORY}}/_default_values/terraform.tfvars'
|
||||
- '{{.CONFIG_DIRECTORY}}/{{.ENV}}/terraform.tfvars'
|
||||
generates:
|
||||
- '{{.PLAN}}'
|
||||
- '{{.JSON_PLAN_FILE}}'
|
||||
cmds:
|
||||
- terraform fmt -check -diff -recursive
|
||||
- terraform validate
|
||||
- terraform plan --var-file=${DEFAULT_VARS} --var-file=${TFVARS} -out=${PLAN} -input=false
|
||||
- terraform show --json ${PLAN} | jq -r '([.resource_changes[]?.change.actions?]|flatten)|{"create":(map(select(.=="create"))|length),"update":(map(select(.=="update"))|length),"delete":(map(select(.=="delete"))|length)}' > ${JSON_PLAN_FILE}
|
||||
|
||||
terraform_apply:
|
||||
deps: [terraform_plan]
|
||||
dir: "{{.TERRAFORM_DIRECTORY}}"
|
||||
sources:
|
||||
- '{{.PLAN}}'
|
||||
cmds:
|
||||
- terraform apply --var-file=${DEFAULT_VARS} --var-file=${TFVARS} -input=false -auto-approve
|
||||
|
||||
terraform_destroy:
|
||||
deps: [terraform_init]
|
||||
dir: "{{.TERRAFORM_DIRECTORY}}"
|
||||
cmds:
|
||||
- terraform destroy --var-file=${DEFAULT_VARS} --var-file=${TFVARS} -input=false -auto-approve
|
||||
- rm ${PLAN}
|
|
@ -0,0 +1,7 @@
|
|||
import json
|
||||
|
||||
def lambda_handler(event, context):
|
||||
return {
|
||||
'statusCode': 200,
|
||||
'body': json.dumps('Hello World!')
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,25 @@
|
|||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/hashicorp/aws" {
|
||||
version = "4.44.0"
|
||||
constraints = "4.44.0"
|
||||
hashes = [
|
||||
"h1:IicMBt+WvFATiN4j/oaJYB4Kvk6LCxxpnokv2PXo1ag=",
|
||||
"zh:08da139140530900ebb07baedd9044b5002f0296f5f160d96783e72080158326",
|
||||
"zh:2d677b9e4f195481098cec843d0138f3a198f1f93be42c1d1654b71438e2f5ab",
|
||||
"zh:3cdf4e06b9b8f30f6652a4519d586febc4ab92168a39df2610f06e04a8e6dda7",
|
||||
"zh:677933957d1de40c8b5ae252c5cd369617af4cb7a26e8f750ad6f175fef4f767",
|
||||
"zh:6a266ae5488d6daa53bbe6c2cb8368833381eacd9de7f05f059f5100535a0cb2",
|
||||
"zh:6cdccaab0a444314b10246c8d58b0ffb84d32ddac70e36a12b45eb518e0ae065",
|
||||
"zh:6ed49c7680298761416d408bc91cd137ae7cff38181fc143b1dfab1a32b44516",
|
||||
"zh:8403a0fbf439009b3b0c77969c560cf426aeb3d99a78fdd27afbf4694ca0f3e7",
|
||||
"zh:8ddfd85c6789bca7c66346da1f3488488c20bc4d773cd26669059c437cfbabd6",
|
||||
"zh:941455d0fa54b0451387cfd611d63766f4b18cb24038f25ee0794097755e654d",
|
||||
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
|
||||
"zh:bc01398c714d621ad9f4e81863446cd8e1135a63a5ff7c36d3fb01ab27e96439",
|
||||
"zh:ce3b39a43b9c5b34a62047fe2a395b72a62cc0b5dc7e8a5be0f778159ac486d2",
|
||||
"zh:d5891b82511af25570287578318aaee4fa86e05599cc8c81d44ea9e094f4a728",
|
||||
"zh:df5329c186545d273f9abaeeb39251d6cfcc446bccc44e27d1d7d02ebf145e2f",
|
||||
]
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
terraform {
|
||||
required_version = ">= 1.2.0"
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "= 4.44.0"
|
||||
}
|
||||
}
|
||||
backend "s3" {
|
||||
key = "test_projects/terraform_api_gateway.tfstate"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
data "aws_caller_identity" "current" {}
|
|
@ -0,0 +1,11 @@
|
|||
locals {
|
||||
default_tags = {
|
||||
ManagedBy = "Terraform"
|
||||
Application = var.application
|
||||
Environment = var.environment
|
||||
Region = var.region
|
||||
}
|
||||
|
||||
current_account_id = data.aws_caller_identity.current.account_id
|
||||
resource_prefix = "${var.application}-${var.environment}-${var.region}"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
output "invoke_url" {
|
||||
value = aws_api_gateway_deployment.this.invoke_url
|
||||
}
|
||||
|
||||
output "client_id" {
|
||||
value = aws_cognito_user_pool_client.client.id
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
provider "aws" {
|
||||
region = var.region
|
||||
|
||||
assume_role {
|
||||
role_arn = var.role_arn
|
||||
}
|
||||
|
||||
default_tags {
|
||||
tags = local.default_tags
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
variable "application" {
|
||||
description = "Application name"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "role_arn" {
|
||||
description = "Role arn used by the provider"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
description = "Environment name"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
description = "Project name"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "services_path" {
|
||||
description = "Path to the services folder"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "xray_tracing_enabled" {
|
||||
description = "Enables the XRay tracing and will create the necessary IAM permissions "
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "api_throttling_burst_limit" {
|
||||
description = "API Gateway total concurrent connections allowed for all API's within a REST endpoint"
|
||||
}
|
||||
|
||||
variable "api_throttling_rate_limit" {
|
||||
description = "API Gateway total requests across all API's within a REST endpoint"
|
||||
}
|
||||
|
||||
variable "api_metrics_enabled" {
|
||||
description = "Enables detailed API Gateway metrics"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "api_logging_level" {
|
||||
description = " (Optional) Specifies the logging level for this method, which effects the log entries pushed to Amazon CloudWatch Logs. The available levels are OFF, ERROR, and INFO."
|
||||
type = string
|
||||
default = "OFF"
|
||||
}
|
||||
|
||||
variable "api_data_trace_enabled" {
|
||||
description = "(Optional) Specifies whether data trace logging is enabled for this method, which effects the log entries pushed to Amazon CloudWatch Logs."
|
||||
type = bool
|
||||
default = false
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
resource "aws_api_gateway_rest_api" "this" {
|
||||
name = "${local.resource_prefix}-api-gateway"
|
||||
}
|
||||
|
||||
resource "aws_api_gateway_deployment" "this" {
|
||||
rest_api_id = aws_api_gateway_rest_api.this.id
|
||||
|
||||
triggers = {
|
||||
redeployment = timestamp()
|
||||
}
|
||||
|
||||
lifecycle {
|
||||
create_before_destroy = true
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
aws_api_gateway_method.hello_world,
|
||||
aws_api_gateway_integration.hello_world,
|
||||
# aws_api_gateway_method.method, aws_api_gateway_method.options_method,
|
||||
# aws_api_gateway_method_response.method_response, aws_api_gateway_method_response.options_200,
|
||||
# aws_api_gateway_integration.integration, aws_api_gateway_integration.options_integration,
|
||||
# aws_api_gateway_integration_response.options_integration_response,
|
||||
# aws_api_gateway_authorizer.api_authorizer,
|
||||
# aws_iam_role.invocation_role, aws_iam_role_policy.invocation_policy,
|
||||
# aws_acm_certificate.ssl_certificate, aws_route53_record.cert_validation,
|
||||
# aws_acm_certificate_validation.ssl_certificate_validation, aws_api_gateway_domain_name.this,
|
||||
# aws_route53_record.api, aws_api_gateway_gateway_response.unauthorized_response, module.apigateway_resources,
|
||||
]
|
||||
}
|
||||
|
||||
resource "aws_api_gateway_stage" "this" {
|
||||
stage_name = var.environment
|
||||
rest_api_id = aws_api_gateway_rest_api.this.id
|
||||
deployment_id = aws_api_gateway_deployment.this.id
|
||||
|
||||
xray_tracing_enabled = var.xray_tracing_enabled
|
||||
|
||||
tags = {
|
||||
Name = local.resource_prefix
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
aws_api_gateway_method.hello_world,
|
||||
aws_api_gateway_integration.hello_world,
|
||||
# aws_api_gateway_method.method, aws_api_gateway_method.options_method,
|
||||
# aws_api_gateway_method_response.method_response, aws_api_gateway_method_response.options_200,
|
||||
# aws_api_gateway_integration.integration, aws_api_gateway_integration.options_integration,
|
||||
# aws_api_gateway_integration_response.options_integration_response,
|
||||
# aws_api_gateway_authorizer.api_authorizer,
|
||||
# aws_iam_role.invocation_role, aws_iam_role_policy.invocation_policy,
|
||||
# aws_acm_certificate.ssl_certificate, aws_route53_record.cert_validation,
|
||||
# aws_acm_certificate_validation.ssl_certificate_validation, aws_api_gateway_domain_name.this,
|
||||
# aws_route53_record.api, aws_api_gateway_gateway_response.unauthorized_response, module.apigateway_resources
|
||||
]
|
||||
}
|
||||
|
||||
resource "aws_api_gateway_method_settings" "this" {
|
||||
rest_api_id = aws_api_gateway_rest_api.this.id
|
||||
stage_name = aws_api_gateway_stage.this.stage_name
|
||||
method_path = "*/*"
|
||||
|
||||
settings {
|
||||
throttling_burst_limit = var.api_throttling_burst_limit
|
||||
throttling_rate_limit = var.api_throttling_rate_limit
|
||||
metrics_enabled = var.api_metrics_enabled
|
||||
logging_level = var.api_logging_level
|
||||
data_trace_enabled = var.api_data_trace_enabled
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "cloudwatch" {
|
||||
name = "api_gateway_cloudwatch_global"
|
||||
|
||||
assume_role_policy = <<EOF
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "",
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"Service": "apigateway.amazonaws.com"
|
||||
},
|
||||
"Action": "sts:AssumeRole"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "cloudwatch" {
|
||||
name = "default"
|
||||
role = aws_iam_role.cloudwatch.id
|
||||
|
||||
policy = <<EOF
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"logs:CreateLogGroup",
|
||||
"logs:CreateLogStream",
|
||||
"logs:DescribeLogGroups",
|
||||
"logs:DescribeLogStreams",
|
||||
"logs:PutLogEvents",
|
||||
"logs:GetLogEvents",
|
||||
"logs:FilterLogEvents"
|
||||
],
|
||||
"Resource": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
resource "aws_api_gateway_account" "this" {
|
||||
cloudwatch_role_arn = aws_iam_role.cloudwatch.arn
|
||||
}
|
||||
|
||||
resource "aws_api_gateway_authorizer" "this" {
|
||||
name = "${local.resource_prefix}-authorizer"
|
||||
type = "COGNITO_USER_POOLS"
|
||||
rest_api_id = aws_api_gateway_rest_api.this.id
|
||||
provider_arns = ["${aws_cognito_user_pool.pool.arn}"]
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
resource "aws_api_gateway_resource" "hello_world" {
|
||||
rest_api_id = aws_api_gateway_rest_api.this.id
|
||||
parent_id = aws_api_gateway_rest_api.this.root_resource_id
|
||||
path_part = "hello_world"
|
||||
}
|
||||
|
||||
resource "aws_api_gateway_method" "hello_world" {
|
||||
rest_api_id = aws_api_gateway_rest_api.this.id
|
||||
resource_id = aws_api_gateway_resource.hello_world.id
|
||||
http_method = "GET"
|
||||
authorization = "COGNITO_USER_POOLS"
|
||||
authorizer_id = aws_api_gateway_authorizer.this.id
|
||||
# authorization_scopes = ["${aws_cognito_resource_server.resource_server.scope_identifiers}"]
|
||||
}
|
||||
|
||||
resource "aws_api_gateway_integration" "hello_world" {
|
||||
rest_api_id = aws_api_gateway_rest_api.this.id
|
||||
resource_id = aws_api_gateway_resource.hello_world.id
|
||||
http_method = aws_api_gateway_method.hello_world.http_method
|
||||
integration_http_method = "POST"
|
||||
type = "AWS_PROXY"
|
||||
uri = aws_lambda_function.hello_world.invoke_arn
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
resource "aws_cognito_user_pool" "pool" {
|
||||
name = "${local.resource_prefix}-user-pool"
|
||||
|
||||
account_recovery_setting {
|
||||
recovery_mechanism {
|
||||
name = "verified_email"
|
||||
priority = 1
|
||||
}
|
||||
recovery_mechanism {
|
||||
name = "verified_phone_number"
|
||||
priority = 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_cognito_user_pool_client" "client" {
|
||||
name = "${local.resource_prefix}-client"
|
||||
user_pool_id = aws_cognito_user_pool.pool.id
|
||||
explicit_auth_flows = [
|
||||
"ALLOW_USER_PASSWORD_AUTH",
|
||||
"ALLOW_USER_SRP_AUTH",
|
||||
"ALLOW_REFRESH_TOKEN_AUTH"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
resource "aws_iam_role" "role" {
|
||||
name = "LambdaRole"
|
||||
|
||||
assume_role_policy = <<POLICY
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Action": "sts:AssumeRole",
|
||||
"Principal": {
|
||||
"Service": "lambda.amazonaws.com"
|
||||
},
|
||||
"Effect": "Allow",
|
||||
"Sid": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
POLICY
|
||||
}
|
||||
|
||||
resource "aws_lambda_function" "hello_world" {
|
||||
filename = "${var.services_path}/hello_world.zip"
|
||||
function_name = "hello_world"
|
||||
role = aws_iam_role.role.arn
|
||||
handler = "hello_world.lambda_handler"
|
||||
runtime = "python3.9"
|
||||
source_code_hash = filebase64sha256("${var.services_path}/hello_world.zip")
|
||||
}
|
||||
|
||||
resource "aws_lambda_permission" "apigw_lambda" {
|
||||
action = "lambda:InvokeFunction"
|
||||
function_name = aws_lambda_function.hello_world.function_name
|
||||
principal = "apigateway.amazonaws.com"
|
||||
source_arn = "arn:aws:execute-api:${var.region}:${local.current_account_id}:${aws_api_gateway_rest_api.this.id}/*/${aws_api_gateway_method.hello_world.http_method}${aws_api_gateway_resource.hello_world.path}"
|
||||
statement_id = "AllowExecutionFromAPIGateway"
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"create": 16,
|
||||
"update": 0,
|
||||
"delete": 0
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
acl = "bucket-owner-full-control"
|
||||
bucket = "flavien-revolve-terraform-tfstate"
|
||||
workspace_key_prefix = "workspace"
|
||||
region = "eu-west-1"
|
||||
profile = "revolve"
|
||||
dynamodb_table = "flavien-revolve-terraform-tfstate"
|
Loading…
Reference in New Issue