From 1eedda14c2778567ea9391cabc27695d8368567b Mon Sep 17 00:00:00 2001 From: Byungjin Park Date: Tue, 13 Dec 2022 02:41:32 +0900 Subject: [PATCH 1/2] Improve federated features for iam-role module --- modules/iam-role/README.md | 10 +-- modules/iam-role/trusted-oidc-providers.tf | 34 ++++----- modules/iam-role/trusted-saml-providers.tf | 13 ++-- modules/iam-role/variables.tf | 88 +++++++++++++--------- 4 files changed, 80 insertions(+), 65 deletions(-) diff --git a/modules/iam-role/README.md b/modules/iam-role/README.md index 8710778..632ba9d 100644 --- a/modules/iam-role/README.md +++ b/modules/iam-role/README.md @@ -60,7 +60,7 @@ When `pgp_key` is specified as `keybase:username`, make sure that that user has | [name](#input\_name) | Desired name for the IAM role. | `string` | n/a | yes | | [assumable\_roles](#input\_assumable\_roles) | List of IAM roles ARNs which can be assumed by the role. | `list(string)` | `[]` | no | | [conditions](#input\_conditions) | Required conditions to assume the role. |
list(object({
key = string
condition = string
values = list(string)
}))
| `[]` | no | -| [description](#input\_description) | The description of the role. | `string` | `""` | no | +| [description](#input\_description) | The description of the role. | `string` | `"Managed by Terraform."` | no | | [effective\_date](#input\_effective\_date) | Allow to assume IAM role only after a specific date and time. | `string` | `null` | no | | [expiration\_date](#input\_expiration\_date) | Allow to assume IAM role only before a specific date and time. | `string` | `null` | no | | [force\_detach\_policies](#input\_force\_detach\_policies) | Specifies to force detaching any policies the role has before destroying it. | `bool` | `false` | no | @@ -71,7 +71,7 @@ When `pgp_key` is specified as `keybase:username`, make sure that that user has | [mfa\_ttl](#input\_mfa\_ttl) | Max age of valid MFA (in seconds) for roles which require MFA. | `number` | `86400` | no | | [module\_tags\_enabled](#input\_module\_tags\_enabled) | Whether to create AWS Resource Tags for the module informations. | `bool` | `true` | no | | [path](#input\_path) | Desired path for the IAM role. | `string` | `"/"` | no | -| [permissions\_boundary](#input\_permissions\_boundary) | The ARN of the policy that is used to set the permissions boundary for the role. | `string` | `""` | no | +| [permissions\_boundary](#input\_permissions\_boundary) | The ARN of the policy that is used to set the permissions boundary for the role. | `string` | `null` | no | | [policies](#input\_policies) | List of IAM policies ARNs to attach to IAM role. | `list(string)` | `[]` | no | | [resource\_group\_description](#input\_resource\_group\_description) | The description of Resource Group. | `string` | `"Managed by Terraform."` | no | | [resource\_group\_enabled](#input\_resource\_group\_enabled) | Whether to create Resource Group to find and group AWS resources which are created by this module. | `bool` | `true` | no | @@ -80,10 +80,8 @@ When `pgp_key` is specified as `keybase:username`, make sure that that user has | [source\_ip\_whitelist](#input\_source\_ip\_whitelist) | A list of source IP addresses or CIDRs allowed to assume IAM role from. | `list(string)` | `[]` | no | | [tags](#input\_tags) | A map of tags to add to all resources. | `map(string)` | `{}` | no | | [trusted\_iam\_entities](#input\_trusted\_iam\_entities) | A list of ARNs of AWS IAM entities who can assume the role. | `list(string)` | `[]` | no | -| [trusted\_oidc\_conditions](#input\_trusted\_oidc\_conditions) | Required conditions to assume the role for OIDC providers. |
list(object({
key = string
condition = string
values = list(string)
}))
| `[]` | no | -| [trusted\_oidc\_providers](#input\_trusted\_oidc\_providers) | A list of OIDC identity providers. Supported types are `amazon`, `aws-cognito`, `aws-iam`, `facebook`, `google`. `type` is required. `url` is required only when `type` is `aws-iam`. | `list(map(string))` | `[]` | no | -| [trusted\_saml\_conditions](#input\_trusted\_saml\_conditions) | Required conditions to assume the role for SAML providers. |
list(object({
key = string
condition = string
values = list(string)
}))
| `[]` | no | -| [trusted\_saml\_providers](#input\_trusted\_saml\_providers) | A list of ARNs of SAML identity providers in AWS IAM. | `list(string)` | `[]` | no | +| [trusted\_oidc\_providers](#input\_trusted\_oidc\_providers) | (Optional) A list of configurations of OIDC identity providers. Each value of `trusted_oidc_providers` as defined below.
(Required) `name` - The name of the OIDC identity provider.
(Required) `url` - The URL of the OIDC identity provider. If the provider is not common, the corresponding IAM OIDC Provider should be created before. Supported common OIDC providers are `accounts.google.com`, `cognito-identity.amazonaws.com`, `graph.facebook.com`, `www.amazon.com`.
(Optional) `conditions` - A list of required conditions to assume the role via OIDC providers. |
list(object({
name = string
url = string
conditions = optional(list(object({
key = string
condition = string
values = list(string)
})), [])
}))
| `[]` | no | +| [trusted\_saml\_providers](#input\_trusted\_saml\_providers) | (Optional) A list of configurations of SAML identity providers. Each value of `trusted_saml_providers` as defined below.
(Required) `name` - The name of the SAML identity provider.
(Required) `arn` - The ARN of the SAML identity provider.
(Optional) `conditions` - A list of required conditions to assume the role via SAML providers. |
list(object({
name = string
arn = string
conditions = optional(list(object({
key = string
condition = string
values = list(string)
})), [])
}))
| `[]` | no | | [trusted\_services](#input\_trusted\_services) | AWS Services that can assume the role. | `list(string)` | `[]` | no | ## Outputs diff --git a/modules/iam-role/trusted-oidc-providers.tf b/modules/iam-role/trusted-oidc-providers.tf index 5e233f4..edb36f1 100644 --- a/modules/iam-role/trusted-oidc-providers.tf +++ b/modules/iam-role/trusted-oidc-providers.tf @@ -2,46 +2,42 @@ data "aws_caller_identity" "this" {} data "aws_partition" "this" {} locals { - oidc_provider_urls = { - "google" = "accounts.google.com" - "facebook" = "graph.facebook.com" - "amazon" = "www.amazon.com" - "aws-cognito" = "cognito-identity.amazonaws.com" - } + oidc_provider_common_urls = [ + "accounts.google.com", + "cognito-identity.amazonaws.com", + "graph.facebook.com", + "www.amazon.com", + ] oidc_arn_prefix = "arn:${data.aws_partition.this.partition}:iam::${data.aws_caller_identity.this.account_id}:oidc-provider/" } data "aws_iam_policy_document" "trusted_oidc_providers" { for_each = { - for idx, provider in var.trusted_oidc_providers : - idx + 1 => provider + for provider in var.trusted_oidc_providers : + provider.name => provider } statement { - sid = "TrustedOidcProviders${each.key}" + sid = "TrustedOIDC${each.key}" effect = "Allow" actions = ["sts:AssumeRoleWithWebIdentity"] principals { type = "Federated" identifiers = [ - each.value.type != "aws-iam" - ? local.oidc_provider_urls[each.value.type] + contains(local.oidc_provider_common_urls, each.value.url) + ? each.value.url : "${local.oidc_arn_prefix}${each.value.url}" ] } dynamic "condition" { - for_each = var.trusted_oidc_conditions + for_each = each.value.conditions content { - variable = ( - each.value.type != "aws-iam" - ? "${local.oidc_provider_urls[each.value.type]}:${condition.value.key}" - : "${each.value.url}:${condition.value.key}" - ) - test = condition.value.condition - values = condition.value.values + variable = "${each.value.url}:${condition.value.key}" + test = condition.value.condition + values = condition.value.values } } diff --git a/modules/iam-role/trusted-saml-providers.tf b/modules/iam-role/trusted-saml-providers.tf index 9dcdab1..ae2c652 100644 --- a/modules/iam-role/trusted-saml-providers.tf +++ b/modules/iam-role/trusted-saml-providers.tf @@ -1,20 +1,21 @@ data "aws_iam_policy_document" "trusted_saml_providers" { - for_each = toset( - length(var.trusted_saml_providers) > 0 ? ["this"] : [] - ) + for_each = { + for provider in var.trusted_saml_providers : + provider.name => provider + } statement { - sid = "TrustedSamlProviders" + sid = "TrustedSAML${each.key}" effect = "Allow" actions = ["sts:AssumeRoleWithSAML"] principals { type = "Federated" - identifiers = var.trusted_saml_providers + identifiers = [each.value.arn] } dynamic "condition" { - for_each = var.trusted_saml_conditions + for_each = each.value.conditions content { variable = "saml:${condition.value.key}" diff --git a/modules/iam-role/variables.tf b/modules/iam-role/variables.tf index f3f502e..11d87ae 100644 --- a/modules/iam-role/variables.tf +++ b/modules/iam-role/variables.tf @@ -7,82 +7,88 @@ variable "path" { description = "Desired path for the IAM role." type = string default = "/" + nullable = false } variable "description" { description = "The description of the role." type = string - default = "" + default = "Managed by Terraform." + nullable = false } variable "max_session_duration" { description = "Maximum CLI/API session duration in seconds between 3600 and 43200." type = number default = 3600 + nullable = false } variable "force_detach_policies" { description = "Specifies to force detaching any policies the role has before destroying it." type = bool default = false + nullable = false } variable "permissions_boundary" { description = "The ARN of the policy that is used to set the permissions boundary for the role." type = string - default = "" + default = null } variable "trusted_iam_entities" { description = "A list of ARNs of AWS IAM entities who can assume the role." type = list(string) default = [] + nullable = false } variable "trusted_services" { description = "AWS Services that can assume the role." type = list(string) default = [] + nullable = false } variable "trusted_oidc_providers" { - description = "A list of OIDC identity providers. Supported types are `amazon`, `aws-cognito`, `aws-iam`, `facebook`, `google`. `type` is required. `url` is required only when `type` is `aws-iam`." - type = list(map(string)) - default = [] - - validation { - condition = alltrue([ - for provider in var.trusted_oidc_providers : - contains(["amazon", "aws-cognito", "aws-iam", "facebook", "google"], provider.type) - ]) - error_message = "Type of provider must be either `amazon`, `aws-cognito`, `aws-iam`, `facebook`, or `google`." - } -} - -variable "trusted_saml_providers" { - description = "A list of ARNs of SAML identity providers in AWS IAM." - type = list(string) - default = [] -} - -variable "trusted_oidc_conditions" { - description = "Required conditions to assume the role for OIDC providers." + description = < Date: Tue, 13 Dec 2022 02:41:46 +0900 Subject: [PATCH 2/2] Add github-trusted-iam-roles example --- examples/github-trusted-iam-roles/main.tf | 97 +++++++++++++++++++ examples/github-trusted-iam-roles/outputs.tf | 7 ++ examples/github-trusted-iam-roles/versions.tf | 14 +++ 3 files changed, 118 insertions(+) create mode 100644 examples/github-trusted-iam-roles/main.tf create mode 100644 examples/github-trusted-iam-roles/outputs.tf create mode 100644 examples/github-trusted-iam-roles/versions.tf diff --git a/examples/github-trusted-iam-roles/main.tf b/examples/github-trusted-iam-roles/main.tf new file mode 100644 index 0000000..347a88c --- /dev/null +++ b/examples/github-trusted-iam-roles/main.tf @@ -0,0 +1,97 @@ +provider "aws" { + region = "us-east-1" +} + + +################################################### +# IAM OIDC Identity Providers +################################################### + +locals { + providers = [ + { + url = "https://token.actions.githubusercontent.com" + audiences = ["sts.amazonaws.com"] + }, + ] +} + +module "oidc_provider" { + source = "../../modules/iam-oidc-identity-provider" + # source = "tedilabs/account/aws//modules/iam-oidc-identity-provider" + # version = "~> 0.23.0" + + for_each = { + for provider in try(local.providers, []) : + provider.url => provider + } + + url = each.key + audiences = try(each.value.audiences, null) + + thumbprints = try(each.value.thumbprints, null) + auto_thumbprint_enabled = try(each.value.auto_thumbprint_enabled, true) + + tags = { + "project" = "terraform-aws-account-examples" + } +} + + +################################################### +# IAM Roles +################################################### + +locals { + roles = [ + { + name = "github-readonly" + policies = ["arn:aws:iam::aws:policy/ReadOnlyAccess"] + }, + ] +} + +module "role" { + source = "../../modules/iam-role" + # source = "tedilabs/account/aws//modules/iam-role" + # version = "~> 0.23.0" + + for_each = { + for role in try(local.roles, []) : + role.name => role + } + + name = each.key + description = try(each.value.description, "Managed by Terraform.") + path = try(each.value.path, "/") + + trusted_oidc_providers = [ + { + name = "github" + url = "token.actions.githubusercontent.com" + conditions = [ + { + key = "aud" + condition = "StringEquals" + values = ["sts.amazonaws.com"] + }, + { + key = "sub" + condition = "StringLike" + values = ["repo:tedilabs/*"] + }, + ] + } + ] + + assumable_roles = try(each.value.assumable_roles, []) + policies = try(each.value.policies, []) + inline_policies = { + for name, path in try(each.value.inline_policies, {}) : + name => file(path) + } + + tags = { + "project" = "terraform-aws-account-examples" + } +} diff --git a/examples/github-trusted-iam-roles/outputs.tf b/examples/github-trusted-iam-roles/outputs.tf new file mode 100644 index 0000000..eb7c3fb --- /dev/null +++ b/examples/github-trusted-iam-roles/outputs.tf @@ -0,0 +1,7 @@ +output "oidc_providers" { + value = module.oidc_provider +} + +output "roles" { + value = module.role +} diff --git a/examples/github-trusted-iam-roles/versions.tf b/examples/github-trusted-iam-roles/versions.tf new file mode 100644 index 0000000..716dbe7 --- /dev/null +++ b/examples/github-trusted-iam-roles/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = "~> 1.3" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 4.0" + } + tls = { + source = "hashicorp/tls" + version = "~> 4.0" + } + } +}