Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 55 additions & 55 deletions .terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ When `enable_secrets_manager = true`, the module creates an IAM role for **workl

**IAM policy:** Use `secrets_manager_secret_name_prefixes` (e.g. `["bitwarden/sm-operator"]`) for least-privilege access to specific secrets. When empty, attaches `AWSSecretsManagerClientReadOnlyAccess` (broad access).

### EKS Capabilities (ACK, KRO, Argo CD)

When using the `capabilities` variable, see [Security considerations for EKS Capabilities](https://docs.aws.amazon.com/eks/latest/userguide/capabilities-security.html) for IAM least privilege, access entries, and namespace isolation.

EKS automatically creates an access entry for each capability role with default capability policies. For ACK controllers that need to read Kubernetes secrets (e.g. documentdb, rds), associate the managed policy `arn:aws:eks::aws:cluster-access-policy/AmazonEKSSecretReaderPolicy` with the capability's principal—either use the optional `access_entry_policy_associations` on the capability (with the desired `access_scope`, e.g. namespace-scoped) or create an `aws_eks_access_policy_association` elsewhere for the capability role ARN. Apply least privilege for ACK (scope IAM and, if used, access policies) and keep only Argo CD–relevant secrets in the Argo CD namespace (default `argocd`).

## Examples

- **[examples/basic](examples/basic/)** - Basic EKS cluster with EC2 node groups
Expand Down Expand Up @@ -386,8 +392,9 @@ terraform test
terraform-aws-eks-basic/
├── main.tf # Core EKS cluster, node groups, addons, OIDC provider
├── access-entries.tf # EKS access entries for authentication
├── capabilities.tf # EKS Capabilities (ACK, KRO, ArgoCD)
├── capabilities-iam.tf # IAM roles for EKS Capabilities
├── capabilities.tf # EKS Capabilities (ACK, KRO, ArgoCD)
├── capabilities-iam.tf # IAM roles for EKS Capabilities
├── capabilities-access-entries.tf # Optional access entry policy associations (e.g. ACK Secret Reader)
├── addons-iam.tf # IAM roles for addons (EBS CSI, Secrets Manager, etc)
├── locals.tf # Local values and computed configurations
├── cluster-auth.tf # Cluster authentication data source
Expand Down
24 changes: 24 additions & 0 deletions capabilities-access-entries.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
################################################################################
# EKS Capabilities – Additional Access Entry Policy Associations
# EKS auto-creates an access entry for each capability role; this associates
# extra policies (e.g. AmazonEKSSecretReaderPolicy for ACK) with that entry.
################################################################################

resource "aws_eks_access_policy_association" "capability" {
for_each = local.flattened_capability_policy_associations

region = var.region

cluster_name = aws_eks_cluster.this.id
policy_arn = each.value.policy_arn
principal_arn = each.value.principal_arn

access_scope {
type = each.value.scope_type
namespaces = each.value.scope_namespaces
}

depends_on = [
aws_eks_capability.this,
]
}
7 changes: 4 additions & 3 deletions capabilities-iam.tf
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,16 @@ resource "aws_iam_role" "capability" {
tags = var.tags
}

# Attach IAM policies to capability roles (primarily for ACK)
# Attach IAM policies to capability roles (primarily for ACK). Only when role is created by the module.
resource "aws_iam_role_policy_attachment" "capability" {
for_each = merge([
for cap_key, cap_val in var.capabilities : {
for cap_key, cap_val in var.capabilities :
try(cap_val.role_arn, null) == null ? {
for pol_key, pol_arn in try(cap_val.iam_policy_arns, {}) : "${cap_key}_${pol_key}" => {
role = aws_iam_role.capability[cap_key].name
policy_arn = pol_arn
}
}
} : {}
]...)

role = each.value.role
Expand Down
64 changes: 53 additions & 11 deletions capabilities.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
# EKS Capabilities
# Managed ACK, KRO, and ArgoCD capabilities running in AWS-managed infrastructure
################################################################################
# EKS checks the IAM role trust policy very early; wait for IAM to propagate before creating the capability.
resource "time_sleep" "capability" {
for_each = var.capabilities

create_duration = "20s"

triggers = {
iam_role_arn = try(each.value.role_arn, null) != null ? each.value.role_arn : aws_iam_role.capability[each.key].arn
}
}

resource "aws_eks_capability" "this" {
for_each = var.capabilities
Expand All @@ -10,22 +20,54 @@ resource "aws_eks_capability" "this" {
capability_name = upper(each.key)
type = local.capability_types[each.key]

# Use provided role_arn if specified, otherwise use created role
role_arn = try(each.value.role_arn, null) != null ? each.value.role_arn : aws_iam_role.capability[each.key].arn
role_arn = time_sleep.capability[each.key].triggers["iam_role_arn"]

# Set delete_propagation_policy from config (default: "RETAIN")
# Set delete_propagation_policy from config (default: "RETAIN"). AWS currently only supports RETAIN.
delete_propagation_policy = try(each.value.delete_propagation_policy, "RETAIN")

tags = var.tags

# Argo CD configuration (Identity Center, RBAC, namespace, network access)
dynamic "configuration" {
for_each = local.capability_types[each.key] == "ARGOCD" && try(each.value.configuration.argo_cd, null) != null ? [each.value.configuration] : []
content {
dynamic "argo_cd" {
for_each = [configuration.value.argo_cd]
content {
namespace = try(argo_cd.value.namespace, null)
dynamic "aws_idc" {
for_each = try(argo_cd.value.aws_idc, null) != null ? [argo_cd.value.aws_idc] : []
content {
idc_instance_arn = aws_idc.value.idc_instance_arn
idc_region = try(aws_idc.value.idc_region, null)
}
}
dynamic "rbac_role_mapping" {
for_each = coalesce(try(argo_cd.value.rbac_role_mapping, []), [])
content {
role = rbac_role_mapping.value.role
dynamic "identity" {
for_each = rbac_role_mapping.value.identity
content {
type = identity.value.type
id = identity.value.id
}
}
}
}
dynamic "network_access" {
for_each = try(argo_cd.value.network_access, null) != null ? [argo_cd.value.network_access] : []
content {
vpce_ids = try(network_access.value.vpce_ids, null)
}
}
}
}
}
}

depends_on = [
aws_eks_cluster.this
aws_eks_cluster.this,
time_sleep.capability
]

# IAM role dependency is handled conditionally - only when role_arn is not provided
# The role_arn reference will create an implicit dependency

# Note: Configuration parameter for ArgoCD is not directly supported in the resource
# ArgoCD capability configuration must be handled via AWS Identity Center setup
# before enabling the capability
}
Loading