mirror of
https://github.com/ghndrx/terraform-foundation.git
synced 2026-02-10 06:45:06 +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:
501
terraform/05-workloads/_template/cognito-auth/main.tf
Normal file
501
terraform/05-workloads/_template/cognito-auth/main.tf
Normal file
@@ -0,0 +1,501 @@
|
||||
################################################################################
|
||||
# Workload: Cognito User Pool
|
||||
#
|
||||
# User authentication infrastructure:
|
||||
# - User Pool with customizable password policy
|
||||
# - App clients (web, mobile, machine-to-machine)
|
||||
# - Identity Pool for AWS credential federation
|
||||
# - Social/SAML/OIDC identity providers
|
||||
# - Custom domain
|
||||
# - Lambda triggers (pre/post auth, migration)
|
||||
#
|
||||
# Use cases: Web/mobile auth, B2C apps, admin portals
|
||||
################################################################################
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.5"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
}
|
||||
|
||||
backend "s3" {
|
||||
key = "05-workloads/<TENANT>-<NAME>-auth/terraform.tfstate"
|
||||
}
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Configuration - UPDATE THESE
|
||||
################################################################################
|
||||
|
||||
locals {
|
||||
# Naming
|
||||
tenant = "<TENANT>"
|
||||
name = "<NAME>"
|
||||
env = "prod"
|
||||
|
||||
pool_name = "${local.tenant}-${local.name}-${local.env}"
|
||||
|
||||
# Email configuration
|
||||
email_sending_account = "COGNITO_DEFAULT" # COGNITO_DEFAULT or DEVELOPER
|
||||
ses_email_from = null # Required if DEVELOPER
|
||||
|
||||
# Password policy
|
||||
password_minimum_length = 12
|
||||
password_require_lowercase = true
|
||||
password_require_numbers = true
|
||||
password_require_symbols = true
|
||||
password_require_uppercase = true
|
||||
temporary_password_validity_days = 7
|
||||
|
||||
# MFA
|
||||
mfa_configuration = "OPTIONAL" # OFF, ON, OPTIONAL
|
||||
mfa_methods = ["SOFTWARE_TOKEN_MFA"] # SOFTWARE_TOKEN_MFA, SMS_MFA
|
||||
|
||||
# Account recovery
|
||||
recovery_mechanisms = [
|
||||
{ name = "verified_email", priority = 1 },
|
||||
{ name = "verified_phone_number", priority = 2 }
|
||||
]
|
||||
|
||||
# User attributes
|
||||
auto_verified_attributes = ["email"]
|
||||
username_attributes = ["email"] # email, phone_number
|
||||
alias_attributes = [] # email, phone_number, preferred_username
|
||||
|
||||
# Custom attributes
|
||||
custom_attributes = {
|
||||
# "tenant_id" = {
|
||||
# type = "String"
|
||||
# mutable = false
|
||||
# min_length = 1
|
||||
# max_length = 50
|
||||
# }
|
||||
}
|
||||
|
||||
# App clients
|
||||
app_clients = {
|
||||
web = {
|
||||
generate_secret = false
|
||||
explicit_auth_flows = ["ALLOW_USER_SRP_AUTH", "ALLOW_REFRESH_TOKEN_AUTH"]
|
||||
supported_identity_providers = ["COGNITO"]
|
||||
callback_urls = ["https://example.com/callback"]
|
||||
logout_urls = ["https://example.com/logout"]
|
||||
allowed_oauth_flows = ["code"]
|
||||
allowed_oauth_scopes = ["email", "openid", "profile"]
|
||||
allowed_oauth_flows_user_pool_client = true
|
||||
access_token_validity = 60 # minutes
|
||||
id_token_validity = 60
|
||||
refresh_token_validity = 30 # days
|
||||
}
|
||||
mobile = {
|
||||
generate_secret = false
|
||||
explicit_auth_flows = ["ALLOW_USER_SRP_AUTH", "ALLOW_REFRESH_TOKEN_AUTH"]
|
||||
supported_identity_providers = ["COGNITO"]
|
||||
callback_urls = ["myapp://callback"]
|
||||
logout_urls = ["myapp://logout"]
|
||||
allowed_oauth_flows = ["code"]
|
||||
allowed_oauth_scopes = ["email", "openid", "profile"]
|
||||
allowed_oauth_flows_user_pool_client = true
|
||||
access_token_validity = 60
|
||||
id_token_validity = 60
|
||||
refresh_token_validity = 30
|
||||
}
|
||||
# m2m = {
|
||||
# generate_secret = true
|
||||
# explicit_auth_flows = ["ALLOW_ADMIN_USER_PASSWORD_AUTH"]
|
||||
# supported_identity_providers = ["COGNITO"]
|
||||
# allowed_oauth_flows = ["client_credentials"]
|
||||
# allowed_oauth_scopes = ["api/read", "api/write"]
|
||||
# allowed_oauth_flows_user_pool_client = true
|
||||
# }
|
||||
}
|
||||
|
||||
# Custom domain (requires ACM cert in us-east-1 for CloudFront)
|
||||
custom_domain = null # e.g., "auth.example.com"
|
||||
custom_domain_cert = null # ACM certificate ARN
|
||||
hosted_zone_id = null
|
||||
|
||||
# Identity Pool (for AWS credential federation)
|
||||
enable_identity_pool = false
|
||||
|
||||
# Lambda triggers
|
||||
lambda_triggers = {
|
||||
# pre_sign_up = "arn:aws:lambda:..."
|
||||
# post_confirmation = "arn:aws:lambda:..."
|
||||
# pre_authentication = "arn:aws:lambda:..."
|
||||
# post_authentication = "arn:aws:lambda:..."
|
||||
# pre_token_generation = "arn:aws:lambda:..."
|
||||
# user_migration = "arn:aws:lambda:..."
|
||||
# custom_message = "arn:aws:lambda:..."
|
||||
}
|
||||
|
||||
# Social identity providers
|
||||
social_providers = {
|
||||
# google = {
|
||||
# client_id = "..."
|
||||
# client_secret = "..."
|
||||
# scopes = ["email", "profile", "openid"]
|
||||
# }
|
||||
# facebook = {
|
||||
# client_id = "..."
|
||||
# client_secret = "..."
|
||||
# scopes = ["email", "public_profile"]
|
||||
# }
|
||||
}
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# 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" {}
|
||||
|
||||
################################################################################
|
||||
# Cognito User Pool
|
||||
################################################################################
|
||||
|
||||
resource "aws_cognito_user_pool" "main" {
|
||||
name = local.pool_name
|
||||
|
||||
# Username configuration
|
||||
username_attributes = local.username_attributes
|
||||
alias_attributes = length(local.alias_attributes) > 0 ? local.alias_attributes : null
|
||||
auto_verified_attributes = local.auto_verified_attributes
|
||||
|
||||
# Password policy
|
||||
password_policy {
|
||||
minimum_length = local.password_minimum_length
|
||||
require_lowercase = local.password_require_lowercase
|
||||
require_numbers = local.password_require_numbers
|
||||
require_symbols = local.password_require_symbols
|
||||
require_uppercase = local.password_require_uppercase
|
||||
temporary_password_validity_days = local.temporary_password_validity_days
|
||||
}
|
||||
|
||||
# MFA
|
||||
mfa_configuration = local.mfa_configuration
|
||||
|
||||
dynamic "software_token_mfa_configuration" {
|
||||
for_each = contains(local.mfa_methods, "SOFTWARE_TOKEN_MFA") && local.mfa_configuration != "OFF" ? [1] : []
|
||||
content {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
# Account recovery
|
||||
account_recovery_setting {
|
||||
dynamic "recovery_mechanism" {
|
||||
for_each = local.recovery_mechanisms
|
||||
content {
|
||||
name = recovery_mechanism.value.name
|
||||
priority = recovery_mechanism.value.priority
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Email configuration
|
||||
email_configuration {
|
||||
email_sending_account = local.email_sending_account
|
||||
source_arn = local.email_sending_account == "DEVELOPER" ? local.ses_email_from : null
|
||||
}
|
||||
|
||||
# User attribute verification
|
||||
user_attribute_update_settings {
|
||||
attributes_require_verification_before_update = ["email"]
|
||||
}
|
||||
|
||||
# Admin create user config
|
||||
admin_create_user_config {
|
||||
allow_admin_create_user_only = false
|
||||
|
||||
invite_message_template {
|
||||
email_subject = "Your ${local.pool_name} account"
|
||||
email_message = "Your username is {username} and temporary password is {####}"
|
||||
sms_message = "Your username is {username} and temporary password is {####}"
|
||||
}
|
||||
}
|
||||
|
||||
# Verification message
|
||||
verification_message_template {
|
||||
default_email_option = "CONFIRM_WITH_CODE"
|
||||
email_subject = "Verify your email for ${local.pool_name}"
|
||||
email_message = "Your verification code is {####}"
|
||||
}
|
||||
|
||||
# Schema (custom attributes)
|
||||
dynamic "schema" {
|
||||
for_each = local.custom_attributes
|
||||
content {
|
||||
name = schema.key
|
||||
attribute_data_type = schema.value.type
|
||||
mutable = schema.value.mutable
|
||||
required = false
|
||||
developer_only_attribute = false
|
||||
|
||||
dynamic "string_attribute_constraints" {
|
||||
for_each = schema.value.type == "String" ? [1] : []
|
||||
content {
|
||||
min_length = lookup(schema.value, "min_length", 0)
|
||||
max_length = lookup(schema.value, "max_length", 2048)
|
||||
}
|
||||
}
|
||||
|
||||
dynamic "number_attribute_constraints" {
|
||||
for_each = schema.value.type == "Number" ? [1] : []
|
||||
content {
|
||||
min_value = lookup(schema.value, "min_value", null)
|
||||
max_value = lookup(schema.value, "max_value", null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Lambda triggers
|
||||
lambda_config {
|
||||
pre_sign_up = lookup(local.lambda_triggers, "pre_sign_up", null)
|
||||
post_confirmation = lookup(local.lambda_triggers, "post_confirmation", null)
|
||||
pre_authentication = lookup(local.lambda_triggers, "pre_authentication", null)
|
||||
post_authentication = lookup(local.lambda_triggers, "post_authentication", null)
|
||||
pre_token_generation = lookup(local.lambda_triggers, "pre_token_generation", null)
|
||||
user_migration = lookup(local.lambda_triggers, "user_migration", null)
|
||||
custom_message = lookup(local.lambda_triggers, "custom_message", null)
|
||||
}
|
||||
|
||||
tags = { Name = local.pool_name }
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# User Pool Domain
|
||||
################################################################################
|
||||
|
||||
resource "aws_cognito_user_pool_domain" "main" {
|
||||
domain = local.custom_domain != null ? local.custom_domain : local.pool_name
|
||||
user_pool_id = aws_cognito_user_pool.main.id
|
||||
certificate_arn = local.custom_domain_cert
|
||||
}
|
||||
|
||||
# Route53 record for custom domain
|
||||
resource "aws_route53_record" "cognito" {
|
||||
count = local.custom_domain != null ? 1 : 0
|
||||
zone_id = local.hosted_zone_id
|
||||
name = local.custom_domain
|
||||
type = "A"
|
||||
|
||||
alias {
|
||||
name = aws_cognito_user_pool_domain.main.cloudfront_distribution_arn
|
||||
zone_id = "Z2FDTNDATAQYW2" # CloudFront zone ID
|
||||
evaluate_target_health = false
|
||||
}
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# App Clients
|
||||
################################################################################
|
||||
|
||||
resource "aws_cognito_user_pool_client" "clients" {
|
||||
for_each = local.app_clients
|
||||
|
||||
name = "${local.pool_name}-${each.key}"
|
||||
user_pool_id = aws_cognito_user_pool.main.id
|
||||
|
||||
generate_secret = each.value.generate_secret
|
||||
explicit_auth_flows = each.value.explicit_auth_flows
|
||||
supported_identity_providers = each.value.supported_identity_providers
|
||||
callback_urls = lookup(each.value, "callback_urls", null)
|
||||
logout_urls = lookup(each.value, "logout_urls", null)
|
||||
allowed_oauth_flows = lookup(each.value, "allowed_oauth_flows", null)
|
||||
allowed_oauth_scopes = lookup(each.value, "allowed_oauth_scopes", null)
|
||||
allowed_oauth_flows_user_pool_client = lookup(each.value, "allowed_oauth_flows_user_pool_client", false)
|
||||
|
||||
access_token_validity = lookup(each.value, "access_token_validity", 60)
|
||||
id_token_validity = lookup(each.value, "id_token_validity", 60)
|
||||
refresh_token_validity = lookup(each.value, "refresh_token_validity", 30)
|
||||
|
||||
token_validity_units {
|
||||
access_token = "minutes"
|
||||
id_token = "minutes"
|
||||
refresh_token = "days"
|
||||
}
|
||||
|
||||
prevent_user_existence_errors = "ENABLED"
|
||||
enable_token_revocation = true
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Social Identity Providers
|
||||
################################################################################
|
||||
|
||||
resource "aws_cognito_identity_provider" "google" {
|
||||
count = contains(keys(local.social_providers), "google") ? 1 : 0
|
||||
user_pool_id = aws_cognito_user_pool.main.id
|
||||
provider_name = "Google"
|
||||
provider_type = "Google"
|
||||
|
||||
provider_details = {
|
||||
client_id = local.social_providers.google.client_id
|
||||
client_secret = local.social_providers.google.client_secret
|
||||
authorize_scopes = join(" ", local.social_providers.google.scopes)
|
||||
}
|
||||
|
||||
attribute_mapping = {
|
||||
email = "email"
|
||||
username = "sub"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_cognito_identity_provider" "facebook" {
|
||||
count = contains(keys(local.social_providers), "facebook") ? 1 : 0
|
||||
user_pool_id = aws_cognito_user_pool.main.id
|
||||
provider_name = "Facebook"
|
||||
provider_type = "Facebook"
|
||||
|
||||
provider_details = {
|
||||
client_id = local.social_providers.facebook.client_id
|
||||
client_secret = local.social_providers.facebook.client_secret
|
||||
authorize_scopes = join(",", local.social_providers.facebook.scopes)
|
||||
}
|
||||
|
||||
attribute_mapping = {
|
||||
email = "email"
|
||||
username = "id"
|
||||
}
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Identity Pool (Optional)
|
||||
################################################################################
|
||||
|
||||
resource "aws_cognito_identity_pool" "main" {
|
||||
count = local.enable_identity_pool ? 1 : 0
|
||||
identity_pool_name = replace(local.pool_name, "-", "_")
|
||||
allow_unauthenticated_identities = false
|
||||
|
||||
cognito_identity_providers {
|
||||
client_id = aws_cognito_user_pool_client.clients["web"].id
|
||||
provider_name = aws_cognito_user_pool.main.endpoint
|
||||
server_side_token_check = true
|
||||
}
|
||||
|
||||
tags = { Name = local.pool_name }
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "authenticated" {
|
||||
count = local.enable_identity_pool ? 1 : 0
|
||||
name = "${local.pool_name}-authenticated"
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Federated = "cognito-identity.amazonaws.com"
|
||||
}
|
||||
Action = "sts:AssumeRoleWithWebIdentity"
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"cognito-identity.amazonaws.com:aud" = aws_cognito_identity_pool.main[0].id
|
||||
}
|
||||
"ForAnyValue:StringLike" = {
|
||||
"cognito-identity.amazonaws.com:amr" = "authenticated"
|
||||
}
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
tags = { Name = "${local.pool_name}-authenticated" }
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "authenticated" {
|
||||
count = local.enable_identity_pool ? 1 : 0
|
||||
name = "authenticated-policy"
|
||||
role = aws_iam_role.authenticated[0].id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"mobileanalytics:PutEvents",
|
||||
"cognito-sync:*",
|
||||
"cognito-identity:*"
|
||||
]
|
||||
Resource = "*"
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
resource "aws_cognito_identity_pool_roles_attachment" "main" {
|
||||
count = local.enable_identity_pool ? 1 : 0
|
||||
identity_pool_id = aws_cognito_identity_pool.main[0].id
|
||||
|
||||
roles = {
|
||||
authenticated = aws_iam_role.authenticated[0].arn
|
||||
}
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Outputs
|
||||
################################################################################
|
||||
|
||||
output "user_pool_id" {
|
||||
value = aws_cognito_user_pool.main.id
|
||||
}
|
||||
|
||||
output "user_pool_arn" {
|
||||
value = aws_cognito_user_pool.main.arn
|
||||
}
|
||||
|
||||
output "user_pool_endpoint" {
|
||||
value = aws_cognito_user_pool.main.endpoint
|
||||
}
|
||||
|
||||
output "user_pool_domain" {
|
||||
value = local.custom_domain != null ? "https://${local.custom_domain}" : "https://${aws_cognito_user_pool_domain.main.domain}.auth.${data.aws_region.current.name}.amazoncognito.com"
|
||||
}
|
||||
|
||||
output "client_ids" {
|
||||
value = { for k, v in aws_cognito_user_pool_client.clients : k => v.id }
|
||||
}
|
||||
|
||||
output "identity_pool_id" {
|
||||
value = local.enable_identity_pool ? aws_cognito_identity_pool.main[0].id : null
|
||||
}
|
||||
|
||||
output "hosted_ui_url" {
|
||||
value = "${local.custom_domain != null ? "https://${local.custom_domain}" : "https://${aws_cognito_user_pool_domain.main.domain}.auth.${data.aws_region.current.name}.amazoncognito.com"}/login?client_id=${aws_cognito_user_pool_client.clients["web"].id}&response_type=code&redirect_uri=${urlencode(local.app_clients.web.callback_urls[0])}"
|
||||
}
|
||||
Reference in New Issue
Block a user