From 5794d3b1377036191f13316aae76a70b975abc51 Mon Sep 17 00:00:00 2001 From: Bento Luiz Date: Tue, 2 Dec 2025 12:49:16 -0300 Subject: [PATCH] Revert "Remove Lambda module outputs, variables, and API Gateway module files" --- terraform/env/dev/main.tf | 52 ++ terraform/env/dev/outputs.tf | 71 +++ terraform/env/dev/variables.tf | 84 +++ terraform/modules/05.lambda/main.tf | 484 ++++++++++++++++++ terraform/modules/05.lambda/outputs.tf | 75 +++ terraform/modules/05.lambda/variables.tf | 162 ++++++ terraform/modules/06.api-gateway/README.md | 175 +++++++ terraform/modules/06.api-gateway/main.tf | 440 ++++++++++++++++ terraform/modules/06.api-gateway/outputs.tf | 56 ++ terraform/modules/06.api-gateway/variables.tf | 44 ++ terraform/modules/API_KEY_USAGE.md | 180 +++++++ terraform/variables.tf | 84 +++ 12 files changed, 1907 insertions(+) create mode 100644 terraform/modules/05.lambda/main.tf create mode 100644 terraform/modules/05.lambda/outputs.tf create mode 100644 terraform/modules/05.lambda/variables.tf create mode 100644 terraform/modules/06.api-gateway/README.md create mode 100644 terraform/modules/06.api-gateway/main.tf create mode 100644 terraform/modules/06.api-gateway/outputs.tf create mode 100644 terraform/modules/06.api-gateway/variables.tf create mode 100644 terraform/modules/API_KEY_USAGE.md diff --git a/terraform/env/dev/main.tf b/terraform/env/dev/main.tf index 49a4751..6c1f89c 100644 --- a/terraform/env/dev/main.tf +++ b/terraform/env/dev/main.tf @@ -59,3 +59,55 @@ module "sqs" { project_name = var.project_name environment = var.environment } + +# Lambda +module "lambda" { + source = "../../modules/05.lambda" + project_name = var.project_name + environment = var.environment + aws_region = var.aws_region + ecr_api_repository_name = module.ecr.api_repository_name + ecr_api_repository_url = module.ecr.api_repository_url + ecr_builder_repository_name = module.ecr.builder_repository_name + ecr_builder_repository_url = module.ecr.builder_repository_url + ecr_builder_repository_arn = module.ecr.builder_repository_arn + ecr_notification_repository_name = module.ecr.notification_repository_name + ecr_notification_repository_url = module.ecr.notification_repository_url + ecr_notification_repository_arn = module.ecr.notification_repository_arn + image_tag = var.lambda_image_tag + lambda_timeout = var.lambda_timeout + lambda_memory_size = var.lambda_memory_size + log_retention_days = var.log_retention_days + url_service_tech = var.url_service_tech + prefix_api_version = var.prefix_api_version + service_url_registration_api_solana = var.service_url_registration_api_solana + service_api_key_registration_api_solana = var.service_api_key_registration_api_solana + tech_floripa_certificate_validate_url = var.tech_floripa_certificate_validate_url + tech_floripa_logo_url = var.tech_floripa_logo_url + builder_queue_url = module.sqs.builder_queue_url + builder_queue_arn = module.sqs.builder_queue_arn + notification_queue_arn = module.sqs.notification_queue_arn + notification_queue_url = module.sqs.notification_queue_url + dynamodb_table_arns = [ + module.certificates_table.table_arn, + module.orders_table.table_arn, + module.participants_table.table_arn, + module.products_table.table_arn + ] + ecr_repository_arn = module.ecr.api_repository_arn + s3_bucket_arn = module.s3.bucket_arn + s3_bucket_name = module.s3.bucket_name + api_gateway_download_url = module.api_gateway.api_endpoint_download_certificate +} + +# API Gateway +module "api_gateway" { + source = "../../modules/06.api-gateway" + project_name = var.project_name + environment = var.environment + lambda_function_name = module.lambda.function_name + lambda_invoke_arn = module.lambda.invoke_arn + throttle_rate_limit = var.api_throttle_rate_limit + throttle_burst_limit = var.api_throttle_burst_limit + api_key_value = var.api_key_value +} diff --git a/terraform/env/dev/outputs.tf b/terraform/env/dev/outputs.tf index 986c04f..ec39d65 100644 --- a/terraform/env/dev/outputs.tf +++ b/terraform/env/dev/outputs.tf @@ -79,3 +79,74 @@ output "sqs_dlq_url" { description = "URL da Dead Letter Queue (builder)" value = module.sqs.builder_dlq_url } + +# Informações do Lambda +output "lambda_function_name" { + description = "Nome da função Lambda" + value = module.lambda.function_name +} + +output "lambda_function_arn" { + description = "ARN da função Lambda" + value = module.lambda.function_arn +} + +# Informações da Lambda de Notificação +output "lambda_notification_function_name" { + description = "Nome da função Lambda de Notificação" + value = module.lambda.notification_function_name +} + +output "lambda_notification_function_arn" { + description = "ARN da função Lambda de Notificação" + value = module.lambda.notification_function_arn +} + +# Informações do API Gateway +output "api_gateway_url" { + description = "URL base do API Gateway" + value = module.api_gateway.api_url +} + +output "api_gateway_endpoint_create_certificate" { + description = "URL completa do endpoint para criar certificado" + value = module.api_gateway.api_endpoint_create_certificate +} + +output "api_gateway_endpoint_download_certificate" { + description = "URL base do endpoint para download de certificados" + value = module.api_gateway.api_endpoint_download_certificate +} + +output "api_gateway_id" { + description = "ID do API Gateway" + value = module.api_gateway.api_id +} + +# Resumo completo da infraestrutura +output "infrastructure_summary" { + description = "Resumo completo da infraestrutura criada" + value = { + api_gateway = { + id = module.api_gateway.api_id + url = module.api_gateway.api_url + endpoint_create_cert = module.api_gateway.api_endpoint_create_certificate + } + lambda = { + function_name = module.lambda.function_name + function_arn = module.lambda.function_arn + notification_function_name = module.lambda.notification_function_name + notification_function_arn = module.lambda.notification_function_arn + } + ecr = { + repository_url = module.ecr.api_repository_url + api_repository_name = module.ecr.api_repository_name + } + sqs = { + queue_url = module.sqs.queue_url + dlq_url = module.sqs.builder_dlq_url + } + environment = var.environment + project = var.project_name + } +} diff --git a/terraform/env/dev/variables.tf b/terraform/env/dev/variables.tf index da51a4f..474a277 100644 --- a/terraform/env/dev/variables.tf +++ b/terraform/env/dev/variables.tf @@ -52,3 +52,87 @@ variable "project_name" { # Configuração do DynamoDB - Modo de Baixo Custo # Todas as tabelas usam PAY_PER_REQUEST por padrão (paga apenas pelo que usar) + +# Variáveis para Lambda Function +variable "lambda_image_tag" { + description = "Tag da imagem Docker para o Lambda" + type = string + default = "latest" +} + +variable "lambda_timeout" { + description = "Timeout da função Lambda em segundos" + type = number + default = 60 +} + +variable "lambda_memory_size" { + description = "Memória alocada para a função Lambda em MB" + type = number + default = 512 +} + +variable "log_retention_days" { + description = "Dias de retenção dos logs do CloudWatch" + type = number + default = 3 +} + +variable "url_service_tech" { + description = "URL do serviço Tech Floripa" + type = string + default = "https://tech.floripa.br/wp-json/custom/v1" +} + +variable "prefix_api_version" { + description = "Prefixo da versão da API" + type = string + default = "/api/v1" +} + +# Variáveis para API Gateway +variable "api_throttle_rate_limit" { + description = "Limite de taxa por segundo para throttling do API Gateway" + type = number + default = 100 +} + +variable "api_throttle_burst_limit" { + description = "Limite de burst para throttling do API Gateway" + type = number + default = 200 +} + +variable "api_key_value" { + description = "Valor da API Key para autenticação do API Gateway (deve ser fornecido via terraform.tfvars)" + type = string + sensitive = true + + validation { + condition = can(regex("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$", var.api_key_value)) + error_message = "A API Key deve estar no formato UUID válido (ex: 8a3f1e2c-9d7b-4f4a-8453-bf3c1d2a6f29)." + } +} + +# Variáveis para integração com API Solana +variable "service_url_registration_api_solana" { + description = "URL do serviço de registro da API Solana" + type = string +} + +variable "service_api_key_registration_api_solana" { + description = "API Key do serviço de registro da API Solana" + type = string + sensitive = true +} + +variable "tech_floripa_certificate_validate_url" { + description = "URL para validação de certificados do Tech Floripa" + type = string +} + +variable "tech_floripa_logo_url" { + description = "URL do logo do Tech Floripa" + type = string + default = "https://tech.floripa.br/wp-content/uploads/2025/03/Tech-Floripa-Qr.png" +} diff --git a/terraform/modules/05.lambda/main.tf b/terraform/modules/05.lambda/main.tf new file mode 100644 index 0000000..b8e8f4b --- /dev/null +++ b/terraform/modules/05.lambda/main.tf @@ -0,0 +1,484 @@ +# Módulo Lambda para Certified Builder API +# Responsável por criar a função Lambda que executa a aplicação containerizada + +# IAM Role para a função Lambda para acessar o ECR, SQS e S3 +resource "aws_iam_role" "lambda_execution_role" { + name = "${var.project_name}-lambda-role-${var.environment}" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + } + ] + }) + + tags = { + Name = "${var.project_name}-lambda-role-${var.environment}" + Environment = var.environment + Project = var.project_name + } +} + +# Policy básica para execução do Lambda +resource "aws_iam_role_policy_attachment" "lambda_basic_execution" { + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + role = aws_iam_role.lambda_execution_role.name +} + +# Policy personalizada para acessar DynamoDB, SQS e ECR +resource "aws_iam_role_policy" "lambda_custom_policy" { + name = "${var.project_name}-lambda-policy-${var.environment}" + role = aws_iam_role.lambda_execution_role.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "dynamodb:GetItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem", + "dynamodb:Query", + "dynamodb:Scan" + ] + Resource = var.dynamodb_table_arns + }, + { + Effect = "Allow" + Action = [ + "sqs:SendMessage", + "sqs:ReceiveMessage", + "sqs:DeleteMessage", + "sqs:GetQueueAttributes" + ] + Resource = [ + var.builder_queue_arn, + var.notification_queue_arn + ] + }, + { + Effect = "Allow" + Action = [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + ] + Resource = [ + var.ecr_repository_arn, + "${var.ecr_repository_arn}:*" + ] + }, + { + Effect = "Allow" + Action = [ + "s3:GetObject", + "s3:GetObjectVersion", + "s3:PutObject", + "s3:DeleteObject", + "s3:ListBucket", + ] + Resource = [ + var.s3_bucket_arn, + "${var.s3_bucket_arn}/*" + ] + } + ] + }) +} + +resource "aws_iam_role" "lambda_builder_execution_role" { + name = "${var.project_name}-lambda-builder-role-${var.environment}" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + } + ] + }) + + tags = { + Name = "${var.project_name}-lambda-ecr-builder-execution-role-${var.environment}" + Environment = var.environment + Project = var.project_name + } +} + +# Policy básica para execução do Lambda Builder +resource "aws_iam_role_policy_attachment" "lambda_builder_basic_execution" { + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + role = aws_iam_role.lambda_builder_execution_role.name +} + +# Policy para acessar ECR +resource "aws_iam_role_policy_attachment" "lambda_builder_execution" { + policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + role = aws_iam_role.lambda_builder_execution_role.name +} + +resource "aws_iam_role_policy" "lambda_builder_custom_policy" { + name = "${var.project_name}-lambda-builder-policy-${var.environment}" + role = aws_iam_role.lambda_builder_execution_role.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ] + Resource = [ + var.ecr_builder_repository_arn, + "${var.ecr_builder_repository_arn}:*" + ] + }, + { + Effect = "Allow" + Action = [ + "s3:GetObject", + "s3:GetObjectVersion", + "s3:PutObject", + "s3:DeleteObject", + "s3:ListBucket", + ] + Resource = [ + var.s3_bucket_arn, + "${var.s3_bucket_arn}/*" + ] + }, + { + Effect = "Allow" + Action = [ + "sqs:SendMessage", + "sqs:ReceiveMessage", + "sqs:DeleteMessage", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl" + ] + Resource = [ + var.builder_queue_arn, + var.notification_queue_arn + ] + }, + ] + }) +} + +# Data source para detectar mudanças na imagem ECR +data "aws_ecr_image" "api_lambda_image" { + repository_name = split("/", var.ecr_api_repository_url)[1] # Extrai o nome do repositório da URL + image_tag = var.image_tag +} + +# Função Lambda +resource "aws_lambda_function" "api_function" { + function_name = "${var.project_name}-api-${var.environment}" + role = aws_iam_role.lambda_execution_role.arn + + # Configuração da imagem Docker usando digest para forçar atualização + package_type = "Image" + image_uri = "${var.ecr_api_repository_url}@${data.aws_ecr_image.api_lambda_image.image_digest}" + + # Configurações de performance e timeout + timeout = var.lambda_timeout + memory_size = var.lambda_memory_size + + # Variáveis de ambiente baseadas no env.example + environment { + variables = { + REGION = var.aws_region + BUILDER_QUEUE_URL = var.builder_queue_url + S3_BUCKET_NAME = var.s3_bucket_name + ENVIRONMENT = var.environment + PROJECT_NAME = var.project_name + URL_SERVICE_TECH = var.url_service_tech + PREFIX_API_VERSION = var.prefix_api_version + } + } + + # Configuração para atualizar quando a imagem ECR mudar + lifecycle { + create_before_destroy = true + # Remove ignore_changes para permitir detecção automática de mudanças + } + + tags = { + Name = "${var.project_name}-api-${var.environment}" + Environment = var.environment + Project = var.project_name + } +} + +# CloudWatch Log Group para a função Lambda +resource "aws_cloudwatch_log_group" "lambda_logs" { + name = "/aws/lambda/${aws_lambda_function.api_function.function_name}" + retention_in_days = var.log_retention_days + + tags = { + Name = "${var.project_name}-lambda-logs-${var.environment}" + Environment = var.environment + Project = var.project_name + } +} + +# Data source para detectar mudanças na imagem ECR do builder +data "aws_ecr_image" "builder_lambda_image" { + repository_name = split("/", var.ecr_builder_repository_url)[1] # Extrai o nome do repositório da URL + image_tag = var.image_tag +} + +resource "aws_lambda_function" "builder_function" { + function_name = "${var.project_name}-builder-${var.environment}" + role = aws_iam_role.lambda_builder_execution_role.arn + package_type = "Image" + # Configuração da imagem Docker usando digest para forçar atualização + image_uri = "${var.ecr_builder_repository_url}@${data.aws_ecr_image.builder_lambda_image.image_digest}" + timeout = var.lambda_timeout + memory_size = var.lambda_memory_size + + environment { + variables = { + REGION = var.aws_region + QUEUE_URL = var.notification_queue_url + BUCKET_NAME = var.s3_bucket_name + SERVICE_URL_REGISTRATION_API_SOLANA = var.service_url_registration_api_solana + SERVICE_API_KEY_REGISTRATION_API_SOLANA = var.service_api_key_registration_api_solana + TECH_FLORIPA_CERTIFICATE_VALIDATE_URL = var.tech_floripa_certificate_validate_url + TECH_FLORIPA_LOGO_URL = var.tech_floripa_logo_url + } + } + + # Configuração para atualizar quando a imagem ECR mudar + lifecycle { + create_before_destroy = true + # Remove ignore_changes para permitir detecção automática de mudanças + } + + tags = { + Name = "${var.project_name}-builder-${var.environment}" + Environment = var.environment + Project = var.project_name + } +} + +resource "aws_cloudwatch_log_group" "builder_logs" { + name = "/aws/lambda/${aws_lambda_function.builder_function.function_name}" + retention_in_days = var.log_retention_days + + tags = { + Name = "${var.project_name}-builder-logs-${var.environment}" + Environment = var.environment + Project = var.project_name + } +} + +# Event Source Mapping - Conecta a fila SQS builder com a função Lambda builder +# Quando uma mensagem chegar na fila, a Lambda será executada automaticamente +resource "aws_lambda_event_source_mapping" "builder_sqs_trigger" { + event_source_arn = var.builder_queue_arn + function_name = aws_lambda_function.builder_function.arn + + # Configurações do processamento em lote + batch_size = 1 # Processa 1 mensagem por vez + maximum_batching_window_in_seconds = 5 # Aguarda até 5 segundos para formar um lote + scaling_config { + maximum_concurrency = 3 + } + # Configurações de retry e erro + function_response_types = ["ReportBatchItemFailures"] + + tags = { + Name = "${var.project_name}-builder-sqs-trigger-${var.environment}" + Environment = var.environment + Project = var.project_name + } +} + +# IAM Role para a função Lambda de notificação +resource "aws_iam_role" "lambda_notification_execution_role" { + name = "${var.project_name}-lambda-notification-role-${var.environment}" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + } + ] + }) + + tags = { + Name = "${var.project_name}-lambda-notification-role-${var.environment}" + Environment = var.environment + Project = var.project_name + } +} + +# Policy básica para execução do Lambda de notificação +resource "aws_iam_role_policy_attachment" "lambda_notification_basic_execution" { + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + role = aws_iam_role.lambda_notification_execution_role.name +} + +# Policy personalizada para a Lambda de notificação acessar DynamoDB, S3 e SQS +resource "aws_iam_role_policy" "lambda_notification_custom_policy" { + name = "${var.project_name}-lambda-notification-policy-${var.environment}" + role = aws_iam_role.lambda_notification_execution_role.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "dynamodb:GetItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem", + "dynamodb:Query", + "dynamodb:Scan" + ] + Resource = var.dynamodb_table_arns + }, + { + Effect = "Allow" + Action = [ + "sqs:SendMessage", + "sqs:ReceiveMessage", + "sqs:DeleteMessage", + "sqs:GetQueueAttributes" + ] + Resource = [ + var.builder_queue_arn, + var.notification_queue_arn + ] + }, + { + Effect = "Allow" + Action = [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ] + Resource = [ + var.ecr_notification_repository_arn, + "${var.ecr_notification_repository_arn}:*" + ] + }, + { + Effect = "Allow" + Action = [ + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject", + "s3:ListBucket" + ] + Resource = [ + var.s3_bucket_arn, + "${var.s3_bucket_arn}/*" + ] + } + ] + }) +} + +# Data source para detectar mudanças na imagem ECR de notificação +data "aws_ecr_image" "notification_lambda_image" { + repository_name = split("/", var.ecr_notification_repository_url)[1] # Extrai o nome do repositório da URL + image_tag = var.image_tag +} + +# Função Lambda para processamento de notificações +resource "aws_lambda_function" "notification_function" { + function_name = "${var.project_name}-notification-${var.environment}" + role = aws_iam_role.lambda_notification_execution_role.arn + package_type = "Image" + + # Configuração da imagem Docker usando digest para forçar atualização + image_uri = "${var.ecr_notification_repository_url}@${data.aws_ecr_image.notification_lambda_image.image_digest}" + + # Configurações de performance e timeout + timeout = var.lambda_timeout + memory_size = var.lambda_memory_size + + # Variáveis de ambiente para a Lambda de notificação + environment { + variables = { + REGION = var.aws_region + S3_BUCKET_NAME = var.s3_bucket_name + ENVIRONMENT = var.environment + PROJECT_NAME = var.project_name + URL_SERVICE_TECH = var.url_service_tech + API_GATEWAY_DOWNLOAD_URL = var.api_gateway_download_url + } + } + + # Configuração para atualizar quando a imagem ECR mudar + lifecycle { + create_before_destroy = true + } + + tags = { + Name = "${var.project_name}-notification-${var.environment}" + Environment = var.environment + Project = var.project_name + } +} + +# CloudWatch Log Group para a função Lambda de notificação +resource "aws_cloudwatch_log_group" "notification_logs" { + name = "/aws/lambda/${aws_lambda_function.notification_function.function_name}" + retention_in_days = var.log_retention_days + + tags = { + Name = "${var.project_name}-notification-logs-${var.environment}" + Environment = var.environment + Project = var.project_name + } +} + +# Event Source Mapping - Conecta a fila SQS de notificação com a função Lambda de notificação +# Quando uma mensagem chegar na fila de notificação, a Lambda será executada automaticamente +resource "aws_lambda_event_source_mapping" "notification_sqs_trigger" { + event_source_arn = var.notification_queue_arn + function_name = aws_lambda_function.notification_function.arn + + # Configurações do processamento em lote + batch_size = 1 # Processa 1 mensagem por vez + maximum_batching_window_in_seconds = 5 # Aguarda até 5 segundos para formar um lote + scaling_config { + maximum_concurrency = 2 # Limite menor para notificações + } + + # Configurações de retry e erro + function_response_types = ["ReportBatchItemFailures"] + + tags = { + Name = "${var.project_name}-notification-sqs-trigger-${var.environment}" + Environment = var.environment + Project = var.project_name + } +} diff --git a/terraform/modules/05.lambda/outputs.tf b/terraform/modules/05.lambda/outputs.tf new file mode 100644 index 0000000..52d26f5 --- /dev/null +++ b/terraform/modules/05.lambda/outputs.tf @@ -0,0 +1,75 @@ +# Outputs do módulo Lambda + +output "function_name" { + description = "Nome da função Lambda" + value = aws_lambda_function.api_function.function_name +} + +output "function_arn" { + description = "ARN da função Lambda" + value = aws_lambda_function.api_function.arn +} + +output "invoke_arn" { + description = "ARN para invocar a função Lambda" + value = aws_lambda_function.api_function.invoke_arn +} + +output "role_arn" { + description = "ARN da role IAM da função Lambda" + value = aws_iam_role.lambda_execution_role.arn +} + +# Outputs da função Lambda Builder +output "builder_function_name" { + description = "Nome da função Lambda Builder" + value = aws_lambda_function.builder_function.function_name +} + +output "builder_function_arn" { + description = "ARN da função Lambda Builder" + value = aws_lambda_function.builder_function.arn +} + +output "builder_invoke_arn" { + description = "ARN para invocar a função Lambda Builder" + value = aws_lambda_function.builder_function.invoke_arn +} + +output "builder_role_arn" { + description = "ARN da role IAM da função Lambda Builder" + value = aws_iam_role.lambda_builder_execution_role.arn +} + +# Output do Event Source Mapping (trigger SQS → Lambda) +output "builder_sqs_trigger_uuid" { + description = "UUID do trigger SQS para Lambda Builder" + value = aws_lambda_event_source_mapping.builder_sqs_trigger.uuid +} + +# Outputs da função Lambda de Notificação +output "notification_function_name" { + description = "Nome da função Lambda de Notificação" + value = aws_lambda_function.notification_function.function_name +} + +output "notification_function_arn" { + description = "ARN da função Lambda de Notificação" + value = aws_lambda_function.notification_function.arn +} + +output "notification_invoke_arn" { + description = "ARN para invocar a função Lambda de Notificação" + value = aws_lambda_function.notification_function.invoke_arn +} + +output "notification_role_arn" { + description = "ARN da role IAM da função Lambda de Notificação" + value = aws_iam_role.lambda_notification_execution_role.arn +} + +# Output do Event Source Mapping (trigger SQS → Lambda Notification) +output "notification_sqs_trigger_uuid" { + description = "UUID do trigger SQS para Lambda de Notificação" + value = aws_lambda_event_source_mapping.notification_sqs_trigger.uuid +} diff --git a/terraform/modules/05.lambda/variables.tf b/terraform/modules/05.lambda/variables.tf new file mode 100644 index 0000000..0c17ef5 --- /dev/null +++ b/terraform/modules/05.lambda/variables.tf @@ -0,0 +1,162 @@ +# Variáveis para o módulo Lambda + +variable "project_name" { + description = "Nome do projeto" + type = string +} + +variable "environment" { + description = "Ambiente de deploy (dev, staging, prod)" + type = string +} + +variable "aws_region" { + description = "Região AWS" + type = string +} + +variable "ecr_api_repository_name" { + description = "Nome do repositório ECR" + type = string +} + +variable "ecr_api_repository_url" { + description = "URL do repositório ECR" + type = string +} + +variable "ecr_builder_repository_name" { + description = "Nome do repositório ECR" + type = string +} + +variable "ecr_builder_repository_url" { + description = "URL do repositório ECR" + type = string +} + +variable "ecr_builder_repository_arn" { + description = "ARN do repositório ECR" + type = string +} + +variable "ecr_notification_repository_name" { + description = "Nome do repositório ECR" + type = string +} + +variable "ecr_notification_repository_url" { + description = "URL do repositório ECR" + type = string +} + +variable "ecr_notification_repository_arn" { + description = "ARN do repositório ECR" + type = string +} + +variable "image_tag" { + description = "Tag da imagem Docker" + type = string + default = "latest" +} + +variable "lambda_timeout" { + description = "Timeout da função Lambda em segundos" + type = number + default = 60 +} + +variable "lambda_memory_size" { + description = "Memória alocada para a função Lambda em MB" + type = number + default = 512 +} + +variable "log_retention_days" { + description = "Dias de retenção dos logs do CloudWatch" + type = number + default = 3 +} + + +variable "builder_queue_url" { + description = "URL da fila SQS builder" + type = string +} + +variable "url_service_tech" { + description = "URL do serviço Tech Floripa" + type = string +} + +variable "prefix_api_version" { + description = "Prefixo da versão da API" + type = string + default = "/api/v1" +} + +# ARNs das tabelas DynamoDB para políticas IAM +variable "dynamodb_table_arns" { + description = "ARNs das tabelas DynamoDB" + type = list(string) +} + + +variable "builder_queue_arn" { + description = "ARN da fila SQS builder" + type = string +} + +variable "notification_queue_arn" { + description = "ARN da fila SQS notification" + type = string +} + +variable "notification_queue_url" { + description = "URL da fila SQS notification" + type = string +} + +# ARN do repositório ECR para políticas IAM +variable "ecr_repository_arn" { + description = "ARN do repositório ECR" + type = string +} + +# ARN do bucket S3 para políticas IAM +variable "s3_bucket_arn" { + description = "ARN do bucket S3" + type = string +} + +variable "s3_bucket_name" { + description = "Nome do bucket S3" + type = string +} + +variable "api_gateway_download_url" { + description = "URL base do endpoint de download do API Gateway" + type = string +} + +variable "service_url_registration_api_solana" { + description = "URL do serviço de registro da API Solana" + type = string +} + +variable "service_api_key_registration_api_solana" { + description = "API Key do serviço de registro da API Solana" + type = string + sensitive = true +} + +variable "tech_floripa_certificate_validate_url" { + description = "URL para validação de certificados do Tech Floripa" + type = string +} + +variable "tech_floripa_logo_url" { + description = "URL do logo do Tech Floripa" + type = string +} diff --git a/terraform/modules/06.api-gateway/README.md b/terraform/modules/06.api-gateway/README.md new file mode 100644 index 0000000..c428e3d --- /dev/null +++ b/terraform/modules/06.api-gateway/README.md @@ -0,0 +1,175 @@ +# API Gateway - Estrutura Escalável + +Este módulo implementa uma organização escalável e dinâmica para o AWS API Gateway usando Terraform. + +## ✨ Vantagens da Nova Estrutura + +### 🔄 **Antes (Código Manual)** +- ❌ Cada endpoint precisava de 4-5 recursos separados +- ❌ Código repetitivo e difícil manutenção +- ❌ Para adicionar `/api/v1/certificate/list` = +15 linhas de código +- ❌ CORS configurado manualmente para cada endpoint + +### 🚀 **Depois (Código Dinâmico)** +- ✅ Estrutura declarativa em `locals` +- ✅ Recursos criados automaticamente com `for_each` +- ✅ Para adicionar `/api/v1/certificate/list` = +7 linhas simples +- ✅ CORS automático para todos os endpoints + +## 📖 Como Adicionar Novos Endpoints + +### 1. **Novo Endpoint no Recurso Existente** + +Para adicionar `GET /api/v1/certificate/list`: + +```hcl +# No locals.api_structure, dentro de certificate.endpoints: +"list" = { + method = "GET" + authorization = "NONE" + lambda_integration = true + cors_enabled = true +} +``` + +### 2. **Novo Recurso Completo** + +Para adicionar `POST /api/v1/orders/create`: + +```hcl +# No locals.api_structure, dentro de v1: +"orders" = { + endpoints = { + "create" = { + method = "POST" + authorization = "NONE" + lambda_integration = true + cors_enabled = true + } + "list" = { + method = "GET" + authorization = "NONE" + lambda_integration = true + cors_enabled = true + } + } +} +``` + +### 3. **Nova Versão da API** + +Para adicionar `POST /api/v2/certificate/create`: + +```hcl +# No locals.api_structure, dentro de api: +"v2" = { + "certificate" = { + endpoints = { + "create" = { + method = "POST" + authorization = "NONE" + lambda_integration = true + cors_enabled = true + } + } + } +} +``` + +## ⚙️ Configurações Disponíveis + +### Opções por Endpoint: + +```hcl +"endpoint_name" = { + method = "POST|GET|PUT|DELETE|PATCH" # Método HTTP + authorization = "NONE|AWS_IAM|COGNITO_USER_POOLS" # Tipo de autorização + lambda_integration = true|false # Integração com Lambda + cors_enabled = true|false # Habilitar CORS automático +} +``` + +## 🔧 Funcionalidades Automáticas + +### 1. **Recursos Hierárquicos** +- Cria automaticamente toda a estrutura de recursos +- Gerencia dependências entre níveis +- Evita duplicação de recursos + +### 2. **CORS Inteligente** +- Métodos OPTIONS criados automaticamente +- Headers CORS configurados dinamicamente +- Permite métodos específicos por endpoint + +### 3. **Deployment Inteligente** +- Trigger automático quando estrutura muda +- Dependency tracking completo +- Zero downtime com `create_before_destroy` + +### 4. **Integração Lambda** +- Uma única permissão para todos os endpoints +- Proxy integration automática +- URN flexível para múltiplas funções + +## 📊 Exemplo de Estrutura Completa + +```hcl +api_structure = { + "api" = { + "v1" = { + "certificate" = { + endpoints = { + "create" = { method = "POST", authorization = "NONE", lambda_integration = true, cors_enabled = true } + "list" = { method = "GET", authorization = "NONE", lambda_integration = true, cors_enabled = true } + "status" = { method = "GET", authorization = "NONE", lambda_integration = true, cors_enabled = true } + } + } + "orders" = { + endpoints = { + "create" = { method = "POST", authorization = "NONE", lambda_integration = true, cors_enabled = true } + "list" = { method = "GET", authorization = "NONE", lambda_integration = true, cors_enabled = true } + } + } + "health" = { + endpoints = { + "check" = { method = "GET", authorization = "NONE", lambda_integration = true, cors_enabled = false } + } + } + } + "v2" = { + "certificate" = { + endpoints = { + "create" = { method = "POST", authorization = "AWS_IAM", lambda_integration = true, cors_enabled = true } + } + } + } + } +} +``` + +### URLs Geradas: +- `POST /api/v1/certificate/create` +- `GET /api/v1/certificate/list` +- `GET /api/v1/certificate/status` +- `POST /api/v1/orders/create` +- `GET /api/v1/orders/list` +- `GET /api/v1/health/check` +- `POST /api/v2/certificate/create` + +## 🚦 Migration Path + +Para migrar endpoints existentes: + +1. **Mantenha o código antigo** temporariamente +2. **Adicione o endpoint na nova estrutura** +3. **Teste que ambos funcionam** +4. **Remova o código antigo** +5. **Execute `terraform apply`** + +## 💡 Melhores Práticas + +1. **Sempre use `cors_enabled = true`** para endpoints web +2. **Mantenha estrutura hierárquica consistente** +3. **Use nomes descritivos para endpoints** +4. **Considere autenticação por endpoint** +5. **Teste mudanças em ambiente de desenvolvimento primeiro** diff --git a/terraform/modules/06.api-gateway/main.tf b/terraform/modules/06.api-gateway/main.tf new file mode 100644 index 0000000..b129284 --- /dev/null +++ b/terraform/modules/06.api-gateway/main.tf @@ -0,0 +1,440 @@ +# Módulo API Gateway REST para Certified Builder API +# Responsável por criar o API Gateway REST com integração ao Lambda de forma escalável + +# Definição da estrutura de endpoints da API de forma declarativa +locals { + # Estrutura hierárquica dos endpoints + api_structure = { + "api" = { + "v1" = { + "certificate" = { + endpoints = { + "create" = { + method = "POST" + authorization = "NONE" + api_key_required = true # Exige API Key + lambda_integration = true + cors_enabled = true + } + # Exemplo de como adicionar novos endpoints facilmente: + # "list" = { + # method = "GET" + # authorization = "NONE" + # lambda_integration = true + # cors_enabled = true + # } + "fetch" = { + method = "GET" + query_string_parameters = { + /* Endpoint unificado para busca de certificados. + Suporta busca por order_id, email, product_id ou combinações. + + Query Parameters (todos opcionais, mas pelo menos um deve ser fornecido): + - order_id: Busca certificados por ID do pedido (string) + - email: Busca certificados por email do participante (string válido) + - product_id: Busca certificados por ID do produto (string) + + Combinações válidas: + - order_id (sozinho) + - email (sozinho) + - product_id (sozinho) + - email + product_id (combinação específica) + - order_id + product_id (para filtragem adicional) + */ + "order_id" = false # Query parameter opcional + "email" = false # Query parameter opcional + "product_id" = false # Query parameter opcional + } + authorization = "NONE" + api_key_required = true # Exige API Key + lambda_integration = true + cors_enabled = true + # Validação será realizada no Lambda, não no API Gateway + # pois precisamos verificar se pelo menos um parâmetro foi fornecido + } + "download" = { + method = "GET" + query_string_parameters = { + /* Endpoint para download de certificados via URL pré-assinada do S3. + + Query Parameters: + - id: UUID do certificado (obrigatório) + + Retorna: + - HTML com redirecionamento automático para URL pré-assinada (30min) + - HTML informativo se certificado não foi gerado + - 404 se certificado não encontrado ou UUID inválido + */ + "id" = true # UUID do certificado (obrigatório) + } + authorization = "NONE" + api_key_required = false # Não exige API Key para downloads + lambda_integration = true + cors_enabled = true + } + } + } + # Exemplo de como adicionar novos recursos facilmente: + # "orders" = { + # endpoints = { + # "create" = { + # method = "POST" + # authorization = "NONE" + # lambda_integration = true + # cors_enabled = true + # } + # } + # } + } + } + } + + # Flatten da estrutura para criar recursos dinamicamente + api_paths = flatten([ + for level1_key, level1_value in local.api_structure : [ + for level2_key, level2_value in level1_value : [ + for level3_key, level3_value in level2_value : [ + for endpoint_key, endpoint_config in level3_value.endpoints : { + path_key = "${level1_key}-${level2_key}-${level3_key}-${endpoint_key}" + level1 = level1_key + level2 = level2_key + level3 = level3_key + endpoint = endpoint_key + full_path = "/${level1_key}/${level2_key}/${level3_key}/${endpoint_key}" + config = endpoint_config + } + ] + ] + ] + ]) + + # Criar mapa para facilitar referências + paths_map = { + for path in local.api_paths : path.path_key => path + } +} + +# API Gateway REST API +resource "aws_api_gateway_rest_api" "api" { + name = "${var.project_name}-api-${var.environment}" + description = "API REST para Certified Builder API - ${var.environment}" + + endpoint_configuration { + types = ["REGIONAL"] + } + + tags = { + Name = "${var.project_name}-api-${var.environment}" + Environment = var.environment + Project = var.project_name + } +} + +# Criação dinâmica dos recursos de primeiro nível (/api) +resource "aws_api_gateway_resource" "level1_resources" { + for_each = toset([for path in local.api_paths : path.level1]) + + rest_api_id = aws_api_gateway_rest_api.api.id + parent_id = aws_api_gateway_rest_api.api.root_resource_id + path_part = each.value +} + +# Criação dinâmica dos recursos de segundo nível (/api/v1) +resource "aws_api_gateway_resource" "level2_resources" { + for_each = toset([for path in local.api_paths : "${path.level1}/${path.level2}"]) + + rest_api_id = aws_api_gateway_rest_api.api.id + parent_id = aws_api_gateway_resource.level1_resources[split("/", each.value)[0]].id + path_part = split("/", each.value)[1] +} + +# Criação dinâmica dos recursos de terceiro nível (/api/v1/certificate) +resource "aws_api_gateway_resource" "level3_resources" { + for_each = toset([for path in local.api_paths : "${path.level1}/${path.level2}/${path.level3}"]) + + rest_api_id = aws_api_gateway_rest_api.api.id + parent_id = aws_api_gateway_resource.level2_resources["${split("/", each.value)[0]}/${split("/", each.value)[1]}"].id + path_part = split("/", each.value)[2] +} + +# Criação dinâmica dos recursos de quarto nível (/api/v1/certificate/create) +resource "aws_api_gateway_resource" "endpoint_resources" { + for_each = local.paths_map + + rest_api_id = aws_api_gateway_rest_api.api.id + parent_id = aws_api_gateway_resource.level3_resources["${each.value.level1}/${each.value.level2}/${each.value.level3}"].id + path_part = each.value.endpoint +} + +# Criação dinâmica dos métodos HTTP +resource "aws_api_gateway_method" "endpoint_methods" { + for_each = local.paths_map + + rest_api_id = aws_api_gateway_rest_api.api.id + resource_id = aws_api_gateway_resource.endpoint_resources[each.key].id + http_method = each.value.config.method + authorization = each.value.config.authorization + api_key_required = lookup(each.value.config, "api_key_required", false) + + # Configuração dos parâmetros de requisição incluindo query parameters + request_parameters = merge( + { + "method.request.header.Content-Type" = true + }, + # Adiciona query parameters se definidos na configuração do endpoint + can(each.value.config.query_string_parameters) ? { + for param_name, required in each.value.config.query_string_parameters : + "method.request.querystring.${param_name}" => required + } : {} + ) +} + +# Criação dinâmica das integrações com Lambda +resource "aws_api_gateway_integration" "lambda_integrations" { + for_each = { + for key, path in local.paths_map : key => path + if path.config.lambda_integration + } + + rest_api_id = aws_api_gateway_rest_api.api.id + resource_id = aws_api_gateway_resource.endpoint_resources[each.key].id + http_method = aws_api_gateway_method.endpoint_methods[each.key].http_method + + integration_http_method = "POST" + type = "AWS_PROXY" + uri = var.lambda_invoke_arn +} + +# Permissão para API Gateway invocar o Lambda +resource "aws_lambda_permission" "api_gateway_invoke" { + statement_id = "AllowExecutionFromAPIGateway" + action = "lambda:InvokeFunction" + function_name = var.lambda_function_name + principal = "apigateway.amazonaws.com" + + # Permite qualquer método do API Gateway (mais flexível para múltiplos endpoints) + source_arn = "${aws_api_gateway_rest_api.api.execution_arn}/*/*" +} + +# Criação dinâmica dos métodos OPTIONS para CORS +resource "aws_api_gateway_method" "cors_methods" { + for_each = { + for key, path in local.paths_map : key => path + if path.config.cors_enabled + } + + rest_api_id = aws_api_gateway_rest_api.api.id + resource_id = aws_api_gateway_resource.endpoint_resources[each.key].id + http_method = "OPTIONS" + authorization = "NONE" +} + +# Criação dinâmica das integrações MOCK para CORS +resource "aws_api_gateway_integration" "cors_integrations" { + for_each = { + for key, path in local.paths_map : key => path + if path.config.cors_enabled + } + + rest_api_id = aws_api_gateway_rest_api.api.id + resource_id = aws_api_gateway_resource.endpoint_resources[each.key].id + http_method = aws_api_gateway_method.cors_methods[each.key].http_method + + type = "MOCK" + + request_templates = { + "application/json" = "{\"statusCode\": 200}" + } +} + +# Criação dinâmica das respostas CORS +resource "aws_api_gateway_method_response" "cors_responses" { + for_each = { + for key, path in local.paths_map : key => path + if path.config.cors_enabled + } + + rest_api_id = aws_api_gateway_rest_api.api.id + resource_id = aws_api_gateway_resource.endpoint_resources[each.key].id + http_method = aws_api_gateway_method.cors_methods[each.key].http_method + status_code = "200" + + response_parameters = { + "method.response.header.Access-Control-Allow-Headers" = true + "method.response.header.Access-Control-Allow-Methods" = true + "method.response.header.Access-Control-Allow-Origin" = true + } +} + +# Criação dinâmica das respostas de integração CORS +resource "aws_api_gateway_integration_response" "cors_integration_responses" { + for_each = { + for key, path in local.paths_map : key => path + if path.config.cors_enabled + } + + rest_api_id = aws_api_gateway_rest_api.api.id + resource_id = aws_api_gateway_resource.endpoint_resources[each.key].id + http_method = aws_api_gateway_method.cors_methods[each.key].http_method + status_code = aws_api_gateway_method_response.cors_responses[each.key].status_code + + response_parameters = { + "method.response.header.Access-Control-Allow-Headers" = "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" + "method.response.header.Access-Control-Allow-Methods" = "'${each.value.config.method},OPTIONS'" + "method.response.header.Access-Control-Allow-Origin" = "'*'" + } +} + +# Deployment do API Gateway com triggers dinâmicos +resource "aws_api_gateway_deployment" "api_deployment" { + depends_on = [ + aws_api_gateway_method.endpoint_methods, + aws_api_gateway_integration.lambda_integrations, + aws_api_gateway_method.cors_methods, + aws_api_gateway_integration.cors_integrations + ] + + rest_api_id = aws_api_gateway_rest_api.api.id + + # Triggers dinâmicos baseados na estrutura da API + triggers = { + redeployment = sha1(jsonencode([ + local.api_structure, + var.lambda_invoke_arn + ])) + } + + lifecycle { + create_before_destroy = true + } +} + +# Stage do API Gateway +resource "aws_api_gateway_stage" "api_stage" { + deployment_id = aws_api_gateway_deployment.api_deployment.id + rest_api_id = aws_api_gateway_rest_api.api.id + stage_name = var.environment + + # Configuração de logs (desabilitada por padrão para economizar) + xray_tracing_enabled = false + + tags = { + Name = "${var.project_name}-api-stage-${var.environment}" + Environment = var.environment + Project = var.project_name + } +} + +# Configuração de throttling para controle de custos +resource "aws_api_gateway_method_settings" "api_throttling" { + rest_api_id = aws_api_gateway_rest_api.api.id + stage_name = aws_api_gateway_stage.api_stage.stage_name + method_path = "*/*" + + settings { + throttling_rate_limit = var.throttle_rate_limit + throttling_burst_limit = var.throttle_burst_limit + logging_level = "OFF" + data_trace_enabled = false + metrics_enabled = false + } +} + +# ============================== +# CONFIGURAÇÃO DE API KEY +# ============================== + +# API Key para autenticação +resource "aws_api_gateway_api_key" "main_api_key" { + name = "${var.project_name}-api-key-${var.environment}" + description = "API Key para autenticação do ${var.project_name} em ${var.environment}" + enabled = true + + # Valor da API Key configurável via variável + value = var.api_key_value + + tags = { + Name = "${var.project_name}-api-key-${var.environment}" + Environment = var.environment + Project = var.project_name + } +} + +# Usage Plan para controlar o uso da API Key +resource "aws_api_gateway_usage_plan" "main_usage_plan" { + name = "${var.project_name}-usage-plan-${var.environment}" + description = "Usage plan para ${var.project_name} em ${var.environment}" + + api_stages { + api_id = aws_api_gateway_rest_api.api.id + stage = aws_api_gateway_stage.api_stage.stage_name + } + + quota_settings { + limit = 1000 # 1k requests por mês + period = "MONTH" + } + + throttle_settings { + rate_limit = var.throttle_rate_limit # requests por segundo + burst_limit = var.throttle_burst_limit # burst máximo + } + + tags = { + Name = "${var.project_name}-usage-plan-${var.environment}" + Environment = var.environment + Project = var.project_name + } +} + +# Vinculação da API Key ao Usage Plan +resource "aws_api_gateway_usage_plan_key" "main_usage_plan_key" { + key_id = aws_api_gateway_api_key.main_api_key.id + key_type = "API_KEY" + usage_plan_id = aws_api_gateway_usage_plan.main_usage_plan.id +} + +# Method settings específicos para endpoint de download com rate limit muito baixo +resource "aws_api_gateway_method_settings" "download_throttling" { + rest_api_id = aws_api_gateway_rest_api.api.id + stage_name = aws_api_gateway_stage.api_stage.stage_name + method_path = "*/certificate/download/GET" + + settings { + throttling_rate_limit = 0.083 # ~5 requests por minuto + throttling_burst_limit = 2 # burst máximo muito baixo + metrics_enabled = true + } +} + +# Usage Plan específico para endpoint de download com rate limit muito baixo +resource "aws_api_gateway_usage_plan" "download_usage_plan" { + name = "${var.project_name}-download-usage-plan-${var.environment}" + description = "Usage plan restritivo para endpoint de download de certificados" + + api_stages { + api_id = aws_api_gateway_rest_api.api.id + stage = aws_api_gateway_stage.api_stage.stage_name + } + + quota_settings { + limit = 50 # 50 requests por mês + period = "MONTH" + } + + throttle_settings { + rate_limit = 0.083 # ~5 requests por minuto (5/60 = 0.083) + burst_limit = 2 # burst máximo muito baixo + } + + tags = { + Name = "${var.project_name}-download-usage-plan-${var.environment}" + Environment = var.environment + Project = var.project_name + Purpose = "download-rate-limiting" + } +} + +# ============================== +# OUTPUTS PARA API KEY +# ============================== diff --git a/terraform/modules/06.api-gateway/outputs.tf b/terraform/modules/06.api-gateway/outputs.tf new file mode 100644 index 0000000..7cbeaa4 --- /dev/null +++ b/terraform/modules/06.api-gateway/outputs.tf @@ -0,0 +1,56 @@ +# Outputs do módulo API Gateway + +output "api_id" { + description = "ID do API Gateway" + value = aws_api_gateway_rest_api.api.id +} + +output "api_arn" { + description = "ARN do API Gateway" + value = aws_api_gateway_rest_api.api.arn +} + +output "api_url" { + description = "URL base do API Gateway" + value = "https://${aws_api_gateway_rest_api.api.id}.execute-api.${data.aws_region.current.id}.amazonaws.com/${aws_api_gateway_stage.api_stage.stage_name}" +} + +output "api_endpoint_create_certificate" { + description = "URL completa do endpoint para criar certificado" + value = "https://${aws_api_gateway_rest_api.api.id}.execute-api.${data.aws_region.current.id}.amazonaws.com/${aws_api_gateway_stage.api_stage.stage_name}/api/v1/certificate/create" +} + +output "stage_name" { + description = "Nome do stage do API Gateway" + value = aws_api_gateway_stage.api_stage.stage_name +} + +# Outputs da API Key +output "api_key_id" { + description = "ID da API Key" + value = aws_api_gateway_api_key.main_api_key.id +} + +output "api_key_value" { + description = "Valor da API Key (sensível)" + value = aws_api_gateway_api_key.main_api_key.value + sensitive = true +} + +output "usage_plan_id" { + description = "ID do Usage Plan" + value = aws_api_gateway_usage_plan.main_usage_plan.id +} + +output "api_endpoint_fetch_certificate" { + description = "URL completa do endpoint para buscar certificados" + value = "https://${aws_api_gateway_rest_api.api.id}.execute-api.${data.aws_region.current.id}.amazonaws.com/${aws_api_gateway_stage.api_stage.stage_name}/api/v1/certificate/fetch" +} + +output "api_endpoint_download_certificate" { + description = "URL base do endpoint para download de certificados" + value = "https://${aws_api_gateway_rest_api.api.id}.execute-api.${data.aws_region.current.id}.amazonaws.com/${aws_api_gateway_stage.api_stage.stage_name}/api/v1/certificate/download" +} + +# Data source para obter a região atual +data "aws_region" "current" {} diff --git a/terraform/modules/06.api-gateway/variables.tf b/terraform/modules/06.api-gateway/variables.tf new file mode 100644 index 0000000..4d2a42b --- /dev/null +++ b/terraform/modules/06.api-gateway/variables.tf @@ -0,0 +1,44 @@ +# Variáveis para o módulo API Gateway + +variable "project_name" { + description = "Nome do projeto" + type = string +} + +variable "environment" { + description = "Ambiente de deploy (dev, staging, prod)" + type = string +} + +variable "lambda_function_name" { + description = "Nome da função Lambda" + type = string +} + +variable "lambda_invoke_arn" { + description = "ARN para invocar a função Lambda" + type = string +} + +variable "throttle_rate_limit" { + description = "Limite de taxa por segundo para throttling" + type = number + default = 100 +} + +variable "throttle_burst_limit" { + description = "Limite de burst para throttling" + type = number + default = 200 +} + +variable "api_key_value" { + description = "Valor da API Key para autenticação (deve ser um UUID válido)" + type = string + sensitive = true + + validation { + condition = can(regex("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$", var.api_key_value)) + error_message = "A API Key deve estar no formato UUID válido (ex: 8a3f1e2c-9d7b-4f4a-8453-bf3c1d2a6f29)." + } +} diff --git a/terraform/modules/API_KEY_USAGE.md b/terraform/modules/API_KEY_USAGE.md new file mode 100644 index 0000000..5606070 --- /dev/null +++ b/terraform/modules/API_KEY_USAGE.md @@ -0,0 +1,180 @@ +# Configuração e Uso da API Key + +## 🔑 **Implementação Completa de API Key** + +A API agora exige autenticação via API Key para todos os endpoints protegidos. + +## 📋 **Configuração Implementada** + +### **1. Endpoints que Exigem API Key:** +- `POST /api/v1/certificate/create` +- `GET /api/v1/certificate/fetch` + +### **2. Header Obrigatório:** +``` +X-API-Key: 8a3f1e2c-9d7b-4f4a-8453-bf3c1d2a6f29 +``` + +### **3. Recursos Criados:** +- **API Key**: `aws_api_gateway_api_key.main_api_key` +- **Usage Plan**: `aws_api_gateway_usage_plan.main_usage_plan` +- **Vinculação**: `aws_api_gateway_usage_plan_key.main_usage_plan_key` + +## 🔧 **Como Funciona** + +### **1. Validação no API Gateway:** +- O API Gateway valida automaticamente o header `X-API-Key` +- Se não fornecido ou inválido: **HTTP 403 Forbidden** +- Se válido: request é encaminhado para a Lambda + +### **2. Controle de Uso:** +- **Quota**: 10.000 requests/mês +- **Rate Limit**: Configurável via `throttle_rate_limit` +- **Burst Limit**: Configurável via `throttle_burst_limit` + +## 🚀 **Como Usar na Aplicação** + +### **JavaScript/Node.js:** +```javascript +const response = await fetch('https://your-api-gateway-url/api/v1/certificate/create', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': '8a3f1e2c-9d7b-4f4a-8453-bf3c1d2a6f29' + }, + body: JSON.stringify({ + // seus dados aqui + }) +}); +``` + +### **cURL:** +```bash +curl -X POST \ + 'https://your-api-gateway-url/api/v1/certificate/create' \ + -H 'Content-Type: application/json' \ + -H 'X-API-Key: 8a3f1e2c-9d7b-4f4a-8453-bf3c1d2a6f29' \ + -d '{ + "order_id": "123", + "participant_email": "test@example.com" + }' +``` + +### **Python:** +```python +import requests + +headers = { + 'Content-Type': 'application/json', + 'X-API-Key': '8a3f1e2c-9d7b-4f4a-8453-bf3c1d2a6f29' +} + +response = requests.post( + 'https://your-api-gateway-url/api/v1/certificate/create', + headers=headers, + json={ + 'order_id': '123', + 'participant_email': 'test@example.com' + } +) +``` + +## 🔒 **Gerenciamento da API Key** + +### **1. Valor Atual:** +``` +8a3f1e2c-9d7b-4f4a-8453-bf3c1d2a6f29 +``` + +### **2. Alteração da API Key:** +```hcl +# No arquivo terraform.tfvars +api_key_value = "nova-uuid-aqui" +``` + +### **3. Rotação da API Key:** +1. Altere o valor em `terraform.tfvars` +2. Execute: `terraform apply` +3. Atualize todas as aplicações cliente +4. **Importante**: A API Key antiga para de funcionar imediatamente + +## 📊 **Monitoramento e Outputs** + +### **Outputs Disponíveis:** +```bash +# Obter informações da API Key +terraform output api_key_id +terraform output usage_plan_id + +# API Key value (sensível) +terraform output -json api_key_value +``` + +### **URLs dos Endpoints:** +```bash +# Endpoint de criação +terraform output api_endpoint_create_certificate + +# Endpoint de busca +terraform output api_endpoint_fetch_certificate +``` + +## 🛡️ **Segurança** + +### **1. API Key como Variável Sensível:** +- Marcada como `sensitive = true` +- Não aparece em logs do Terraform +- Protegida no state file + +### **2. Validação de Formato:** +- Só aceita UUIDs válidos +- Formato: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` + +### **3. Controle de Acesso:** +- Usage Plan limita o uso por API Key +- Throttling previne abuso +- Logs de acesso no CloudWatch + +## ⚠️ **Troubleshooting** + +### **403 Forbidden:** +```json +{ + "message": "Forbidden" +} +``` +**Solução**: Verificar se o header `X-API-Key` está correto. + +### **429 Too Many Requests:** +```json +{ + "message": "Too Many Requests" +} +``` +**Solução**: Aguardar ou ajustar limites no Usage Plan. + +### **Verificação da API Key:** +```bash +# No AWS CLI +aws apigateway get-api-key --api-key your-api-key-id --include-value +``` + +## 📝 **Exemplo Completo de Teste** + +```bash +# 1. Obter URL do endpoint +API_URL=$(terraform output -raw api_endpoint_create_certificate) + +# 2. Testar com API Key +curl -X POST "$API_URL" \ + -H "Content-Type: application/json" \ + -H "X-API-Key: 8a3f1e2c-9d7b-4f4a-8453-bf3c1d2a6f29" \ + -d '{"order_id": "test123", "participant_email": "test@example.com"}' + +# 3. Testar SEM API Key (deve falhar) +curl -X POST "$API_URL" \ + -H "Content-Type: application/json" \ + -d '{"order_id": "test123", "participant_email": "test@example.com"}' +``` + +A implementação está completa e todos os endpoints agora exigem a API Key especificada! diff --git a/terraform/variables.tf b/terraform/variables.tf index da51a4f..474a277 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -52,3 +52,87 @@ variable "project_name" { # Configuração do DynamoDB - Modo de Baixo Custo # Todas as tabelas usam PAY_PER_REQUEST por padrão (paga apenas pelo que usar) + +# Variáveis para Lambda Function +variable "lambda_image_tag" { + description = "Tag da imagem Docker para o Lambda" + type = string + default = "latest" +} + +variable "lambda_timeout" { + description = "Timeout da função Lambda em segundos" + type = number + default = 60 +} + +variable "lambda_memory_size" { + description = "Memória alocada para a função Lambda em MB" + type = number + default = 512 +} + +variable "log_retention_days" { + description = "Dias de retenção dos logs do CloudWatch" + type = number + default = 3 +} + +variable "url_service_tech" { + description = "URL do serviço Tech Floripa" + type = string + default = "https://tech.floripa.br/wp-json/custom/v1" +} + +variable "prefix_api_version" { + description = "Prefixo da versão da API" + type = string + default = "/api/v1" +} + +# Variáveis para API Gateway +variable "api_throttle_rate_limit" { + description = "Limite de taxa por segundo para throttling do API Gateway" + type = number + default = 100 +} + +variable "api_throttle_burst_limit" { + description = "Limite de burst para throttling do API Gateway" + type = number + default = 200 +} + +variable "api_key_value" { + description = "Valor da API Key para autenticação do API Gateway (deve ser fornecido via terraform.tfvars)" + type = string + sensitive = true + + validation { + condition = can(regex("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$", var.api_key_value)) + error_message = "A API Key deve estar no formato UUID válido (ex: 8a3f1e2c-9d7b-4f4a-8453-bf3c1d2a6f29)." + } +} + +# Variáveis para integração com API Solana +variable "service_url_registration_api_solana" { + description = "URL do serviço de registro da API Solana" + type = string +} + +variable "service_api_key_registration_api_solana" { + description = "API Key do serviço de registro da API Solana" + type = string + sensitive = true +} + +variable "tech_floripa_certificate_validate_url" { + description = "URL para validação de certificados do Tech Floripa" + type = string +} + +variable "tech_floripa_logo_url" { + description = "URL do logo do Tech Floripa" + type = string + default = "https://tech.floripa.br/wp-content/uploads/2025/03/Tech-Floripa-Qr.png" +}