mirror of
https://github.com/ghndrx/terraform-foundation.git
synced 2026-02-13 00:05:03 +00:00
feat: Terraform Foundation - AWS Landing Zone
Enterprise-grade multi-tenant AWS cloud foundation. Modules: - GitHub OIDC for keyless CI/CD authentication - IAM account settings and security baseline - AWS Config Rules for compliance - ABAC (Attribute-Based Access Control) - SCPs (Service Control Policies) Features: - Multi-account architecture - Cost optimization patterns - Security best practices - Comprehensive documentation Tech: Terraform, AWS Organizations, IAM Identity Center
This commit is contained in:
499
terraform/05-workloads/_template/step-function/main.tf
Normal file
499
terraform/05-workloads/_template/step-function/main.tf
Normal file
@@ -0,0 +1,499 @@
|
||||
################################################################################
|
||||
# Workload: Step Functions State Machine
|
||||
#
|
||||
# Deploys a serverless workflow:
|
||||
# - Step Functions state machine
|
||||
# - IAM role with least-privilege
|
||||
# - CloudWatch logging
|
||||
# - X-Ray tracing
|
||||
# - EventBridge trigger (optional)
|
||||
# - API Gateway trigger (optional)
|
||||
#
|
||||
# Usage:
|
||||
# Copy this folder to 05-workloads/<tenant>-<workflow-name>/
|
||||
# Update the state machine definition in definition.json
|
||||
# terraform init -backend-config=../../00-bootstrap/backend.hcl
|
||||
# terraform apply
|
||||
################################################################################
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.5"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
}
|
||||
|
||||
backend "s3" {
|
||||
key = "05-workloads/<TENANT>-<NAME>-workflow/terraform.tfstate"
|
||||
}
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Configuration - UPDATE THESE
|
||||
################################################################################
|
||||
|
||||
locals {
|
||||
# Naming
|
||||
tenant = "<TENANT>"
|
||||
name = "<NAME>"
|
||||
env = "prod"
|
||||
|
||||
state_machine_name = "${local.tenant}-${local.name}-${local.env}"
|
||||
|
||||
# State machine type: STANDARD or EXPRESS
|
||||
# STANDARD: Long-running (up to 1 year), exactly-once execution
|
||||
# EXPRESS: Short-duration (up to 5 min), at-least-once, cheaper
|
||||
type = "STANDARD"
|
||||
|
||||
# Logging level: OFF, ALL, ERROR, FATAL
|
||||
logging_level = "ERROR"
|
||||
|
||||
# X-Ray tracing
|
||||
tracing_enabled = true
|
||||
|
||||
# EventBridge trigger (set to null to disable)
|
||||
schedule_expression = null # e.g., "rate(1 hour)" or "cron(0 12 * * ? *)"
|
||||
|
||||
# API Gateway trigger
|
||||
enable_api_trigger = false
|
||||
|
||||
# Lambda functions this workflow can invoke (ARNs)
|
||||
lambda_arns = [
|
||||
# "arn:aws:lambda:us-east-1:123456789012:function:my-function",
|
||||
]
|
||||
|
||||
# DynamoDB tables this workflow can access (ARNs)
|
||||
dynamodb_arns = [
|
||||
# "arn:aws:dynamodb:us-east-1:123456789012:table/my-table",
|
||||
]
|
||||
|
||||
# SQS queues this workflow can send to (ARNs)
|
||||
sqs_arns = [
|
||||
# "arn:aws:sqs:us-east-1:123456789012:my-queue",
|
||||
]
|
||||
|
||||
# SNS topics this workflow can publish to (ARNs)
|
||||
sns_arns = [
|
||||
# "arn:aws:sns:us-east-1:123456789012:my-topic",
|
||||
]
|
||||
|
||||
# S3 buckets this workflow can access (ARNs)
|
||||
s3_arns = [
|
||||
# "arn:aws:s3:::my-bucket/*",
|
||||
]
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Variables
|
||||
################################################################################
|
||||
|
||||
variable "region" {
|
||||
type = string
|
||||
default = "us-east-1"
|
||||
}
|
||||
|
||||
variable "state_bucket" {
|
||||
type = string
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Provider
|
||||
################################################################################
|
||||
|
||||
provider "aws" {
|
||||
region = var.region
|
||||
|
||||
default_tags {
|
||||
tags = {
|
||||
Tenant = local.tenant
|
||||
App = local.name
|
||||
Environment = local.env
|
||||
ManagedBy = "terraform"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Data Sources
|
||||
################################################################################
|
||||
|
||||
data "aws_caller_identity" "current" {}
|
||||
data "aws_region" "current" {}
|
||||
|
||||
################################################################################
|
||||
# CloudWatch Log Group
|
||||
################################################################################
|
||||
|
||||
resource "aws_cloudwatch_log_group" "main" {
|
||||
name = "/aws/states/${local.state_machine_name}"
|
||||
retention_in_days = 30
|
||||
|
||||
tags = { Name = local.state_machine_name }
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# IAM Role
|
||||
################################################################################
|
||||
|
||||
resource "aws_iam_role" "state_machine" {
|
||||
name = "${local.state_machine_name}-role"
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Action = "sts:AssumeRole"
|
||||
Principal = { Service = "states.amazonaws.com" }
|
||||
}]
|
||||
})
|
||||
|
||||
tags = { Name = "${local.state_machine_name}-role" }
|
||||
}
|
||||
|
||||
# CloudWatch Logs permissions
|
||||
resource "aws_iam_role_policy" "logs" {
|
||||
name = "cloudwatch-logs"
|
||||
role = aws_iam_role.state_machine.id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"logs:CreateLogDelivery",
|
||||
"logs:CreateLogStream",
|
||||
"logs:GetLogDelivery",
|
||||
"logs:UpdateLogDelivery",
|
||||
"logs:DeleteLogDelivery",
|
||||
"logs:ListLogDeliveries",
|
||||
"logs:PutLogEvents",
|
||||
"logs:PutResourcePolicy",
|
||||
"logs:DescribeResourcePolicies",
|
||||
"logs:DescribeLogGroups"
|
||||
]
|
||||
Resource = "*"
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
# X-Ray permissions
|
||||
resource "aws_iam_role_policy" "xray" {
|
||||
count = local.tracing_enabled ? 1 : 0
|
||||
name = "xray"
|
||||
role = aws_iam_role.state_machine.id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"xray:PutTraceSegments",
|
||||
"xray:PutTelemetryRecords",
|
||||
"xray:GetSamplingRules",
|
||||
"xray:GetSamplingTargets"
|
||||
]
|
||||
Resource = "*"
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
# Lambda invocation permissions
|
||||
resource "aws_iam_role_policy" "lambda" {
|
||||
count = length(local.lambda_arns) > 0 ? 1 : 0
|
||||
name = "lambda"
|
||||
role = aws_iam_role.state_machine.id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Action = "lambda:InvokeFunction"
|
||||
Resource = local.lambda_arns
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
# DynamoDB permissions
|
||||
resource "aws_iam_role_policy" "dynamodb" {
|
||||
count = length(local.dynamodb_arns) > 0 ? 1 : 0
|
||||
name = "dynamodb"
|
||||
role = aws_iam_role.state_machine.id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"dynamodb:GetItem",
|
||||
"dynamodb:PutItem",
|
||||
"dynamodb:UpdateItem",
|
||||
"dynamodb:DeleteItem",
|
||||
"dynamodb:Query",
|
||||
"dynamodb:Scan"
|
||||
]
|
||||
Resource = local.dynamodb_arns
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
# SQS permissions
|
||||
resource "aws_iam_role_policy" "sqs" {
|
||||
count = length(local.sqs_arns) > 0 ? 1 : 0
|
||||
name = "sqs"
|
||||
role = aws_iam_role.state_machine.id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"sqs:SendMessage",
|
||||
"sqs:GetQueueUrl"
|
||||
]
|
||||
Resource = local.sqs_arns
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
# SNS permissions
|
||||
resource "aws_iam_role_policy" "sns" {
|
||||
count = length(local.sns_arns) > 0 ? 1 : 0
|
||||
name = "sns"
|
||||
role = aws_iam_role.state_machine.id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Action = "sns:Publish"
|
||||
Resource = local.sns_arns
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
# S3 permissions
|
||||
resource "aws_iam_role_policy" "s3" {
|
||||
count = length(local.s3_arns) > 0 ? 1 : 0
|
||||
name = "s3"
|
||||
role = aws_iam_role.state_machine.id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"s3:GetObject",
|
||||
"s3:PutObject",
|
||||
"s3:DeleteObject"
|
||||
]
|
||||
Resource = local.s3_arns
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# State Machine Definition
|
||||
################################################################################
|
||||
|
||||
# Simple example - replace with your actual workflow
|
||||
locals {
|
||||
state_machine_definition = jsonencode({
|
||||
Comment = "Example workflow for ${local.tenant} ${local.name}"
|
||||
StartAt = "ProcessInput"
|
||||
States = {
|
||||
ProcessInput = {
|
||||
Type = "Pass"
|
||||
Parameters = {
|
||||
"input.$" = "$"
|
||||
"timestamp" = "$$.State.EnteredTime"
|
||||
}
|
||||
Next = "Success"
|
||||
}
|
||||
Success = {
|
||||
Type = "Succeed"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Step Functions State Machine
|
||||
################################################################################
|
||||
|
||||
resource "aws_sfn_state_machine" "main" {
|
||||
name = local.state_machine_name
|
||||
role_arn = aws_iam_role.state_machine.arn
|
||||
type = local.type
|
||||
|
||||
definition = local.state_machine_definition
|
||||
|
||||
logging_configuration {
|
||||
log_destination = "${aws_cloudwatch_log_group.main.arn}:*"
|
||||
include_execution_data = true
|
||||
level = local.logging_level
|
||||
}
|
||||
|
||||
tracing_configuration {
|
||||
enabled = local.tracing_enabled
|
||||
}
|
||||
|
||||
tags = { Name = local.state_machine_name }
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# EventBridge Schedule Trigger
|
||||
################################################################################
|
||||
|
||||
resource "aws_cloudwatch_event_rule" "schedule" {
|
||||
count = local.schedule_expression != null ? 1 : 0
|
||||
name = "${local.state_machine_name}-schedule"
|
||||
description = "Trigger ${local.state_machine_name} on schedule"
|
||||
schedule_expression = local.schedule_expression
|
||||
|
||||
tags = { Name = "${local.state_machine_name}-schedule" }
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_event_target" "schedule" {
|
||||
count = local.schedule_expression != null ? 1 : 0
|
||||
rule = aws_cloudwatch_event_rule.schedule[0].name
|
||||
target_id = "StepFunctions"
|
||||
arn = aws_sfn_state_machine.main.arn
|
||||
role_arn = aws_iam_role.eventbridge[0].arn
|
||||
|
||||
input = jsonencode({
|
||||
source = "scheduled"
|
||||
timestamp = "$.time"
|
||||
})
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "eventbridge" {
|
||||
count = local.schedule_expression != null ? 1 : 0
|
||||
name = "${local.state_machine_name}-eventbridge"
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Action = "sts:AssumeRole"
|
||||
Principal = { Service = "events.amazonaws.com" }
|
||||
}]
|
||||
})
|
||||
|
||||
tags = { Name = "${local.state_machine_name}-eventbridge" }
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "eventbridge" {
|
||||
count = local.schedule_expression != null ? 1 : 0
|
||||
name = "start-execution"
|
||||
role = aws_iam_role.eventbridge[0].id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Action = "states:StartExecution"
|
||||
Resource = aws_sfn_state_machine.main.arn
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# API Gateway Trigger
|
||||
################################################################################
|
||||
|
||||
resource "aws_apigatewayv2_api" "main" {
|
||||
count = local.enable_api_trigger ? 1 : 0
|
||||
name = local.state_machine_name
|
||||
protocol_type = "HTTP"
|
||||
|
||||
tags = { Name = local.state_machine_name }
|
||||
}
|
||||
|
||||
resource "aws_apigatewayv2_stage" "main" {
|
||||
count = local.enable_api_trigger ? 1 : 0
|
||||
api_id = aws_apigatewayv2_api.main[0].id
|
||||
name = "$default"
|
||||
auto_deploy = true
|
||||
}
|
||||
|
||||
resource "aws_apigatewayv2_integration" "main" {
|
||||
count = local.enable_api_trigger ? 1 : 0
|
||||
api_id = aws_apigatewayv2_api.main[0].id
|
||||
integration_type = "AWS_PROXY"
|
||||
integration_subtype = "StepFunctions-StartExecution"
|
||||
credentials_arn = aws_iam_role.api[0].arn
|
||||
|
||||
request_parameters = {
|
||||
StateMachineArn = aws_sfn_state_machine.main.arn
|
||||
Input = "$request.body"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_apigatewayv2_route" "main" {
|
||||
count = local.enable_api_trigger ? 1 : 0
|
||||
api_id = aws_apigatewayv2_api.main[0].id
|
||||
route_key = "POST /execute"
|
||||
target = "integrations/${aws_apigatewayv2_integration.main[0].id}"
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "api" {
|
||||
count = local.enable_api_trigger ? 1 : 0
|
||||
name = "${local.state_machine_name}-api"
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Action = "sts:AssumeRole"
|
||||
Principal = { Service = "apigateway.amazonaws.com" }
|
||||
}]
|
||||
})
|
||||
|
||||
tags = { Name = "${local.state_machine_name}-api" }
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "api" {
|
||||
count = local.enable_api_trigger ? 1 : 0
|
||||
name = "start-execution"
|
||||
role = aws_iam_role.api[0].id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Action = "states:StartExecution"
|
||||
Resource = aws_sfn_state_machine.main.arn
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Outputs
|
||||
################################################################################
|
||||
|
||||
output "state_machine_arn" {
|
||||
value = aws_sfn_state_machine.main.arn
|
||||
}
|
||||
|
||||
output "state_machine_name" {
|
||||
value = aws_sfn_state_machine.main.name
|
||||
}
|
||||
|
||||
output "role_arn" {
|
||||
value = aws_iam_role.state_machine.arn
|
||||
}
|
||||
|
||||
output "log_group" {
|
||||
value = aws_cloudwatch_log_group.main.name
|
||||
}
|
||||
|
||||
output "api_endpoint" {
|
||||
value = local.enable_api_trigger ? "${aws_apigatewayv2_api.main[0].api_endpoint}/execute" : null
|
||||
}
|
||||
|
||||
output "execution_command" {
|
||||
value = "aws stepfunctions start-execution --state-machine-arn ${aws_sfn_state_machine.main.arn} --input '{\"key\": \"value\"}'"
|
||||
}
|
||||
Reference in New Issue
Block a user