From d014730ada0ab95d9f97d98b3cbf5192055083bf Mon Sep 17 00:00:00 2001 From: Max Williams <8859277+max-rocket-internet@users.noreply.github.com> Date: Mon, 25 Apr 2022 19:14:45 +0200 Subject: [PATCH] feat: Improved iam-eks-role module (simplified, removed provider_url_sa_pairs, updated docs) (#236) --- README.md | 2 +- examples/iam-eks-role/main.tf | 10 +---- modules/iam-eks-role/README.md | 67 +++++++++++++++++++++---------- modules/iam-eks-role/main.tf | 24 ----------- modules/iam-eks-role/outputs.tf | 8 ++-- modules/iam-eks-role/variables.tf | 6 --- 6 files changed, 52 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 1f032069..c65e869a 100644 --- a/README.md +++ b/README.md @@ -344,7 +344,7 @@ Use [iam-read-only-policy module](https://github.com/terraform-aws-modules/terra - [iam-assumable-role-with-saml](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/examples/iam-assumable-role-with-saml) - Create individual IAM role which can be assumed by users with a SAML Identity Provider - [iam-assumable-roles](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/examples/iam-assumable-roles) - Create several IAM roles which can be assumed from specified ARNs (AWS accounts, IAM users, etc) - [iam-assumable-roles-with-saml](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/examples/iam-assumable-roles-with-saml) - Create several IAM roles which can be assumed by users with a SAML Identity Provider -- [iam-eks-role](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/examples/iam-eks-role) - Create an IAM role which can be assumed by one or more EKS `ServiceAccount` +- [iam-eks-role](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/examples/iam-eks-role) - Create an IAM role that can be assumed by one or more EKS `ServiceAccount` - [iam-group-complete](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/examples/iam-group-complete) - IAM group with users who are allowed to assume IAM roles in another AWS account and have access to specified IAM policies - [iam-group-with-assumable-roles-policy](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/examples/iam-group-with-assumable-roles-policy) - IAM group with users who are allowed to assume IAM roles in the same or in separate AWS account - [iam-group-with-policies](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/examples/iam-group-with-policies) - IAM group with users who are allowed specified IAM policies (eg, "manage their own IAM user") diff --git a/examples/iam-eks-role/main.tf b/examples/iam-eks-role/main.tf index 85d37d0b..4a674619 100644 --- a/examples/iam-eks-role/main.tf +++ b/examples/iam-eks-role/main.tf @@ -7,15 +7,7 @@ module "iam_eks_role" { role_name = "my-app" cluster_service_accounts = { - (random_pet.this.id) = ["default:my-app", "canary:my-app"] - } - - provider_url_sa_pairs = { - "oidc.eks.us-east-1.amazonaws.com/id/5C54DDF35ER19312844C7333374CC09D" = ["default:my-app2"] - "oidc.eks.ap-southeast-1.amazonaws.com/id/5C54DDF35ER54476848E7333374FF09G" = [ - "default:my-app2", - "canary:my-app2", - ] + (random_pet.this.id) = ["default:my-app"] } tags = { diff --git a/modules/iam-eks-role/README.md b/modules/iam-eks-role/README.md index 34d5233d..68209948 100644 --- a/modules/iam-eks-role/README.md +++ b/modules/iam-eks-role/README.md @@ -1,52 +1,78 @@ # iam-eks-role -Creates single IAM role which can be assumed by one or more EKS `ServiceAccount` and optionally also OpenID Connect Federated Users. +Creates an IAM role that can be assumed by one or more EKS `ServiceAccount` in one or more EKS clusters. Unlike [iam-assumable-role-with-oidc](https://github.com/terraform-aws-modules/terraform-aws-iam/blob/master/modules/iam-assumable-role-with-oidc/), this module: + +- Does not require any knowledge of cluster OIDC information as `data` resources are used +- Supports assuming the role from multiple EKS clusters, for example used in DR or when a workload is spread across clusters +- Support multiple `ServiceAccount` in the same cluster, for example when a workload runs in multiple namespaces +- More suitable for non-cluster admins as implementation is simpler +- More suitable for when the IAM role and cluster resources are in separate Terraform configurations This module is for use with AWS EKS. For details of how a `ServiceAccount` in EKS can assume an IAM role, see the [EKS documentation](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). -This module supports multiple `ServiceAccount` in multiple clusters and/or namespaces. This allows for a single IAM role to be used when an application may span multiple clusters (e.g. for DR) or multiple namespaces (e.g. for canary deployments). The variables `cluster_service_accounts` and `provider_url_sa_pairs` are used for this as follows: +Implementation notes: + +- The EKS cluster needs to exist first, in the current AWS account and region +- The key in the `cluster_service_accounts` is the exact name of the EKS cluster + +## Basic example + +To create an IAM role named `my-app` that can be assumed in EKS cluster `cluster1` by a `ServiceAccount` called `my-serviceaccount` in the `default` namespace: ```hcl module "iam_eks_role" { source = "terraform-aws-modules/iam/aws//modules/iam-eks-role" + role_name = "my-app" + cluster_service_accounts = { - "" = [ - ":", - ":" - ] + "cluster1" = ["default:my-serviceaccount"] } +} +``` - provider_url_sa_pairs = { - "" = [ - ":", - ":" - ] +## Multi cluster example: + +To create an IAM role named `my-app` that can be assumed from: + +- EKS cluster `staging-main-1`, namespace `default`, `ServiceAccount` called `my-app-staging` +- EKS cluster `staging-backup-1`, namespace `default`, `ServiceAccount` called `my-app-staging` + +```hcl +module "iam_eks_role" { + source = "terraform-aws-modules/iam/aws//modules/iam-eks-role" + + role_name = "my-app" + + cluster_service_accounts = { + "staging-main-1" = ["default:my-app-staging"] + "staging-backup-1" = ["default:my-app-staging"] } } ``` -For example, to create an IAM role named `my-app` that can be assumed from the `ServiceAccount` named `my-app-staging` in the namespace `default` and `canary` in EKS cluster named `cluster-main-1`; and also the `ServiceAccount` name `my-app-staging` in the namespace `default` in EKS cluster named `cluster-backup-1`, the configuration would be: +## Multi `ServiceAccount` example + +To create an IAM role named `cloudwatch-exporter` that can be assumed in EKS cluster `production-main-1` from: + +- namespace `kube-system`, `ServiceAccount` called `cloudwatch-exporter` +- namespace `app1`, `ServiceAccount` called `cloudwatch-exporter` ```hcl module "iam_eks_role" { source = "terraform-aws-modules/iam/aws//modules/iam-eks-role" + role_name = "my-app" cluster_service_accounts = { - "cluster-main-1" = [ - "default:my-app-staging", - "canary:my-app-staging" - ], - "cluster-backup-1" = [ - "default:my-app-staging", + "production-main-1" = [ + "kube-system:cloudwatch-exporter", + "app1:cloudwatch-exporter", ] } } ``` -Note: the EKS clusters must in the current AWS region and account as they use the default AWS provider. - ## Requirements @@ -84,7 +110,6 @@ No modules. | [create\_role](#input\_create\_role) | Whether to create a role | `bool` | `true` | no | | [force\_detach\_policies](#input\_force\_detach\_policies) | Whether policies should be detached from this role when destroying | `bool` | `false` | no | | [max\_session\_duration](#input\_max\_session\_duration) | Maximum CLI/API session duration in seconds between 3600 and 43200 | `number` | `43200` | no | -| [provider\_url\_sa\_pairs](#input\_provider\_url\_sa\_pairs) | OIDC provider URL and k8s ServiceAccount pairs. If the assume role policy requires a mix of EKS clusters and other OIDC providers then this can be used | `map(list(string))` | `{}` | no | | [role\_description](#input\_role\_description) | IAM Role description | `string` | `""` | no | | [role\_name](#input\_role\_name) | Name of IAM role | `string` | `null` | no | | [role\_name\_prefix](#input\_role\_name\_prefix) | IAM role name prefix | `string` | `null` | no | diff --git a/modules/iam-eks-role/main.tf b/modules/iam-eks-role/main.tf index 6b6fe30e..2f23873f 100644 --- a/modules/iam-eks-role/main.tf +++ b/modules/iam-eks-role/main.tf @@ -32,30 +32,6 @@ data "aws_iam_policy_document" "assume_role_with_oidc" { } } } - - dynamic "statement" { - for_each = var.provider_url_sa_pairs - - content { - effect = "Allow" - - actions = ["sts:AssumeRoleWithWebIdentity"] - - principals { - type = "Federated" - - identifiers = [ - "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${statement.key}" - ] - } - - condition { - test = "StringEquals" - variable = "${statement.key}:sub" - values = [for s in statement.value : "system:serviceaccount:${s}"] - } - } - } } resource "aws_iam_role" "this" { diff --git a/modules/iam-eks-role/outputs.tf b/modules/iam-eks-role/outputs.tf index cdd317a3..a6805310 100644 --- a/modules/iam-eks-role/outputs.tf +++ b/modules/iam-eks-role/outputs.tf @@ -1,19 +1,19 @@ output "iam_role_arn" { description = "ARN of IAM role" - value = element(concat(aws_iam_role.this.*.arn, [""]), 0) + value = try(aws_iam_role.this[0].arn, "") } output "iam_role_name" { description = "Name of IAM role" - value = element(concat(aws_iam_role.this.*.name, [""]), 0) + value = try(aws_iam_role.this[0].name, "") } output "iam_role_path" { description = "Path of IAM role" - value = element(concat(aws_iam_role.this.*.path, [""]), 0) + value = try(aws_iam_role.this[0].path, "") } output "iam_role_unique_id" { description = "Unique ID of IAM role" - value = element(concat(aws_iam_role.this.*.unique_id, [""]), 0) + value = try(aws_iam_role.this[0].unique_id, "") } diff --git a/modules/iam-eks-role/variables.tf b/modules/iam-eks-role/variables.tf index 6a90d25c..aada84ee 100644 --- a/modules/iam-eks-role/variables.tf +++ b/modules/iam-eks-role/variables.tf @@ -46,12 +46,6 @@ variable "cluster_service_accounts" { default = {} } -variable "provider_url_sa_pairs" { - description = "OIDC provider URL and k8s ServiceAccount pairs. If the assume role policy requires a mix of EKS clusters and other OIDC providers then this can be used" - type = map(list(string)) - default = {} -} - variable "tags" { description = "A map of tags to add the the IAM role" type = map(any)