From 990050b224f92c1470f1fa2014bf5bb3b6b2f021 Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Tue, 13 Jan 2026 07:14:00 -0600 Subject: [PATCH] feat: Add support for EKS Capabilities (#3624) --- README.md | 1 + examples/eks-auto-mode/README.md | 2 +- examples/eks-capabilities/README.md | 74 ++++++++ examples/eks-capabilities/main.tf | 163 ++++++++++++++++ examples/eks-capabilities/outputs.tf | 104 ++++++++++ examples/eks-capabilities/variables.tf | 0 examples/eks-capabilities/versions.tf | 10 + modules/capability/README.md | 174 +++++++++++++++++ modules/capability/main.tf | 222 ++++++++++++++++++++++ modules/capability/outputs.tf | 37 ++++ modules/capability/variables.tf | 215 +++++++++++++++++++++ modules/capability/versions.tf | 20 ++ modules/eks-managed-node-group/README.md | 4 + modules/fargate-profile/README.md | 4 + modules/hybrid-node-role/README.md | 4 + modules/karpenter/README.md | 6 + modules/self-managed-node-group/README.md | 4 + 17 files changed, 1043 insertions(+), 1 deletion(-) create mode 100644 examples/eks-capabilities/README.md create mode 100644 examples/eks-capabilities/main.tf create mode 100644 examples/eks-capabilities/outputs.tf create mode 100644 examples/eks-capabilities/variables.tf create mode 100644 examples/eks-capabilities/versions.tf create mode 100644 modules/capability/README.md create mode 100644 modules/capability/main.tf create mode 100644 modules/capability/outputs.tf create mode 100644 modules/capability/variables.tf create mode 100644 modules/capability/versions.tf diff --git a/README.md b/README.md index e10940a..368326f 100644 --- a/README.md +++ b/README.md @@ -376,6 +376,7 @@ module "eks" { ## Examples - [EKS Auto Mode](https://github.com/terraform-aws-modules/terraform-aws-eks/tree/master/examples/eks-auto-mode): EKS Cluster with EKS Auto Mode +- [EKS Capabilities](https://github.com/terraform-aws-modules/terraform-aws-eks/tree/master/examples/eks-capabilities): EKS Cluster with EKS Capabilities - [EKS Hybrid Nodes](https://github.com/terraform-aws-modules/terraform-aws-eks/tree/master/examples/eks-hybrid-nodes): EKS Cluster with EKS Hybrid nodes - [EKS Managed Node Group](https://github.com/terraform-aws-modules/terraform-aws-eks/tree/master/examples/eks-managed-node-group): EKS Cluster with EKS managed node group(s) - [Karpenter](https://github.com/terraform-aws-modules/terraform-aws-eks/tree/master/examples/karpenter): EKS Cluster with [Karpenter](https://karpenter.sh/) provisioned for intelligent data plane management diff --git a/examples/eks-auto-mode/README.md b/examples/eks-auto-mode/README.md index 0b338d1..d4c54a4 100644 --- a/examples/eks-auto-mode/README.md +++ b/examples/eks-auto-mode/README.md @@ -1,4 +1,4 @@ -# EKS Auto Mode +# EKS Auto Mode Example ## Usage diff --git a/examples/eks-capabilities/README.md b/examples/eks-capabilities/README.md new file mode 100644 index 0000000..d15d1c1 --- /dev/null +++ b/examples/eks-capabilities/README.md @@ -0,0 +1,74 @@ +# EKS Capabilities Example + +## Usage + +To provision the provided configurations you need to execute: + +```bash +terraform init +terraform plan +terraform apply --auto-approve +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.28 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 6.28 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [ack\_eks\_capability](#module\_ack\_eks\_capability) | ../../modules/capability | n/a | +| [argocd\_eks\_capability](#module\_argocd\_eks\_capability) | ../../modules/capability | n/a | +| [disabled\_eks\_capability](#module\_disabled\_eks\_capability) | ../../modules/capability | n/a | +| [eks](#module\_eks) | ../.. | n/a | +| [kro\_eks\_capability](#module\_kro\_eks\_capability) | ../../modules/capability | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 6.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | +| [aws_identitystore_group.aws_administrator](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/identitystore_group) | data source | +| [aws_ssoadmin_instances.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssoadmin_instances) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [ack\_argocd\_server\_url](#output\_ack\_argocd\_server\_url) | URL of the Argo CD server | +| [ack\_arn](#output\_ack\_arn) | The ARN of the EKS Capability | +| [ack\_iam\_role\_arn](#output\_ack\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [ack\_iam\_role\_name](#output\_ack\_iam\_role\_name) | The name of the IAM role | +| [ack\_iam\_role\_unique\_id](#output\_ack\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [ack\_version](#output\_ack\_version) | The version of the EKS Capability | +| [argocd\_arn](#output\_argocd\_arn) | The ARN of the EKS Capability | +| [argocd\_iam\_role\_arn](#output\_argocd\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [argocd\_iam\_role\_name](#output\_argocd\_iam\_role\_name) | The name of the IAM role | +| [argocd\_iam\_role\_unique\_id](#output\_argocd\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [argocd\_server\_url](#output\_argocd\_server\_url) | URL of the Argo CD server | +| [argocd\_version](#output\_argocd\_version) | The version of the EKS Capability | +| [kro\_argocd\_server\_url](#output\_kro\_argocd\_server\_url) | URL of the Argo CD server | +| [kro\_arn](#output\_kro\_arn) | The ARN of the EKS Capability | +| [kro\_iam\_role\_arn](#output\_kro\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [kro\_iam\_role\_name](#output\_kro\_iam\_role\_name) | The name of the IAM role | +| [kro\_iam\_role\_unique\_id](#output\_kro\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [kro\_version](#output\_kro\_version) | The version of the EKS Capability | + diff --git a/examples/eks-capabilities/main.tf b/examples/eks-capabilities/main.tf new file mode 100644 index 0000000..a220c7f --- /dev/null +++ b/examples/eks-capabilities/main.tf @@ -0,0 +1,163 @@ +provider "aws" { + region = local.region +} + +data "aws_availability_zones" "available" { + # Exclude local zones + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +data "aws_ssoadmin_instances" "this" {} + +data "aws_identitystore_group" "aws_administrator" { + identity_store_id = one(data.aws_ssoadmin_instances.this.identity_store_ids) + + alternate_identifier { + unique_attribute { + attribute_path = "DisplayName" + attribute_value = "AWSAdministrator" + } + } +} + +locals { + name = "ex-${basename(path.cwd)}" + region = "us-east-1" # will need to match where your AWS Identity Center is configured + + vpc_cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 3) + + tags = { + Test = local.name + GithubRepo = "terraform-aws-eks" + GithubOrg = "terraform-aws-modules" + } +} + +################################################################################ +# EKS Capability Module +################################################################################ + +module "ack_eks_capability" { + source = "../../modules/capability" + + type = "ACK" + cluster_name = module.eks.cluster_name + + # IAM Role/Policy + iam_role_policies = { + AdministratorAccess = "arn:aws:iam::aws:policy/AdministratorAccess" + } + + tags = local.tags +} + +module "argocd_eks_capability" { + source = "../../modules/capability" + + type = "ARGOCD" + cluster_name = module.eks.cluster_name + + configuration = { + argo_cd = { + aws_idc = { + idc_instance_arn = one(data.aws_ssoadmin_instances.this.arns) + } + namespace = "argocd" + rbac_role_mapping = [{ + role = "ADMIN" + identity = [{ + id = data.aws_identitystore_group.aws_administrator.group_id + type = "SSO_GROUP" + }] + }] + } + } + + # IAM Role/Policy + iam_policy_statements = { + ECRRead = { + actions = [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + ] + resources = ["*"] + } + } + + tags = local.tags +} + +module "kro_eks_capability" { + source = "../../modules/capability" + + type = "KRO" + cluster_name = module.eks.cluster_name + + tags = local.tags +} + +module "disabled_eks_capability" { + source = "../../modules/capability" + + create = false +} + +################################################################################ +# EKS Module +################################################################################ + +module "eks" { + source = "../.." + + name = local.name + kubernetes_version = "1.34" + endpoint_public_access = true + + enable_cluster_creator_admin_permissions = true + + compute_config = { + enabled = true + node_pools = ["general-purpose"] + } + + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets + + tags = local.tags +} + +################################################################################ +# Supporting Resources +################################################################################ + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 6.0" + + name = local.name + cidr = local.vpc_cidr + + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] + intra_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 52)] + + enable_nat_gateway = true + single_nat_gateway = true + + public_subnet_tags = { + "kubernetes.io/role/elb" = 1 + } + + private_subnet_tags = { + "kubernetes.io/role/internal-elb" = 1 + } + + tags = local.tags +} diff --git a/examples/eks-capabilities/outputs.tf b/examples/eks-capabilities/outputs.tf new file mode 100644 index 0000000..3023628 --- /dev/null +++ b/examples/eks-capabilities/outputs.tf @@ -0,0 +1,104 @@ +################################################################################ +# Capability - ACK +################################################################################ + +output "ack_arn" { + description = "The ARN of the EKS Capability" + value = module.ack_eks_capability.arn +} + +output "ack_version" { + description = "The version of the EKS Capability" + value = module.ack_eks_capability.version +} + +output "ack_argocd_server_url" { + description = "URL of the Argo CD server" + value = module.ack_eks_capability.argocd_server_url +} + +# IAM Role +output "ack_iam_role_name" { + description = "The name of the IAM role" + value = module.ack_eks_capability.iam_role_name +} + +output "ack_iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = module.ack_eks_capability.iam_role_arn +} + +output "ack_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = module.ack_eks_capability.iam_role_unique_id +} + +################################################################################ +# Capability - ArgoCD +################################################################################ + +output "argocd_arn" { + description = "The ARN of the EKS Capability" + value = module.argocd_eks_capability.arn +} + +output "argocd_version" { + description = "The version of the EKS Capability" + value = module.argocd_eks_capability.version +} + +output "argocd_server_url" { + description = "URL of the Argo CD server" + value = module.argocd_eks_capability.argocd_server_url +} + +# IAM Role +output "argocd_iam_role_name" { + description = "The name of the IAM role" + value = module.argocd_eks_capability.iam_role_name +} + +output "argocd_iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = module.argocd_eks_capability.iam_role_arn +} + +output "argocd_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = module.argocd_eks_capability.iam_role_unique_id +} + +################################################################################ +# Capability - KRO +################################################################################ + +output "kro_arn" { + description = "The ARN of the EKS Capability" + value = module.kro_eks_capability.arn +} + +output "kro_version" { + description = "The version of the EKS Capability" + value = module.kro_eks_capability.version +} + +output "kro_argocd_server_url" { + description = "URL of the Argo CD server" + value = module.kro_eks_capability.argocd_server_url +} + +# IAM Role +output "kro_iam_role_name" { + description = "The name of the IAM role" + value = module.kro_eks_capability.iam_role_name +} + +output "kro_iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = module.kro_eks_capability.iam_role_arn +} + +output "kro_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = module.kro_eks_capability.iam_role_unique_id +} diff --git a/examples/eks-capabilities/variables.tf b/examples/eks-capabilities/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/examples/eks-capabilities/versions.tf b/examples/eks-capabilities/versions.tf new file mode 100644 index 0000000..d2afd5f --- /dev/null +++ b/examples/eks-capabilities/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.28" + } + } +} diff --git a/modules/capability/README.md b/modules/capability/README.md new file mode 100644 index 0000000..e1ec510 --- /dev/null +++ b/modules/capability/README.md @@ -0,0 +1,174 @@ +# EKS Capability Module + +Configuration in this directory creates the AWS resources required by EKS capabilities + +## Usage + +### ACK + +```hcl +module "ack_eks_capability" { + source = "terraform-aws-modules/eks/aws//modules/capability" + + name = "example-ack" + cluster_name = "example" + type = "ACK" + + # IAM Role/Policy + iam_role_policies = { + AdministratorAccess = "arn:aws:iam::aws:policy/AdministratorAccess" + } + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + +### ArgoCD + +```hcl +module "argocd_eks_capability" { + source = "terraform-aws-modules/eks/aws//modules/capability" + + name = "example-argocd" + cluster_name = "example" + type = "ARGOCD" + + configuration = { + configuration = { + argo_cd = { + aws_idc = { + idc_instance_arn = "arn:aws:sso:::instance/ssoins-1234567890abcdef0" + } + namespace = "argocd" + rbac_role_mapping = [{ + role = "ADMIN" + identity = [{ + id = "686103e0-f051-7068-b225-e6392b959d9e" + type = "SSO_GROUP" + }] + }] + } + } + } + + # IAM Role/Policy + iam_policy_statements = { + ECRRead = { + actions = [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + ] + resources = ["*"] + } + } + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + +### KRO + +```hcl +module "kro_eks_capability" { + source = "terraform-aws-modules/eks/aws//modules/capability" + + name = "example-kro" + cluster_name = "example" + type = "KRO" + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.28 | +| [time](#requirement\_time) | >= 0.9 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 6.28 | +| [time](#provider\_time) | >= 0.9 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_eks_capability.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_capability) | resource | +| [aws_iam_policy.this](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.additional](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 | +| [time_sleep.this](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | +| [aws_iam_policy_document.assume_role](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_service_principal.capabilities_eks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/service_principal) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [cluster\_name](#input\_cluster\_name) | The name of the EKS cluster | `string` | `""` | no | +| [configuration](#input\_configuration) | Configuration for the capability |
object({
argo_cd = optional(object({
aws_idc = object({
idc_instance_arn = string
idc_region = optional(string)
})
namespace = optional(string)
network_access = optional(object({
vpce_ids = optional(list(string))
}))
rbac_role_mapping = optional(list(object({
identity = list(object({
id = string
type = string
}))
role = string
})))
}))
})
| `null` | no | +| [create](#input\_create) | Controls if resources should be created (affects nearly all resources) | `bool` | `true` | no | +| [create\_iam\_role](#input\_create\_iam\_role) | Determines whether an IAM role is created | `bool` | `true` | no | +| [delete\_propagation\_policy](#input\_delete\_propagation\_policy) | The propagation policy to use when deleting the capability. Valid values: `RETAIN` | `string` | `"RETAIN"` | no | +| [iam\_policy\_description](#input\_iam\_policy\_description) | IAM policy description | `string` | `null` | no | +| [iam\_policy\_name](#input\_iam\_policy\_name) | Name of the IAM policy | `string` | `null` | no | +| [iam\_policy\_path](#input\_iam\_policy\_path) | Path of the IAM policy | `string` | `null` | no | +| [iam\_policy\_statements](#input\_iam\_policy\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) - used for adding specific IAM permissions as needed |
map(object({
sid = optional(string)
actions = optional(list(string))
not_actions = optional(list(string))
effect = optional(string)
resources = optional(list(string))
not_resources = optional(list(string))
principals = optional(list(object({
type = string
identifiers = list(string)
})))
not_principals = optional(list(object({
type = string
identifiers = list(string)
})))
condition = optional(list(object({
test = string
values = list(string)
variable = string
})))
}))
| `null` | no | +| [iam\_policy\_use\_name\_prefix](#input\_iam\_policy\_use\_name\_prefix) | Determines whether the name of the IAM policy (`iam_policy_name`) is used as a prefix | `bool` | `true` | no | +| [iam\_role\_arn](#input\_iam\_role\_arn) | The ARN of the IAM role that provides permissions for the capability | `string` | `null` | no | +| [iam\_role\_description](#input\_iam\_role\_description) | IAM role description | `string` | `null` | no | +| [iam\_role\_max\_session\_duration](#input\_iam\_role\_max\_session\_duration) | Maximum API session duration in seconds between 3600 and 43200 | `number` | `null` | no | +| [iam\_role\_name](#input\_iam\_role\_name) | Name of the IAM role | `string` | `null` | no | +| [iam\_role\_override\_assume\_policy\_documents](#input\_iam\_role\_override\_assume\_policy\_documents) | A list of IAM policy documents to override the default assume role policy document for the Karpenter controller IAM role | `list(string)` | `[]` | no | +| [iam\_role\_path](#input\_iam\_role\_path) | Path of the IAM role | `string` | `null` | no | +| [iam\_role\_permissions\_boundary\_arn](#input\_iam\_role\_permissions\_boundary\_arn) | Permissions boundary ARN to use for the IAM role | `string` | `null` | no | +| [iam\_role\_policies](#input\_iam\_role\_policies) | Policies to attach to the IAM role in `{'static_name' = 'policy_arn'}` format | `map(string)` | `{}` | no | +| [iam\_role\_source\_assume\_policy\_documents](#input\_iam\_role\_source\_assume\_policy\_documents) | A list of IAM policy documents to use as a source for the assume role policy document for the Karpenter controller IAM role | `list(string)` | `[]` | no | +| [iam\_role\_tags](#input\_iam\_role\_tags) | A map of additional tags to add the the IAM role | `map(string)` | `{}` | no | +| [iam\_role\_use\_name\_prefix](#input\_iam\_role\_use\_name\_prefix) | Determines whether the name of the IAM role (`iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [name](#input\_name) | The name of the capability to add to the cluster | `string` | `""` | no | +| [region](#input\_region) | Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration | `string` | `null` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [timeouts](#input\_timeouts) | Create, update, and delete timeout configurations for the capability |
object({
create = optional(string)
update = optional(string)
delete = optional(string)
})
| `null` | no | +| [type](#input\_type) | Type of the capability. Valid values: `ACK`, `KRO`, `ARGOCD` | `string` | `""` | no | +| [wait\_duration](#input\_wait\_duration) | Duration to wait between creating the IAM role/policy and creating the capability | `string` | `"20s"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [argocd\_server\_url](#output\_argocd\_server\_url) | URL of the Argo CD server | +| [arn](#output\_arn) | The ARN of the EKS Capability | +| [iam\_role\_arn](#output\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [iam\_role\_name](#output\_iam\_role\_name) | The name of the IAM role | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [version](#output\_version) | The version of the EKS Capability | + + +## License + +Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-eks/tree/master/LICENSE) for full details. diff --git a/modules/capability/main.tf b/modules/capability/main.tf new file mode 100644 index 0000000..51c7bd6 --- /dev/null +++ b/modules/capability/main.tf @@ -0,0 +1,222 @@ +data "aws_service_principal" "capabilities_eks" { + count = var.create ? 1 : 0 + + service_name = "capabilities.eks" +} + +# It appears that the EKS capability API checks for the IAM role trust policy *VERY* early in the process +# Our standard approach to ordering IAM role/permission dependencies does not work here, so we add an explicit wait +resource "time_sleep" "this" { + count = var.create ? 1 : 0 + + create_duration = var.wait_duration + + triggers = { + iam_role_arn = local.create_iam_role ? aws_iam_role.this[0].arn : var.iam_role_arn + } +} + +################################################################################ +# Capability +################################################################################ + +resource "aws_eks_capability" "this" { + count = var.create ? 1 : 0 + + region = var.region + + capability_name = try(coalesce(var.name, lower(var.type))) + cluster_name = var.cluster_name + + dynamic "configuration" { + for_each = var.configuration != null ? [var.configuration] : [] + + content { + dynamic "argo_cd" { + for_each = configuration.value.argo_cd != null ? [configuration.value.argo_cd] : [] + + content { + dynamic "aws_idc" { + for_each = [argo_cd.value.aws_idc] + + content { + idc_instance_arn = aws_idc.value.idc_instance_arn + idc_region = aws_idc.value.idc_region + } + } + + namespace = argo_cd.value.namespace + + dynamic "network_access" { + for_each = argo_cd.value.network_access != null ? [argo_cd.value.network_access] : [] + + content { + vpce_ids = network_access.value.vpce_ids + } + } + + dynamic "rbac_role_mapping" { + for_each = argo_cd.value.rbac_role_mapping != null ? argo_cd.value.rbac_role_mapping : [] + + content { + dynamic "identity" { + for_each = rbac_role_mapping.value.identity + + content { + id = identity.value.id + type = identity.value.type + } + } + + role = rbac_role_mapping.value.role + } + } + } + } + } + } + + delete_propagation_policy = var.delete_propagation_policy + role_arn = time_sleep.this[0].triggers["iam_role_arn"] + type = var.type + + dynamic "timeouts" { + for_each = var.timeouts != null ? [var.timeouts] : [] + + content { + create = timeouts.value.create + delete = timeouts.value.delete + update = timeouts.value.update + } + } + + tags = var.tags + + depends_on = [ + aws_iam_role_policy_attachment.this, + aws_iam_role_policy_attachment.additional, + ] +} + +################################################################################ +# IAM Role +################################################################################ + +locals { + create_iam_role = var.create && var.create_iam_role + iam_role_name = try(coalesce(var.iam_role_name, var.name), "") +} + +data "aws_iam_policy_document" "assume_role" { + count = local.create_iam_role ? 1 : 0 + + override_policy_documents = var.iam_role_override_assume_policy_documents + source_policy_documents = var.iam_role_source_assume_policy_documents + + statement { + sid = "EKSCapabilitiesAssumeRole" + actions = [ + "sts:AssumeRole", + "sts:TagSession", + ] + + principals { + type = "Service" + identifiers = [data.aws_service_principal.capabilities_eks[0].name] + } + } +} + +resource "aws_iam_role" "this" { + count = local.create_iam_role ? 1 : 0 + + name = var.iam_role_use_name_prefix ? null : local.iam_role_name + name_prefix = var.iam_role_use_name_prefix ? "${local.iam_role_name}-" : null + path = var.iam_role_path + description = coalesce(var.iam_role_description, "EKS Capability IAM role for ${var.type} capability in cluster ${var.cluster_name}") + + assume_role_policy = data.aws_iam_policy_document.assume_role[0].json + max_session_duration = var.iam_role_max_session_duration + permissions_boundary = var.iam_role_permissions_boundary_arn + force_detach_policies = true + + tags = merge(var.tags, var.iam_role_tags) +} + +################################################################################ +# IAM Role Policy +################################################################################ + +locals { + create_iam_role_policy = local.create_iam_role && var.iam_policy_statements != null + iam_policy_name = try(coalesce(var.iam_policy_name, local.iam_role_name), "") +} + +data "aws_iam_policy_document" "this" { + count = local.create_iam_role_policy ? 1 : 0 + + dynamic "statement" { + for_each = var.iam_policy_statements != null ? var.iam_policy_statements : {} + + content { + sid = try(coalesce(statement.value.sid, statement.key)) + actions = statement.value.actions + not_actions = statement.value.not_actions + effect = statement.value.effect + resources = statement.value.resources + not_resources = statement.value.not_resources + + dynamic "principals" { + for_each = statement.value.principals != null ? statement.value.principals : [] + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = statement.value.not_principals != null ? statement.value.not_principals : [] + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = statement.value.condition != null ? statement.value.condition : [] + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +resource "aws_iam_policy" "this" { + count = local.create_iam_role_policy ? 1 : 0 + + name = var.iam_policy_use_name_prefix ? null : local.iam_policy_name + name_prefix = var.iam_policy_use_name_prefix ? "${local.iam_policy_name}-" : null + path = var.iam_policy_path + description = coalesce(var.iam_policy_description, "IAM policy for EKS Capability ${var.type} capability in cluster ${var.cluster_name}") + policy = data.aws_iam_policy_document.this[0].json +} + +resource "aws_iam_role_policy_attachment" "this" { + count = local.create_iam_role_policy ? 1 : 0 + + role = aws_iam_role.this[0].name + policy_arn = aws_iam_policy.this[0].arn +} + +resource "aws_iam_role_policy_attachment" "additional" { + for_each = { for k, v in var.iam_role_policies : k => v if local.create_iam_role } + + role = aws_iam_role.this[0].name + policy_arn = each.value +} diff --git a/modules/capability/outputs.tf b/modules/capability/outputs.tf new file mode 100644 index 0000000..b812a91 --- /dev/null +++ b/modules/capability/outputs.tf @@ -0,0 +1,37 @@ +################################################################################ +# Capability +################################################################################ + +output "arn" { + description = "The ARN of the EKS Capability" + value = try(aws_eks_capability.this[0].arn, null) +} + +output "version" { + description = "The version of the EKS Capability" + value = try(aws_eks_capability.this[0].version, null) +} + +output "argocd_server_url" { + description = "URL of the Argo CD server" + value = try(aws_eks_capability.this[0].configuration[0].argo_cd[0].server_url, null) +} + +################################################################################ +# IAM Role +################################################################################ + +output "iam_role_name" { + description = "The name of the IAM role" + value = try(aws_iam_role.this[0].name, null) +} + +output "iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = try(aws_iam_role.this[0].arn, null) +} + +output "iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = try(aws_iam_role.this[0].unique_id, null) +} diff --git a/modules/capability/variables.tf b/modules/capability/variables.tf new file mode 100644 index 0000000..4458453 --- /dev/null +++ b/modules/capability/variables.tf @@ -0,0 +1,215 @@ +variable "create" { + description = "Controls if resources should be created (affects nearly all resources)" + type = bool + default = true +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +variable "region" { + description = "Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration" + type = string + default = null +} + +################################################################################ +# Capabilities +################################################################################ + +variable "name" { + description = "The name of the capability to add to the cluster" + type = string + default = "" +} + +variable "cluster_name" { + description = "The name of the EKS cluster" + type = string + default = "" +} + +variable "configuration" { + description = "Configuration for the capability" + type = object({ + argo_cd = optional(object({ + aws_idc = object({ + idc_instance_arn = string + idc_region = optional(string) + }) + namespace = optional(string) + network_access = optional(object({ + vpce_ids = optional(list(string)) + })) + rbac_role_mapping = optional(list(object({ + identity = list(object({ + id = string + type = string + })) + role = string + }))) + })) + }) + default = null +} + +variable "delete_propagation_policy" { + description = "The propagation policy to use when deleting the capability. Valid values: `RETAIN`" + type = string + default = "RETAIN" +} + +variable "type" { + description = "Type of the capability. Valid values: `ACK`, `KRO`, `ARGOCD`" + type = string + default = "" +} + +variable "timeouts" { + description = "Create, update, and delete timeout configurations for the capability" + type = object({ + create = optional(string) + update = optional(string) + delete = optional(string) + }) + default = null +} + +variable "wait_duration" { + description = "Duration to wait between creating the IAM role/policy and creating the capability" + type = string + default = "20s" +} + +################################################################################ +# IAM Role +################################################################################ + +variable "create_iam_role" { + description = "Determines whether an IAM role is created" + type = bool + default = true +} + +variable "iam_role_arn" { + description = "The ARN of the IAM role that provides permissions for the capability" + type = string + default = null +} + +variable "iam_role_name" { + description = "Name of the IAM role" + type = string + default = null +} + +variable "iam_role_use_name_prefix" { + description = "Determines whether the name of the IAM role (`iam_role_name`) is used as a prefix" + type = bool + default = true +} + +variable "iam_role_path" { + description = "Path of the IAM role" + type = string + default = null +} + +variable "iam_role_description" { + description = "IAM role description" + type = string + default = null +} + +variable "iam_role_max_session_duration" { + description = "Maximum API session duration in seconds between 3600 and 43200" + type = number + default = null +} + +variable "iam_role_permissions_boundary_arn" { + description = "Permissions boundary ARN to use for the IAM role" + type = string + default = null +} + +variable "iam_role_tags" { + description = "A map of additional tags to add the the IAM role" + type = map(string) + default = {} +} + +################################################################################ +# IAM Role Policy +################################################################################ + +variable "iam_policy_name" { + description = "Name of the IAM policy" + type = string + default = null +} + +variable "iam_policy_use_name_prefix" { + description = "Determines whether the name of the IAM policy (`iam_policy_name`) is used as a prefix" + type = bool + default = true +} + +variable "iam_policy_path" { + description = "Path of the IAM policy" + type = string + default = null +} + +variable "iam_policy_description" { + description = "IAM policy description" + type = string + default = null +} + +variable "iam_role_override_assume_policy_documents" { + description = "A list of IAM policy documents to override the default assume role policy document for the Karpenter controller IAM role" + type = list(string) + default = [] +} + +variable "iam_role_source_assume_policy_documents" { + description = "A list of IAM policy documents to use as a source for the assume role policy document for the Karpenter controller IAM role" + type = list(string) + default = [] +} + +variable "iam_policy_statements" { + description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) - used for adding specific IAM permissions as needed" + type = map(object({ + sid = optional(string) + actions = optional(list(string)) + not_actions = optional(list(string)) + effect = optional(string) + resources = optional(list(string)) + not_resources = optional(list(string)) + principals = optional(list(object({ + type = string + identifiers = list(string) + }))) + not_principals = optional(list(object({ + type = string + identifiers = list(string) + }))) + condition = optional(list(object({ + test = string + values = list(string) + variable = string + }))) + })) + default = null +} + +variable "iam_role_policies" { + description = "Policies to attach to the IAM role in `{'static_name' = 'policy_arn'}` format" + type = map(string) + default = {} +} diff --git a/modules/capability/versions.tf b/modules/capability/versions.tf new file mode 100644 index 0000000..25ff202 --- /dev/null +++ b/modules/capability/versions.tf @@ -0,0 +1,20 @@ +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.28" + } + time = { + source = "hashicorp/time" + version = ">= 0.9" + } + } + + provider_meta "aws" { + user_agent = [ + "github.com/terraform-aws-modules/terraform-aws-eks" + ] + } +} diff --git a/modules/eks-managed-node-group/README.md b/modules/eks-managed-node-group/README.md index ca0e63e..f3ca2fb 100644 --- a/modules/eks-managed-node-group/README.md +++ b/modules/eks-managed-node-group/README.md @@ -219,3 +219,7 @@ module "eks_managed_node_group" { | [security\_group\_arn](#output\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group | | [security\_group\_id](#output\_security\_group\_id) | ID of the security group | + +## License + +Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-eks/tree/master/LICENSE) for full details. diff --git a/modules/fargate-profile/README.md b/modules/fargate-profile/README.md index 5e0506f..4af5fd3 100644 --- a/modules/fargate-profile/README.md +++ b/modules/fargate-profile/README.md @@ -96,3 +96,7 @@ No modules. | [iam\_role\_name](#output\_iam\_role\_name) | The name of the IAM role | | [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | + +## License + +Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-eks/tree/master/LICENSE) for full details. diff --git a/modules/hybrid-node-role/README.md b/modules/hybrid-node-role/README.md index 4d85f48..874021b 100644 --- a/modules/hybrid-node-role/README.md +++ b/modules/hybrid-node-role/README.md @@ -157,3 +157,7 @@ No modules. | [name](#output\_name) | The name of the node IAM role | | [unique\_id](#output\_unique\_id) | Stable and unique string identifying the node IAM role | + +## License + +Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-eks/tree/master/LICENSE) for full details. diff --git a/modules/karpenter/README.md b/modules/karpenter/README.md index 5b25eaa..69c03af 100644 --- a/modules/karpenter/README.md +++ b/modules/karpenter/README.md @@ -7,6 +7,7 @@ Configuration in this directory creates the AWS resources required by Karpenter ### All Resources (Default) In the following example, the Karpenter module will create: + - An IAM role for use with Pod Identity and a scoped IAM policy for the Karpenter controller - A Pod Identity association to grant Karpenter controller access provided by the IAM Role - A Node IAM role that Karpenter will use to create an Instance Profile for the nodes to receive IAM permissions @@ -40,6 +41,7 @@ module "karpenter" { ### Re-Use Existing Node IAM Role In the following example, the Karpenter module will create: + - An IAM role for use with Pod Identity and a scoped IAM policy for the Karpenter controller - SQS queue and EventBridge event rules for Karpenter to utilize for spot termination handling, capacity re-balancing, etc. @@ -200,3 +202,7 @@ No modules. | [queue\_url](#output\_queue\_url) | The URL for the created Amazon SQS queue | | [service\_account](#output\_service\_account) | Service Account associated with the Karpenter Pod Identity | + +## License + +Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-eks/tree/master/LICENSE) for full details. diff --git a/modules/self-managed-node-group/README.md b/modules/self-managed-node-group/README.md index 21f55a9..fb267b2 100644 --- a/modules/self-managed-node-group/README.md +++ b/modules/self-managed-node-group/README.md @@ -225,3 +225,7 @@ module "self_managed_node_group" { | [security\_group\_id](#output\_security\_group\_id) | ID of the security group | | [user\_data](#output\_user\_data) | Base64 encoded user data | + +## License + +Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-eks/tree/master/LICENSE) for full details.