Skip to content

Commit

Permalink
feat: Add new IAM module iam-eks-role (#179)
Browse files Browse the repository at this point in the history
  • Loading branch information
max-rocket-internet authored Jan 14, 2022
1 parent 54f327b commit 61cf542
Show file tree
Hide file tree
Showing 12 changed files with 416 additions and 2 deletions.
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

0 comments on commit 61cf542

Please sign in to comment.