From 6eda19f527879d0e1b5266eabc4f5370a75e36ca Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Tue, 1 Feb 2022 18:48:43 -0500 Subject: [PATCH 01/12] chore: update outputs and required providers to current standards --- modules/iam-account/outputs.tf | 8 +-- modules/iam-account/versions.tf | 5 +- .../iam-assumable-role-with-oidc/outputs.tf | 8 +-- .../iam-assumable-role-with-oidc/versions.tf | 5 +- .../iam-assumable-role-with-saml/outputs.tf | 8 +-- .../iam-assumable-role-with-saml/versions.tf | 5 +- modules/iam-assumable-role/outputs.tf | 16 ++--- modules/iam-assumable-role/versions.tf | 5 +- .../iam-assumable-roles-with-saml/outputs.tf | 24 ++++---- .../iam-assumable-roles-with-saml/versions.tf | 5 +- modules/iam-assumable-roles/outputs.tf | 24 ++++---- modules/iam-assumable-roles/versions.tf | 5 +- modules/iam-eks-role/outputs.tf | 8 +-- modules/iam-eks-role/versions.tf | 5 +- .../outputs.tf | 2 +- .../versions.tf | 5 +- modules/iam-group-with-policies/outputs.tf | 6 +- modules/iam-group-with-policies/versions.tf | 5 +- modules/iam-policy/outputs.tf | 12 ++-- modules/iam-policy/versions.tf | 5 +- modules/iam-read-only-policy/outputs.tf | 12 ++-- modules/iam-read-only-policy/versions.tf | 5 +- modules/iam-user/outputs.tf | 61 ++++++------------- modules/iam-user/versions.tf | 5 +- 24 files changed, 132 insertions(+), 117 deletions(-) diff --git a/modules/iam-account/outputs.tf b/modules/iam-account/outputs.tf index 3145d45d..c286d1e5 100644 --- a/modules/iam-account/outputs.tf +++ b/modules/iam-account/outputs.tf @@ -1,19 +1,19 @@ output "caller_identity_account_id" { description = "The AWS Account ID number of the account that owns or contains the calling entity" - value = element(concat(data.aws_caller_identity.this.*.account_id, [""]), 0) + value = try(data.aws_caller_identity.this[0].account_id, "") } output "caller_identity_arn" { description = "The AWS ARN associated with the calling entity" - value = element(concat(data.aws_caller_identity.this.*.arn, [""]), 0) + value = try(data.aws_caller_identity.this[0].arn, "") } output "caller_identity_user_id" { description = "The unique identifier of the calling entity" - value = element(concat(data.aws_caller_identity.this.*.user_id, [""]), 0) + value = try(data.aws_caller_identity.this[0].user_id, "") } output "iam_account_password_policy_expire_passwords" { description = "Indicates whether passwords in the account expire. Returns true if max_password_age contains a value greater than 0. Returns false if it is 0 or not present." - value = element(concat(aws_iam_account_password_policy.this.*.expire_passwords, [""]), 0) + value = try(aws_iam_account_password_policy.this[0].expire_passwords, "") } diff --git a/modules/iam-account/versions.tf b/modules/iam-account/versions.tf index fff6b757..4e3fe457 100644 --- a/modules/iam-account/versions.tf +++ b/modules/iam-account/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } diff --git a/modules/iam-assumable-role-with-oidc/outputs.tf b/modules/iam-assumable-role-with-oidc/outputs.tf index cdd317a3..a6805310 100644 --- a/modules/iam-assumable-role-with-oidc/outputs.tf +++ b/modules/iam-assumable-role-with-oidc/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-assumable-role-with-oidc/versions.tf b/modules/iam-assumable-role-with-oidc/versions.tf index fff6b757..4e3fe457 100644 --- a/modules/iam-assumable-role-with-oidc/versions.tf +++ b/modules/iam-assumable-role-with-oidc/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } diff --git a/modules/iam-assumable-role-with-saml/outputs.tf b/modules/iam-assumable-role-with-saml/outputs.tf index cdd317a3..a6805310 100644 --- a/modules/iam-assumable-role-with-saml/outputs.tf +++ b/modules/iam-assumable-role-with-saml/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-assumable-role-with-saml/versions.tf b/modules/iam-assumable-role-with-saml/versions.tf index fff6b757..4e3fe457 100644 --- a/modules/iam-assumable-role-with-saml/versions.tf +++ b/modules/iam-assumable-role-with-saml/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } diff --git a/modules/iam-assumable-role/outputs.tf b/modules/iam-assumable-role/outputs.tf index a8d1c563..117ff077 100644 --- a/modules/iam-assumable-role/outputs.tf +++ b/modules/iam-assumable-role/outputs.tf @@ -1,21 +1,21 @@ 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, "") } output "role_requires_mfa" { @@ -25,22 +25,22 @@ output "role_requires_mfa" { output "iam_instance_profile_arn" { description = "ARN of IAM instance profile" - value = element(concat(aws_iam_instance_profile.this.*.arn, [""]), 0) + value = try(aws_iam_instance_profile.this[0].arn, "") } output "iam_instance_profile_name" { description = "Name of IAM instance profile" - value = element(concat(aws_iam_instance_profile.this.*.name, [""]), 0) + value = try(aws_iam_instance_profile.this[0].name, "") } output "iam_instance_profile_id" { description = "IAM Instance profile's ID." - value = element(concat(aws_iam_instance_profile.this.*.id, [""]), 0) + value = try(aws_iam_instance_profile.this[0].id, "") } output "iam_instance_profile_path" { description = "Path of IAM instance profile" - value = element(concat(aws_iam_instance_profile.this.*.path, [""]), 0) + value = try(aws_iam_instance_profile.this[0].path, "") } output "role_sts_externalid" { diff --git a/modules/iam-assumable-role/versions.tf b/modules/iam-assumable-role/versions.tf index cd54581c..1fe6583f 100644 --- a/modules/iam-assumable-role/versions.tf +++ b/modules/iam-assumable-role/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 3.34" + aws = { + source = "hashicorp/aws" + version = ">= 3.34" + } } } diff --git a/modules/iam-assumable-roles-with-saml/outputs.tf b/modules/iam-assumable-roles-with-saml/outputs.tf index 2ffbd2be..3b1c4d1a 100644 --- a/modules/iam-assumable-roles-with-saml/outputs.tf +++ b/modules/iam-assumable-roles-with-saml/outputs.tf @@ -1,61 +1,61 @@ #Admin output "admin_iam_role_arn" { description = "ARN of admin IAM role" - value = element(concat(aws_iam_role.admin.*.arn, [""]), 0) + value = try(aws_iam_role.admin[0].arn, "") } output "admin_iam_role_name" { description = "Name of admin IAM role" - value = element(concat(aws_iam_role.admin.*.name, [""]), 0) + value = try(aws_iam_role.admin[0].name, "") } output "admin_iam_role_path" { description = "Path of admin IAM role" - value = element(concat(aws_iam_role.admin.*.path, [""]), 0) + value = try(aws_iam_role.admin[0].path, "") } output "admin_iam_role_unique_id" { description = "Unique ID of IAM role" - value = element(concat(aws_iam_role.admin.*.unique_id, [""]), 0) + value = try(aws_iam_role.admin[0].unique_id, "") } output "poweruser_iam_role_arn" { description = "ARN of poweruser IAM role" - value = element(concat(aws_iam_role.poweruser.*.arn, [""]), 0) + value = try(aws_iam_role.poweruser[0].arn, "") } output "poweruser_iam_role_name" { description = "Name of poweruser IAM role" - value = element(concat(aws_iam_role.poweruser.*.name, [""]), 0) + value = try(aws_iam_role.poweruser[0].name, "") } output "poweruser_iam_role_path" { description = "Path of poweruser IAM role" - value = element(concat(aws_iam_role.poweruser.*.path, [""]), 0) + value = try(aws_iam_role.poweruser[0].path, "") } output "poweruser_iam_role_unique_id" { description = "Unique ID of IAM role" - value = element(concat(aws_iam_role.poweruser.*.unique_id, [""]), 0) + value = try(aws_iam_role.poweruser[0].unique_id, "") } # Readonly output "readonly_iam_role_arn" { description = "ARN of readonly IAM role" - value = element(concat(aws_iam_role.readonly.*.arn, [""]), 0) + value = try(aws_iam_role.readonly[0].arn, "") } output "readonly_iam_role_name" { description = "Name of readonly IAM role" - value = element(concat(aws_iam_role.readonly.*.name, [""]), 0) + value = try(aws_iam_role.readonly[0].name, "") } output "readonly_iam_role_path" { description = "Path of readonly IAM role" - value = element(concat(aws_iam_role.readonly.*.path, [""]), 0) + value = try(aws_iam_role.readonly[0].path, "") } output "readonly_iam_role_unique_id" { description = "Unique ID of IAM role" - value = element(concat(aws_iam_role.readonly.*.unique_id, [""]), 0) + value = try(aws_iam_role.readonly[0].unique_id, "") } diff --git a/modules/iam-assumable-roles-with-saml/versions.tf b/modules/iam-assumable-roles-with-saml/versions.tf index fff6b757..4e3fe457 100644 --- a/modules/iam-assumable-roles-with-saml/versions.tf +++ b/modules/iam-assumable-roles-with-saml/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } diff --git a/modules/iam-assumable-roles/outputs.tf b/modules/iam-assumable-roles/outputs.tf index dff95d74..5918ba46 100644 --- a/modules/iam-assumable-roles/outputs.tf +++ b/modules/iam-assumable-roles/outputs.tf @@ -1,22 +1,22 @@ #Admin output "admin_iam_role_arn" { description = "ARN of admin IAM role" - value = element(concat(aws_iam_role.admin.*.arn, [""]), 0) + value = try(aws_iam_role.admin[0].arn, "") } output "admin_iam_role_name" { description = "Name of admin IAM role" - value = element(concat(aws_iam_role.admin.*.name, [""]), 0) + value = try(aws_iam_role.admin[0].name, "") } output "admin_iam_role_path" { description = "Path of admin IAM role" - value = element(concat(aws_iam_role.admin.*.path, [""]), 0) + value = try(aws_iam_role.admin[0].path, "") } output "admin_iam_role_unique_id" { description = "Unique ID of IAM role" - value = element(concat(aws_iam_role.admin.*.unique_id, [""]), 0) + value = try(aws_iam_role.admin[0].unique_id, "") } output "admin_iam_role_requires_mfa" { @@ -27,22 +27,22 @@ output "admin_iam_role_requires_mfa" { # Poweruser output "poweruser_iam_role_arn" { description = "ARN of poweruser IAM role" - value = element(concat(aws_iam_role.poweruser.*.arn, [""]), 0) + value = try(aws_iam_role.poweruser[0].arn, "") } output "poweruser_iam_role_name" { description = "Name of poweruser IAM role" - value = element(concat(aws_iam_role.poweruser.*.name, [""]), 0) + value = try(aws_iam_role.poweruser[0].name, "") } output "poweruser_iam_role_path" { description = "Path of poweruser IAM role" - value = element(concat(aws_iam_role.poweruser.*.path, [""]), 0) + value = try(aws_iam_role.poweruser[0].path, "") } output "poweruser_iam_role_unique_id" { description = "Unique ID of IAM role" - value = element(concat(aws_iam_role.poweruser.*.unique_id, [""]), 0) + value = try(aws_iam_role.poweruser[0].unique_id, "") } output "poweruser_iam_role_requires_mfa" { @@ -53,22 +53,22 @@ output "poweruser_iam_role_requires_mfa" { # Readonly output "readonly_iam_role_arn" { description = "ARN of readonly IAM role" - value = element(concat(aws_iam_role.readonly.*.arn, [""]), 0) + value = try(aws_iam_role.readonly[0].arn, "") } output "readonly_iam_role_name" { description = "Name of readonly IAM role" - value = element(concat(aws_iam_role.readonly.*.name, [""]), 0) + value = try(aws_iam_role.readonly[0].name, "") } output "readonly_iam_role_path" { description = "Path of readonly IAM role" - value = element(concat(aws_iam_role.readonly.*.path, [""]), 0) + value = try(aws_iam_role.readonly[0].path, "") } output "readonly_iam_role_unique_id" { description = "Unique ID of IAM role" - value = element(concat(aws_iam_role.readonly.*.unique_id, [""]), 0) + value = try(aws_iam_role.readonly[0].unique_id, "") } output "readonly_iam_role_requires_mfa" { diff --git a/modules/iam-assumable-roles/versions.tf b/modules/iam-assumable-roles/versions.tf index fff6b757..4e3fe457 100644 --- a/modules/iam-assumable-roles/versions.tf +++ b/modules/iam-assumable-roles/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } 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/versions.tf b/modules/iam-eks-role/versions.tf index fff6b757..4e3fe457 100644 --- a/modules/iam-eks-role/versions.tf +++ b/modules/iam-eks-role/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } diff --git a/modules/iam-group-with-assumable-roles-policy/outputs.tf b/modules/iam-group-with-assumable-roles-policy/outputs.tf index 89913015..a172a664 100644 --- a/modules/iam-group-with-assumable-roles-policy/outputs.tf +++ b/modules/iam-group-with-assumable-roles-policy/outputs.tf @@ -1,6 +1,6 @@ output "group_users" { description = "List of IAM users in IAM group" - value = flatten(aws_iam_group_membership.this.*.users) + value = flatten(aws_iam_group_membership.this[*].users) } output "assumable_roles" { diff --git a/modules/iam-group-with-assumable-roles-policy/versions.tf b/modules/iam-group-with-assumable-roles-policy/versions.tf index fff6b757..4e3fe457 100644 --- a/modules/iam-group-with-assumable-roles-policy/versions.tf +++ b/modules/iam-group-with-assumable-roles-policy/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } diff --git a/modules/iam-group-with-policies/outputs.tf b/modules/iam-group-with-policies/outputs.tf index 64d93040..ee0835c0 100644 --- a/modules/iam-group-with-policies/outputs.tf +++ b/modules/iam-group-with-policies/outputs.tf @@ -5,15 +5,15 @@ output "aws_account_id" { output "group_arn" { description = "IAM group arn" - value = element(concat(aws_iam_group.this.*.arn, [""]), 0) + value = try(aws_iam_group.this[0].arn, "") } output "group_users" { description = "List of IAM users in IAM group" - value = flatten(aws_iam_group_membership.this.*.users) + value = flatten(aws_iam_group_membership.this[*].users) } output "group_name" { description = "IAM group name" - value = element(concat(aws_iam_group.this.*.name, [var.name]), 0) + value = try(aws_iam_group.this[0].name, var.name) } diff --git a/modules/iam-group-with-policies/versions.tf b/modules/iam-group-with-policies/versions.tf index fff6b757..4e3fe457 100644 --- a/modules/iam-group-with-policies/versions.tf +++ b/modules/iam-group-with-policies/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } diff --git a/modules/iam-policy/outputs.tf b/modules/iam-policy/outputs.tf index d125c849..d8900802 100644 --- a/modules/iam-policy/outputs.tf +++ b/modules/iam-policy/outputs.tf @@ -1,29 +1,29 @@ output "id" { description = "The policy's ID" - value = element(concat(aws_iam_policy.policy.*.id, [""]), 0) + value = try(aws_iam_policy.policy[0].id, "") } output "arn" { description = "The ARN assigned by AWS to this policy" - value = element(concat(aws_iam_policy.policy.*.arn, [""]), 0) + value = try(aws_iam_policy.policy[0].arn, "") } output "description" { description = "The description of the policy" - value = element(concat(aws_iam_policy.policy.*.description, [""]), 0) + value = try(aws_iam_policy.policy[0].description, "") } output "name" { description = "The name of the policy" - value = element(concat(aws_iam_policy.policy.*.name, [""]), 0) + value = try(aws_iam_policy.policy[0].name, "") } output "path" { description = "The path of the policy in IAM" - value = element(concat(aws_iam_policy.policy.*.path, [""]), 0) + value = try(aws_iam_policy.policy[0].path, "") } output "policy" { description = "The policy document" - value = element(concat(aws_iam_policy.policy.*.policy, [""]), 0) + value = try(aws_iam_policy.policy[0].policy, "") } diff --git a/modules/iam-policy/versions.tf b/modules/iam-policy/versions.tf index c7bef0b2..38ab12c5 100644 --- a/modules/iam-policy/versions.tf +++ b/modules/iam-policy/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 3.35" + aws = { + source = "hashicorp/aws" + version = ">= 3.35" + } } } diff --git a/modules/iam-read-only-policy/outputs.tf b/modules/iam-read-only-policy/outputs.tf index 17989a74..c18731d5 100644 --- a/modules/iam-read-only-policy/outputs.tf +++ b/modules/iam-read-only-policy/outputs.tf @@ -5,30 +5,30 @@ output "policy_json" { output "id" { description = "The policy's ID" - value = element(concat(aws_iam_policy.policy.*.id, [""]), 0) + value = try(aws_iam_policy.policy[0].id, "") } output "arn" { description = "The ARN assigned by AWS to this policy" - value = element(concat(aws_iam_policy.policy.*.arn, [""]), 0) + value = try(aws_iam_policy.policy[0].arn, "") } output "description" { description = "The description of the policy" - value = element(concat(aws_iam_policy.policy.*.description, [""]), 0) + value = try(aws_iam_policy.policy[0].description, "") } output "name" { description = "The name of the policy" - value = element(concat(aws_iam_policy.policy.*.name, [""]), 0) + value = try(aws_iam_policy.policy[0].name, "") } output "path" { description = "The path of the policy in IAM" - value = element(concat(aws_iam_policy.policy.*.path, [""]), 0) + value = try(aws_iam_policy.policy[0].path, "") } output "policy" { description = "The policy document" - value = element(concat(aws_iam_policy.policy.*.policy, [""]), 0) + value = try(aws_iam_policy.policy[0].policy, "") } diff --git a/modules/iam-read-only-policy/versions.tf b/modules/iam-read-only-policy/versions.tf index fff6b757..4e3fe457 100644 --- a/modules/iam-read-only-policy/versions.tf +++ b/modules/iam-read-only-policy/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } diff --git a/modules/iam-user/outputs.tf b/modules/iam-user/outputs.tf index bf2be4f6..65eab49b 100644 --- a/modules/iam-user/outputs.tf +++ b/modules/iam-user/outputs.tf @@ -1,84 +1,63 @@ locals { - has_encrypted_password = length(compact(aws_iam_user_login_profile.this.*.encrypted_password)) > 0 - has_encrypted_secret = length(compact(aws_iam_access_key.this.*.encrypted_secret)) > 0 + has_encrypted_password = length(compact(aws_iam_user_login_profile.this[*].encrypted_password)) > 0 + has_encrypted_secret = length(compact(aws_iam_access_key.this[*].encrypted_secret)) > 0 } output "iam_user_name" { description = "The user's name" - value = element(concat(aws_iam_user.this.*.name, [""]), 0) + value = try(aws_iam_user.this[0].name, "") } output "iam_user_arn" { description = "The ARN assigned by AWS for this user" - value = element(concat(aws_iam_user.this.*.arn, [""]), 0) + value = try(aws_iam_user.this[0].arn, "") } output "iam_user_unique_id" { description = "The unique ID assigned by AWS" - value = element(concat(aws_iam_user.this.*.unique_id, [""]), 0) + value = try(aws_iam_user.this[0].unique_id, "") } output "iam_user_login_profile_key_fingerprint" { description = "The fingerprint of the PGP key used to encrypt the password" - value = element(concat(aws_iam_user_login_profile.this.*.key_fingerprint, [""]), 0) + value = try(aws_iam_user_login_profile.this[0].key_fingerprint, "") } output "iam_user_login_profile_encrypted_password" { description = "The encrypted password, base64 encoded" - value = element(concat(aws_iam_user_login_profile.this.*.encrypted_password, [""]), 0) + value = try(aws_iam_user_login_profile.this[0].encrypted_password, "") } output "iam_access_key_id" { description = "The access key ID" - value = element( - concat( - aws_iam_access_key.this.*.id, - aws_iam_access_key.this_no_pgp.*.id, - [""], - ), - 0 - ) + value = try(aws_iam_access_key.this[0].id, aws_iam_access_key.this_no_pgp[0].id, "") } output "iam_access_key_secret" { description = "The access key secret" - value = element(concat(aws_iam_access_key.this_no_pgp.*.secret, [""]), 0) + value = try(aws_iam_access_key.this_no_pgp[0].secret, "") sensitive = true } output "iam_access_key_key_fingerprint" { description = "The fingerprint of the PGP key used to encrypt the secret" - value = element(concat(aws_iam_access_key.this.*.key_fingerprint, [""]), 0) + value = try(aws_iam_access_key.this[0].key_fingerprint, "") } output "iam_access_key_encrypted_secret" { description = "The encrypted secret, base64 encoded" - value = element(concat(aws_iam_access_key.this.*.encrypted_secret, [""]), 0) + value = try(aws_iam_access_key.this[0].encrypted_secret, "") } output "iam_access_key_ses_smtp_password_v4" { description = "The secret access key converted into an SES SMTP password by applying AWS's Sigv4 conversion algorithm" - value = element( - concat( - aws_iam_access_key.this.*.ses_smtp_password_v4, - aws_iam_access_key.this_no_pgp.*.ses_smtp_password_v4, - [""], - ), - 0 - ) - sensitive = true + value = try(aws_iam_access_key.this[0].ses_smtp_password_v4, aws_iam_access_key.this_no_pgp[0].ses_smtp_password_v4, "") + sensitive = true } output "iam_access_key_status" { description = "Active or Inactive. Keys are initially active, but can be made inactive by other means." - value = element( - concat( - aws_iam_access_key.this.*.status, - aws_iam_access_key.this_no_pgp.*.status, - [""], - ), - 0 - ) + value = try(aws_iam_access_key.this[0].status, aws_iam_access_key.this_no_pgp[0].status, "") } output "pgp_key" { @@ -89,7 +68,7 @@ output "pgp_key" { output "keybase_password_decrypt_command" { description = "Decrypt user password command" value = !local.has_encrypted_password ? null : < Date: Tue, 1 Feb 2022 20:31:11 -0500 Subject: [PATCH 02/12] feat: add conditional policy statement attachments for EKS IAM role module --- examples/iam-eks-role/README.md | 8 +- examples/iam-eks-role/main.tf | 124 ++++++++++--- modules/iam-eks-role/README.md | 14 ++ modules/iam-eks-role/main.tf | 6 +- modules/iam-eks-role/policies.tf | 278 ++++++++++++++++++++++++++++++ modules/iam-eks-role/variables.tf | 37 ++++ 6 files changed, 436 insertions(+), 31 deletions(-) create mode 100644 modules/iam-eks-role/policies.tf diff --git a/examples/iam-eks-role/README.md b/examples/iam-eks-role/README.md index 54348219..bd3aee75 100644 --- a/examples/iam-eks-role/README.md +++ b/examples/iam-eks-role/README.md @@ -9,6 +9,7 @@ To run this example you need to execute: ```bash $ terraform init $ terraform plan +$ terraform apply -target module.vpc -target module.eks $ terraform apply ``` @@ -27,23 +28,24 @@ Run `terraform destroy` when you don't need these resources. | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 2.23 | | [random](#provider\_random) | >= 2 | ## Modules | Name | Source | Version | |------|--------|---------| +| [cluster\_autoscaler\_irsa\_role](#module\_cluster\_autoscaler\_irsa\_role) | ../../modules/iam-eks-role | n/a | +| [ebs\_csi\_irsa\_role](#module\_ebs\_csi\_irsa\_role) | ../../modules/iam-eks-role | n/a | | [eks](#module\_eks) | terraform-aws-modules/eks/aws | ~> 18.0 | +| [external\_dns\_irsa\_role](#module\_external\_dns\_irsa\_role) | ../../modules/iam-eks-role | n/a | | [iam\_eks\_role](#module\_iam\_eks\_role) | ../../modules/iam-eks-role | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 3.0 | ## Resources | Name | Type | |------|------| | [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | -| [aws_subnet_ids.all](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet_ids) | data source | -| [aws_vpc.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc) | data source | ## Inputs diff --git a/examples/iam-eks-role/main.tf b/examples/iam-eks-role/main.tf index 85d37d0b..e3b4db8d 100644 --- a/examples/iam-eks-role/main.tf +++ b/examples/iam-eks-role/main.tf @@ -1,10 +1,27 @@ provider "aws" { - region = "eu-west-1" + region = local.region } +locals { + name = random_pet.this.id + cluster_version = "1.21" + region = "eu-west-1" + + tags = { + Example = local.name + GithubRepo = "terraform-aws-iam" + GithubOrg = "terraform-aws-modules" + } +} + +################################################################################ +# AWS IAM EKS Role Module +################################################################################ + module "iam_eks_role" { - source = "../../modules/iam-eks-role" - role_name = "my-app" + source = "../../modules/iam-eks-role" + + role_name = local.name cluster_service_accounts = { (random_pet.this.id) = ["default:my-app", "canary:my-app"] @@ -18,41 +35,100 @@ module "iam_eks_role" { ] } - tags = { - Name = "eks-role" - } - role_policy_arns = [ "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy" ] + + tags = local.tags +} + +module "cluster_autoscaler_irsa_role" { + source = "../../modules/iam-eks-role" + + role_name = "cluster-autoscaler" + attach_cluster_autoscaler_policy = true + + cluster_service_accounts = { + (random_pet.this.id) = ["default:my-app", "canary:my-app"] + } + + tags = local.tags +} + +module "external_dns_irsa_role" { + source = "../../modules/iam-eks-role" + + role_name = "external-dns" + attach_external_dns_policy = true + external_dns_hosted_zones = ["IClearlyMadeThisUp"] + + cluster_service_accounts = { + (random_pet.this.id) = ["default:my-app", "canary:my-app"] + } + + tags = local.tags } -################## -# Extra resources -################## +module "ebs_csi_irsa_role" { + source = "../../modules/iam-eks-role" + + role_name = "ebs_csi" + attach_ebs_csi_policy = true + + cluster_service_accounts = { + (random_pet.this.id) = ["default:my-app", "canary:my-app"] + } + + tags = local.tags +} + +################################################################################ +# Supporting Resources +################################################################################ resource "random_pet" "this" { length = 2 } +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 3.0" + + name = local.name + cidr = "10.0.0.0/16" + + azs = ["${local.region}a", "${local.region}b", "${local.region}c"] + private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] + public_subnets = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"] + + enable_nat_gateway = true + single_nat_gateway = true + enable_dns_hostnames = true + + enable_flow_log = true + create_flow_log_cloudwatch_iam_role = true + create_flow_log_cloudwatch_log_group = true + + public_subnet_tags = { + "kubernetes.io/cluster/${local.name}" = "shared" + "kubernetes.io/role/elb" = 1 + } + + private_subnet_tags = { + "kubernetes.io/cluster/${local.name}" = "shared" + "kubernetes.io/role/internal-elb" = 1 + } + + tags = local.tags +} + module "eks" { source = "terraform-aws-modules/eks/aws" version = "~> 18.0" cluster_name = random_pet.this.id - cluster_version = "1.21" - - vpc_id = data.aws_vpc.default.id - subnet_ids = data.aws_subnet_ids.all.ids -} - -################################################################## -# Data sources to get VPC, subnet, security group and AMI details -################################################################## -data "aws_vpc" "default" { - default = true -} + cluster_version = local.cluster_version -data "aws_subnet_ids" "all" { - vpc_id = data.aws_vpc.default.id + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets } diff --git a/modules/iam-eks-role/README.md b/modules/iam-eks-role/README.md index 34d5233d..1e0af32b 100644 --- a/modules/iam-eks-role/README.md +++ b/modules/iam-eks-role/README.md @@ -69,19 +69,33 @@ No modules. | Name | Type | |------|------| +| [aws_iam_policy.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.ebs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.external_dns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.ebs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.external_dns](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_iam_policy_document.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.ebs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.external_dns](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 | |------|-------------|------|---------|:--------:| +| [attach\_cluster\_autoscaler\_policy](#input\_attach\_cluster\_autoscaler\_policy) | Whether to attach the Cluster Autoscaler IAM policy to the role | `bool` | `false` | no | +| [attach\_ebs\_csi\_policy](#input\_attach\_ebs\_csi\_policy) | Whether to attach the EBS CSI IAM policy to the role | `bool` | `false` | no | +| [attach\_external\_dns\_policy](#input\_attach\_external\_dns\_policy) | Whether to attach the External DNS IAM policy to the role | `bool` | `false` | no | | [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 | | [create\_role](#input\_create\_role) | Whether to create a role | `bool` | `true` | no | +| [ebs\_csi\_kms\_cmk\_ids](#input\_ebs\_csi\_kms\_cmk\_ids) | KMS CMK IDs to allow EBS CSI to manage encrypted volumes | `list(string)` | `[]` | no | +| [external\_dns\_hosted\_zones](#input\_external\_dns\_hosted\_zones) | Route53 hosted zone IDs to allow external DNS to manage records | `list(string)` |
[
"*"
]
| 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 | diff --git a/modules/iam-eks-role/main.tf b/modules/iam-eks-role/main.tf index f4fd4664..9fec5dec 100644 --- a/modules/iam-eks-role/main.tf +++ b/modules/iam-eks-role/main.tf @@ -13,8 +13,7 @@ data "aws_iam_policy_document" "assume_role_with_oidc" { for_each = var.cluster_service_accounts content { - effect = "Allow" - + effect = "Allow" actions = ["sts:AssumeRoleWithWebIdentity"] principals { @@ -37,8 +36,7 @@ data "aws_iam_policy_document" "assume_role_with_oidc" { for_each = var.provider_url_sa_pairs content { - effect = "Allow" - + effect = "Allow" actions = ["sts:AssumeRoleWithWebIdentity"] principals { diff --git a/modules/iam-eks-role/policies.tf b/modules/iam-eks-role/policies.tf new file mode 100644 index 00000000..b8db3836 --- /dev/null +++ b/modules/iam-eks-role/policies.tf @@ -0,0 +1,278 @@ +################################################################################ +# Cluster Autoscaler Policy +################################################################################ + +# https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md +data "aws_iam_policy_document" "cluster_autoscaler" { + count = var.create_role && var.attach_cluster_autoscaler_policy ? 1 : 0 + + statement { + actions = [ + "autoscaling:DescribeAutoScalingGroups", + "autoscaling:DescribeAutoScalingInstances", + "autoscaling:DescribeLaunchConfigurations", + "autoscaling:SetDesiredCapacity", + "autoscaling:TerminateInstanceInAutoScalingGroup", + "ec2:DescribeInstanceTypes", + ] + + resources = ["*"] + } +} + +resource "aws_iam_policy" "cluster_autoscaler" { + count = var.create_role && var.attach_cluster_autoscaler_policy ? 1 : 0 + + name_prefix = "cluster-autoscaler-" + path = var.role_path + description = "Cluster autoscaler policy to allow examination and modification of EC2 Auto Scaling Groups" + policy = data.aws_iam_policy_document.cluster_autoscaler[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "cluster_autoscaler" { + count = var.create_role && var.attach_cluster_autoscaler_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.cluster_autoscaler[0].arn +} + +################################################################################ +# External DNS Policy +################################################################################ + +# https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md#iam-policy +data "aws_iam_policy_document" "external_dns" { + count = var.create_role && var.attach_external_dns_policy ? 1 : 0 + + statement { + actions = ["route53:ChangeResourceRecordSets"] + resources = [ + for hosted_zone in var.external_dns_hosted_zones : + "arn:${data.aws_partition.current.partition}:route53:::hostedzone/${hosted_zone}" + ] + } + + statement { + actions = [ + "route53:ListHostedZones", + "route53:ListResourceRecordSets", + ] + + resources = ["*"] + } +} + +resource "aws_iam_policy" "external_dns" { + count = var.create_role && var.attach_external_dns_policy ? 1 : 0 + + name_prefix = "external-dns-" + path = var.role_path + description = "External DNS policy to allow management of Route53 hosted zone records" + policy = data.aws_iam_policy_document.external_dns[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "external_dns" { + count = var.create_role && var.attach_external_dns_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.external_dns[0].arn +} + +################################################################################ +# EBS CSI Policy +################################################################################ + +# https://github.com/kubernetes-sigs/aws-ebs-csi-driver/blob/master/docs/example-iam-policy.json +data "aws_iam_policy_document" "ebs_csi" { + count = var.create_role && var.attach_ebs_csi_policy ? 1 : 0 + + statement { + actions = [ + "ec2:CreateSnapshot", + "ec2:AttachVolume", + "ec2:DetachVolume", + "ec2:ModifyVolume", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeInstances", + "ec2:DescribeSnapshots", + "ec2:DescribeTags", + "ec2:DescribeVolumes", + "ec2:DescribeVolumesModifications", + ] + + resources = ["*"] + } + + statement { + actions = ["ec2:CreateTags"] + + resources = [ + "arn:aws:ec2:*:*:volume/*", + "arn:aws:ec2:*:*:snapshot/*", + ] + + condition { + test = "StringEquals" + variable = "ec2:CreateAction" + values = [ + "CreateVolume", + "CreateSnapshot" + ] + } + } + + statement { + actions = ["ec2:DeleteTags"] + + resources = [ + "arn:aws:ec2:*:*:volume/*", + "arn:aws:ec2:*:*:snapshot/*", + ] + } + + statement { + actions = ["ec2:CreateVolume"] + resources = ["*"] + + condition { + test = "StringLike" + variable = "aws:RequestTag/ebs.csi.aws.com/cluster" + values = [ + true + ] + } + } + + statement { + actions = ["ec2:CreateVolume"] + resources = ["*"] + + condition { + test = "StringLike" + variable = "aws:RequestTag/CSIVolumeName" + values = ["*"] + } + } + + statement { + actions = ["ec2:CreateVolume"] + resources = ["*"] + + condition { + test = "StringLike" + variable = "aws:RequestTag/kubernetes.io/cluster/*" + values = ["owned"] + } + } + + statement { + actions = ["ec2:DeleteVolume"] + resources = ["*"] + + condition { + test = "StringLike" + variable = "ec2:ResourceTag/ebs.csi.aws.com/cluster" + values = [true] + } + } + + statement { + actions = ["ec2:DeleteVolume"] + resources = ["*"] + + condition { + test = "StringLike" + variable = "ec2:ResourceTag/CSIVolumeName" + values = ["*"] + } + } + + statement { + actions = ["ec2:DeleteVolume"] + resources = ["*"] + + condition { + test = "StringLike" + variable = "ec2:ResourceTag/kubernetes.io/cluster/*" + values = ["owned"] + } + } + + statement { + actions = ["ec2:DeleteSnapshot"] + resources = ["*"] + + condition { + test = "StringLike" + variable = "ec2:ResourceTag/CSIVolumeSnapshotName" + values = ["*"] + } + } + + statement { + actions = ["ec2:DeleteSnapshot"] + resources = ["*"] + + condition { + test = "StringLike" + variable = "ec2:ResourceTag/ebs.csi.aws.com/cluster" + values = [true] + } + } + + dynamic "statement" { + for_each = length(var.ebs_csi_kms_cmk_ids) > 0 ? [1] : [] + content { + actions = [ + "kms:CreateGrant", + "kms:ListGrants", + "kms:RevokeGrant" + ] + + resources = statement.value + + condition { + test = "Bool" + variable = "kms:GrantIsForAWSResource" + values = [true] + } + } + } + + dynamic "statement" { + for_each = length(var.ebs_csi_kms_cmk_ids) > 0 ? [1] : [] + content { + actions = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey" + ] + + resources = statement.value + } + } +} + +resource "aws_iam_policy" "ebs_csi" { + count = var.create_role && var.attach_ebs_csi_policy ? 1 : 0 + + name_prefix = "ebs-csi-" + path = var.role_path + description = "External DNS policy to allow management of Route53 hosted zone records" + policy = data.aws_iam_policy_document.ebs_csi[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "ebs_csi" { + count = var.create_role && var.attach_ebs_csi_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.ebs_csi[0].arn +} diff --git a/modules/iam-eks-role/variables.tf b/modules/iam-eks-role/variables.tf index 6a90d25c..5ec4a8da 100644 --- a/modules/iam-eks-role/variables.tf +++ b/modules/iam-eks-role/variables.tf @@ -69,3 +69,40 @@ variable "max_session_duration" { type = number default = 43200 } + +################################################################################ +# Policies +################################################################################ + +# Cluster autoscaler +variable "attach_cluster_autoscaler_policy" { + description = "Whether to attach the Cluster Autoscaler IAM policy to the role" + type = bool + default = false +} + +# External DNS +variable "attach_external_dns_policy" { + description = "Whether to attach the External DNS IAM policy to the role" + type = bool + default = false +} + +variable "external_dns_hosted_zones" { + description = "Route53 hosted zone IDs to allow external DNS to manage records" + type = list(string) + default = ["*"] +} + +# EBS CSI +variable "attach_ebs_csi_policy" { + description = "Whether to attach the EBS CSI IAM policy to the role" + type = bool + default = false +} + +variable "ebs_csi_kms_cmk_ids" { + description = "KMS CMK IDs to allow EBS CSI to manage encrypted volumes" + type = list(string) + default = [] +} From 3e741819cfc51e84e707fe75f0523ae5878541cc Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Tue, 1 Feb 2022 20:36:31 -0500 Subject: [PATCH 03/12] chore: additional updates, missed the example versions --- examples/iam-account/versions.tf | 5 ++++- .../iam-assumable-role-with-oidc/versions.tf | 5 ++++- .../iam-assumable-role-with-saml/versions.tf | 5 ++++- examples/iam-assumable-role/versions.tf | 5 ++++- .../iam-assumable-roles-with-saml/versions.tf | 5 ++++- examples/iam-assumable-roles/versions.tf | 5 ++++- examples/iam-eks-role/README.md | 9 ++------- examples/iam-eks-role/main.tf | 16 ++++++---------- examples/iam-eks-role/versions.tf | 6 ++++-- examples/iam-group-complete/versions.tf | 5 ++++- .../versions.tf | 5 ++++- examples/iam-group-with-policies/versions.tf | 5 ++++- examples/iam-policy/versions.tf | 5 ++++- examples/iam-read-only-policy/versions.tf | 5 ++++- examples/iam-user/versions.tf | 5 ++++- 15 files changed, 60 insertions(+), 31 deletions(-) diff --git a/examples/iam-account/versions.tf b/examples/iam-account/versions.tf index fff6b757..4e3fe457 100644 --- a/examples/iam-account/versions.tf +++ b/examples/iam-account/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } diff --git a/examples/iam-assumable-role-with-oidc/versions.tf b/examples/iam-assumable-role-with-oidc/versions.tf index fff6b757..4e3fe457 100644 --- a/examples/iam-assumable-role-with-oidc/versions.tf +++ b/examples/iam-assumable-role-with-oidc/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } diff --git a/examples/iam-assumable-role-with-saml/versions.tf b/examples/iam-assumable-role-with-saml/versions.tf index fff6b757..4e3fe457 100644 --- a/examples/iam-assumable-role-with-saml/versions.tf +++ b/examples/iam-assumable-role-with-saml/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } diff --git a/examples/iam-assumable-role/versions.tf b/examples/iam-assumable-role/versions.tf index fff6b757..4e3fe457 100644 --- a/examples/iam-assumable-role/versions.tf +++ b/examples/iam-assumable-role/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } diff --git a/examples/iam-assumable-roles-with-saml/versions.tf b/examples/iam-assumable-roles-with-saml/versions.tf index fff6b757..4e3fe457 100644 --- a/examples/iam-assumable-roles-with-saml/versions.tf +++ b/examples/iam-assumable-roles-with-saml/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } diff --git a/examples/iam-assumable-roles/versions.tf b/examples/iam-assumable-roles/versions.tf index fff6b757..4e3fe457 100644 --- a/examples/iam-assumable-roles/versions.tf +++ b/examples/iam-assumable-roles/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } diff --git a/examples/iam-eks-role/README.md b/examples/iam-eks-role/README.md index bd3aee75..601e6826 100644 --- a/examples/iam-eks-role/README.md +++ b/examples/iam-eks-role/README.md @@ -22,13 +22,10 @@ Run `terraform destroy` when you don't need these resources. |------|---------| | [terraform](#requirement\_terraform) | >= 0.12.6 | | [aws](#requirement\_aws) | >= 2.23 | -| [random](#requirement\_random) | >= 2 | ## Providers -| Name | Version | -|------|---------| -| [random](#provider\_random) | >= 2 | +No providers. ## Modules @@ -43,9 +40,7 @@ Run `terraform destroy` when you don't need these resources. ## Resources -| Name | Type | -|------|------| -| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | +No resources. ## Inputs diff --git a/examples/iam-eks-role/main.tf b/examples/iam-eks-role/main.tf index e3b4db8d..7652ecb8 100644 --- a/examples/iam-eks-role/main.tf +++ b/examples/iam-eks-role/main.tf @@ -3,7 +3,7 @@ provider "aws" { } locals { - name = random_pet.this.id + name = "ex-iam-eks-role" cluster_version = "1.21" region = "eu-west-1" @@ -24,7 +24,7 @@ module "iam_eks_role" { role_name = local.name cluster_service_accounts = { - (random_pet.this.id) = ["default:my-app", "canary:my-app"] + (module.eks.cluster_id) = ["default:my-app", "canary:my-app"] } provider_url_sa_pairs = { @@ -49,7 +49,7 @@ module "cluster_autoscaler_irsa_role" { attach_cluster_autoscaler_policy = true cluster_service_accounts = { - (random_pet.this.id) = ["default:my-app", "canary:my-app"] + (module.eks.cluster_id) = ["default:my-app", "canary:my-app"] } tags = local.tags @@ -63,7 +63,7 @@ module "external_dns_irsa_role" { external_dns_hosted_zones = ["IClearlyMadeThisUp"] cluster_service_accounts = { - (random_pet.this.id) = ["default:my-app", "canary:my-app"] + (module.eks.cluster_id) = ["default:my-app", "canary:my-app"] } tags = local.tags @@ -76,7 +76,7 @@ module "ebs_csi_irsa_role" { attach_ebs_csi_policy = true cluster_service_accounts = { - (random_pet.this.id) = ["default:my-app", "canary:my-app"] + (module.eks.cluster_id) = ["default:my-app", "canary:my-app"] } tags = local.tags @@ -86,10 +86,6 @@ module "ebs_csi_irsa_role" { # Supporting Resources ################################################################################ -resource "random_pet" "this" { - length = 2 -} - module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "~> 3.0" @@ -126,7 +122,7 @@ module "eks" { source = "terraform-aws-modules/eks/aws" version = "~> 18.0" - cluster_name = random_pet.this.id + cluster_name = local.name cluster_version = local.cluster_version vpc_id = module.vpc.vpc_id diff --git a/examples/iam-eks-role/versions.tf b/examples/iam-eks-role/versions.tf index de054d6e..4e3fe457 100644 --- a/examples/iam-eks-role/versions.tf +++ b/examples/iam-eks-role/versions.tf @@ -2,7 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" - random = ">= 2" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } diff --git a/examples/iam-group-complete/versions.tf b/examples/iam-group-complete/versions.tf index fff6b757..4e3fe457 100644 --- a/examples/iam-group-complete/versions.tf +++ b/examples/iam-group-complete/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } diff --git a/examples/iam-group-with-assumable-roles-policy/versions.tf b/examples/iam-group-with-assumable-roles-policy/versions.tf index fff6b757..4e3fe457 100644 --- a/examples/iam-group-with-assumable-roles-policy/versions.tf +++ b/examples/iam-group-with-assumable-roles-policy/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } diff --git a/examples/iam-group-with-policies/versions.tf b/examples/iam-group-with-policies/versions.tf index fff6b757..4e3fe457 100644 --- a/examples/iam-group-with-policies/versions.tf +++ b/examples/iam-group-with-policies/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } diff --git a/examples/iam-policy/versions.tf b/examples/iam-policy/versions.tf index fff6b757..4e3fe457 100644 --- a/examples/iam-policy/versions.tf +++ b/examples/iam-policy/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } diff --git a/examples/iam-read-only-policy/versions.tf b/examples/iam-read-only-policy/versions.tf index fff6b757..4e3fe457 100644 --- a/examples/iam-read-only-policy/versions.tf +++ b/examples/iam-read-only-policy/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.23" + aws = { + source = "hashicorp/aws" + version = ">= 2.23" + } } } diff --git a/examples/iam-user/versions.tf b/examples/iam-user/versions.tf index 132c7df3..6c8fa913 100644 --- a/examples/iam-user/versions.tf +++ b/examples/iam-user/versions.tf @@ -2,6 +2,9 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.50" + aws = { + source = "hashicorp/aws" + version = ">= 2.50" + } } } From bd1a209c9d1ff1d27f45fea2b8ab23d0ca8f50f7 Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Fri, 11 Feb 2022 16:43:50 -0500 Subject: [PATCH 04/12] refactor: update to have users pass in details instead of deriving, this solves dependency issue --- .pre-commit-config.yaml | 2 +- examples/iam-eks-role/README.md | 7 +-- examples/iam-eks-role/main.tf | 83 ++++++++++++++++++++++++------- modules/iam-eks-role/README.md | 27 +++++----- modules/iam-eks-role/main.tf | 62 ++++++----------------- modules/iam-eks-role/policies.tf | 80 +++++++++++++++++++++++++++-- modules/iam-eks-role/variables.tf | 47 ++++++++++------- 7 files changed, 207 insertions(+), 101 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 093121e0..8a010fdd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.62.3 + rev: v1.64.0 hooks: - id: terraform_fmt - id: terraform_validate diff --git a/examples/iam-eks-role/README.md b/examples/iam-eks-role/README.md index 601e6826..bba67ac4 100644 --- a/examples/iam-eks-role/README.md +++ b/examples/iam-eks-role/README.md @@ -1,6 +1,6 @@ # IAM EKS role -Configuration in this directory creates an IAM role that can be assumed by multiple EKS `ServiceAccount`. +Configuration in this directory creates IAM roles that can be assumed by multiple EKS `ServiceAccount`s for various tasks. # Usage @@ -9,7 +9,6 @@ To run this example you need to execute: ```bash $ terraform init $ terraform plan -$ terraform apply -target module.vpc -target module.eks $ terraform apply ``` @@ -33,10 +32,12 @@ No providers. |------|--------|---------| | [cluster\_autoscaler\_irsa\_role](#module\_cluster\_autoscaler\_irsa\_role) | ../../modules/iam-eks-role | n/a | | [ebs\_csi\_irsa\_role](#module\_ebs\_csi\_irsa\_role) | ../../modules/iam-eks-role | n/a | -| [eks](#module\_eks) | terraform-aws-modules/eks/aws | ~> 18.0 | +| [eks](#module\_eks) | terraform-aws-modules/eks/aws | ~> 18.6 | | [external\_dns\_irsa\_role](#module\_external\_dns\_irsa\_role) | ../../modules/iam-eks-role | n/a | | [iam\_eks\_role](#module\_iam\_eks\_role) | ../../modules/iam-eks-role | n/a | | [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 3.0 | +| [vpc\_cni\_ipv4\_irsa\_role](#module\_vpc\_cni\_ipv4\_irsa\_role) | ../../modules/iam-eks-role | n/a | +| [vpc\_cni\_ipv6\_irsa\_role](#module\_vpc\_cni\_ipv6\_irsa\_role) | ../../modules/iam-eks-role | n/a | ## Resources diff --git a/examples/iam-eks-role/main.tf b/examples/iam-eks-role/main.tf index 7652ecb8..80f137c7 100644 --- a/examples/iam-eks-role/main.tf +++ b/examples/iam-eks-role/main.tf @@ -23,16 +23,17 @@ module "iam_eks_role" { role_name = local.name - cluster_service_accounts = { - (module.eks.cluster_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", - ] + oidc_providers = { + one = { + provider = module.eks.oidc_provider + provider_arns = [module.eks.oidc_provider_arn] + service_accounts = ["default:my-app", "canary:my-app"] + } + two = { + provider = module.eks.oidc_provider + provider_arns = [module.eks.oidc_provider_arn] + service_accounts = ["default:blue", "canary:blue"] + } } role_policy_arns = [ @@ -48,8 +49,12 @@ module "cluster_autoscaler_irsa_role" { role_name = "cluster-autoscaler" attach_cluster_autoscaler_policy = true - cluster_service_accounts = { - (module.eks.cluster_id) = ["default:my-app", "canary:my-app"] + oidc_providers = { + ex = { + provider = module.eks.oidc_provider + provider_arns = [module.eks.oidc_provider_arn] + service_accounts = ["default:my-app", "canary:my-app"] + } } tags = local.tags @@ -62,8 +67,12 @@ module "external_dns_irsa_role" { attach_external_dns_policy = true external_dns_hosted_zones = ["IClearlyMadeThisUp"] - cluster_service_accounts = { - (module.eks.cluster_id) = ["default:my-app", "canary:my-app"] + oidc_providers = { + ex = { + provider = module.eks.oidc_provider + provider_arns = [module.eks.oidc_provider_arn] + service_accounts = ["default:my-app", "canary:my-app"] + } } tags = local.tags @@ -75,8 +84,48 @@ module "ebs_csi_irsa_role" { role_name = "ebs_csi" attach_ebs_csi_policy = true - cluster_service_accounts = { - (module.eks.cluster_id) = ["default:my-app", "canary:my-app"] + oidc_providers = { + ex = { + provider = module.eks.oidc_provider + provider_arns = [module.eks.oidc_provider_arn] + service_accounts = ["default:my-app", "canary:my-app"] + } + } + + tags = local.tags +} + +module "vpc_cni_ipv4_irsa_role" { + source = "../../modules/iam-eks-role" + + role_name = "vpc_cni_ipv4" + attach_vpc_cni_policy = true + vpc_cni_enable_ipv4 = true + + oidc_providers = { + ex = { + provider = module.eks.oidc_provider + provider_arns = [module.eks.oidc_provider_arn] + service_accounts = ["default:my-app", "canary:my-app"] + } + } + + tags = local.tags +} + +module "vpc_cni_ipv6_irsa_role" { + source = "../../modules/iam-eks-role" + + role_name = "vpc_cni_ipv6" + attach_vpc_cni_policy = true + vpc_cni_enable_ipv6 = true + + oidc_providers = { + ex = { + provider = module.eks.oidc_provider + provider_arns = [module.eks.oidc_provider_arn] + service_accounts = ["default:my-app", "canary:my-app"] + } } tags = local.tags @@ -120,7 +169,7 @@ module "vpc" { module "eks" { source = "terraform-aws-modules/eks/aws" - version = "~> 18.0" + version = "~> 18.6" cluster_name = local.name cluster_version = local.cluster_version diff --git a/modules/iam-eks-role/README.md b/modules/iam-eks-role/README.md index 1e0af32b..c6e99b71 100644 --- a/modules/iam-eks-role/README.md +++ b/modules/iam-eks-role/README.md @@ -72,40 +72,43 @@ No modules. | [aws_iam_policy.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.ebs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.external_dns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.vpc_cni](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role_policy_attachment.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.ebs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.external_dns](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_role_policy_attachment.vpc_cni](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [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_iam_policy_document.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.ebs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.external_dns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.vpc_cni](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 | |------|-------------|------|---------|:--------:| -| [attach\_cluster\_autoscaler\_policy](#input\_attach\_cluster\_autoscaler\_policy) | Whether to attach the Cluster Autoscaler IAM policy to the role | `bool` | `false` | no | -| [attach\_ebs\_csi\_policy](#input\_attach\_ebs\_csi\_policy) | Whether to attach the EBS CSI IAM policy to the role | `bool` | `false` | no | -| [attach\_external\_dns\_policy](#input\_attach\_external\_dns\_policy) | Whether to attach the External DNS IAM policy to the role | `bool` | `false` | no | -| [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 | +| [attach\_cluster\_autoscaler\_policy](#input\_attach\_cluster\_autoscaler\_policy) | Determines whether to attach the Cluster Autoscaler IAM policy to the role | `bool` | `false` | no | +| [attach\_ebs\_csi\_policy](#input\_attach\_ebs\_csi\_policy) | Determines whether to attach the EBS CSI IAM policy to the role | `bool` | `false` | no | +| [attach\_external\_dns\_policy](#input\_attach\_external\_dns\_policy) | Determines whether to attach the External DNS IAM policy to the role | `bool` | `false` | no | +| [attach\_vpc\_cni\_policy](#input\_attach\_vpc\_cni\_policy) | Determines whether to attach the VPC CNI IAM policy to the role | `bool` | `false` | no | | [create\_role](#input\_create\_role) | Whether to create a role | `bool` | `true` | no | | [ebs\_csi\_kms\_cmk\_ids](#input\_ebs\_csi\_kms\_cmk\_ids) | KMS CMK IDs to allow EBS CSI to manage encrypted volumes | `list(string)` | `[]` | no | | [external\_dns\_hosted\_zones](#input\_external\_dns\_hosted\_zones) | Route53 hosted zone IDs to allow external DNS to manage records | `list(string)` |
[
"*"
]
| 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 | +| [force\_detach\_policies](#input\_force\_detach\_policies) | Whether policies should be detached from this role when destroying | `bool` | `true` | no | +| [max\_session\_duration](#input\_max\_session\_duration) | Maximum CLI/API session duration in seconds between 3600 and 43200 | `number` | `null` | no | +| [oidc\_providers](#input\_oidc\_providers) | Map of OIDC providers where each provdier map should contain the `provider`, `provider_arns`, and `service_accounts` | `any` | `{}` | no | +| [role\_description](#input\_role\_description) | IAM Role description | `string` | `null` | 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 | -| [role\_path](#input\_role\_path) | Path of IAM role | `string` | `"/"` | no | -| [role\_permissions\_boundary\_arn](#input\_role\_permissions\_boundary\_arn) | Permissions boundary ARN to use for IAM role | `string` | `""` | no | +| [role\_path](#input\_role\_path) | Path of IAM role | `string` | `null` | no | +| [role\_permissions\_boundary\_arn](#input\_role\_permissions\_boundary\_arn) | Permissions boundary ARN to use for IAM role | `string` | `null` | no | | [role\_policy\_arns](#input\_role\_policy\_arns) | ARNs of any policies to attach to the IAM role | `list(string)` | `[]` | no | | [tags](#input\_tags) | A map of tags to add the the IAM role | `map(any)` | `{}` | no | +| [vpc\_cni\_enable\_ipv4](#input\_vpc\_cni\_enable\_ipv4) | Determines whether to enable IPv4 permissions for VPC CNI policy | `bool` | `false` | no | +| [vpc\_cni\_enable\_ipv6](#input\_vpc\_cni\_enable\_ipv6) | Determines whether to enable IPv6 permissions for VPC CNI policy | `bool` | `false` | no | ## Outputs diff --git a/modules/iam-eks-role/main.tf b/modules/iam-eks-role/main.tf index 9fec5dec..640de9e1 100644 --- a/modules/iam-eks-role/main.tf +++ b/modules/iam-eks-role/main.tf @@ -1,56 +1,22 @@ -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}"] - } - } - } + count = var.create_role ? 1 : 0 dynamic "statement" { - for_each = var.provider_url_sa_pairs + for_each = var.oidc_providers content { effect = "Allow" actions = ["sts:AssumeRoleWithWebIdentity"] principals { - type = "Federated" - - identifiers = [ - "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${statement.key}" - ] + type = "Federated" + identifiers = statement.value.provider_arns } condition { test = "StringEquals" - variable = "${statement.key}:sub" - values = [for s in statement.value : "system:serviceaccount:${s}"] + variable = "${statement.value.provider}:sub" + values = [for sa in statement.value.service_accounts : "system:serviceaccount:${sa}"] } } } @@ -59,15 +25,17 @@ data "aws_iam_policy_document" "assume_role_with_oidc" { 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 + name = var.role_name + name_prefix = var.role_name_prefix + path = var.role_path + description = var.role_description + + assume_role_policy = data.aws_iam_policy_document.assume_role_with_oidc[0].json 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 + force_detach_policies = var.force_detach_policies + + tags = var.tags } resource "aws_iam_role_policy_attachment" "custom" { diff --git a/modules/iam-eks-role/policies.tf b/modules/iam-eks-role/policies.tf index b8db3836..c5bca700 100644 --- a/modules/iam-eks-role/policies.tf +++ b/modules/iam-eks-role/policies.tf @@ -1,3 +1,5 @@ +data "aws_partition" "current" {} + ################################################################################ # Cluster Autoscaler Policy ################################################################################ @@ -23,7 +25,7 @@ data "aws_iam_policy_document" "cluster_autoscaler" { resource "aws_iam_policy" "cluster_autoscaler" { count = var.create_role && var.attach_cluster_autoscaler_policy ? 1 : 0 - name_prefix = "cluster-autoscaler-" + name_prefix = "AmazonEKS_Cluster_Autoscaler_Policy-" path = var.role_path description = "Cluster autoscaler policy to allow examination and modification of EC2 Auto Scaling Groups" policy = data.aws_iam_policy_document.cluster_autoscaler[0].json @@ -67,7 +69,7 @@ data "aws_iam_policy_document" "external_dns" { resource "aws_iam_policy" "external_dns" { count = var.create_role && var.attach_external_dns_policy ? 1 : 0 - name_prefix = "external-dns-" + name_prefix = "AmazonEKS_External_DNS_Policy-" path = var.role_path description = "External DNS policy to allow management of Route53 hosted zone records" policy = data.aws_iam_policy_document.external_dns[0].json @@ -262,9 +264,9 @@ data "aws_iam_policy_document" "ebs_csi" { resource "aws_iam_policy" "ebs_csi" { count = var.create_role && var.attach_ebs_csi_policy ? 1 : 0 - name_prefix = "ebs-csi-" + name_prefix = "AmazonEKS_EBS_CSI_Policy-" path = var.role_path - description = "External DNS policy to allow management of Route53 hosted zone records" + description = "Provides permissions to manage EBS volumes via the container storage interface driver" policy = data.aws_iam_policy_document.ebs_csi[0].json tags = var.tags @@ -276,3 +278,73 @@ resource "aws_iam_role_policy_attachment" "ebs_csi" { role = aws_iam_role.this[0].name policy_arn = aws_iam_policy.ebs_csi[0].arn } + +################################################################################ +# VPC CNI Policy +################################################################################ + +data "aws_iam_policy_document" "vpc_cni" { + count = var.create_role && var.attach_vpc_cni_policy ? 1 : 0 + + # arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy + dynamic "statement" { + for_each = var.vpc_cni_enable_ipv4 ? [1] : [] + content { + sid = "IPV4" + actions = [ + "ec2:AssignPrivateIpAddresses", + "ec2:AttachNetworkInterface", + "ec2:CreateNetworkInterface", + "ec2:DeleteNetworkInterface", + "ec2:DescribeInstances", + "ec2:DescribeTags", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeInstanceTypes", + "ec2:DetachNetworkInterface", + "ec2:ModifyNetworkInterfaceAttribute", + "ec2:UnassignPrivateIpAddresses" + ] + resources = ["*"] + } + } + + # https://docs.aws.amazon.com/eks/latest/userguide/cni-iam-role.html#cni-iam-role-create-ipv6-policy + dynamic "statement" { + for_each = var.vpc_cni_enable_ipv6 ? [1] : [] + content { + sid = "IPV6" + actions = [ + "ec2:AssignIpv6Addresses", + "ec2:DescribeInstances", + "ec2:DescribeTags", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeInstanceTypes" + ] + resources = ["*"] + } + } + + statement { + sid = "CreateTags" + actions = ["ec2:CreateTags"] + resources = ["arn:aws:ec2:*:*:network-interface/*"] + } +} + +resource "aws_iam_policy" "vpc_cni" { + count = var.create_role && var.attach_vpc_cni_policy ? 1 : 0 + + name_prefix = "AmazonEKS_CNI_Policy-" + path = var.role_path + description = "Provides the Amazon VPC CNI Plugin (amazon-vpc-cni-k8s) the permissions it requires to modify the IPv4/IPv6 address configuration on your EKS worker nodes" + policy = data.aws_iam_policy_document.vpc_cni[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "vpc_cni" { + count = var.create_role && var.attach_vpc_cni_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.vpc_cni[0].arn +} diff --git a/modules/iam-eks-role/variables.tf b/modules/iam-eks-role/variables.tf index 5ec4a8da..e29d7e66 100644 --- a/modules/iam-eks-role/variables.tf +++ b/modules/iam-eks-role/variables.tf @@ -13,19 +13,19 @@ variable "role_name" { variable "role_path" { description = "Path of IAM role" type = string - default = "/" + default = null } variable "role_permissions_boundary_arn" { description = "Permissions boundary ARN to use for IAM role" type = string - default = "" + default = null } variable "role_description" { description = "IAM Role description" type = string - default = "" + default = null } variable "role_name_prefix" { @@ -40,15 +40,9 @@ variable "role_policy_arns" { default = [] } -variable "cluster_service_accounts" { - description = "EKS cluster and k8s ServiceAccount pairs. Each EKS cluster can have multiple k8s ServiceAccount. See README for details" - type = map(list(string)) - 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)) +variable "oidc_providers" { + description = "Map of OIDC providers where each provdier map should contain the `provider`, `provider_arns`, and `service_accounts`" + type = any default = {} } @@ -61,13 +55,13 @@ variable "tags" { variable "force_detach_policies" { description = "Whether policies should be detached from this role when destroying" type = bool - default = false + default = true } variable "max_session_duration" { description = "Maximum CLI/API session duration in seconds between 3600 and 43200" type = number - default = 43200 + default = null } ################################################################################ @@ -76,14 +70,14 @@ variable "max_session_duration" { # Cluster autoscaler variable "attach_cluster_autoscaler_policy" { - description = "Whether to attach the Cluster Autoscaler IAM policy to the role" + description = "Determines whether to attach the Cluster Autoscaler IAM policy to the role" type = bool default = false } # External DNS variable "attach_external_dns_policy" { - description = "Whether to attach the External DNS IAM policy to the role" + description = "Determines whether to attach the External DNS IAM policy to the role" type = bool default = false } @@ -96,7 +90,7 @@ variable "external_dns_hosted_zones" { # EBS CSI variable "attach_ebs_csi_policy" { - description = "Whether to attach the EBS CSI IAM policy to the role" + description = "Determines whether to attach the EBS CSI IAM policy to the role" type = bool default = false } @@ -106,3 +100,22 @@ variable "ebs_csi_kms_cmk_ids" { type = list(string) default = [] } + +# VPC CNI +variable "attach_vpc_cni_policy" { + description = "Determines whether to attach the VPC CNI IAM policy to the role" + type = bool + default = false +} + +variable "vpc_cni_enable_ipv4" { + description = "Determines whether to enable IPv4 permissions for VPC CNI policy" + type = bool + default = false +} + +variable "vpc_cni_enable_ipv6" { + description = "Determines whether to enable IPv6 permissions for VPC CNI policy" + type = bool + default = false +} From 7122f7abc5f4fcb162638fd45ff0c06cdc85a2ed Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Fri, 11 Feb 2022 16:59:11 -0500 Subject: [PATCH 05/12] chore: update documentation --- examples/iam-eks-role/main.tf | 14 +++--- modules/iam-eks-role/README.md | 88 ++++++++++++++++++++++++---------- modules/iam-eks-role/main.tf | 2 +- 3 files changed, 72 insertions(+), 32 deletions(-) diff --git a/examples/iam-eks-role/main.tf b/examples/iam-eks-role/main.tf index 80f137c7..083e2b95 100644 --- a/examples/iam-eks-role/main.tf +++ b/examples/iam-eks-role/main.tf @@ -26,12 +26,12 @@ module "iam_eks_role" { oidc_providers = { one = { provider = module.eks.oidc_provider - provider_arns = [module.eks.oidc_provider_arn] + provider_arn = module.eks.oidc_provider_arn service_accounts = ["default:my-app", "canary:my-app"] } two = { provider = module.eks.oidc_provider - provider_arns = [module.eks.oidc_provider_arn] + provider_arn = module.eks.oidc_provider_arn service_accounts = ["default:blue", "canary:blue"] } } @@ -52,7 +52,7 @@ module "cluster_autoscaler_irsa_role" { oidc_providers = { ex = { provider = module.eks.oidc_provider - provider_arns = [module.eks.oidc_provider_arn] + provider_arn = module.eks.oidc_provider_arn service_accounts = ["default:my-app", "canary:my-app"] } } @@ -70,7 +70,7 @@ module "external_dns_irsa_role" { oidc_providers = { ex = { provider = module.eks.oidc_provider - provider_arns = [module.eks.oidc_provider_arn] + provider_arn = module.eks.oidc_provider_arn service_accounts = ["default:my-app", "canary:my-app"] } } @@ -87,7 +87,7 @@ module "ebs_csi_irsa_role" { oidc_providers = { ex = { provider = module.eks.oidc_provider - provider_arns = [module.eks.oidc_provider_arn] + provider_arn = module.eks.oidc_provider_arn service_accounts = ["default:my-app", "canary:my-app"] } } @@ -105,7 +105,7 @@ module "vpc_cni_ipv4_irsa_role" { oidc_providers = { ex = { provider = module.eks.oidc_provider - provider_arns = [module.eks.oidc_provider_arn] + provider_arn = module.eks.oidc_provider_arn service_accounts = ["default:my-app", "canary:my-app"] } } @@ -123,7 +123,7 @@ module "vpc_cni_ipv6_irsa_role" { oidc_providers = { ex = { provider = module.eks.oidc_provider - provider_arns = [module.eks.oidc_provider_arn] + provider_arn = module.eks.oidc_provider_arn service_accounts = ["default:my-app", "canary:my-app"] } } diff --git a/modules/iam-eks-role/README.md b/modules/iam-eks-role/README.md index c6e99b71..850e7731 100644 --- a/modules/iam-eks-role/README.md +++ b/modules/iam-eks-role/README.md @@ -1,27 +1,32 @@ # 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 which can be assumed by AWS EKS `ServiceAccount`s. -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 is intended to be used 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: +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): ```hcl module "iam_eks_role" { source = "terraform-aws-modules/iam/aws//modules/iam-eks-role" - cluster_service_accounts = { - "" = [ - ":", - ":" - ] - } - - provider_url_sa_pairs = { - "" = [ - ":", - ":" - ] + oidc_providers = { + one = { + provider = "" + provider_arn = "" + service_accounts = [ + ":", + ":" + ] + } + two = { + provider = "" + provider_arn = "" + service_accounts = [ + ":", + ":" + ] + } } } ``` @@ -33,19 +38,54 @@ 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", - ] + oidc_providers = { + one = { + provider = "oidc.eks.us-east-1.amazonaws.com/id/5C54DDF35ER19312844C7333374CC09D" + provider_arn = "arn:aws:iam::012345678901:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/5C54DDF35ER19312844C7333374CC09D" + service_accounts = ["default:my-app-staging", "canary:my-app-staging"] + } + two = { + provider = "oidc.eks.ap-southeast-1.amazonaws.com/id/5C54DDF35ER54476848E7333374FF09G" + provider_arn = "arn:aws:iam::012345678901:oidc-provider/oidc.eks.ap-southeast-1.amazonaws.com/id/5C54DDF35ER54476848E7333374FF09G" + service_accounts = ["default:my-app-staging"] + } } } ``` -Note: the EKS clusters must in the current AWS region and account as they use the default AWS provider. +This module has been design in conjunction with the [`terraform-aws-eks`](https://github.com/terraform-aws-modules/terraform-aws-eks) module and easily integrates with it: + +```hcl +module "iam_eks_role" { + source = "terraform-aws-modules/iam/aws//modules/iam-eks-role" + + role_name = "my-app" + + oidc_providers = { + one = { + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + service_accounts = ["default:my-app", "canary:my-app"] + } + two = { + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + service_accounts = ["default:blue", "canary:blue"] + } + } +} + +module "eks" { + source = "terraform-aws-modules/eks/aws" + version = "~> 18.6" + + cluster_name = "my-cluster" + cluster_version = "1.21" + + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets +} +``` ## Requirements diff --git a/modules/iam-eks-role/main.tf b/modules/iam-eks-role/main.tf index 640de9e1..a18759ee 100644 --- a/modules/iam-eks-role/main.tf +++ b/modules/iam-eks-role/main.tf @@ -10,7 +10,7 @@ data "aws_iam_policy_document" "assume_role_with_oidc" { principals { type = "Federated" - identifiers = statement.value.provider_arns + identifiers = [statement.value.provider_arn] } condition { From 9590acb533e4302325a566eb3025dc76d53c6f82 Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Sun, 13 Feb 2022 09:51:54 -0500 Subject: [PATCH 06/12] feat: add policies for node termiantion handler and Karpenter --- examples/iam-eks-role/README.md | 2 + examples/iam-eks-role/main.tf | 34 ++++++++++ modules/iam-eks-role/README.md | 10 +++ modules/iam-eks-role/policies.tf | 104 ++++++++++++++++++++++++++++++ modules/iam-eks-role/variables.tf | 27 ++++++++ 5 files changed, 177 insertions(+) diff --git a/examples/iam-eks-role/README.md b/examples/iam-eks-role/README.md index bba67ac4..7a2e6b81 100644 --- a/examples/iam-eks-role/README.md +++ b/examples/iam-eks-role/README.md @@ -35,6 +35,8 @@ No providers. | [eks](#module\_eks) | terraform-aws-modules/eks/aws | ~> 18.6 | | [external\_dns\_irsa\_role](#module\_external\_dns\_irsa\_role) | ../../modules/iam-eks-role | n/a | | [iam\_eks\_role](#module\_iam\_eks\_role) | ../../modules/iam-eks-role | n/a | +| [karpenter\_controller\_irsa\_role](#module\_karpenter\_controller\_irsa\_role) | ../../modules/iam-eks-role | n/a | +| [node\_termination\_handler\_irsa\_role](#module\_node\_termination\_handler\_irsa\_role) | ../../modules/iam-eks-role | n/a | | [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 3.0 | | [vpc\_cni\_ipv4\_irsa\_role](#module\_vpc\_cni\_ipv4\_irsa\_role) | ../../modules/iam-eks-role | n/a | | [vpc\_cni\_ipv6\_irsa\_role](#module\_vpc\_cni\_ipv6\_irsa\_role) | ../../modules/iam-eks-role | n/a | diff --git a/examples/iam-eks-role/main.tf b/examples/iam-eks-role/main.tf index 083e2b95..636bf70c 100644 --- a/examples/iam-eks-role/main.tf +++ b/examples/iam-eks-role/main.tf @@ -131,6 +131,40 @@ module "vpc_cni_ipv6_irsa_role" { tags = local.tags } +module "node_termination_handler_irsa_role" { + source = "../../modules/iam-eks-role" + + role_name = "node_termination_handler" + attach_node_termination_handler_policy = true + + oidc_providers = { + ex = { + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + service_accounts = ["default:my-app", "canary:my-app"] + } + } + + tags = local.tags +} + +module "karpenter_controller_irsa_role" { + source = "../../modules/iam-eks-role" + + role_name = "karpenter_controller" + attach_karpenter_controller_policy = true + + oidc_providers = { + ex = { + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + service_accounts = ["default:my-app", "canary:my-app"] + } + } + + tags = local.tags +} + ################################################################################ # Supporting Resources ################################################################################ diff --git a/modules/iam-eks-role/README.md b/modules/iam-eks-role/README.md index 850e7731..63fff093 100644 --- a/modules/iam-eks-role/README.md +++ b/modules/iam-eks-role/README.md @@ -112,17 +112,23 @@ No modules. | [aws_iam_policy.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.ebs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.external_dns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.karpenter_controller](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.node_termination_handler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.vpc_cni](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role_policy_attachment.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.ebs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.external_dns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.karpenter_controller](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.node_termination_handler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.vpc_cni](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [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_iam_policy_document.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.ebs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.external_dns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.karpenter_controller](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.node_termination_handler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.vpc_cni](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 | @@ -133,12 +139,16 @@ No modules. | [attach\_cluster\_autoscaler\_policy](#input\_attach\_cluster\_autoscaler\_policy) | Determines whether to attach the Cluster Autoscaler IAM policy to the role | `bool` | `false` | no | | [attach\_ebs\_csi\_policy](#input\_attach\_ebs\_csi\_policy) | Determines whether to attach the EBS CSI IAM policy to the role | `bool` | `false` | no | | [attach\_external\_dns\_policy](#input\_attach\_external\_dns\_policy) | Determines whether to attach the External DNS IAM policy to the role | `bool` | `false` | no | +| [attach\_karpenter\_controller\_policy](#input\_attach\_karpenter\_controller\_policy) | Determines whether to attach the Karpenter Controller policy to the role | `bool` | `false` | no | +| [attach\_node\_termination\_handler\_policy](#input\_attach\_node\_termination\_handler\_policy) | Determines whether to attach the Node Termination Handler policy to the role | `bool` | `false` | no | | [attach\_vpc\_cni\_policy](#input\_attach\_vpc\_cni\_policy) | Determines whether to attach the VPC CNI IAM policy to the role | `bool` | `false` | no | | [create\_role](#input\_create\_role) | Whether to create a role | `bool` | `true` | no | | [ebs\_csi\_kms\_cmk\_ids](#input\_ebs\_csi\_kms\_cmk\_ids) | KMS CMK IDs to allow EBS CSI to manage encrypted volumes | `list(string)` | `[]` | no | | [external\_dns\_hosted\_zones](#input\_external\_dns\_hosted\_zones) | Route53 hosted zone IDs to allow external DNS to manage records | `list(string)` |
[
"*"
]
| no | | [force\_detach\_policies](#input\_force\_detach\_policies) | Whether policies should be detached from this role when destroying | `bool` | `true` | no | +| [karpenter\_controller\_ssm\_parameter\_arns](#input\_karpenter\_controller\_ssm\_parameter\_arns) | List of SSM Parameter ARNs that contain AMI IDs launched by Karpenter | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/aws/service/*"
]
| no | | [max\_session\_duration](#input\_max\_session\_duration) | Maximum CLI/API session duration in seconds between 3600 and 43200 | `number` | `null` | no | +| [node\_termination\_handler\_sqs\_queue\_arns](#input\_node\_termination\_handler\_sqs\_queue\_arns) | List of SQS ARNs that contain node termination events | `list(string)` |
[
"*"
]
| no | | [oidc\_providers](#input\_oidc\_providers) | Map of OIDC providers where each provdier map should contain the `provider`, `provider_arns`, and `service_accounts` | `any` | `{}` | no | | [role\_description](#input\_role\_description) | IAM Role description | `string` | `null` | no | | [role\_name](#input\_role\_name) | Name of IAM role | `string` | `null` | no | diff --git a/modules/iam-eks-role/policies.tf b/modules/iam-eks-role/policies.tf index c5bca700..bda7c45b 100644 --- a/modules/iam-eks-role/policies.tf +++ b/modules/iam-eks-role/policies.tf @@ -348,3 +348,107 @@ resource "aws_iam_role_policy_attachment" "vpc_cni" { role = aws_iam_role.this[0].name policy_arn = aws_iam_policy.vpc_cni[0].arn } + +################################################################################ +# Node Termination Handler Policy +################################################################################ + +# https://github.com/aws/aws-node-termination-handler#5-create-an-iam-role-for-the-pods +data "aws_iam_policy_document" "node_termination_handler" { + count = var.create_role && var.attach_node_termination_handler_policy ? 1 : 0 + + statement { + actions = [ + "autoscaling:CompleteLifecycleAction", + "autoscaling:DescribeAutoScalingInstances", + "autoscaling:DescribeTags", + "ec2:DescribeInstances", + ] + + resources = ["*"] + } + + statement { + actions = [ + "sqs:DeleteMessage", + "sqs:ReceiveMessage" + ] + + resources = var.node_termination_handler_sqs_queue_arns + } +} + +resource "aws_iam_policy" "node_termination_handler" { + count = var.create_role && var.attach_node_termination_handler_policy ? 1 : 0 + + name_prefix = "AmazonEKS_Node_Termination_Handler_Policy-" + path = var.role_path + description = "Provides permissions to handle node termination events via the Node Termination Handler" + policy = data.aws_iam_policy_document.node_termination_handler[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "node_termination_handler" { + count = var.create_role && var.attach_node_termination_handler_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.node_termination_handler[0].arn +} + +################################################################################ +# Karpenter Controller Policy +################################################################################ + +# curl -fsSL https://karpenter.sh/v0.6.1/getting-started/cloudformation.yaml +data "aws_iam_policy_document" "karpenter_controller" { + count = var.create_role && var.attach_karpenter_controller_policy ? 1 : 0 + + statement { + actions = [ + # Write Operations + "ec2:CreateLaunchTemplate", + "ec2:CreateFleet", + "ec2:RunInstances", + "ec2:CreateTags", + "iam:PassRole", + "ec2:TerminateInstances", + # Read Operations + "ec2:DescribeLaunchTemplates", + "ec2:DescribeInstances", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeInstanceTypes", + "ec2:DescribeInstanceTypeOfferings", + "ec2:DescribeAvailabilityZones", + ] + + resources = ["*"] + } + + statement { + actions = [ + "ssm:GetParameter", + ] + + resources = var.karpenter_controller_ssm_parameter_arns + } +} + +resource "aws_iam_policy" "karpenter_controller" { + count = var.create_role && var.attach_karpenter_controller_policy ? 1 : 0 + + name_prefix = "AmazonEKS_Karpenter_Controller_Policy-" + path = var.role_path + description = "Provides permissions to handle node termination events via the Node Termination Handler" + policy = data.aws_iam_policy_document.karpenter_controller[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "karpenter_controller" { + count = var.create_role && var.attach_karpenter_controller_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.karpenter_controller[0].arn +} diff --git a/modules/iam-eks-role/variables.tf b/modules/iam-eks-role/variables.tf index e29d7e66..60ba520f 100644 --- a/modules/iam-eks-role/variables.tf +++ b/modules/iam-eks-role/variables.tf @@ -119,3 +119,30 @@ variable "vpc_cni_enable_ipv6" { type = bool default = false } + +# Node termination handler +variable "attach_node_termination_handler_policy" { + description = "Determines whether to attach the Node Termination Handler policy to the role" + type = bool + default = false +} + +variable "node_termination_handler_sqs_queue_arns" { + description = "List of SQS ARNs that contain node termination events" + type = list(string) + default = ["*"] +} + +# Karpetner controller +variable "attach_karpenter_controller_policy" { + description = "Determines whether to attach the Karpenter Controller policy to the role" + type = bool + default = false +} + +variable "karpenter_controller_ssm_parameter_arns" { + description = "List of SSM Parameter ARNs that contain AMI IDs launched by Karpenter" + type = list(string) + # https://github.com/aws/karpenter/blob/ed9473a9863ca949b61b9846c8b9f33f35b86dbd/pkg/cloudprovider/aws/ami.go#L105-L123 + default = ["arn:aws:ssm:*:*:parameter/aws/service/*"] +} From 41d38278638f6ea2df00492b69deedfd5c0a304d Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Mon, 14 Feb 2022 09:50:41 -0500 Subject: [PATCH 07/12] fix: updates from PR feedback --- examples/iam-eks-role/main.tf | 61 ++++++++++++++++--------------- modules/iam-eks-role/README.md | 5 ++- modules/iam-eks-role/main.tf | 2 +- modules/iam-eks-role/policies.tf | 45 ++++++++++++++++------- modules/iam-eks-role/variables.tf | 14 +++++-- 5 files changed, 77 insertions(+), 50 deletions(-) diff --git a/examples/iam-eks-role/main.tf b/examples/iam-eks-role/main.tf index 636bf70c..e5acba33 100644 --- a/examples/iam-eks-role/main.tf +++ b/examples/iam-eks-role/main.tf @@ -25,14 +25,14 @@ module "iam_eks_role" { oidc_providers = { one = { - provider = module.eks.oidc_provider - provider_arn = module.eks.oidc_provider_arn - service_accounts = ["default:my-app", "canary:my-app"] + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:my-app", "canary:my-app"] } two = { - provider = module.eks.oidc_provider - provider_arn = module.eks.oidc_provider_arn - service_accounts = ["default:blue", "canary:blue"] + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:blue", "canary:blue"] } } @@ -48,12 +48,13 @@ module "cluster_autoscaler_irsa_role" { role_name = "cluster-autoscaler" attach_cluster_autoscaler_policy = true + cluster_autoscaler_cluster_ids = [module.eks.cluster_id] oidc_providers = { ex = { - provider = module.eks.oidc_provider - provider_arn = module.eks.oidc_provider_arn - service_accounts = ["default:my-app", "canary:my-app"] + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:my-app", "canary:my-app"] } } @@ -63,15 +64,15 @@ module "cluster_autoscaler_irsa_role" { module "external_dns_irsa_role" { source = "../../modules/iam-eks-role" - role_name = "external-dns" - attach_external_dns_policy = true - external_dns_hosted_zones = ["IClearlyMadeThisUp"] + role_name = "external-dns" + attach_external_dns_policy = true + external_dns_hosted_zone_arns = ["arn:aws:route53:::hostedzone/IClearlyMadeThisUp"] oidc_providers = { ex = { - provider = module.eks.oidc_provider - provider_arn = module.eks.oidc_provider_arn - service_accounts = ["default:my-app", "canary:my-app"] + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:my-app", "canary:my-app"] } } @@ -86,9 +87,9 @@ module "ebs_csi_irsa_role" { oidc_providers = { ex = { - provider = module.eks.oidc_provider - provider_arn = module.eks.oidc_provider_arn - service_accounts = ["default:my-app", "canary:my-app"] + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:my-app", "canary:my-app"] } } @@ -104,9 +105,9 @@ module "vpc_cni_ipv4_irsa_role" { oidc_providers = { ex = { - provider = module.eks.oidc_provider - provider_arn = module.eks.oidc_provider_arn - service_accounts = ["default:my-app", "canary:my-app"] + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:my-app", "canary:my-app"] } } @@ -122,9 +123,9 @@ module "vpc_cni_ipv6_irsa_role" { oidc_providers = { ex = { - provider = module.eks.oidc_provider - provider_arn = module.eks.oidc_provider_arn - service_accounts = ["default:my-app", "canary:my-app"] + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:my-app", "canary:my-app"] } } @@ -139,9 +140,9 @@ module "node_termination_handler_irsa_role" { oidc_providers = { ex = { - provider = module.eks.oidc_provider - provider_arn = module.eks.oidc_provider_arn - service_accounts = ["default:my-app", "canary:my-app"] + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:my-app", "canary:my-app"] } } @@ -156,9 +157,9 @@ module "karpenter_controller_irsa_role" { oidc_providers = { ex = { - provider = module.eks.oidc_provider - provider_arn = module.eks.oidc_provider_arn - service_accounts = ["default:my-app", "canary:my-app"] + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:my-app", "canary:my-app"] } } diff --git a/modules/iam-eks-role/README.md b/modules/iam-eks-role/README.md index 63fff093..ac960fe0 100644 --- a/modules/iam-eks-role/README.md +++ b/modules/iam-eks-role/README.md @@ -142,14 +142,15 @@ No modules. | [attach\_karpenter\_controller\_policy](#input\_attach\_karpenter\_controller\_policy) | Determines whether to attach the Karpenter Controller policy to the role | `bool` | `false` | no | | [attach\_node\_termination\_handler\_policy](#input\_attach\_node\_termination\_handler\_policy) | Determines whether to attach the Node Termination Handler policy to the role | `bool` | `false` | no | | [attach\_vpc\_cni\_policy](#input\_attach\_vpc\_cni\_policy) | Determines whether to attach the VPC CNI IAM policy to the role | `bool` | `false` | no | +| [cluster\_autoscaler\_cluster\_ids](#input\_cluster\_autoscaler\_cluster\_ids) | List of cluster IDs to appropriately scope permissions within the Cluster Autoscaler IAM policy | `list(string)` | `[]` | no | | [create\_role](#input\_create\_role) | Whether to create a role | `bool` | `true` | no | | [ebs\_csi\_kms\_cmk\_ids](#input\_ebs\_csi\_kms\_cmk\_ids) | KMS CMK IDs to allow EBS CSI to manage encrypted volumes | `list(string)` | `[]` | no | -| [external\_dns\_hosted\_zones](#input\_external\_dns\_hosted\_zones) | Route53 hosted zone IDs to allow external DNS to manage records | `list(string)` |
[
"*"
]
| no | +| [external\_dns\_hosted\_zone\_arns](#input\_external\_dns\_hosted\_zone\_arns) | Route53 hosted zone ARNs to allow external DNS to manage records | `list(string)` |
[
"arn:aws:route53:::hostedzone/*"
]
| no | | [force\_detach\_policies](#input\_force\_detach\_policies) | Whether policies should be detached from this role when destroying | `bool` | `true` | no | | [karpenter\_controller\_ssm\_parameter\_arns](#input\_karpenter\_controller\_ssm\_parameter\_arns) | List of SSM Parameter ARNs that contain AMI IDs launched by Karpenter | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/aws/service/*"
]
| no | | [max\_session\_duration](#input\_max\_session\_duration) | Maximum CLI/API session duration in seconds between 3600 and 43200 | `number` | `null` | no | | [node\_termination\_handler\_sqs\_queue\_arns](#input\_node\_termination\_handler\_sqs\_queue\_arns) | List of SQS ARNs that contain node termination events | `list(string)` |
[
"*"
]
| no | -| [oidc\_providers](#input\_oidc\_providers) | Map of OIDC providers where each provdier map should contain the `provider`, `provider_arns`, and `service_accounts` | `any` | `{}` | no | +| [oidc\_providers](#input\_oidc\_providers) | Map of OIDC providers where each provdier map should contain the `provider`, `provider_arns`, and `namespace_service_accounts` | `any` | `{}` | no | | [role\_description](#input\_role\_description) | IAM Role description | `string` | `null` | 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 a18759ee..0a73b86d 100644 --- a/modules/iam-eks-role/main.tf +++ b/modules/iam-eks-role/main.tf @@ -16,7 +16,7 @@ data "aws_iam_policy_document" "assume_role_with_oidc" { condition { test = "StringEquals" variable = "${statement.value.provider}:sub" - values = [for sa in statement.value.service_accounts : "system:serviceaccount:${sa}"] + values = [for sa in statement.value.namespace_service_accounts : "system:serviceaccount:${sa}"] } } } diff --git a/modules/iam-eks-role/policies.tf b/modules/iam-eks-role/policies.tf index bda7c45b..047f797c 100644 --- a/modules/iam-eks-role/policies.tf +++ b/modules/iam-eks-role/policies.tf @@ -1,5 +1,9 @@ data "aws_partition" "current" {} +locals { + partition = data.aws_partition.current.partition +} + ################################################################################ # Cluster Autoscaler Policy ################################################################################ @@ -13,13 +17,31 @@ data "aws_iam_policy_document" "cluster_autoscaler" { "autoscaling:DescribeAutoScalingGroups", "autoscaling:DescribeAutoScalingInstances", "autoscaling:DescribeLaunchConfigurations", - "autoscaling:SetDesiredCapacity", - "autoscaling:TerminateInstanceInAutoScalingGroup", - "ec2:DescribeInstanceTypes", + "autoscaling:DescribeTags", + "ec2:DescribeLaunchTemplateVersions", ] resources = ["*"] } + + dynamic "statement" { + for_each = toset(var.cluster_autoscaler_cluster_ids) + content { + actions = [ + "autoscaling:SetDesiredCapacity", + "autoscaling:TerminateInstanceInAutoScalingGroup", + "autoscaling:UpdateAutoScalingGroup", + ] + + resources = ["*"] + + condition { + test = "StringEquals" + variable = "autoscaling:ResourceTag/kubernetes.io/cluster/${statement.value}" + values = ["owned"] + } + } + } } resource "aws_iam_policy" "cluster_autoscaler" { @@ -49,11 +71,8 @@ data "aws_iam_policy_document" "external_dns" { count = var.create_role && var.attach_external_dns_policy ? 1 : 0 statement { - actions = ["route53:ChangeResourceRecordSets"] - resources = [ - for hosted_zone in var.external_dns_hosted_zones : - "arn:${data.aws_partition.current.partition}:route53:::hostedzone/${hosted_zone}" - ] + actions = ["route53:ChangeResourceRecordSets"] + resources = var.external_dns_hosted_zone_arns } statement { @@ -113,8 +132,8 @@ data "aws_iam_policy_document" "ebs_csi" { actions = ["ec2:CreateTags"] resources = [ - "arn:aws:ec2:*:*:volume/*", - "arn:aws:ec2:*:*:snapshot/*", + "arn:${local.partition}:ec2:*:*:volume/*", + "arn:${local.partition}:ec2:*:*:snapshot/*", ] condition { @@ -131,8 +150,8 @@ data "aws_iam_policy_document" "ebs_csi" { actions = ["ec2:DeleteTags"] resources = [ - "arn:aws:ec2:*:*:volume/*", - "arn:aws:ec2:*:*:snapshot/*", + "arn:${local.partition}:ec2:*:*:volume/*", + "arn:${local.partition}:ec2:*:*:snapshot/*", ] } @@ -327,7 +346,7 @@ data "aws_iam_policy_document" "vpc_cni" { statement { sid = "CreateTags" actions = ["ec2:CreateTags"] - resources = ["arn:aws:ec2:*:*:network-interface/*"] + resources = ["arn:${local.partition}:ec2:*:*:network-interface/*"] } } diff --git a/modules/iam-eks-role/variables.tf b/modules/iam-eks-role/variables.tf index 60ba520f..6f8b66bd 100644 --- a/modules/iam-eks-role/variables.tf +++ b/modules/iam-eks-role/variables.tf @@ -41,7 +41,7 @@ variable "role_policy_arns" { } variable "oidc_providers" { - description = "Map of OIDC providers where each provdier map should contain the `provider`, `provider_arns`, and `service_accounts`" + description = "Map of OIDC providers where each provdier map should contain the `provider`, `provider_arns`, and `namespace_service_accounts`" type = any default = {} } @@ -75,6 +75,12 @@ variable "attach_cluster_autoscaler_policy" { default = false } +variable "cluster_autoscaler_cluster_ids" { + description = "List of cluster IDs to appropriately scope permissions within the Cluster Autoscaler IAM policy" + type = list(string) + default = [] +} + # External DNS variable "attach_external_dns_policy" { description = "Determines whether to attach the External DNS IAM policy to the role" @@ -82,10 +88,10 @@ variable "attach_external_dns_policy" { default = false } -variable "external_dns_hosted_zones" { - description = "Route53 hosted zone IDs to allow external DNS to manage records" +variable "external_dns_hosted_zone_arns" { + description = "Route53 hosted zone ARNs to allow external DNS to manage records" type = list(string) - default = ["*"] + default = ["arn:aws:route53:::hostedzone/*"] } # EBS CSI From 08285a4e4b417e340bf08ce9286f8aee1cf29d18 Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Mon, 14 Feb 2022 16:58:26 -0500 Subject: [PATCH 08/12] fix: update Karpenter policy following changes in upstream PR --- examples/iam-eks-role/main.tf | 9 ++++++++ modules/iam-eks-role/README.md | 2 ++ modules/iam-eks-role/policies.tf | 34 +++++++++++++++++++++++-------- modules/iam-eks-role/variables.tf | 12 +++++++++++ 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/examples/iam-eks-role/main.tf b/examples/iam-eks-role/main.tf index e5acba33..db5b6412 100644 --- a/examples/iam-eks-role/main.tf +++ b/examples/iam-eks-role/main.tf @@ -155,6 +155,9 @@ module "karpenter_controller_irsa_role" { role_name = "karpenter_controller" attach_karpenter_controller_policy = true + karpenter_controller_cluster_ids = [module.eks.cluster_id] + karpenter_controller_node_iam_role_arns = [module.eks.eks_managed_node_groups["default"].iam_role_arn] + oidc_providers = { ex = { provider = module.eks.oidc_provider @@ -211,4 +214,10 @@ module "eks" { vpc_id = module.vpc.vpc_id subnet_ids = module.vpc.private_subnets + + eks_managed_node_groups = { + default = {} + } + + tags = local.tags } diff --git a/modules/iam-eks-role/README.md b/modules/iam-eks-role/README.md index ac960fe0..2e73dd4e 100644 --- a/modules/iam-eks-role/README.md +++ b/modules/iam-eks-role/README.md @@ -147,6 +147,8 @@ No modules. | [ebs\_csi\_kms\_cmk\_ids](#input\_ebs\_csi\_kms\_cmk\_ids) | KMS CMK IDs to allow EBS CSI to manage encrypted volumes | `list(string)` | `[]` | no | | [external\_dns\_hosted\_zone\_arns](#input\_external\_dns\_hosted\_zone\_arns) | Route53 hosted zone ARNs to allow external DNS to manage records | `list(string)` |
[
"arn:aws:route53:::hostedzone/*"
]
| no | | [force\_detach\_policies](#input\_force\_detach\_policies) | Whether policies should be detached from this role when destroying | `bool` | `true` | no | +| [karpenter\_controller\_cluster\_ids](#input\_karpenter\_controller\_cluster\_ids) | List of cluster IDs to appropriately scope EC2 permissions within the Karpenter Controller policy | `list(string)` | `[]` | no | +| [karpenter\_controller\_node\_iam\_role\_arns](#input\_karpenter\_controller\_node\_iam\_role\_arns) | List of node IAM role ARNs Karpenter can use to launch nodes | `list(string)` |
[
"*"
]
| no | | [karpenter\_controller\_ssm\_parameter\_arns](#input\_karpenter\_controller\_ssm\_parameter\_arns) | List of SSM Parameter ARNs that contain AMI IDs launched by Karpenter | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/aws/service/*"
]
| no | | [max\_session\_duration](#input\_max\_session\_duration) | Maximum CLI/API session duration in seconds between 3600 and 43200 | `number` | `null` | no | | [node\_termination\_handler\_sqs\_queue\_arns](#input\_node\_termination\_handler\_sqs\_queue\_arns) | List of SQS ARNs that contain node termination events | `list(string)` |
[
"*"
]
| no | diff --git a/modules/iam-eks-role/policies.tf b/modules/iam-eks-role/policies.tf index 047f797c..d8a92f62 100644 --- a/modules/iam-eks-role/policies.tf +++ b/modules/iam-eks-role/policies.tf @@ -425,14 +425,9 @@ data "aws_iam_policy_document" "karpenter_controller" { statement { actions = [ - # Write Operations "ec2:CreateLaunchTemplate", "ec2:CreateFleet", - "ec2:RunInstances", "ec2:CreateTags", - "iam:PassRole", - "ec2:TerminateInstances", - # Read Operations "ec2:DescribeLaunchTemplates", "ec2:DescribeInstances", "ec2:DescribeSecurityGroups", @@ -445,13 +440,34 @@ data "aws_iam_policy_document" "karpenter_controller" { resources = ["*"] } - statement { - actions = [ - "ssm:GetParameter", - ] + dynamic "statement" { + for_each = toset(var.karpenter_controller_cluster_ids) + content { + actions = [ + "ec2:RunInstances", + "ec2:TerminateInstances", + "ec2:DeleteLaunchTemplate", + ] + resources = ["*"] + + condition { + test = "StringEquals" + variable = "ec2:ResourceTag/karpenter.sh/discovery" + values = [statement.value] + } + } + } + + statement { + actions = ["ssm:GetParameter"] resources = var.karpenter_controller_ssm_parameter_arns } + + statement { + actions = ["iam:PassRole"] + resources = var.karpenter_controller_node_iam_role_arns + } } resource "aws_iam_policy" "karpenter_controller" { diff --git a/modules/iam-eks-role/variables.tf b/modules/iam-eks-role/variables.tf index 6f8b66bd..ab0403b2 100644 --- a/modules/iam-eks-role/variables.tf +++ b/modules/iam-eks-role/variables.tf @@ -146,9 +146,21 @@ variable "attach_karpenter_controller_policy" { default = false } +variable "karpenter_controller_cluster_ids" { + description = "List of cluster IDs to appropriately scope EC2 permissions within the Karpenter Controller policy" + type = list(string) + default = [] +} + variable "karpenter_controller_ssm_parameter_arns" { description = "List of SSM Parameter ARNs that contain AMI IDs launched by Karpenter" type = list(string) # https://github.com/aws/karpenter/blob/ed9473a9863ca949b61b9846c8b9f33f35b86dbd/pkg/cloudprovider/aws/ami.go#L105-L123 default = ["arn:aws:ssm:*:*:parameter/aws/service/*"] } + +variable "karpenter_controller_node_iam_role_arns" { + description = "List of node IAM role ARNs Karpenter can use to launch nodes" + type = list(string) + default = ["*"] +} From 84cf6032af372ed8d35cdb74a311c9aba7a40c95 Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Tue, 15 Feb 2022 14:04:47 -0500 Subject: [PATCH 09/12] chore: split to new sub-module --- examples/iam-eks-role/README.md | 24 +- examples/iam-eks-role/main.tf | 233 +++--------------- examples/iam-eks-role/versions.tf | 6 +- .../README.md | 60 +++++ .../iam-role-for-service-accounts-eks/main.tf | 223 +++++++++++++++++ .../outputs.tf | 19 ++ .../variables.tf | 0 .../versions.tf | 10 + modules/iam-eks-role/README.md | 136 +++------- modules/iam-eks-role/main.tf | 66 +++-- modules/iam-eks-role/outputs.tf | 8 +- modules/iam-eks-role/variables.tf | 123 ++------- modules/iam-eks-role/versions.tf | 5 +- .../README.md | 174 +++++++++++++ .../iam-role-for-service-accounts-eks/main.tf | 46 ++++ .../outputs.tf | 19 ++ .../policies.tf | 0 .../variables.tf | 166 +++++++++++++ .../versions.tf | 10 + 19 files changed, 877 insertions(+), 451 deletions(-) create mode 100644 examples/iam-role-for-service-accounts-eks/README.md create mode 100644 examples/iam-role-for-service-accounts-eks/main.tf create mode 100644 examples/iam-role-for-service-accounts-eks/outputs.tf create mode 100644 examples/iam-role-for-service-accounts-eks/variables.tf create mode 100644 examples/iam-role-for-service-accounts-eks/versions.tf create mode 100644 modules/iam-role-for-service-accounts-eks/README.md create mode 100644 modules/iam-role-for-service-accounts-eks/main.tf create mode 100644 modules/iam-role-for-service-accounts-eks/outputs.tf rename modules/{iam-eks-role => iam-role-for-service-accounts-eks}/policies.tf (100%) create mode 100644 modules/iam-role-for-service-accounts-eks/variables.tf create mode 100644 modules/iam-role-for-service-accounts-eks/versions.tf diff --git a/examples/iam-eks-role/README.md b/examples/iam-eks-role/README.md index 7a2e6b81..54348219 100644 --- a/examples/iam-eks-role/README.md +++ b/examples/iam-eks-role/README.md @@ -1,6 +1,6 @@ # IAM EKS role -Configuration in this directory creates IAM roles that can be assumed by multiple EKS `ServiceAccount`s for various tasks. +Configuration in this directory creates an IAM role that can be assumed by multiple EKS `ServiceAccount`. # Usage @@ -21,29 +21,29 @@ Run `terraform destroy` when you don't need these resources. |------|---------| | [terraform](#requirement\_terraform) | >= 0.12.6 | | [aws](#requirement\_aws) | >= 2.23 | +| [random](#requirement\_random) | >= 2 | ## Providers -No providers. +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 2.23 | +| [random](#provider\_random) | >= 2 | ## Modules | Name | Source | Version | |------|--------|---------| -| [cluster\_autoscaler\_irsa\_role](#module\_cluster\_autoscaler\_irsa\_role) | ../../modules/iam-eks-role | n/a | -| [ebs\_csi\_irsa\_role](#module\_ebs\_csi\_irsa\_role) | ../../modules/iam-eks-role | n/a | -| [eks](#module\_eks) | terraform-aws-modules/eks/aws | ~> 18.6 | -| [external\_dns\_irsa\_role](#module\_external\_dns\_irsa\_role) | ../../modules/iam-eks-role | n/a | +| [eks](#module\_eks) | terraform-aws-modules/eks/aws | ~> 18.0 | | [iam\_eks\_role](#module\_iam\_eks\_role) | ../../modules/iam-eks-role | n/a | -| [karpenter\_controller\_irsa\_role](#module\_karpenter\_controller\_irsa\_role) | ../../modules/iam-eks-role | n/a | -| [node\_termination\_handler\_irsa\_role](#module\_node\_termination\_handler\_irsa\_role) | ../../modules/iam-eks-role | n/a | -| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 3.0 | -| [vpc\_cni\_ipv4\_irsa\_role](#module\_vpc\_cni\_ipv4\_irsa\_role) | ../../modules/iam-eks-role | n/a | -| [vpc\_cni\_ipv6\_irsa\_role](#module\_vpc\_cni\_ipv6\_irsa\_role) | ../../modules/iam-eks-role | n/a | ## Resources -No resources. +| Name | Type | +|------|------| +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | +| [aws_subnet_ids.all](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet_ids) | data source | +| [aws_vpc.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc) | data source | ## Inputs diff --git a/examples/iam-eks-role/main.tf b/examples/iam-eks-role/main.tf index db5b6412..85d37d0b 100644 --- a/examples/iam-eks-role/main.tf +++ b/examples/iam-eks-role/main.tf @@ -1,223 +1,58 @@ provider "aws" { - region = local.region + region = "eu-west-1" } -locals { - name = "ex-iam-eks-role" - cluster_version = "1.21" - region = "eu-west-1" +module "iam_eks_role" { + source = "../../modules/iam-eks-role" + role_name = "my-app" - tags = { - Example = local.name - GithubRepo = "terraform-aws-iam" - GithubOrg = "terraform-aws-modules" + cluster_service_accounts = { + (random_pet.this.id) = ["default:my-app", "canary:my-app"] } -} - -################################################################################ -# AWS IAM EKS Role Module -################################################################################ -module "iam_eks_role" { - source = "../../modules/iam-eks-role" - - role_name = local.name + 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", + ] + } - oidc_providers = { - one = { - provider = module.eks.oidc_provider - provider_arn = module.eks.oidc_provider_arn - namespace_service_accounts = ["default:my-app", "canary:my-app"] - } - two = { - provider = module.eks.oidc_provider - provider_arn = module.eks.oidc_provider_arn - namespace_service_accounts = ["default:blue", "canary:blue"] - } + tags = { + Name = "eks-role" } role_policy_arns = [ "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy" ] - - tags = local.tags -} - -module "cluster_autoscaler_irsa_role" { - source = "../../modules/iam-eks-role" - - role_name = "cluster-autoscaler" - attach_cluster_autoscaler_policy = true - cluster_autoscaler_cluster_ids = [module.eks.cluster_id] - - oidc_providers = { - ex = { - provider = module.eks.oidc_provider - provider_arn = module.eks.oidc_provider_arn - namespace_service_accounts = ["default:my-app", "canary:my-app"] - } - } - - tags = local.tags -} - -module "external_dns_irsa_role" { - source = "../../modules/iam-eks-role" - - role_name = "external-dns" - attach_external_dns_policy = true - external_dns_hosted_zone_arns = ["arn:aws:route53:::hostedzone/IClearlyMadeThisUp"] - - oidc_providers = { - ex = { - provider = module.eks.oidc_provider - provider_arn = module.eks.oidc_provider_arn - namespace_service_accounts = ["default:my-app", "canary:my-app"] - } - } - - tags = local.tags -} - -module "ebs_csi_irsa_role" { - source = "../../modules/iam-eks-role" - - role_name = "ebs_csi" - attach_ebs_csi_policy = true - - oidc_providers = { - ex = { - provider = module.eks.oidc_provider - provider_arn = module.eks.oidc_provider_arn - namespace_service_accounts = ["default:my-app", "canary:my-app"] - } - } - - tags = local.tags -} - -module "vpc_cni_ipv4_irsa_role" { - source = "../../modules/iam-eks-role" - - role_name = "vpc_cni_ipv4" - attach_vpc_cni_policy = true - vpc_cni_enable_ipv4 = true - - oidc_providers = { - ex = { - provider = module.eks.oidc_provider - provider_arn = module.eks.oidc_provider_arn - namespace_service_accounts = ["default:my-app", "canary:my-app"] - } - } - - tags = local.tags } -module "vpc_cni_ipv6_irsa_role" { - source = "../../modules/iam-eks-role" - - role_name = "vpc_cni_ipv6" - attach_vpc_cni_policy = true - vpc_cni_enable_ipv6 = true - - oidc_providers = { - ex = { - provider = module.eks.oidc_provider - provider_arn = module.eks.oidc_provider_arn - namespace_service_accounts = ["default:my-app", "canary:my-app"] - } - } - - tags = local.tags -} - -module "node_termination_handler_irsa_role" { - source = "../../modules/iam-eks-role" - - role_name = "node_termination_handler" - attach_node_termination_handler_policy = true - - oidc_providers = { - ex = { - provider = module.eks.oidc_provider - provider_arn = module.eks.oidc_provider_arn - namespace_service_accounts = ["default:my-app", "canary:my-app"] - } - } - - tags = local.tags -} - -module "karpenter_controller_irsa_role" { - source = "../../modules/iam-eks-role" - - role_name = "karpenter_controller" - attach_karpenter_controller_policy = true - - karpenter_controller_cluster_ids = [module.eks.cluster_id] - karpenter_controller_node_iam_role_arns = [module.eks.eks_managed_node_groups["default"].iam_role_arn] - - oidc_providers = { - ex = { - provider = module.eks.oidc_provider - provider_arn = module.eks.oidc_provider_arn - namespace_service_accounts = ["default:my-app", "canary:my-app"] - } - } +################## +# Extra resources +################## - tags = local.tags -} - -################################################################################ -# Supporting Resources -################################################################################ - -module "vpc" { - source = "terraform-aws-modules/vpc/aws" - version = "~> 3.0" - - name = local.name - cidr = "10.0.0.0/16" - - azs = ["${local.region}a", "${local.region}b", "${local.region}c"] - private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] - public_subnets = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"] - - enable_nat_gateway = true - single_nat_gateway = true - enable_dns_hostnames = true - - enable_flow_log = true - create_flow_log_cloudwatch_iam_role = true - create_flow_log_cloudwatch_log_group = true - - public_subnet_tags = { - "kubernetes.io/cluster/${local.name}" = "shared" - "kubernetes.io/role/elb" = 1 - } - - private_subnet_tags = { - "kubernetes.io/cluster/${local.name}" = "shared" - "kubernetes.io/role/internal-elb" = 1 - } - - tags = local.tags +resource "random_pet" "this" { + length = 2 } module "eks" { source = "terraform-aws-modules/eks/aws" - version = "~> 18.6" + version = "~> 18.0" - cluster_name = local.name - cluster_version = local.cluster_version + cluster_name = random_pet.this.id + cluster_version = "1.21" - vpc_id = module.vpc.vpc_id - subnet_ids = module.vpc.private_subnets + vpc_id = data.aws_vpc.default.id + subnet_ids = data.aws_subnet_ids.all.ids +} - eks_managed_node_groups = { - default = {} - } +################################################################## +# Data sources to get VPC, subnet, security group and AMI details +################################################################## +data "aws_vpc" "default" { + default = true +} - tags = local.tags +data "aws_subnet_ids" "all" { + vpc_id = data.aws_vpc.default.id } diff --git a/examples/iam-eks-role/versions.tf b/examples/iam-eks-role/versions.tf index 4e3fe457..de054d6e 100644 --- a/examples/iam-eks-role/versions.tf +++ b/examples/iam-eks-role/versions.tf @@ -2,9 +2,7 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 2.23" - } + aws = ">= 2.23" + random = ">= 2" } } diff --git a/examples/iam-role-for-service-accounts-eks/README.md b/examples/iam-role-for-service-accounts-eks/README.md new file mode 100644 index 00000000..201dda33 --- /dev/null +++ b/examples/iam-role-for-service-accounts-eks/README.md @@ -0,0 +1,60 @@ +# IAM Role for Service Accounts in EKS + +Configuration in this directory creates IAM roles that can be assumed by multiple EKS `ServiceAccount`s for various tasks. + +# 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. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.0 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [cluster\_autoscaler\_irsa\_role](#module\_cluster\_autoscaler\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [ebs\_csi\_irsa\_role](#module\_ebs\_csi\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [eks](#module\_eks) | terraform-aws-modules/eks/aws | ~> 18.6 | +| [external\_dns\_irsa\_role](#module\_external\_dns\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [irsa\_role](#module\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [karpenter\_controller\_irsa\_role](#module\_karpenter\_controller\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [node\_termination\_handler\_irsa\_role](#module\_node\_termination\_handler\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 3.0 | +| [vpc\_cni\_ipv4\_irsa\_role](#module\_vpc\_cni\_ipv4\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [vpc\_cni\_ipv6\_irsa\_role](#module\_vpc\_cni\_ipv6\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | + +## Resources + +No resources. + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [iam\_role\_arn](#output\_iam\_role\_arn) | ARN of IAM role | +| [iam\_role\_name](#output\_iam\_role\_name) | Name of IAM role | +| [iam\_role\_path](#output\_iam\_role\_path) | Path of IAM role | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Unique ID of IAM role | + diff --git a/examples/iam-role-for-service-accounts-eks/main.tf b/examples/iam-role-for-service-accounts-eks/main.tf new file mode 100644 index 00000000..f687b4b8 --- /dev/null +++ b/examples/iam-role-for-service-accounts-eks/main.tf @@ -0,0 +1,223 @@ +provider "aws" { + region = local.region +} + +locals { + name = "ex-iam-eks-role" + cluster_version = "1.21" + region = "eu-west-1" + + tags = { + Example = local.name + GithubRepo = "terraform-aws-iam" + GithubOrg = "terraform-aws-modules" + } +} + +################################################################################ +# IRSA Roles +################################################################################ + +module "irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = local.name + + oidc_providers = { + one = { + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:my-app", "canary:my-app"] + } + two = { + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:blue", "canary:blue"] + } + } + + role_policy_arns = [ + "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy" + ] + + tags = local.tags +} + +module "cluster_autoscaler_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "cluster-autoscaler" + attach_cluster_autoscaler_policy = true + cluster_autoscaler_cluster_ids = [module.eks.cluster_id] + + oidc_providers = { + ex = { + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:my-app", "canary:my-app"] + } + } + + tags = local.tags +} + +module "external_dns_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "external-dns" + attach_external_dns_policy = true + external_dns_hosted_zone_arns = ["arn:aws:route53:::hostedzone/IClearlyMadeThisUp"] + + oidc_providers = { + ex = { + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:my-app", "canary:my-app"] + } + } + + tags = local.tags +} + +module "ebs_csi_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "ebs_csi" + attach_ebs_csi_policy = true + + oidc_providers = { + ex = { + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:my-app", "canary:my-app"] + } + } + + tags = local.tags +} + +module "vpc_cni_ipv4_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "vpc_cni_ipv4" + attach_vpc_cni_policy = true + vpc_cni_enable_ipv4 = true + + oidc_providers = { + ex = { + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:my-app", "canary:my-app"] + } + } + + tags = local.tags +} + +module "vpc_cni_ipv6_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "vpc_cni_ipv6" + attach_vpc_cni_policy = true + vpc_cni_enable_ipv6 = true + + oidc_providers = { + ex = { + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:my-app", "canary:my-app"] + } + } + + tags = local.tags +} + +module "node_termination_handler_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "node_termination_handler" + attach_node_termination_handler_policy = true + + oidc_providers = { + ex = { + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:my-app", "canary:my-app"] + } + } + + tags = local.tags +} + +module "karpenter_controller_irsa_role" { + source = "../../modules/iam-role-for-service-accounts-eks" + + role_name = "karpenter_controller" + attach_karpenter_controller_policy = true + + karpenter_controller_cluster_ids = [module.eks.cluster_id] + karpenter_controller_node_iam_role_arns = [module.eks.eks_managed_node_groups["default"].iam_role_arn] + + oidc_providers = { + ex = { + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["default:my-app", "canary:my-app"] + } + } + + tags = local.tags +} + +################################################################################ +# Supporting Resources +################################################################################ + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 3.0" + + name = local.name + cidr = "10.0.0.0/16" + + azs = ["${local.region}a", "${local.region}b", "${local.region}c"] + private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] + public_subnets = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"] + + enable_nat_gateway = true + single_nat_gateway = true + enable_dns_hostnames = true + + enable_flow_log = true + create_flow_log_cloudwatch_iam_role = true + create_flow_log_cloudwatch_log_group = true + + public_subnet_tags = { + "kubernetes.io/cluster/${local.name}" = "shared" + "kubernetes.io/role/elb" = 1 + } + + private_subnet_tags = { + "kubernetes.io/cluster/${local.name}" = "shared" + "kubernetes.io/role/internal-elb" = 1 + } + + tags = local.tags +} + +module "eks" { + source = "terraform-aws-modules/eks/aws" + version = "~> 18.6" + + cluster_name = local.name + cluster_version = local.cluster_version + + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets + + eks_managed_node_groups = { + default = {} + } + + tags = local.tags +} diff --git a/examples/iam-role-for-service-accounts-eks/outputs.tf b/examples/iam-role-for-service-accounts-eks/outputs.tf new file mode 100644 index 00000000..f0754538 --- /dev/null +++ b/examples/iam-role-for-service-accounts-eks/outputs.tf @@ -0,0 +1,19 @@ +output "iam_role_arn" { + description = "ARN of IAM role" + value = module.irsa_role.iam_role_arn +} + +output "iam_role_name" { + description = "Name of IAM role" + value = module.irsa_role.iam_role_name +} + +output "iam_role_path" { + description = "Path of IAM role" + value = module.irsa_role.iam_role_path +} + +output "iam_role_unique_id" { + description = "Unique ID of IAM role" + value = module.irsa_role.iam_role_unique_id +} diff --git a/examples/iam-role-for-service-accounts-eks/variables.tf b/examples/iam-role-for-service-accounts-eks/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/iam-role-for-service-accounts-eks/versions.tf b/examples/iam-role-for-service-accounts-eks/versions.tf new file mode 100644 index 00000000..fe1f6e88 --- /dev/null +++ b/examples/iam-role-for-service-accounts-eks/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.0" + } + } +} diff --git a/modules/iam-eks-role/README.md b/modules/iam-eks-role/README.md index 2e73dd4e..34d5233d 100644 --- a/modules/iam-eks-role/README.md +++ b/modules/iam-eks-role/README.md @@ -1,92 +1,52 @@ # iam-eks-role -Creates an IAM role which can be assumed by AWS EKS `ServiceAccount`s. +Creates single IAM role which can be assumed by one or more EKS `ServiceAccount` and optionally also OpenID Connect Federated Users. -This module is intended to be used 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 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): +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" - oidc_providers = { - one = { - provider = "" - provider_arn = "" - service_accounts = [ - ":", - ":" - ] - } - two = { - provider = "" - provider_arn = "" - service_accounts = [ - ":", - ":" - ] - } + cluster_service_accounts = { + "" = [ + ":", + ":" + ] } -} -``` - -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" - oidc_providers = { - one = { - provider = "oidc.eks.us-east-1.amazonaws.com/id/5C54DDF35ER19312844C7333374CC09D" - provider_arn = "arn:aws:iam::012345678901:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/5C54DDF35ER19312844C7333374CC09D" - service_accounts = ["default:my-app-staging", "canary:my-app-staging"] - } - two = { - provider = "oidc.eks.ap-southeast-1.amazonaws.com/id/5C54DDF35ER54476848E7333374FF09G" - provider_arn = "arn:aws:iam::012345678901:oidc-provider/oidc.eks.ap-southeast-1.amazonaws.com/id/5C54DDF35ER54476848E7333374FF09G" - service_accounts = ["default:my-app-staging"] - } + provider_url_sa_pairs = { + "" = [ + ":", + ":" + ] } } ``` -This module has been design in conjunction with the [`terraform-aws-eks`](https://github.com/terraform-aws-modules/terraform-aws-eks) module and easily integrates with it: +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" - oidc_providers = { - one = { - provider = module.eks.oidc_provider - provider_arn = module.eks.oidc_provider_arn - service_accounts = ["default:my-app", "canary:my-app"] - } - two = { - provider = module.eks.oidc_provider - provider_arn = module.eks.oidc_provider_arn - service_accounts = ["default:blue", "canary:blue"] - } + cluster_service_accounts = { + "cluster-main-1" = [ + "default:my-app-staging", + "canary:my-app-staging" + ], + "cluster-backup-1" = [ + "default:my-app-staging", + ] } } - -module "eks" { - source = "terraform-aws-modules/eks/aws" - version = "~> 18.6" - - cluster_name = "my-cluster" - cluster_version = "1.21" - - vpc_id = module.vpc.vpc_id - subnet_ids = module.vpc.private_subnets -} ``` +Note: the EKS clusters must in the current AWS region and account as they use the default AWS provider. + ## Requirements @@ -109,59 +69,29 @@ No modules. | Name | Type | |------|------| -| [aws_iam_policy.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | -| [aws_iam_policy.ebs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | -| [aws_iam_policy.external_dns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | -| [aws_iam_policy.karpenter_controller](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | -| [aws_iam_policy.node_termination_handler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | -| [aws_iam_policy.vpc_cni](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | -| [aws_iam_role_policy_attachment.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_role_policy_attachment.ebs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_role_policy_attachment.external_dns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_role_policy_attachment.karpenter_controller](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_role_policy_attachment.node_termination_handler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_role_policy_attachment.vpc_cni](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_iam_policy_document.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_iam_policy_document.ebs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_iam_policy_document.external_dns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_iam_policy_document.karpenter_controller](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_iam_policy_document.node_termination_handler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_iam_policy_document.vpc_cni](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 | |------|-------------|------|---------|:--------:| -| [attach\_cluster\_autoscaler\_policy](#input\_attach\_cluster\_autoscaler\_policy) | Determines whether to attach the Cluster Autoscaler IAM policy to the role | `bool` | `false` | no | -| [attach\_ebs\_csi\_policy](#input\_attach\_ebs\_csi\_policy) | Determines whether to attach the EBS CSI IAM policy to the role | `bool` | `false` | no | -| [attach\_external\_dns\_policy](#input\_attach\_external\_dns\_policy) | Determines whether to attach the External DNS IAM policy to the role | `bool` | `false` | no | -| [attach\_karpenter\_controller\_policy](#input\_attach\_karpenter\_controller\_policy) | Determines whether to attach the Karpenter Controller policy to the role | `bool` | `false` | no | -| [attach\_node\_termination\_handler\_policy](#input\_attach\_node\_termination\_handler\_policy) | Determines whether to attach the Node Termination Handler policy to the role | `bool` | `false` | no | -| [attach\_vpc\_cni\_policy](#input\_attach\_vpc\_cni\_policy) | Determines whether to attach the VPC CNI IAM policy to the role | `bool` | `false` | no | -| [cluster\_autoscaler\_cluster\_ids](#input\_cluster\_autoscaler\_cluster\_ids) | List of cluster IDs to appropriately scope permissions within the Cluster Autoscaler IAM policy | `list(string)` | `[]` | no | +| [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 | | [create\_role](#input\_create\_role) | Whether to create a role | `bool` | `true` | no | -| [ebs\_csi\_kms\_cmk\_ids](#input\_ebs\_csi\_kms\_cmk\_ids) | KMS CMK IDs to allow EBS CSI to manage encrypted volumes | `list(string)` | `[]` | no | -| [external\_dns\_hosted\_zone\_arns](#input\_external\_dns\_hosted\_zone\_arns) | Route53 hosted zone ARNs to allow external DNS to manage records | `list(string)` |
[
"arn:aws:route53:::hostedzone/*"
]
| no | -| [force\_detach\_policies](#input\_force\_detach\_policies) | Whether policies should be detached from this role when destroying | `bool` | `true` | no | -| [karpenter\_controller\_cluster\_ids](#input\_karpenter\_controller\_cluster\_ids) | List of cluster IDs to appropriately scope EC2 permissions within the Karpenter Controller policy | `list(string)` | `[]` | no | -| [karpenter\_controller\_node\_iam\_role\_arns](#input\_karpenter\_controller\_node\_iam\_role\_arns) | List of node IAM role ARNs Karpenter can use to launch nodes | `list(string)` |
[
"*"
]
| no | -| [karpenter\_controller\_ssm\_parameter\_arns](#input\_karpenter\_controller\_ssm\_parameter\_arns) | List of SSM Parameter ARNs that contain AMI IDs launched by Karpenter | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/aws/service/*"
]
| no | -| [max\_session\_duration](#input\_max\_session\_duration) | Maximum CLI/API session duration in seconds between 3600 and 43200 | `number` | `null` | no | -| [node\_termination\_handler\_sqs\_queue\_arns](#input\_node\_termination\_handler\_sqs\_queue\_arns) | List of SQS ARNs that contain node termination events | `list(string)` |
[
"*"
]
| no | -| [oidc\_providers](#input\_oidc\_providers) | Map of OIDC providers where each provdier map should contain the `provider`, `provider_arns`, and `namespace_service_accounts` | `any` | `{}` | no | -| [role\_description](#input\_role\_description) | IAM Role description | `string` | `null` | 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 | -| [role\_path](#input\_role\_path) | Path of IAM role | `string` | `null` | no | -| [role\_permissions\_boundary\_arn](#input\_role\_permissions\_boundary\_arn) | Permissions boundary ARN to use for IAM role | `string` | `null` | no | +| [role\_path](#input\_role\_path) | Path of IAM role | `string` | `"/"` | no | +| [role\_permissions\_boundary\_arn](#input\_role\_permissions\_boundary\_arn) | Permissions boundary ARN to use for IAM role | `string` | `""` | no | | [role\_policy\_arns](#input\_role\_policy\_arns) | ARNs of any policies to attach to the IAM role | `list(string)` | `[]` | no | | [tags](#input\_tags) | A map of tags to add the the IAM role | `map(any)` | `{}` | no | -| [vpc\_cni\_enable\_ipv4](#input\_vpc\_cni\_enable\_ipv4) | Determines whether to enable IPv4 permissions for VPC CNI policy | `bool` | `false` | no | -| [vpc\_cni\_enable\_ipv6](#input\_vpc\_cni\_enable\_ipv6) | Determines whether to enable IPv6 permissions for VPC CNI policy | `bool` | `false` | no | ## Outputs diff --git a/modules/iam-eks-role/main.tf b/modules/iam-eks-role/main.tf index 0a73b86d..f4fd4664 100644 --- a/modules/iam-eks-role/main.tf +++ b/modules/iam-eks-role/main.tf @@ -1,22 +1,58 @@ +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" { - count = var.create_role ? 1 : 0 + 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.oidc_providers + for_each = var.provider_url_sa_pairs content { - effect = "Allow" + effect = "Allow" + actions = ["sts:AssumeRoleWithWebIdentity"] principals { - type = "Federated" - identifiers = [statement.value.provider_arn] + type = "Federated" + + identifiers = [ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${statement.key}" + ] } condition { test = "StringEquals" - variable = "${statement.value.provider}:sub" - values = [for sa in statement.value.namespace_service_accounts : "system:serviceaccount:${sa}"] + variable = "${statement.key}:sub" + values = [for s in statement.value : "system:serviceaccount:${s}"] } } } @@ -25,17 +61,15 @@ data "aws_iam_policy_document" "assume_role_with_oidc" { resource "aws_iam_role" "this" { count = var.create_role ? 1 : 0 - name = var.role_name - name_prefix = var.role_name_prefix - path = var.role_path - description = var.role_description - - assume_role_policy = data.aws_iam_policy_document.assume_role_with_oidc[0].json + 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 - force_detach_policies = var.force_detach_policies - - tags = var.tags + tags = var.tags } resource "aws_iam_role_policy_attachment" "custom" { diff --git a/modules/iam-eks-role/outputs.tf b/modules/iam-eks-role/outputs.tf index a6805310..cdd317a3 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 = try(aws_iam_role.this[0].arn, "") + value = element(concat(aws_iam_role.this.*.arn, [""]), 0) } output "iam_role_name" { description = "Name of IAM role" - value = try(aws_iam_role.this[0].name, "") + value = element(concat(aws_iam_role.this.*.name, [""]), 0) } output "iam_role_path" { description = "Path of IAM role" - value = try(aws_iam_role.this[0].path, "") + value = element(concat(aws_iam_role.this.*.path, [""]), 0) } output "iam_role_unique_id" { description = "Unique ID of IAM role" - value = try(aws_iam_role.this[0].unique_id, "") + value = element(concat(aws_iam_role.this.*.unique_id, [""]), 0) } diff --git a/modules/iam-eks-role/variables.tf b/modules/iam-eks-role/variables.tf index ab0403b2..6a90d25c 100644 --- a/modules/iam-eks-role/variables.tf +++ b/modules/iam-eks-role/variables.tf @@ -13,19 +13,19 @@ variable "role_name" { variable "role_path" { description = "Path of IAM role" type = string - default = null + default = "/" } variable "role_permissions_boundary_arn" { description = "Permissions boundary ARN to use for IAM role" type = string - default = null + default = "" } variable "role_description" { description = "IAM Role description" type = string - default = null + default = "" } variable "role_name_prefix" { @@ -40,9 +40,15 @@ variable "role_policy_arns" { default = [] } -variable "oidc_providers" { - description = "Map of OIDC providers where each provdier map should contain the `provider`, `provider_arns`, and `namespace_service_accounts`" - type = any +variable "cluster_service_accounts" { + description = "EKS cluster and k8s ServiceAccount pairs. Each EKS cluster can have multiple k8s ServiceAccount. See README for details" + type = map(list(string)) + 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 = {} } @@ -55,112 +61,11 @@ variable "tags" { variable "force_detach_policies" { description = "Whether policies should be detached from this role when destroying" type = bool - default = true + default = false } variable "max_session_duration" { description = "Maximum CLI/API session duration in seconds between 3600 and 43200" type = number - default = null -} - -################################################################################ -# Policies -################################################################################ - -# Cluster autoscaler -variable "attach_cluster_autoscaler_policy" { - description = "Determines whether to attach the Cluster Autoscaler IAM policy to the role" - type = bool - default = false -} - -variable "cluster_autoscaler_cluster_ids" { - description = "List of cluster IDs to appropriately scope permissions within the Cluster Autoscaler IAM policy" - type = list(string) - default = [] -} - -# External DNS -variable "attach_external_dns_policy" { - description = "Determines whether to attach the External DNS IAM policy to the role" - type = bool - default = false -} - -variable "external_dns_hosted_zone_arns" { - description = "Route53 hosted zone ARNs to allow external DNS to manage records" - type = list(string) - default = ["arn:aws:route53:::hostedzone/*"] -} - -# EBS CSI -variable "attach_ebs_csi_policy" { - description = "Determines whether to attach the EBS CSI IAM policy to the role" - type = bool - default = false -} - -variable "ebs_csi_kms_cmk_ids" { - description = "KMS CMK IDs to allow EBS CSI to manage encrypted volumes" - type = list(string) - default = [] -} - -# VPC CNI -variable "attach_vpc_cni_policy" { - description = "Determines whether to attach the VPC CNI IAM policy to the role" - type = bool - default = false -} - -variable "vpc_cni_enable_ipv4" { - description = "Determines whether to enable IPv4 permissions for VPC CNI policy" - type = bool - default = false -} - -variable "vpc_cni_enable_ipv6" { - description = "Determines whether to enable IPv6 permissions for VPC CNI policy" - type = bool - default = false -} - -# Node termination handler -variable "attach_node_termination_handler_policy" { - description = "Determines whether to attach the Node Termination Handler policy to the role" - type = bool - default = false -} - -variable "node_termination_handler_sqs_queue_arns" { - description = "List of SQS ARNs that contain node termination events" - type = list(string) - default = ["*"] -} - -# Karpetner controller -variable "attach_karpenter_controller_policy" { - description = "Determines whether to attach the Karpenter Controller policy to the role" - type = bool - default = false -} - -variable "karpenter_controller_cluster_ids" { - description = "List of cluster IDs to appropriately scope EC2 permissions within the Karpenter Controller policy" - type = list(string) - default = [] -} - -variable "karpenter_controller_ssm_parameter_arns" { - description = "List of SSM Parameter ARNs that contain AMI IDs launched by Karpenter" - type = list(string) - # https://github.com/aws/karpenter/blob/ed9473a9863ca949b61b9846c8b9f33f35b86dbd/pkg/cloudprovider/aws/ami.go#L105-L123 - default = ["arn:aws:ssm:*:*:parameter/aws/service/*"] -} - -variable "karpenter_controller_node_iam_role_arns" { - description = "List of node IAM role ARNs Karpenter can use to launch nodes" - type = list(string) - default = ["*"] + default = 43200 } diff --git a/modules/iam-eks-role/versions.tf b/modules/iam-eks-role/versions.tf index 4e3fe457..fff6b757 100644 --- a/modules/iam-eks-role/versions.tf +++ b/modules/iam-eks-role/versions.tf @@ -2,9 +2,6 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 2.23" - } + aws = ">= 2.23" } } diff --git a/modules/iam-role-for-service-accounts-eks/README.md b/modules/iam-role-for-service-accounts-eks/README.md new file mode 100644 index 00000000..1bfd7a66 --- /dev/null +++ b/modules/iam-role-for-service-accounts-eks/README.md @@ -0,0 +1,174 @@ +# IAM Role for Service Accounts in EKS + +Creates an IAM role which can be assumed by AWS EKS `ServiceAccount`s. + +This module is intended to be used 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): + +```hcl +module "iam_eks_role" { + source = "terraform-aws-modules/iam/aws//modules/iam-eks-role" + + oidc_providers = { + one = { + provider = "" + provider_arn = "" + service_accounts = [ + ":", + ":" + ] + } + two = { + provider = "" + provider_arn = "" + service_accounts = [ + ":", + ":" + ] + } + } +} +``` + +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" + + oidc_providers = { + one = { + provider = "oidc.eks.us-east-1.amazonaws.com/id/5C54DDF35ER19312844C7333374CC09D" + provider_arn = "arn:aws:iam::012345678901:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/5C54DDF35ER19312844C7333374CC09D" + service_accounts = ["default:my-app-staging", "canary:my-app-staging"] + } + two = { + provider = "oidc.eks.ap-southeast-1.amazonaws.com/id/5C54DDF35ER54476848E7333374FF09G" + provider_arn = "arn:aws:iam::012345678901:oidc-provider/oidc.eks.ap-southeast-1.amazonaws.com/id/5C54DDF35ER54476848E7333374FF09G" + service_accounts = ["default:my-app-staging"] + } + } +} +``` + +This module has been design in conjunction with the [`terraform-aws-eks`](https://github.com/terraform-aws-modules/terraform-aws-eks) module and easily integrates with it: + +```hcl +module "iam_eks_role" { + source = "terraform-aws-modules/iam/aws//modules/iam-eks-role" + + role_name = "my-app" + + oidc_providers = { + one = { + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + service_accounts = ["default:my-app", "canary:my-app"] + } + two = { + provider = module.eks.oidc_provider + provider_arn = module.eks.oidc_provider_arn + service_accounts = ["default:blue", "canary:blue"] + } + } +} + +module "eks" { + source = "terraform-aws-modules/eks/aws" + version = "~> 18.6" + + cluster_name = "my-cluster" + cluster_version = "1.21" + + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | >= 3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 3.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_policy.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.ebs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.external_dns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.karpenter_controller](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.node_termination_handler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.vpc_cni](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.ebs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.external_dns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.karpenter_controller](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.node_termination_handler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.vpc_cni](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_policy_document.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.ebs_csi](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.external_dns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.karpenter_controller](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.node_termination_handler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.vpc_cni](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 | +|------|-------------|------|---------|:--------:| +| [attach\_cluster\_autoscaler\_policy](#input\_attach\_cluster\_autoscaler\_policy) | Determines whether to attach the Cluster Autoscaler IAM policy to the role | `bool` | `false` | no | +| [attach\_ebs\_csi\_policy](#input\_attach\_ebs\_csi\_policy) | Determines whether to attach the EBS CSI IAM policy to the role | `bool` | `false` | no | +| [attach\_external\_dns\_policy](#input\_attach\_external\_dns\_policy) | Determines whether to attach the External DNS IAM policy to the role | `bool` | `false` | no | +| [attach\_karpenter\_controller\_policy](#input\_attach\_karpenter\_controller\_policy) | Determines whether to attach the Karpenter Controller policy to the role | `bool` | `false` | no | +| [attach\_node\_termination\_handler\_policy](#input\_attach\_node\_termination\_handler\_policy) | Determines whether to attach the Node Termination Handler policy to the role | `bool` | `false` | no | +| [attach\_vpc\_cni\_policy](#input\_attach\_vpc\_cni\_policy) | Determines whether to attach the VPC CNI IAM policy to the role | `bool` | `false` | no | +| [cluster\_autoscaler\_cluster\_ids](#input\_cluster\_autoscaler\_cluster\_ids) | List of cluster IDs to appropriately scope permissions within the Cluster Autoscaler IAM policy | `list(string)` | `[]` | no | +| [create\_role](#input\_create\_role) | Whether to create a role | `bool` | `true` | no | +| [ebs\_csi\_kms\_cmk\_ids](#input\_ebs\_csi\_kms\_cmk\_ids) | KMS CMK IDs to allow EBS CSI to manage encrypted volumes | `list(string)` | `[]` | no | +| [external\_dns\_hosted\_zone\_arns](#input\_external\_dns\_hosted\_zone\_arns) | Route53 hosted zone ARNs to allow external DNS to manage records | `list(string)` |
[
"arn:aws:route53:::hostedzone/*"
]
| no | +| [force\_detach\_policies](#input\_force\_detach\_policies) | Whether policies should be detached from this role when destroying | `bool` | `true` | no | +| [karpenter\_controller\_cluster\_ids](#input\_karpenter\_controller\_cluster\_ids) | List of cluster IDs to appropriately scope EC2 permissions within the Karpenter Controller policy | `list(string)` | `[]` | no | +| [karpenter\_controller\_node\_iam\_role\_arns](#input\_karpenter\_controller\_node\_iam\_role\_arns) | List of node IAM role ARNs Karpenter can use to launch nodes | `list(string)` |
[
"*"
]
| no | +| [karpenter\_controller\_ssm\_parameter\_arns](#input\_karpenter\_controller\_ssm\_parameter\_arns) | List of SSM Parameter ARNs that contain AMI IDs launched by Karpenter | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/aws/service/*"
]
| no | +| [max\_session\_duration](#input\_max\_session\_duration) | Maximum CLI/API session duration in seconds between 3600 and 43200 | `number` | `null` | no | +| [node\_termination\_handler\_sqs\_queue\_arns](#input\_node\_termination\_handler\_sqs\_queue\_arns) | List of SQS ARNs that contain node termination events | `list(string)` |
[
"*"
]
| no | +| [oidc\_providers](#input\_oidc\_providers) | Map of OIDC providers where each provdier map should contain the `provider`, `provider_arns`, and `namespace_service_accounts` | `any` | `{}` | no | +| [role\_description](#input\_role\_description) | IAM Role description | `string` | `null` | 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 | +| [role\_path](#input\_role\_path) | Path of IAM role | `string` | `null` | no | +| [role\_permissions\_boundary\_arn](#input\_role\_permissions\_boundary\_arn) | Permissions boundary ARN to use for IAM role | `string` | `null` | no | +| [role\_policy\_arns](#input\_role\_policy\_arns) | ARNs of any policies to attach to the IAM role | `list(string)` | `[]` | no | +| [tags](#input\_tags) | A map of tags to add the the IAM role | `map(any)` | `{}` | no | +| [vpc\_cni\_enable\_ipv4](#input\_vpc\_cni\_enable\_ipv4) | Determines whether to enable IPv4 permissions for VPC CNI policy | `bool` | `false` | no | +| [vpc\_cni\_enable\_ipv6](#input\_vpc\_cni\_enable\_ipv6) | Determines whether to enable IPv6 permissions for VPC CNI policy | `bool` | `false` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [iam\_role\_arn](#output\_iam\_role\_arn) | ARN of IAM role | +| [iam\_role\_name](#output\_iam\_role\_name) | Name of IAM role | +| [iam\_role\_path](#output\_iam\_role\_path) | Path of IAM role | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Unique ID of IAM role | + diff --git a/modules/iam-role-for-service-accounts-eks/main.tf b/modules/iam-role-for-service-accounts-eks/main.tf new file mode 100644 index 00000000..a16f2cef --- /dev/null +++ b/modules/iam-role-for-service-accounts-eks/main.tf @@ -0,0 +1,46 @@ +data "aws_iam_policy_document" "this" { + count = var.create_role ? 1 : 0 + + dynamic "statement" { + for_each = var.oidc_providers + + content { + effect = "Allow" + actions = ["sts:AssumeRoleWithWebIdentity"] + + principals { + type = "Federated" + identifiers = [statement.value.provider_arn] + } + + condition { + test = "StringEquals" + variable = "${statement.value.provider}:sub" + values = [for sa in statement.value.namespace_service_accounts : "system:serviceaccount:${sa}"] + } + } + } +} + +resource "aws_iam_role" "this" { + count = var.create_role ? 1 : 0 + + name = var.role_name + name_prefix = var.role_name_prefix + path = var.role_path + description = var.role_description + + assume_role_policy = data.aws_iam_policy_document.this[0].json + max_session_duration = var.max_session_duration + permissions_boundary = var.role_permissions_boundary_arn + force_detach_policies = var.force_detach_policies + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "this" { + for_each = var.create_role ? toset(var.role_policy_arns) : [] + + role = aws_iam_role.this[0].name + policy_arn = each.key +} diff --git a/modules/iam-role-for-service-accounts-eks/outputs.tf b/modules/iam-role-for-service-accounts-eks/outputs.tf new file mode 100644 index 00000000..a6805310 --- /dev/null +++ b/modules/iam-role-for-service-accounts-eks/outputs.tf @@ -0,0 +1,19 @@ +output "iam_role_arn" { + description = "ARN of IAM role" + value = try(aws_iam_role.this[0].arn, "") +} + +output "iam_role_name" { + description = "Name of IAM role" + value = try(aws_iam_role.this[0].name, "") +} + +output "iam_role_path" { + description = "Path of IAM role" + value = try(aws_iam_role.this[0].path, "") +} + +output "iam_role_unique_id" { + description = "Unique ID of IAM role" + value = try(aws_iam_role.this[0].unique_id, "") +} diff --git a/modules/iam-eks-role/policies.tf b/modules/iam-role-for-service-accounts-eks/policies.tf similarity index 100% rename from modules/iam-eks-role/policies.tf rename to modules/iam-role-for-service-accounts-eks/policies.tf diff --git a/modules/iam-role-for-service-accounts-eks/variables.tf b/modules/iam-role-for-service-accounts-eks/variables.tf new file mode 100644 index 00000000..ab0403b2 --- /dev/null +++ b/modules/iam-role-for-service-accounts-eks/variables.tf @@ -0,0 +1,166 @@ +variable "create_role" { + description = "Whether to create a role" + type = bool + default = true +} + +variable "role_name" { + description = "Name of IAM role" + type = string + default = null +} + +variable "role_path" { + description = "Path of IAM role" + type = string + default = null +} + +variable "role_permissions_boundary_arn" { + description = "Permissions boundary ARN to use for IAM role" + type = string + default = null +} + +variable "role_description" { + description = "IAM Role description" + type = string + default = null +} + +variable "role_name_prefix" { + description = "IAM role name prefix" + type = string + default = null +} + +variable "role_policy_arns" { + description = "ARNs of any policies to attach to the IAM role" + type = list(string) + default = [] +} + +variable "oidc_providers" { + description = "Map of OIDC providers where each provdier map should contain the `provider`, `provider_arns`, and `namespace_service_accounts`" + type = any + default = {} +} + +variable "tags" { + description = "A map of tags to add the the IAM role" + type = map(any) + default = {} +} + +variable "force_detach_policies" { + description = "Whether policies should be detached from this role when destroying" + type = bool + default = true +} + +variable "max_session_duration" { + description = "Maximum CLI/API session duration in seconds between 3600 and 43200" + type = number + default = null +} + +################################################################################ +# Policies +################################################################################ + +# Cluster autoscaler +variable "attach_cluster_autoscaler_policy" { + description = "Determines whether to attach the Cluster Autoscaler IAM policy to the role" + type = bool + default = false +} + +variable "cluster_autoscaler_cluster_ids" { + description = "List of cluster IDs to appropriately scope permissions within the Cluster Autoscaler IAM policy" + type = list(string) + default = [] +} + +# External DNS +variable "attach_external_dns_policy" { + description = "Determines whether to attach the External DNS IAM policy to the role" + type = bool + default = false +} + +variable "external_dns_hosted_zone_arns" { + description = "Route53 hosted zone ARNs to allow external DNS to manage records" + type = list(string) + default = ["arn:aws:route53:::hostedzone/*"] +} + +# EBS CSI +variable "attach_ebs_csi_policy" { + description = "Determines whether to attach the EBS CSI IAM policy to the role" + type = bool + default = false +} + +variable "ebs_csi_kms_cmk_ids" { + description = "KMS CMK IDs to allow EBS CSI to manage encrypted volumes" + type = list(string) + default = [] +} + +# VPC CNI +variable "attach_vpc_cni_policy" { + description = "Determines whether to attach the VPC CNI IAM policy to the role" + type = bool + default = false +} + +variable "vpc_cni_enable_ipv4" { + description = "Determines whether to enable IPv4 permissions for VPC CNI policy" + type = bool + default = false +} + +variable "vpc_cni_enable_ipv6" { + description = "Determines whether to enable IPv6 permissions for VPC CNI policy" + type = bool + default = false +} + +# Node termination handler +variable "attach_node_termination_handler_policy" { + description = "Determines whether to attach the Node Termination Handler policy to the role" + type = bool + default = false +} + +variable "node_termination_handler_sqs_queue_arns" { + description = "List of SQS ARNs that contain node termination events" + type = list(string) + default = ["*"] +} + +# Karpetner controller +variable "attach_karpenter_controller_policy" { + description = "Determines whether to attach the Karpenter Controller policy to the role" + type = bool + default = false +} + +variable "karpenter_controller_cluster_ids" { + description = "List of cluster IDs to appropriately scope EC2 permissions within the Karpenter Controller policy" + type = list(string) + default = [] +} + +variable "karpenter_controller_ssm_parameter_arns" { + description = "List of SSM Parameter ARNs that contain AMI IDs launched by Karpenter" + type = list(string) + # https://github.com/aws/karpenter/blob/ed9473a9863ca949b61b9846c8b9f33f35b86dbd/pkg/cloudprovider/aws/ami.go#L105-L123 + default = ["arn:aws:ssm:*:*:parameter/aws/service/*"] +} + +variable "karpenter_controller_node_iam_role_arns" { + description = "List of node IAM role ARNs Karpenter can use to launch nodes" + type = list(string) + default = ["*"] +} diff --git a/modules/iam-role-for-service-accounts-eks/versions.tf b/modules/iam-role-for-service-accounts-eks/versions.tf new file mode 100644 index 00000000..fe1f6e88 --- /dev/null +++ b/modules/iam-role-for-service-accounts-eks/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13.1" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.0" + } + } +} From 122d1d37f06a00cfcf70238b2d246464130fd4d2 Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Tue, 15 Feb 2022 14:14:16 -0500 Subject: [PATCH 10/12] chore: correct wording --- modules/iam-role-for-service-accounts-eks/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/iam-role-for-service-accounts-eks/README.md b/modules/iam-role-for-service-accounts-eks/README.md index 1bfd7a66..dd9945aa 100644 --- a/modules/iam-role-for-service-accounts-eks/README.md +++ b/modules/iam-role-for-service-accounts-eks/README.md @@ -53,7 +53,7 @@ module "iam_eks_role" { } ``` -This module has been design in conjunction with the [`terraform-aws-eks`](https://github.com/terraform-aws-modules/terraform-aws-eks) module and easily integrates with it: +This module has been designed in conjunction with the [`terraform-aws-eks`](https://github.com/terraform-aws-modules/terraform-aws-eks) module to easily integrate with it: ```hcl module "iam_eks_role" { From 9de3345782c5130a1ff32a5e939116f1d3685c83 Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Wed, 16 Feb 2022 08:24:29 -0500 Subject: [PATCH 11/12] chore: updates for PR feedback --- README.md | 159 ++++++++++-------- .../iam-role-for-service-accounts-eks/main.tf | 14 +- 2 files changed, 99 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index dfc711e5..5bd08c7c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ## Features 1. **Cross-account access.** Define IAM roles using `iam_assumable_role` or `iam_assumable_roles` submodules in "resource AWS accounts (prod, staging, dev)" and IAM groups and users using `iam-group-with-assumable-roles-policy` submodule in "IAM AWS Account" to setup access controls between accounts. See [iam-group-with-assumable-roles-policy example](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/examples/iam-group-with-assumable-roles-policy) for more details. -1. **Individual IAM resources (users, roles, policies).** See usage snippets and [examples](https://github.com/terraform-aws-modules/terraform-aws-iam#examples) listed below. +2. **Individual IAM resources (users, roles, policies).** See usage snippets and [examples](https://github.com/terraform-aws-modules/terraform-aws-iam#examples) listed below. ## Usage @@ -134,63 +134,31 @@ module "iam_assumable_roles_with_saml" { } ``` -`iam-user`: +`iam-eks-role`: ```hcl -module "iam_user" { - source = "terraform-aws-modules/iam/aws//modules/iam-user" - version = "~> 4" - - name = "vasya.pupkin" - force_destroy = true - - pgp_key = "keybase:test" - - password_reset_required = false -} -``` +module "iam_eks_role" { + source = "terraform-aws-modules/iam/aws//modules/iam-eks-role" + version = "~> 4" -`iam-policy`: + role_name = "my-app" -```hcl -module "iam_policy" { - source = "terraform-aws-modules/iam/aws//modules/iam-policy" - version = "~> 4" + cluster_service_accounts = { + "cluster1" = ["default:my-app"] + "cluster2" = [ + "default:my-app", + "canary:my-app", + ] + } - name = "example" - path = "/" - description = "My example policy" + tags = { + Name = "eks-role" + } - policy = < Date: Wed, 16 Feb 2022 09:08:43 -0500 Subject: [PATCH 12/12] feat: one less input attribute that is required/duplicated --- README.md | 3 +- .../README.md | 1 + .../iam-role-for-service-accounts-eks/main.tf | 13 ++-- .../README.md | 72 +++++++++---------- .../iam-role-for-service-accounts-eks/main.tf | 2 +- 5 files changed, 39 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 5bd08c7c..7ac6bc16 100644 --- a/README.md +++ b/README.md @@ -266,8 +266,7 @@ module "vpc_cni_irsa" { vpc_cni_enable_ipv4 = true oidc_providers = { - ex = { - provider = "oidc.eks.us-east-1.amazonaws.com/id/5C54DDF35ER19312844C7333374CC09D" + main = { provider_arn = "arn:aws:iam::012345678901:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/5C54DDF35ER19312844C7333374CC09D" namespace_service_accounts = ["default:my-app", "canary:my-app"] } diff --git a/examples/iam-role-for-service-accounts-eks/README.md b/examples/iam-role-for-service-accounts-eks/README.md index 201dda33..ca44f7d1 100644 --- a/examples/iam-role-for-service-accounts-eks/README.md +++ b/examples/iam-role-for-service-accounts-eks/README.md @@ -31,6 +31,7 @@ No providers. | Name | Source | Version | |------|--------|---------| | [cluster\_autoscaler\_irsa\_role](#module\_cluster\_autoscaler\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | +| [disabled](#module\_disabled) | ../../modules/iam-role-for-service-accounts-eks | n/a | | [ebs\_csi\_irsa\_role](#module\_ebs\_csi\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | | [eks](#module\_eks) | terraform-aws-modules/eks/aws | ~> 18.6 | | [external\_dns\_irsa\_role](#module\_external\_dns\_irsa\_role) | ../../modules/iam-role-for-service-accounts-eks | n/a | diff --git a/examples/iam-role-for-service-accounts-eks/main.tf b/examples/iam-role-for-service-accounts-eks/main.tf index febb6203..3cb20de9 100644 --- a/examples/iam-role-for-service-accounts-eks/main.tf +++ b/examples/iam-role-for-service-accounts-eks/main.tf @@ -31,12 +31,10 @@ module "irsa_role" { oidc_providers = { one = { - provider = module.eks.oidc_provider provider_arn = module.eks.oidc_provider_arn namespace_service_accounts = ["default:my-app", "canary:my-app"] } two = { - provider = module.eks.oidc_provider provider_arn = module.eks.oidc_provider_arn namespace_service_accounts = ["default:blue", "canary:blue"] } @@ -58,7 +56,6 @@ module "cluster_autoscaler_irsa_role" { oidc_providers = { ex = { - provider = module.eks.oidc_provider provider_arn = module.eks.oidc_provider_arn namespace_service_accounts = ["default:my-app", "canary:my-app"] } @@ -76,7 +73,6 @@ module "external_dns_irsa_role" { oidc_providers = { ex = { - provider = module.eks.oidc_provider provider_arn = module.eks.oidc_provider_arn namespace_service_accounts = ["default:my-app", "canary:my-app"] } @@ -93,7 +89,6 @@ module "ebs_csi_irsa_role" { oidc_providers = { ex = { - provider = module.eks.oidc_provider provider_arn = module.eks.oidc_provider_arn namespace_service_accounts = ["default:my-app", "canary:my-app"] } @@ -111,7 +106,6 @@ module "vpc_cni_ipv4_irsa_role" { oidc_providers = { ex = { - provider = module.eks.oidc_provider provider_arn = module.eks.oidc_provider_arn namespace_service_accounts = ["default:my-app", "canary:my-app"] } @@ -129,7 +123,6 @@ module "vpc_cni_ipv6_irsa_role" { oidc_providers = { ex = { - provider = module.eks.oidc_provider provider_arn = module.eks.oidc_provider_arn namespace_service_accounts = ["default:my-app", "canary:my-app"] } @@ -146,7 +139,6 @@ module "node_termination_handler_irsa_role" { oidc_providers = { ex = { - provider = module.eks.oidc_provider provider_arn = module.eks.oidc_provider_arn namespace_service_accounts = ["default:my-app", "canary:my-app"] } @@ -166,7 +158,6 @@ module "karpenter_controller_irsa_role" { oidc_providers = { ex = { - provider = module.eks.oidc_provider provider_arn = module.eks.oidc_provider_arn namespace_service_accounts = ["default:my-app", "canary:my-app"] } @@ -190,6 +181,10 @@ module "vpc" { private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] public_subnets = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"] + enable_nat_gateway = true + single_nat_gateway = true + enable_dns_hostnames = true + public_subnet_tags = { "kubernetes.io/cluster/${local.name}" = "shared" "kubernetes.io/role/elb" = 1 diff --git a/modules/iam-role-for-service-accounts-eks/README.md b/modules/iam-role-for-service-accounts-eks/README.md index dd9945aa..4f8816eb 100644 --- a/modules/iam-role-for-service-accounts-eks/README.md +++ b/modules/iam-role-for-service-accounts-eks/README.md @@ -1,51 +1,22 @@ # IAM Role for Service Accounts in EKS -Creates an IAM role which can be assumed by AWS EKS `ServiceAccount`s. +Creates an IAM role which can be assumed by AWS EKS `ServiceAccount`s with optional policies for commonly used controllers/custom resources within EKS. This module is intended to be used 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): +This module supports multiple `ServiceAccount`s across 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). 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 a cluster in `us-east-1`; and also the `ServiceAccount` name `my-app-staging` in the namespace `default` in a cluster in `ap-southeast-1`, the configuration would be: ```hcl module "iam_eks_role" { - source = "terraform-aws-modules/iam/aws//modules/iam-eks-role" - - oidc_providers = { - one = { - provider = "" - provider_arn = "" - service_accounts = [ - ":", - ":" - ] - } - two = { - provider = "" - provider_arn = "" - service_accounts = [ - ":", - ":" - ] - } - } -} -``` - -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" + source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks" role_name = "my-app" oidc_providers = { one = { - provider = "oidc.eks.us-east-1.amazonaws.com/id/5C54DDF35ER19312844C7333374CC09D" provider_arn = "arn:aws:iam::012345678901:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/5C54DDF35ER19312844C7333374CC09D" service_accounts = ["default:my-app-staging", "canary:my-app-staging"] } two = { - provider = "oidc.eks.ap-southeast-1.amazonaws.com/id/5C54DDF35ER54476848E7333374FF09G" provider_arn = "arn:aws:iam::012345678901:oidc-provider/oidc.eks.ap-southeast-1.amazonaws.com/id/5C54DDF35ER54476848E7333374FF09G" service_accounts = ["default:my-app-staging"] } @@ -56,21 +27,38 @@ module "iam_eks_role" { This module has been designed in conjunction with the [`terraform-aws-eks`](https://github.com/terraform-aws-modules/terraform-aws-eks) module to easily integrate with it: ```hcl -module "iam_eks_role" { - source = "terraform-aws-modules/iam/aws//modules/iam-eks-role" +module "vpc_cni_irsa_role" { + source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks" - role_name = "my-app" + role_name = "vpc-cni" + + attach_vpc_cni_policy = true + vpc_cni_enable_ipv4 = true oidc_providers = { - one = { - provider = module.eks.oidc_provider + main = { provider_arn = module.eks.oidc_provider_arn service_accounts = ["default:my-app", "canary:my-app"] } - two = { - provider = module.eks.oidc_provider + } +} + +module "karpenter_irsa_role" { + source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks" + + role_name = "karpenter_controller" + attach_karpenter_controller_policy = true + + karpenter_controller_cluster_ids = [module.eks.cluster_id] + karpenter_controller_node_iam_role_arns = [module.eks.eks_managed_node_groups["default"].iam_role_arn] + + attach_vpc_cni_policy = true + vpc_cni_enable_ipv4 = true + + oidc_providers = { + main = { provider_arn = module.eks.oidc_provider_arn - service_accounts = ["default:blue", "canary:blue"] + service_accounts = ["default:my-app", "canary:my-app"] } } } @@ -84,6 +72,10 @@ module "eks" { vpc_id = module.vpc.vpc_id subnet_ids = module.vpc.private_subnets + + eks_managed_node_groups = { + default = {} + } } ``` diff --git a/modules/iam-role-for-service-accounts-eks/main.tf b/modules/iam-role-for-service-accounts-eks/main.tf index a16f2cef..f0c339b6 100644 --- a/modules/iam-role-for-service-accounts-eks/main.tf +++ b/modules/iam-role-for-service-accounts-eks/main.tf @@ -15,7 +15,7 @@ data "aws_iam_policy_document" "this" { condition { test = "StringEquals" - variable = "${statement.value.provider}:sub" + variable = "${replace(statement.value.provider_arn, "/^(.*provider/)/", "")}:sub" values = [for sa in statement.value.namespace_service_accounts : "system:serviceaccount:${sa}"] } }