Skip to content

Commit 61cf542

Browse files
feat: Add new IAM module iam-eks-role (#179)
1 parent 54f327b commit 61cf542

File tree

12 files changed

+416
-2
lines changed

12 files changed

+416
-2
lines changed

README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,33 @@ module "iam_group_with_policies" {
242242
}
243243
```
244244

245+
`iam-eks-role`:
246+
247+
```hcl
248+
module "iam_eks_role" {
249+
source = "terraform-aws-modules/iam/aws//modules/iam-eks-role"
250+
version = "~> 4"
251+
252+
role_name = "my-app"
253+
254+
cluster_service_accounts = {
255+
"cluster1" = ["default:my-app"]
256+
"cluster2" = [
257+
"default:my-app",
258+
"canary:my-app",
259+
]
260+
}
261+
262+
tags = {
263+
Name = "eks-role"
264+
}
265+
266+
role_policy_arns = [
267+
"arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy",
268+
]
269+
}
270+
```
271+
245272
## IAM Best Practices
246273

247274
AWS published [IAM Best Practices](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html) and this Terraform module was created to help with some of points listed there:
@@ -290,6 +317,7 @@ Use [iam-read-only-policy module](https://github.com/terraform-aws-modules/terra
290317
- [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
291318
- [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)
292319
- [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
320+
- [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`
293321
- [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
294322
- [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")
295323
- [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
@@ -303,4 +331,4 @@ Module is maintained by [Anton Babenko](https://github.com/antonbabenko) with he
303331

304332
## License
305333

306-
Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/LICENSE) for full details.
334+
Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/LICENSE) for full details.

examples/iam-eks-role/README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# IAM EKS role
2+
3+
Configuration in this directory creates an IAM role that can be assumed by multiple EKS `ServiceAccount`.
4+
5+
# Usage
6+
7+
To run this example you need to execute:
8+
9+
```bash
10+
$ terraform init
11+
$ terraform plan
12+
$ terraform apply
13+
```
14+
15+
Run `terraform destroy` when you don't need these resources.
16+
17+
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
18+
## Requirements
19+
20+
| Name | Version |
21+
|------|---------|
22+
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 0.12.6 |
23+
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 2.23 |
24+
25+
## Providers
26+
27+
No providers.
28+
29+
## Modules
30+
31+
| Name | Source | Version |
32+
|------|--------|---------|
33+
| <a name="module_iam_eks_role"></a> [iam\_eks\_role](#module\_iam\_eks\_role) | ../../modules/iam-eks-role | n/a |
34+
35+
## Resources
36+
37+
No resources.
38+
39+
## Inputs
40+
41+
No inputs.
42+
43+
## Outputs
44+
45+
| Name | Description |
46+
|------|-------------|
47+
| <a name="output_iam_role_arn"></a> [iam\_role\_arn](#output\_iam\_role\_arn) | ARN of IAM role |
48+
| <a name="output_iam_role_name"></a> [iam\_role\_name](#output\_iam\_role\_name) | Name of IAM role |
49+
| <a name="output_iam_role_path"></a> [iam\_role\_path](#output\_iam\_role\_path) | Path of IAM role |
50+
| <a name="output_iam_role_unique_id"></a> [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Unique ID of IAM role |
51+
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->

examples/iam-eks-role/main.tf

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
provider "aws" {
2+
region = "eu-west-1"
3+
}
4+
5+
module "iam_eks_role" {
6+
source = "../../modules/iam-eks-role"
7+
role_name = "my-app"
8+
9+
cluster_service_accounts = {
10+
"cluster1" = ["default:my-app"]
11+
"cluster2" = [
12+
"default:my-app",
13+
"canary:my-app",
14+
]
15+
}
16+
17+
provider_url_sa_pairs = {
18+
"oidc.eks.us-east-1.amazonaws.com/id/5C54DDF35ER19312844C7333374CC09D" = ["default:my-app2"]
19+
"oidc.eks.ap-southeast-1.amazonaws.com/id/5C54DDF35ER54476848E7333374FF09G" = [
20+
"default:my-app2",
21+
"canary:my-app2",
22+
]
23+
}
24+
25+
tags = {
26+
Name = "eks-role"
27+
}
28+
29+
role_policy_arns = [
30+
"arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
31+
]
32+
}

examples/iam-eks-role/outputs.tf

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
output "iam_role_arn" {
2+
description = "ARN of IAM role"
3+
value = module.iam_eks_role.iam_role_arn
4+
}
5+
6+
output "iam_role_name" {
7+
description = "Name of IAM role"
8+
value = module.iam_eks_role.iam_role_name
9+
}
10+
11+
output "iam_role_path" {
12+
description = "Path of IAM role"
13+
value = module.iam_eks_role.iam_role_path
14+
}
15+
16+
output "iam_role_unique_id" {
17+
description = "Unique ID of IAM role"
18+
value = module.iam_eks_role.iam_role_unique_id
19+
}

examples/iam-eks-role/variables.tf

Whitespace-only changes.

examples/iam-eks-role/versions.tf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
terraform {
2+
required_version = ">= 0.12.6"
3+
4+
required_providers {
5+
aws = ">= 2.23"
6+
}
7+
}

modules/iam-assumable-role-with-oidc/variables.tf

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ variable "number_of_role_policy_arns" {
7676
default = null
7777
}
7878

79-
8079
variable "oidc_fully_qualified_subjects" {
8180
description = "The fully qualified OIDC subjects to be added to the role policy"
8281
type = set(string)

modules/iam-eks-role/README.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# iam-eks-role
2+
3+
Creates single IAM role which can be assumed by one or more EKS `ServiceAccount` and optionally also OpenID Connect Federated Users.
4+
5+
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).
6+
7+
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:
8+
9+
```hcl
10+
module "iam_eks_role" {
11+
source = "terraform-aws-modules/iam/aws//modules/iam-eks-role"
12+
13+
cluster_service_accounts = {
14+
"<EKS cluster name>" = [
15+
"<namespace>:<ServiceAccount name>",
16+
"<namespace>:<another ServiceAccount name>"
17+
]
18+
}
19+
20+
provider_url_sa_pairs = {
21+
"<OIDC provider without protocol prefix>" = [
22+
"<namespace>:<ServiceAccount name>",
23+
"<namespace>:<another ServiceAccount name>"
24+
]
25+
}
26+
```
27+
28+
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:
29+
30+
```hcl
31+
module "iam_eks_role" {
32+
source = "terraform-aws-modules/iam/aws//modules/iam-eks-role"
33+
role_name = "my-app"
34+
35+
cluster_service_accounts = {
36+
"cluster-main-1" = [
37+
"default:my-app-staging",
38+
"canary:my-app-staging"
39+
]
40+
"cluster-backup-1" = [
41+
"default:my-app-staging",
42+
]
43+
}
44+
```
45+
46+
Note: the EKS clusters must in the current AWS region and account as they use the default AWS provider.
47+
48+
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
49+
## Requirements
50+
51+
| Name | Version |
52+
|------|---------|
53+
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 0.12.6 |
54+
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 2.23 |
55+
56+
## Providers
57+
58+
| Name | Version |
59+
|------|---------|
60+
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 2.23 |
61+
62+
## Modules
63+
64+
No modules.
65+
66+
## Resources
67+
68+
| Name | Type |
69+
|------|------|
70+
| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
71+
| [aws_iam_role_policy_attachment.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
72+
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
73+
| [aws_eks_cluster.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/eks_cluster) | data source |
74+
| [aws_iam_policy_document.assume_role_with_oidc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
75+
| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source |
76+
77+
## Inputs
78+
79+
| Name | Description | Type | Default | Required |
80+
|------|-------------|------|---------|:--------:|
81+
| <a name="input_cluster_service_accounts"></a> [cluster\_service\_accounts](#input\_cluster\_service\_accounts) | EKS cluster and k8s ServiceAccount pairs. Each EKS cluster can have multiple k8s ServiceAccount. See README for details | `map(list(string))` | `{}` | no |
82+
| <a name="input_create_role"></a> [create\_role](#input\_create\_role) | Whether to create a role | `bool` | `true` | no |
83+
| <a name="input_force_detach_policies"></a> [force\_detach\_policies](#input\_force\_detach\_policies) | Whether policies should be detached from this role when destroying | `bool` | `false` | no |
84+
| <a name="input_max_session_duration"></a> [max\_session\_duration](#input\_max\_session\_duration) | Maximum CLI/API session duration in seconds between 3600 and 43200 | `number` | `43200` | no |
85+
| <a name="input_provider_url_sa_pairs"></a> [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 |
86+
| <a name="input_role_description"></a> [role\_description](#input\_role\_description) | IAM Role description | `string` | `""` | no |
87+
| <a name="input_role_name"></a> [role\_name](#input\_role\_name) | Name of IAM role | `string` | `null` | no |
88+
| <a name="input_role_name_prefix"></a> [role\_name\_prefix](#input\_role\_name\_prefix) | IAM role name prefix | `string` | `null` | no |
89+
| <a name="input_role_path"></a> [role\_path](#input\_role\_path) | Path of IAM role | `string` | `"/"` | no |
90+
| <a name="input_role_permissions_boundary_arn"></a> [role\_permissions\_boundary\_arn](#input\_role\_permissions\_boundary\_arn) | Permissions boundary ARN to use for IAM role | `string` | `""` | no |
91+
| <a name="input_role_policy_arns"></a> [role\_policy\_arns](#input\_role\_policy\_arns) | ARNs of any policies to attach to the IAM role | `list(string)` | `[]` | no |
92+
| <a name="input_tags"></a> [tags](#input\_tags) | A map of tags to add the the IAM role | `map(any)` | `{}` | no |
93+
94+
## Outputs
95+
96+
| Name | Description |
97+
|------|-------------|
98+
| <a name="output_iam_role_arn"></a> [iam\_role\_arn](#output\_iam\_role\_arn) | ARN of IAM role |
99+
| <a name="output_iam_role_name"></a> [iam\_role\_name](#output\_iam\_role\_name) | Name of IAM role |
100+
| <a name="output_iam_role_path"></a> [iam\_role\_path](#output\_iam\_role\_path) | Path of IAM role |
101+
| <a name="output_iam_role_unique_id"></a> [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Unique ID of IAM role |
102+
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->

modules/iam-eks-role/main.tf

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
data "aws_caller_identity" "current" {}
2+
3+
data "aws_partition" "current" {}
4+
5+
data "aws_eks_cluster" "main" {
6+
for_each = var.cluster_service_accounts
7+
8+
name = each.key
9+
}
10+
11+
data "aws_iam_policy_document" "assume_role_with_oidc" {
12+
dynamic "statement" {
13+
for_each = var.cluster_service_accounts
14+
15+
content {
16+
effect = "Allow"
17+
18+
actions = ["sts:AssumeRoleWithWebIdentity"]
19+
20+
principals {
21+
type = "Federated"
22+
23+
identifiers = [
24+
"arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${replace(data.aws_eks_cluster.main[statement.key].identity[0].oidc[0].issuer, "https://", "")}"
25+
]
26+
}
27+
28+
condition {
29+
test = "StringEquals"
30+
variable = "${replace(data.aws_eks_cluster.main[statement.key].identity[0].oidc[0].issuer, "https://", "")}:sub"
31+
values = [for s in statement.value : "system:serviceaccount:${s}"]
32+
}
33+
}
34+
}
35+
36+
dynamic "statement" {
37+
for_each = var.provider_url_sa_pairs
38+
39+
content {
40+
effect = "Allow"
41+
42+
actions = ["sts:AssumeRoleWithWebIdentity"]
43+
44+
principals {
45+
type = "Federated"
46+
47+
identifiers = [
48+
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${statement.key}"
49+
]
50+
}
51+
52+
condition {
53+
test = "StringEquals"
54+
variable = "${statement.key}:sub"
55+
values = [for s in statement.value : "system:serviceaccount:${s}"]
56+
}
57+
}
58+
}
59+
}
60+
61+
resource "aws_iam_role" "this" {
62+
count = var.create_role ? 1 : 0
63+
64+
assume_role_policy = data.aws_iam_policy_document.assume_role_with_oidc.json
65+
description = var.role_description
66+
force_detach_policies = var.force_detach_policies
67+
max_session_duration = var.max_session_duration
68+
name = var.role_name
69+
name_prefix = var.role_name_prefix
70+
path = var.role_path
71+
permissions_boundary = var.role_permissions_boundary_arn
72+
tags = var.tags
73+
}
74+
75+
resource "aws_iam_role_policy_attachment" "custom" {
76+
for_each = var.create_role ? toset(var.role_policy_arns) : []
77+
role = aws_iam_role.this[0].name
78+
policy_arn = each.key
79+
}

modules/iam-eks-role/outputs.tf

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
output "iam_role_arn" {
2+
description = "ARN of IAM role"
3+
value = element(concat(aws_iam_role.this.*.arn, [""]), 0)
4+
}
5+
6+
output "iam_role_name" {
7+
description = "Name of IAM role"
8+
value = element(concat(aws_iam_role.this.*.name, [""]), 0)
9+
}
10+
11+
output "iam_role_path" {
12+
description = "Path of IAM role"
13+
value = element(concat(aws_iam_role.this.*.path, [""]), 0)
14+
}
15+
16+
output "iam_role_unique_id" {
17+
description = "Unique ID of IAM role"
18+
value = element(concat(aws_iam_role.this.*.unique_id, [""]), 0)
19+
}

0 commit comments

Comments
 (0)