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:
2026-02-01 20:06:28 +00:00
commit 6136cde9bb
145 changed files with 30832 additions and 0 deletions

View File

@@ -0,0 +1,248 @@
################################################################################
# GitHub OIDC Module - Variables
# With AWS/Terraform/Security Best Practices Validation
################################################################################
variable "create_provider" {
type = bool
default = true
description = "Create the OIDC provider. Set false if already exists in account."
}
variable "provider_arn" {
type = string
default = ""
description = "Existing OIDC provider ARN (required if create_provider = false)"
validation {
condition = var.provider_arn == "" || can(regex("^arn:aws:iam::[0-9]{12}:oidc-provider/", var.provider_arn))
error_message = "Provider ARN must be a valid IAM OIDC provider ARN."
}
}
variable "github_org" {
type = string
default = ""
description = "GitHub organization. If set, prepended to repos that don't include org."
validation {
condition = var.github_org == "" || can(regex("^[a-zA-Z0-9][a-zA-Z0-9-]*$", var.github_org))
error_message = "GitHub org must be alphanumeric with hyphens (no leading hyphen)."
}
}
variable "name_prefix" {
type = string
default = "github"
description = "Prefix for IAM role names"
validation {
condition = can(regex("^[a-zA-Z][a-zA-Z0-9-_]*$", var.name_prefix))
error_message = "Name prefix must start with letter, contain only alphanumeric, hyphens, underscores."
}
}
variable "path" {
type = string
default = "/github-actions/"
description = "IAM path for roles (enables easier permission boundaries)"
validation {
condition = can(regex("^/[a-zA-Z0-9/_-]*/$", var.path))
error_message = "IAM path must start and end with /, contain only alphanumeric, /, -, _."
}
}
variable "permissions_boundary" {
type = string
default = null
description = "ARN of permissions boundary to attach to roles (RECOMMENDED for defense-in-depth)"
validation {
condition = var.permissions_boundary == null || can(regex("^arn:aws:iam::[0-9]{12}:policy/", var.permissions_boundary))
error_message = "Permissions boundary must be a valid IAM policy ARN."
}
}
variable "require_permissions_boundary" {
type = bool
default = false
description = "Require a permissions boundary to be set (security guardrail)"
}
variable "max_session_hours_limit" {
type = number
default = 4
description = "Maximum allowed session duration in hours (caps role max_session_hours)"
validation {
condition = var.max_session_hours_limit >= 1 && var.max_session_hours_limit <= 12
error_message = "Max session hours must be between 1 and 12."
}
}
variable "deny_wildcard_repos" {
type = bool
default = true
description = "Deny roles that allow all repos (*). Set false only if using workflow_ref restriction."
}
variable "roles" {
type = map(object({
# Repository configuration
repos = list(string) # GitHub repos (owner/repo or just repo if github_org set)
branches = optional(list(string), []) # Branch restrictions (empty = all branches)
tags = optional(list(string), []) # Tag restrictions (e.g., ["v*", "release-*"])
environments = optional(list(string), []) # GitHub environment restrictions
# Event type restrictions
pull_request = optional(bool, false) # Allow from pull_request events
workflow_ref = optional(string, "") # Restrict to specific reusable workflow
# IAM configuration
policy_arns = optional(list(string), []) # Managed policy ARNs to attach
inline_policy = optional(string, "") # Inline policy JSON
policy_statements = optional(list(object({ # Policy statements to generate
sid = optional(string, "")
effect = optional(string, "Allow")
actions = list(string)
resources = list(string)
conditions = optional(list(object({
test = string
variable = string
values = list(string)
})), [])
})), [])
# Session configuration
max_session_hours = optional(number, 1) # Maximum session duration (1-12)
# Extra trust conditions
extra_conditions = optional(map(map(list(string))), {}) # Additional assume role conditions
}))
default = {}
description = "Map of role configurations for GitHub Actions"
validation {
condition = alltrue([
for k, v in var.roles : length(v.repos) > 0
])
error_message = "Each role must specify at least one repository."
}
validation {
condition = alltrue([
for k, v in var.roles : v.max_session_hours >= 1 && v.max_session_hours <= 12
])
error_message = "Role max_session_hours must be between 1 and 12."
}
validation {
condition = alltrue([
for k, v in var.roles : alltrue([
for repo in v.repos : can(regex("^[a-zA-Z0-9][a-zA-Z0-9-_.]*/[a-zA-Z0-9][a-zA-Z0-9-_.]*$|^[a-zA-Z0-9][a-zA-Z0-9-_.]*$|^\\*$", repo))
])
])
error_message = "Repository names must be valid GitHub repo format (owner/repo or repo)."
}
}
# Pre-built role templates
variable "terraform_deploy_role" {
type = object({
enabled = optional(bool, false)
repos = optional(list(string), [])
branches = optional(list(string), ["main"])
environments = optional(list(string), [])
state_bucket = optional(string, "")
state_bucket_key_prefix = optional(string, "*") # Limit to specific paths
dynamodb_table = optional(string, "")
allowed_services = optional(list(string), []) # Limit to specific AWS services
denied_actions = optional(list(string), [ # Explicit denies for safety
"iam:CreateUser",
"iam:CreateAccessKey",
"iam:DeleteAccountPasswordPolicy",
"organizations:*",
"account:*"
])
})
default = {}
description = "Pre-configured role for Terraform deployments"
}
variable "ecr_push_role" {
type = object({
enabled = optional(bool, false)
repos = optional(list(string), [])
branches = optional(list(string), ["main"])
ecr_repos = optional(list(string), []) # Specific ECR repos (no default wildcard)
allow_create = optional(bool, false)
allow_delete = optional(bool, false) # Explicit opt-in for delete
})
default = {}
description = "Pre-configured role for ECR push operations"
validation {
condition = !try(var.ecr_push_role.enabled, false) || length(try(var.ecr_push_role.ecr_repos, [])) > 0
error_message = "ECR push role requires explicit ecr_repos list (no wildcards for security)."
}
}
variable "s3_deploy_role" {
type = object({
enabled = optional(bool, false)
repos = optional(list(string), [])
branches = optional(list(string), ["main"])
bucket_arns = optional(list(string), [])
allowed_prefixes = optional(list(string), ["*"]) # Limit to specific paths
cloudfront_arns = optional(list(string), [])
})
default = {}
description = "Pre-configured role for S3 static site deployments"
validation {
condition = !try(var.s3_deploy_role.enabled, false) || length(try(var.s3_deploy_role.bucket_arns, [])) > 0
error_message = "S3 deploy role requires explicit bucket_arns list."
}
}
variable "lambda_deploy_role" {
type = object({
enabled = optional(bool, false)
repos = optional(list(string), [])
branches = optional(list(string), ["main"])
function_arns = optional(list(string), [])
allow_create = optional(bool, false)
allow_logs = optional(bool, true) # Allow CloudWatch Logs access
})
default = {}
description = "Pre-configured role for Lambda deployments"
validation {
condition = !try(var.lambda_deploy_role.enabled, false) || length(try(var.lambda_deploy_role.function_arns, [])) > 0
error_message = "Lambda deploy role requires explicit function_arns list."
}
}
variable "enable_cloudtrail_logging" {
type = bool
default = true
description = "Create CloudWatch metric alarms for OIDC role assumptions"
}
variable "alarm_sns_topic_arn" {
type = string
default = ""
description = "SNS topic ARN for security alarms"
}
variable "tags" {
type = map(string)
default = {}
description = "Tags to apply to all resources"
validation {
condition = !contains(keys(var.tags), "Name")
error_message = "Name tag is auto-generated, do not specify in tags variable."
}
}