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:
45
terraform/modules/tenant-iam/README.md
Normal file
45
terraform/modules/tenant-iam/README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# tenant-iam
|
||||
|
||||
Terraform module for AWS landing zone pattern.
|
||||
|
||||
Create tenant-specific IAM roles with proper isolation.
|
||||
|
||||
## Planned Features
|
||||
|
||||
- [ ] Tenant admin role (full tenant access)
|
||||
- [ ] Tenant developer role (limited write)
|
||||
- [ ] Tenant readonly role (view only)
|
||||
- [ ] Permissions boundary enforcement
|
||||
- [ ] Resource-based isolation (tenant prefix)
|
||||
- [ ] Cross-account trust configuration
|
||||
|
||||
## Planned Usage
|
||||
|
||||
```hcl
|
||||
module "tenant_iam" {
|
||||
source = "../modules/tenant-iam"
|
||||
|
||||
tenant_name = "acme-corp"
|
||||
tenant_id = "acme"
|
||||
|
||||
create_admin_role = true
|
||||
create_developer_role = true
|
||||
create_readonly_role = true
|
||||
|
||||
trusted_principals = [
|
||||
"arn:aws:iam::111111111111:root" # Identity account
|
||||
]
|
||||
|
||||
allowed_services = ["ec2", "s3", "lambda", "rds"]
|
||||
resource_prefix = "acme-"
|
||||
|
||||
permissions_boundary = aws_iam_policy.tenant_boundary.arn
|
||||
}
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
All tenant roles are created with permissions boundaries to prevent:
|
||||
- Creating IAM users/roles without boundaries
|
||||
- Accessing other tenants' resources
|
||||
- Modifying security services
|
||||
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"
|
||||
}
|
||||
52
terraform/modules/tenant-iam/outputs.tf
Normal file
52
terraform/modules/tenant-iam/outputs.tf
Normal file
@@ -0,0 +1,52 @@
|
||||
################################################################################
|
||||
# Tenant IAM - Outputs
|
||||
################################################################################
|
||||
|
||||
output "permissions_boundary_arn" {
|
||||
value = var.create_permissions_boundary ? aws_iam_policy.boundary[0].arn : var.permissions_boundary_arn
|
||||
description = "Permissions boundary policy ARN"
|
||||
}
|
||||
|
||||
output "admin_role_arn" {
|
||||
value = try(aws_iam_role.admin[0].arn, null)
|
||||
description = "Tenant admin role ARN"
|
||||
}
|
||||
|
||||
output "admin_role_name" {
|
||||
value = try(aws_iam_role.admin[0].name, null)
|
||||
description = "Tenant admin role name"
|
||||
}
|
||||
|
||||
output "developer_role_arn" {
|
||||
value = try(aws_iam_role.developer[0].arn, null)
|
||||
description = "Tenant developer role ARN"
|
||||
}
|
||||
|
||||
output "developer_role_name" {
|
||||
value = try(aws_iam_role.developer[0].name, null)
|
||||
description = "Tenant developer role name"
|
||||
}
|
||||
|
||||
output "readonly_role_arn" {
|
||||
value = try(aws_iam_role.readonly[0].arn, null)
|
||||
description = "Tenant readonly role ARN"
|
||||
}
|
||||
|
||||
output "readonly_role_name" {
|
||||
value = try(aws_iam_role.readonly[0].name, null)
|
||||
description = "Tenant readonly role name"
|
||||
}
|
||||
|
||||
output "all_role_arns" {
|
||||
value = {
|
||||
admin = try(aws_iam_role.admin[0].arn, null)
|
||||
developer = try(aws_iam_role.developer[0].arn, null)
|
||||
readonly = try(aws_iam_role.readonly[0].arn, null)
|
||||
}
|
||||
description = "Map of all tenant role ARNs"
|
||||
}
|
||||
|
||||
output "resource_prefix" {
|
||||
value = local.resource_prefix
|
||||
description = "Resource prefix for tenant naming"
|
||||
}
|
||||
97
terraform/modules/tenant-iam/variables.tf
Normal file
97
terraform/modules/tenant-iam/variables.tf
Normal file
@@ -0,0 +1,97 @@
|
||||
################################################################################
|
||||
# Tenant IAM - Input Variables
|
||||
################################################################################
|
||||
|
||||
variable "tenant_name" {
|
||||
type = string
|
||||
description = "Tenant name (human readable)"
|
||||
}
|
||||
|
||||
variable "tenant_id" {
|
||||
type = string
|
||||
description = "Short tenant ID for resource naming"
|
||||
}
|
||||
|
||||
variable "create_permissions_boundary" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Create permissions boundary policy"
|
||||
}
|
||||
|
||||
variable "permissions_boundary_arn" {
|
||||
type = string
|
||||
default = null
|
||||
description = "Existing permissions boundary ARN (if not creating)"
|
||||
}
|
||||
|
||||
variable "create_admin_role" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Create tenant admin role"
|
||||
}
|
||||
|
||||
variable "create_developer_role" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Create tenant developer role"
|
||||
}
|
||||
|
||||
variable "create_readonly_role" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Create tenant readonly role"
|
||||
}
|
||||
|
||||
variable "trusted_principals" {
|
||||
type = list(string)
|
||||
default = []
|
||||
description = "ARNs allowed to assume tenant roles"
|
||||
}
|
||||
|
||||
variable "allowed_services" {
|
||||
type = list(string)
|
||||
default = ["ec2", "s3", "lambda", "dynamodb", "rds", "ecs", "ecr", "logs", "cloudwatch", "events", "sqs", "sns"]
|
||||
description = "AWS services the tenant can use"
|
||||
}
|
||||
|
||||
variable "resource_prefix" {
|
||||
type = string
|
||||
default = ""
|
||||
description = "Resource naming prefix (defaults to tenant_id-)"
|
||||
}
|
||||
|
||||
variable "iam_path" {
|
||||
type = string
|
||||
default = "/tenants/"
|
||||
description = "IAM path for roles and policies"
|
||||
}
|
||||
|
||||
variable "require_mfa" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Require MFA for admin role"
|
||||
}
|
||||
|
||||
variable "admin_session_duration" {
|
||||
type = number
|
||||
default = 3600
|
||||
description = "Admin role session duration in seconds"
|
||||
}
|
||||
|
||||
variable "developer_session_duration" {
|
||||
type = number
|
||||
default = 14400
|
||||
description = "Developer role session duration in seconds"
|
||||
}
|
||||
|
||||
variable "readonly_session_duration" {
|
||||
type = number
|
||||
default = 14400
|
||||
description = "Readonly role session duration in seconds"
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
type = map(string)
|
||||
default = {}
|
||||
description = "Tags to apply to resources"
|
||||
}
|
||||
Reference in New Issue
Block a user