Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add new IAM module iam-eks-role #179

Merged
merged 10 commits into from
Jan 14, 2022
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,33 @@ module "iam_group_with_policies" {
}
```

`iam-eks-role`:

```hcl
module "iam_eks_role" {
source = "terraform-aws-modules/iam/aws//modules/iam-eks-role"
version = "~> 4"

role_name = "my-app"

cluster_service_accounts = {
"cluster1" = ["default:my-app"]
"cluster2" = [
"default:my-app",
"canary:my-app",
]
}

tags = {
Name = "eks-role"
}

role_policy_arns = [
"arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy",
]
}
```

## IAM Best Practices

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:
Expand Down Expand Up @@ -290,6 +317,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-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")
- [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
Expand All @@ -303,4 +331,4 @@ Module is maintained by [Anton Babenko](https://github.com/antonbabenko) with he

## License

Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/LICENSE) for full details.
Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/LICENSE) for full details.
51 changes: 51 additions & 0 deletions examples/iam-eks-role/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# IAM EKS role

Configuration in this directory creates an IAM role that can be assumed by multiple EKS `ServiceAccount`.

# Usage

To run this example you need to execute:

```bash
$ terraform init
$ terraform plan
$ terraform apply
```

Run `terraform destroy` when you don't need these resources.

<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 0.12.6 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 2.23 |

## Providers

No providers.

## Modules

| Name | Source | Version |
|------|--------|---------|
| <a name="module_iam_eks_role"></a> [iam\_eks\_role](#module\_iam\_eks\_role) | ../../modules/iam-eks-role | n/a |

## Resources

No resources.

## Inputs

No inputs.

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_iam_role_arn"></a> [iam\_role\_arn](#output\_iam\_role\_arn) | ARN of IAM role |
| <a name="output_iam_role_name"></a> [iam\_role\_name](#output\_iam\_role\_name) | Name of IAM role |
| <a name="output_iam_role_path"></a> [iam\_role\_path](#output\_iam\_role\_path) | Path of IAM role |
| <a name="output_iam_role_unique_id"></a> [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Unique ID of IAM role |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
32 changes: 32 additions & 0 deletions examples/iam-eks-role/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
provider "aws" {
region = "eu-west-1"
}

module "iam_eks_role" {
source = "../../modules/iam-eks-role"
role_name = "my-app"

cluster_service_accounts = {
"cluster1" = ["default:my-app"]
"cluster2" = [
"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",
]
}

tags = {
Name = "eks-role"
}

role_policy_arns = [
"arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
]
}
19 changes: 19 additions & 0 deletions examples/iam-eks-role/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
output "iam_role_arn" {
description = "ARN of IAM role"
value = module.iam_eks_role.iam_role_arn
}

output "iam_role_name" {
description = "Name of IAM role"
value = module.iam_eks_role.iam_role_name
}

output "iam_role_path" {
description = "Path of IAM role"
value = module.iam_eks_role.iam_role_path
}

output "iam_role_unique_id" {
description = "Unique ID of IAM role"
value = module.iam_eks_role.iam_role_unique_id
}
Empty file.
7 changes: 7 additions & 0 deletions examples/iam-eks-role/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
terraform {
required_version = ">= 0.12.6"

required_providers {
aws = ">= 2.23"
}
}
1 change: 0 additions & 1 deletion modules/iam-assumable-role-with-oidc/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ variable "number_of_role_policy_arns" {
default = null
}


variable "oidc_fully_qualified_subjects" {
description = "The fully qualified OIDC subjects to be added to the role policy"
type = set(string)
Expand Down
102 changes: 102 additions & 0 deletions modules/iam-eks-role/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# iam-eks-role

Creates single IAM role which can be assumed by one or more EKS `ServiceAccount` and optionally also OpenID Connect Federated Users.

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:

```hcl
module "iam_eks_role" {
source = "terraform-aws-modules/iam/aws//modules/iam-eks-role"
cluster_service_accounts = {
"<EKS cluster name>" = [
"<namespace>:<ServiceAccount name>",
"<namespace>:<another ServiceAccount name>"
]
}
provider_url_sa_pairs = {
"<OIDC provider without protocol prefix>" = [
"<namespace>:<ServiceAccount name>",
"<namespace>:<another ServiceAccount name>"
]
}
```

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:

```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",
]
}
```

Note: the EKS clusters must in the current AWS region and account as they use the default AWS provider.

<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 0.12.6 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 2.23 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 2.23 |

## 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_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_eks_cluster.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/eks_cluster) | 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_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <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 |
| <a name="input_create_role"></a> [create\_role](#input\_create\_role) | Whether to create a role | `bool` | `true` | no |
| <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 |
| <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 |
| <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 |
| <a name="input_role_description"></a> [role\_description](#input\_role\_description) | IAM Role description | `string` | `""` | no |
| <a name="input_role_name"></a> [role\_name](#input\_role\_name) | Name of IAM role | `string` | `null` | no |
| <a name="input_role_name_prefix"></a> [role\_name\_prefix](#input\_role\_name\_prefix) | IAM role name prefix | `string` | `null` | no |
| <a name="input_role_path"></a> [role\_path](#input\_role\_path) | Path of IAM role | `string` | `"/"` | no |
| <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 |
| <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 |
| <a name="input_tags"></a> [tags](#input\_tags) | A map of tags to add the the IAM role | `map(any)` | `{}` | no |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_iam_role_arn"></a> [iam\_role\_arn](#output\_iam\_role\_arn) | ARN of IAM role |
| <a name="output_iam_role_name"></a> [iam\_role\_name](#output\_iam\_role\_name) | Name of IAM role |
| <a name="output_iam_role_path"></a> [iam\_role\_path](#output\_iam\_role\_path) | Path of IAM role |
| <a name="output_iam_role_unique_id"></a> [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Unique ID of IAM role |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
79 changes: 79 additions & 0 deletions modules/iam-eks-role/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
data "aws_caller_identity" "current" {}

data "aws_partition" "current" {}

data "aws_eks_cluster" "main" {
for_each = var.cluster_service_accounts

name = each.key
}

data "aws_iam_policy_document" "assume_role_with_oidc" {
dynamic "statement" {
for_each = var.cluster_service_accounts

content {
effect = "Allow"

actions = ["sts:AssumeRoleWithWebIdentity"]

principals {
type = "Federated"

identifiers = [
"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://", "")}"
]
}

condition {
test = "StringEquals"
variable = "${replace(data.aws_eks_cluster.main[statement.key].identity[0].oidc[0].issuer, "https://", "")}:sub"
values = [for s in statement.value : "system:serviceaccount:${s}"]
}
}
}

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" {
count = var.create_role ? 1 : 0

assume_role_policy = data.aws_iam_policy_document.assume_role_with_oidc.json
description = var.role_description
force_detach_policies = var.force_detach_policies
max_session_duration = var.max_session_duration
name = var.role_name
name_prefix = var.role_name_prefix
path = var.role_path
permissions_boundary = var.role_permissions_boundary_arn
tags = var.tags
}

resource "aws_iam_role_policy_attachment" "custom" {
for_each = var.create_role ? toset(var.role_policy_arns) : []
role = aws_iam_role.this[0].name
policy_arn = each.key
}
19 changes: 19 additions & 0 deletions modules/iam-eks-role/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
output "iam_role_arn" {
description = "ARN of IAM role"
value = element(concat(aws_iam_role.this.*.arn, [""]), 0)
}

output "iam_role_name" {
description = "Name of IAM role"
value = element(concat(aws_iam_role.this.*.name, [""]), 0)
}

output "iam_role_path" {
description = "Path of IAM role"
value = element(concat(aws_iam_role.this.*.path, [""]), 0)
}

output "iam_role_unique_id" {
description = "Unique ID of IAM role"
value = element(concat(aws_iam_role.this.*.unique_id, [""]), 0)
}
Loading