diff --git a/examples/iam-assumable-role-with-oidc/README.md b/examples/iam-assumable-role-with-oidc/README.md index bec60f55..dfba9ce0 100644 --- a/examples/iam-assumable-role-with-oidc/README.md +++ b/examples/iam-assumable-role-with-oidc/README.md @@ -31,6 +31,7 @@ No providers. | Name | Source | Version | |------|--------|---------| | [iam\_assumable\_role\_admin](#module\_iam\_assumable\_role\_admin) | ../../modules/iam-assumable-role-with-oidc | n/a | +| [iam\_assumable\_role\_inline\_policy](#module\_iam\_assumable\_role\_inline\_policy) | ../../modules/iam-assumable-role-with-oidc | n/a | | [iam\_assumable\_role\_self\_assume](#module\_iam\_assumable\_role\_self\_assume) | ../../modules/iam-assumable-role-with-oidc | n/a | ## Resources diff --git a/examples/iam-assumable-role-with-oidc/main.tf b/examples/iam-assumable-role-with-oidc/main.tf index 62bc2822..390970bb 100644 --- a/examples/iam-assumable-role-with-oidc/main.tf +++ b/examples/iam-assumable-role-with-oidc/main.tf @@ -16,14 +16,13 @@ module "iam_assumable_role_admin" { Role = "role-with-oidc" } - provider_url = "oidc.eks.eu-west-1.amazonaws.com/id/BA9E170D464AF7B92084EF72A69B9DC8" - provider_urls = ["oidc.eks.eu-west-1.amazonaws.com/id/AA9E170D464AF7B92084EF72A69B9DC8"] + provider_url = "oidc.circleci.com/org/" + + oidc_fully_qualified_audiences = [""] role_policy_arns = [ - "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy", + "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser", ] - - oidc_fully_qualified_subjects = ["system:serviceaccount:default:sa1", "system:serviceaccount:default:sa2"] } ##################################### @@ -41,12 +40,52 @@ module "iam_assumable_role_self_assume" { Role = "role-with-oidc-self-assume" } - provider_url = "oidc.eks.eu-west-1.amazonaws.com/id/BA9E170D464AF7B92084EF72A69B9DC8" - provider_urls = ["oidc.eks.eu-west-1.amazonaws.com/id/AA9E170D464AF7B92084EF72A69B9DC8"] + provider_url = "oidc.circleci.com/org/" + + oidc_fully_qualified_audiences = [""] role_policy_arns = [ - "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy", + "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser", ] +} - oidc_fully_qualified_subjects = ["system:serviceaccount:default:sa1", "system:serviceaccount:default:sa2"] +##################################### +# IAM assumable role with inline policy +##################################### +module "iam_assumable_role_inline_policy" { + source = "../../modules/iam-assumable-role-with-oidc" + + create_role = true + + role_name = "role-with-oidc-inline-policy" + + tags = { + Role = "role-with-oidc-inline-policy" + } + + provider_url = "oidc.circleci.com/org/" + + oidc_fully_qualified_audiences = [""] + + inline_policy_statements = [ + { + sid = "AllowECRPushPull" + actions = [ + "ecr:GetAuthorizationToken", + "ecr:BatchGetImage", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchCheckLayerAvailability", + "ecr:DescribeImages", + "ecr:DescribeRepositories", + "ecr:GetDownloadUrlForLayer", + "ecr:ListImages", + "ecr:PutImage", + "ecr:InitiateLayerUpload", + "ecr:UploadLayerPart", + "ecr:CompleteLayerUpload" + ] + effect = "Allow" + resources = ["*"] + } + ] } diff --git a/examples/iam-assumable-role/README.md b/examples/iam-assumable-role/README.md index e66df7f8..9c63694c 100644 --- a/examples/iam-assumable-role/README.md +++ b/examples/iam-assumable-role/README.md @@ -37,6 +37,7 @@ Run `terraform destroy` when you don't need these resources. | [iam\_assumable\_role\_admin](#module\_iam\_assumable\_role\_admin) | ../../modules/iam-assumable-role | n/a | | [iam\_assumable\_role\_custom](#module\_iam\_assumable\_role\_custom) | ../../modules/iam-assumable-role | n/a | | [iam\_assumable\_role\_custom\_trust\_policy](#module\_iam\_assumable\_role\_custom\_trust\_policy) | ../../modules/iam-assumable-role | n/a | +| [iam\_assumable\_role\_inline\_policy](#module\_iam\_assumable\_role\_inline\_policy) | ../../modules/iam-assumable-role | n/a | | [iam\_assumable\_role\_sts](#module\_iam\_assumable\_role\_sts) | ../../modules/iam-assumable-role | n/a | | [iam\_policy](#module\_iam\_policy) | ../../modules/iam-policy | n/a | diff --git a/examples/iam-assumable-role/main.tf b/examples/iam-assumable-role/main.tf index 25b227c9..f4e6c3f0 100644 --- a/examples/iam-assumable-role/main.tf +++ b/examples/iam-assumable-role/main.tf @@ -62,6 +62,50 @@ module "iam_assumable_role_custom" { # number_of_custom_role_policy_arns = 3 } +########################################## +# IAM assumable role with inline policy +########################################## +module "iam_assumable_role_inline_policy" { + source = "../../modules/iam-assumable-role" + + trusted_role_arns = [ + "arn:aws:iam::307990089504:root", + ] + + trusted_role_services = [ + "codedeploy.amazonaws.com" + ] + + create_role = true + + role_name_prefix = "custom-" + role_requires_mfa = false + + role_sts_externalid = "some-id-goes-here" + + inline_policy_statements = [ + { + sid = "AllowECRPushPull" + actions = [ + "ecr:GetAuthorizationToken", + "ecr:BatchGetImage", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchCheckLayerAvailability", + "ecr:DescribeImages", + "ecr:DescribeRepositories", + "ecr:GetDownloadUrlForLayer", + "ecr:ListImages", + "ecr:PutImage", + "ecr:InitiateLayerUpload", + "ecr:UploadLayerPart", + "ecr:CompleteLayerUpload" + ] + effect = "Allow" + resources = ["*"] + } + ] +} + #################################################### # IAM assumable role with multiple sts external ids #################################################### diff --git a/modules/iam-assumable-role-with-oidc/README.md b/modules/iam-assumable-role-with-oidc/README.md index 1662a3ed..f38548fb 100644 --- a/modules/iam-assumable-role-with-oidc/README.md +++ b/modules/iam-assumable-role-with-oidc/README.md @@ -29,9 +29,11 @@ No modules. | 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.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | | [aws_iam_policy_document.assume_role_with_oidc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.inline](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | ## Inputs @@ -42,6 +44,7 @@ No modules. | [aws\_account\_id](#input\_aws\_account\_id) | The AWS account ID where the OIDC provider lives, leave empty to use the account for the AWS provider | `string` | `""` | no | | [create\_role](#input\_create\_role) | Whether to create a role | `bool` | `false` | no | | [force\_detach\_policies](#input\_force\_detach\_policies) | Whether policies should be detached from this role when destroying | `bool` | `false` | no | +| [inline\_policy\_statements](#input\_inline\_policy\_statements) | List of inline policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) to attach to IAM role as an inline policy | `any` | `[]` | no | | [max\_session\_duration](#input\_max\_session\_duration) | Maximum CLI/API session duration in seconds between 3600 and 43200 | `number` | `3600` | no | | [number\_of\_role\_policy\_arns](#input\_number\_of\_role\_policy\_arns) | Number of IAM policies to attach to IAM role | `number` | `null` | no | | [oidc\_fully\_qualified\_audiences](#input\_oidc\_fully\_qualified\_audiences) | The audience to be added to the role policy. Set to sts.amazonaws.com for cross-account assumable role. Leave empty otherwise. | `set(string)` | `[]` | no | diff --git a/modules/iam-assumable-role-with-oidc/main.tf b/modules/iam-assumable-role-with-oidc/main.tf index e993a5a5..fb14c928 100644 --- a/modules/iam-assumable-role-with-oidc/main.tf +++ b/modules/iam-assumable-role-with-oidc/main.tf @@ -107,3 +107,64 @@ resource "aws_iam_role_policy_attachment" "custom" { role = aws_iam_role.this[0].name policy_arn = var.role_policy_arns[count.index] } + +############################### +# IAM Role Inline policy +############################### + +locals { + create_iam_role_inline_policy = var.create_role && length(var.inline_policy_statements) > 0 +} + +data "aws_iam_policy_document" "inline" { + count = local.create_iam_role_inline_policy ? 1 : 0 + + dynamic "statement" { + for_each = var.inline_policy_statements + + content { + sid = try(statement.value.sid, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + resources = try(statement.value.resources, null) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +resource "aws_iam_role_policy" "inline" { + count = local.create_iam_role_inline_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + name_prefix = "${var.role_name}_inline_" + policy = data.aws_iam_policy_document.inline[0].json +} diff --git a/modules/iam-assumable-role-with-oidc/variables.tf b/modules/iam-assumable-role-with-oidc/variables.tf index d9b7cd7b..730f90af 100644 --- a/modules/iam-assumable-role-with-oidc/variables.tf +++ b/modules/iam-assumable-role-with-oidc/variables.tf @@ -76,6 +76,12 @@ variable "number_of_role_policy_arns" { default = null } +variable "inline_policy_statements" { + description = "List of inline policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) to attach to IAM role as an inline policy" + type = any + default = [] +} + variable "oidc_fully_qualified_subjects" { description = "The fully qualified OIDC subjects to be added to the role policy" type = set(string) diff --git a/modules/iam-assumable-role/README.md b/modules/iam-assumable-role/README.md index 6270002c..b35a2250 100644 --- a/modules/iam-assumable-role/README.md +++ b/modules/iam-assumable-role/README.md @@ -28,6 +28,7 @@ No modules. |------|------| | [aws_iam_instance_profile.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource | | [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.admin](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.poweruser](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | @@ -35,6 +36,7 @@ No modules. | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | | [aws_iam_policy_document.assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.assume_role_with_mfa](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.inline](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | ## Inputs @@ -52,6 +54,7 @@ No modules. | [custom\_role\_policy\_arns](#input\_custom\_role\_policy\_arns) | List of ARNs of IAM policies to attach to IAM role | `list(string)` | `[]` | no | | [custom\_role\_trust\_policy](#input\_custom\_role\_trust\_policy) | A custom role trust policy. (Only valid if create\_custom\_role\_trust\_policy = true) | `string` | `""` | no | | [force\_detach\_policies](#input\_force\_detach\_policies) | Whether policies should be detached from this role when destroying | `bool` | `false` | no | +| [inline\_policy\_statements](#input\_inline\_policy\_statements) | List of inline policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) to attach to IAM role as an inline policy | `any` | `[]` | no | | [max\_session\_duration](#input\_max\_session\_duration) | Maximum CLI/API session duration in seconds between 3600 and 43200 | `number` | `3600` | no | | [mfa\_age](#input\_mfa\_age) | Max age of valid MFA (in seconds) for roles which require MFA | `number` | `86400` | no | | [number\_of\_custom\_role\_policy\_arns](#input\_number\_of\_custom\_role\_policy\_arns) | Number of IAM policies to attach to IAM role | `number` | `null` | no | diff --git a/modules/iam-assumable-role/main.tf b/modules/iam-assumable-role/main.tf index f444f066..719a3873 100644 --- a/modules/iam-assumable-role/main.tf +++ b/modules/iam-assumable-role/main.tf @@ -197,3 +197,64 @@ resource "aws_iam_instance_profile" "this" { tags = var.tags } + +############################### +# IAM Role Inline policy +############################### + +locals { + create_iam_role_inline_policy = var.create_role && length(var.inline_policy_statements) > 0 +} + +data "aws_iam_policy_document" "inline" { + count = local.create_iam_role_inline_policy ? 1 : 0 + + dynamic "statement" { + for_each = var.inline_policy_statements + + content { + sid = try(statement.value.sid, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + resources = try(statement.value.resources, null) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +resource "aws_iam_role_policy" "inline" { + count = local.create_iam_role_inline_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + name_prefix = "${var.role_name}_inline_" + policy = data.aws_iam_policy_document.inline[0].json +} diff --git a/modules/iam-assumable-role/variables.tf b/modules/iam-assumable-role/variables.tf index 908d55b0..fc9bd2c9 100644 --- a/modules/iam-assumable-role/variables.tf +++ b/modules/iam-assumable-role/variables.tf @@ -100,6 +100,12 @@ variable "number_of_custom_role_policy_arns" { default = null } +variable "inline_policy_statements" { + description = "List of inline policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) to attach to IAM role as an inline policy" + type = any + default = [] +} + # Pre-defined policies variable "admin_role_policy_arn" { description = "Policy ARN to use for admin role" diff --git a/wrappers/iam-assumable-role-with-oidc/main.tf b/wrappers/iam-assumable-role-with-oidc/main.tf index 870acf3a..66b53928 100644 --- a/wrappers/iam-assumable-role-with-oidc/main.tf +++ b/wrappers/iam-assumable-role-with-oidc/main.tf @@ -7,6 +7,7 @@ module "wrapper" { aws_account_id = try(each.value.aws_account_id, var.defaults.aws_account_id, "") create_role = try(each.value.create_role, var.defaults.create_role, false) force_detach_policies = try(each.value.force_detach_policies, var.defaults.force_detach_policies, false) + inline_policy_statements = try(each.value.inline_policy_statements, var.defaults.inline_policy_statements, []) max_session_duration = try(each.value.max_session_duration, var.defaults.max_session_duration, 3600) number_of_role_policy_arns = try(each.value.number_of_role_policy_arns, var.defaults.number_of_role_policy_arns, null) oidc_fully_qualified_audiences = try(each.value.oidc_fully_qualified_audiences, var.defaults.oidc_fully_qualified_audiences, []) diff --git a/wrappers/iam-assumable-role/main.tf b/wrappers/iam-assumable-role/main.tf index a961ee99..aef04cfe 100644 --- a/wrappers/iam-assumable-role/main.tf +++ b/wrappers/iam-assumable-role/main.tf @@ -14,6 +14,7 @@ module "wrapper" { custom_role_policy_arns = try(each.value.custom_role_policy_arns, var.defaults.custom_role_policy_arns, []) custom_role_trust_policy = try(each.value.custom_role_trust_policy, var.defaults.custom_role_trust_policy, "") force_detach_policies = try(each.value.force_detach_policies, var.defaults.force_detach_policies, false) + inline_policy_statements = try(each.value.inline_policy_statements, var.defaults.inline_policy_statements, []) max_session_duration = try(each.value.max_session_duration, var.defaults.max_session_duration, 3600) mfa_age = try(each.value.mfa_age, var.defaults.mfa_age, 86400) number_of_custom_role_policy_arns = try(each.value.number_of_custom_role_policy_arns, var.defaults.number_of_custom_role_policy_arns, null)