Files
terraform-foundation/terraform/modules/tenant-iam/main.tf
Greg Hendrickson 6136cde9bb 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
2026-02-02 02:57:23 +00:00

280 lines
7.9 KiB
HCL

################################################################################
# Tenant IAM Module
#
# Creates tenant-specific IAM roles with isolation:
# - Tenant admin role with permissions boundary
# - Tenant developer role
# - Tenant readonly role
# - Permissions boundary for tenant isolation
################################################################################
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}
data "aws_caller_identity" "current" {}
data "aws_partition" "current" {}
locals {
account_id = data.aws_caller_identity.current.account_id
partition = data.aws_partition.current.partition
# Resource prefix for tenant isolation
resource_prefix = var.resource_prefix != "" ? var.resource_prefix : "${var.tenant_id}-"
}
################################################################################
# Permissions Boundary
################################################################################
resource "aws_iam_policy" "boundary" {
count = var.create_permissions_boundary ? 1 : 0
name = "${var.tenant_id}-permissions-boundary"
path = var.iam_path
description = "Permissions boundary for ${var.tenant_name} tenant"
policy = jsonencode({
Version = "2012-10-17"
Statement = concat(
# Allow specified services
[{
Sid = "AllowedServices"
Effect = "Allow"
Action = [for svc in var.allowed_services : "${svc}:*"]
Resource = "*"
Condition = {
StringLikeIfExists = {
"aws:ResourceTag/Tenant" = [var.tenant_id, var.tenant_name]
}
}
}],
# Restrict to tenant-prefixed resources where possible
[{
Sid = "RestrictToTenantResources"
Effect = "Allow"
Action = [
"s3:*",
"dynamodb:*",
"lambda:*",
"sqs:*",
"sns:*"
]
Resource = [
"arn:${local.partition}:s3:::${local.resource_prefix}*",
"arn:${local.partition}:dynamodb:*:${local.account_id}:table/${local.resource_prefix}*",
"arn:${local.partition}:lambda:*:${local.account_id}:function:${local.resource_prefix}*",
"arn:${local.partition}:sqs:*:${local.account_id}:${local.resource_prefix}*",
"arn:${local.partition}:sns:*:${local.account_id}:${local.resource_prefix}*"
]
}],
# Deny modifying boundary or escalating privileges
[{
Sid = "DenyBoundaryModification"
Effect = "Deny"
Action = [
"iam:DeletePolicy",
"iam:DeletePolicyVersion",
"iam:CreatePolicyVersion",
"iam:SetDefaultPolicyVersion"
]
Resource = "arn:${local.partition}:iam::${local.account_id}:policy/${var.tenant_id}-permissions-boundary"
}],
# Deny creating roles/users without boundary
[{
Sid = "DenyCreatingRolesWithoutBoundary"
Effect = "Deny"
Action = [
"iam:CreateRole",
"iam:CreateUser"
]
Resource = "*"
Condition = {
StringNotEquals = {
"iam:PermissionsBoundary" = "arn:${local.partition}:iam::${local.account_id}:policy/${var.tenant_id}-permissions-boundary"
}
}
}],
# Deny modifying other tenants' resources
[{
Sid = "DenyAccessToOtherTenants"
Effect = "Deny"
Action = "*"
Resource = "*"
Condition = {
StringNotLike = {
"aws:ResourceTag/Tenant" = [var.tenant_id, var.tenant_name, ""]
}
Null = {
"aws:ResourceTag/Tenant" = "false"
}
}
}],
# Deny disabling security services
[{
Sid = "DenySecurityServiceModification"
Effect = "Deny"
Action = [
"guardduty:DeleteDetector",
"guardduty:DisassociateFromMasterAccount",
"securityhub:DisableSecurityHub",
"config:DeleteConfigurationRecorder",
"config:StopConfigurationRecorder",
"cloudtrail:DeleteTrail",
"cloudtrail:StopLogging"
]
Resource = "*"
}]
)
})
tags = merge(var.tags, {
Name = "${var.tenant_id}-permissions-boundary"
Tenant = var.tenant_name
})
}
################################################################################
# Admin Role
################################################################################
resource "aws_iam_role" "admin" {
count = var.create_admin_role ? 1 : 0
name = "${var.tenant_id}-admin"
path = var.iam_path
permissions_boundary = var.create_permissions_boundary ? aws_iam_policy.boundary[0].arn : var.permissions_boundary_arn
max_session_duration = var.admin_session_duration
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
AWS = var.trusted_principals
}
Condition = var.require_mfa ? {
Bool = {
"aws:MultiFactorAuthPresent" = "true"
}
} : {}
}]
})
tags = merge(var.tags, {
Name = "${var.tenant_id}-admin"
Tenant = var.tenant_name
Role = "admin"
})
}
resource "aws_iam_role_policy_attachment" "admin" {
count = var.create_admin_role ? 1 : 0
role = aws_iam_role.admin[0].name
policy_arn = "arn:${local.partition}:iam::aws:policy/PowerUserAccess"
}
################################################################################
# Developer Role
################################################################################
resource "aws_iam_role" "developer" {
count = var.create_developer_role ? 1 : 0
name = "${var.tenant_id}-developer"
path = var.iam_path
permissions_boundary = var.create_permissions_boundary ? aws_iam_policy.boundary[0].arn : var.permissions_boundary_arn
max_session_duration = var.developer_session_duration
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
AWS = var.trusted_principals
}
}]
})
tags = merge(var.tags, {
Name = "${var.tenant_id}-developer"
Tenant = var.tenant_name
Role = "developer"
})
}
resource "aws_iam_role_policy" "developer" {
count = var.create_developer_role ? 1 : 0
name = "developer-access"
role = aws_iam_role.developer[0].id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "DeveloperAccess"
Effect = "Allow"
Action = [for svc in var.allowed_services : "${svc}:*"]
Resource = "*"
},
{
Sid = "DenyAdmin"
Effect = "Deny"
Action = [
"iam:*",
"organizations:*",
"account:*"
]
Resource = "*"
}
]
})
}
################################################################################
# Readonly Role
################################################################################
resource "aws_iam_role" "readonly" {
count = var.create_readonly_role ? 1 : 0
name = "${var.tenant_id}-readonly"
path = var.iam_path
permissions_boundary = var.create_permissions_boundary ? aws_iam_policy.boundary[0].arn : var.permissions_boundary_arn
max_session_duration = var.readonly_session_duration
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
AWS = var.trusted_principals
}
}]
})
tags = merge(var.tags, {
Name = "${var.tenant_id}-readonly"
Tenant = var.tenant_name
Role = "readonly"
})
}
resource "aws_iam_role_policy_attachment" "readonly" {
count = var.create_readonly_role ? 1 : 0
role = aws_iam_role.readonly[0].name
policy_arn = "arn:${local.partition}:iam::aws:policy/ReadOnlyAccess"
}