diff --git a/terraform/aws/iam-role/README.md b/terraform/aws/iam-role/README.md new file mode 100644 index 0000000..734c55a --- /dev/null +++ b/terraform/aws/iam-role/README.md @@ -0,0 +1,52 @@ +# iam-role + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | ~> 5.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | 5.100.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy.inline](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_iam_role_policy_attachment.managed](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [assume\_role\_policy](#input\_assume\_role\_policy) | JSON-formatted assume role policy document (trust policy) | `string` | n/a | yes | +| [description](#input\_description) | Description of the IAM role | `string` | `"IAM role created by Terraform"` | no | +| [force\_detach\_policies](#input\_force\_detach\_policies) | Whether to force detach policies when destroying the role | `bool` | `false` | no | +| [inline\_policies](#input\_inline\_policies) | Map of inline policy names to policy documents (JSON strings) | `map(string)` | `{}` | no | +| [managed\_policy\_arns](#input\_managed\_policy\_arns) | List of ARNs of managed policies to attach to the role | `list(string)` | `[]` | no | +| [max\_session\_duration](#input\_max\_session\_duration) | Maximum session duration (in seconds) for the role (3600-43200) | `number` | `3600` | no | +| [name](#input\_name) | Name of the IAM role | `string` | n/a | yes | +| [path](#input\_path) | Path for the IAM role | `string` | `"/"` | no | +| [permissions\_boundary](#input\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the role | `string` | `null` | no | +| [tags](#input\_tags) | Tags to apply to the IAM role | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [role\_arn](#output\_role\_arn) | ARN of the IAM role | +| [role\_id](#output\_role\_id) | ID of the IAM role | +| [role\_name](#output\_role\_name) | Name of the IAM role | +| [role\_unique\_id](#output\_role\_unique\_id) | Unique ID assigned by AWS | + diff --git a/terraform/aws/iam-role/main.tf b/terraform/aws/iam-role/main.tf new file mode 100644 index 0000000..6bed0ae --- /dev/null +++ b/terraform/aws/iam-role/main.tf @@ -0,0 +1,60 @@ +# ----------------------------------------------------------------------------- +# IAM Role Module +# Creates an IAM role with assume role policy and optional inline/managed policies +# ----------------------------------------------------------------------------- + +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +# ----------------------------------------------------------------------------- +# IAM Role +# ----------------------------------------------------------------------------- + +resource "aws_iam_role" "this" { + name = var.name + description = var.description + assume_role_policy = var.assume_role_policy + + max_session_duration = var.max_session_duration + path = var.path + permissions_boundary = var.permissions_boundary + force_detach_policies = var.force_detach_policies + + tags = merge( + var.tags, + { + Name = var.name + } + ) +} + +# ----------------------------------------------------------------------------- +# Managed Policy Attachments +# ----------------------------------------------------------------------------- + +resource "aws_iam_role_policy_attachment" "managed" { + for_each = toset(var.managed_policy_arns) + + role = aws_iam_role.this.name + policy_arn = each.value +} + +# ----------------------------------------------------------------------------- +# Inline Policies +# ----------------------------------------------------------------------------- + +resource "aws_iam_role_policy" "inline" { + for_each = var.inline_policies + + name = each.key + role = aws_iam_role.this.id + policy = each.value +} diff --git a/terraform/aws/iam-role/outputs.tf b/terraform/aws/iam-role/outputs.tf new file mode 100644 index 0000000..8e57b26 --- /dev/null +++ b/terraform/aws/iam-role/outputs.tf @@ -0,0 +1,23 @@ +# ----------------------------------------------------------------------------- +# IAM Role Outputs +# ----------------------------------------------------------------------------- + +output "role_arn" { + description = "ARN of the IAM role" + value = aws_iam_role.this.arn +} + +output "role_name" { + description = "Name of the IAM role" + value = aws_iam_role.this.name +} + +output "role_id" { + description = "ID of the IAM role" + value = aws_iam_role.this.id +} + +output "role_unique_id" { + description = "Unique ID assigned by AWS" + value = aws_iam_role.this.unique_id +} diff --git a/terraform/aws/iam-role/variables.tf b/terraform/aws/iam-role/variables.tf new file mode 100644 index 0000000..edc9012 --- /dev/null +++ b/terraform/aws/iam-role/variables.tf @@ -0,0 +1,86 @@ +# ----------------------------------------------------------------------------- +# IAM Role Variables +# ----------------------------------------------------------------------------- + +variable "name" { + description = "Name of the IAM role" + type = string + + validation { + condition = can(regex("^[a-zA-Z][a-zA-Z0-9-_+=,.@-]*$", var.name)) && length(var.name) <= 64 + error_message = "Role name must start with a letter, contain only alphanumeric characters and -_+=,.@- symbols, and be up to 64 characters long." + } +} + +variable "description" { + description = "Description of the IAM role" + type = string + default = "IAM role created by Terraform" +} + +variable "assume_role_policy" { + description = "JSON-formatted assume role policy document (trust policy)" + type = string + + validation { + condition = can(jsondecode(var.assume_role_policy)) + error_message = "The assume role policy must be a valid JSON document." + } +} + +variable "managed_policy_arns" { + description = "List of ARNs of managed policies to attach to the role" + type = list(string) + default = [] +} + +variable "inline_policies" { + description = "Map of inline policy names to policy documents (JSON strings)" + type = map(string) + default = {} + + validation { + condition = alltrue([for policy in values(var.inline_policies) : can(jsondecode(policy))]) + error_message = "All inline policies must be valid JSON documents." + } +} + +variable "max_session_duration" { + description = "Maximum session duration (in seconds) for the role (3600-43200)" + type = number + default = 3600 + + validation { + condition = var.max_session_duration >= 3600 && var.max_session_duration <= 43200 + error_message = "Max session duration must be between 3600 (1 hour) and 43200 (12 hours)." + } +} + +variable "path" { + description = "Path for the IAM role" + type = string + default = "/" + + validation { + condition = can(regex("^/.*/$", var.path)) || var.path == "/" + error_message = "Path must begin and end with /." + } +} + +variable "permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the role" + type = string + default = null +} + +variable "force_detach_policies" { + description = "Whether to force detach policies when destroying the role" + type = bool + default = false +} + +variable "tags" { + description = "Tags to apply to the IAM role" + type = map(string) + default = {} +}