mirror of
https://github.com/ghndrx/terraform-foundation.git
synced 2026-02-10 14:54:56 +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:
279
terraform/modules/tenant-iam/main.tf
Normal file
279
terraform/modules/tenant-iam/main.tf
Normal file
@@ -0,0 +1,279 @@
|
||||
################################################################################
|
||||
# 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"
|
||||
}
|
||||
Reference in New Issue
Block a user