feat!: Replace the use of aws-auth configmap with EKS cluster access entry (#2858)

* feat: Replace `resolve_conflicts` with `resolve_conflicts_on_create`/`delete`; raise MSV of AWS provider to `v5.0` to support

* fix: Replace dynamic DNS suffix for `sts:AssumeRole` API calls for static suffix

* feat: Add module tag

* feat: Align Karpenter permissions with Karpenter v1beta1/v0.32 permissions from upstream

* refactor: Move `aws-auth` ConfigMap functionality to its own sub-module

* chore: Update examples

* feat: Add state `moved` block for Karpenter Pod Identity role re-name

* fix: Correct variable `create` description

* feat: Add support for cluster access entries

* chore: Bump MSV of Terraform to `1.3`

* fix: Replace defunct kubectl provider with an updated forked equivalent

* chore: Update and validate examples for access entry; clean up provider usage

* docs: Correct double redundant variable descriptions

* feat: Add support for Cloudwatch log group class argument

* fix: Update usage tag placement, fix Karpenter event spelling, add upcoming changes section to upgrade guide

* feat: Update Karpenter module to generalize naming used and align policy with the upstream Karpenter policy

* feat: Add native support for Windows based managed nodegroups similar to AL2 and Bottlerocket

* feat: Update self-managed nodegroup module to use latest features of ASG

* docs: Update and simplify docs

* fix: Correct variable description for AMI types

* fix: Update upgrade guide with changes; rename Karpenter controller resource names to support migrating for users

* docs: Complete upgrade guide docs for migration and changes applied

* Update examples/karpenter/README.md

Co-authored-by: Anton Babenko <anton@antonbabenko.com>

* Update examples/outposts/README.md

Co-authored-by: Anton Babenko <anton@antonbabenko.com>

* Update modules/karpenter/README.md

Co-authored-by: Anton Babenko <anton@antonbabenko.com>

---------

Co-authored-by: Anton Babenko <anton@antonbabenko.com>
This commit is contained in:
Bryant Biggs
2024-02-02 09:36:25 -05:00
committed by GitHub
parent 2cb1fac31b
commit 6b40bdbb1d
71 changed files with 1809 additions and 2136 deletions

254
main.tf
View File

@@ -12,6 +12,8 @@ data "aws_iam_session_context" "current" {
locals {
create = var.create && var.putin_khuylo
partition = data.aws_partition.current.partition
cluster_role = try(aws_iam_role.this[0].arn, var.iam_role_arn)
create_outposts_local_cluster = length(var.outpost_config) > 0
@@ -30,6 +32,17 @@ resource "aws_eks_cluster" "this" {
version = var.cluster_version
enabled_cluster_log_types = var.cluster_enabled_log_types
access_config {
authentication_mode = var.authentication_mode
# See access entries below - this is a one time operation from the EKS API.
# Instead, we are hardcoding this to false and if users wish to achieve this
# same functionality, we will do that through an access entry which can be
# enabled or disabled at any time of their choosing using the variable
# var.enable_cluster_creator_admin_permissions
bootstrap_cluster_creator_admin_permissions = false
}
vpc_config {
security_group_ids = compact(distinct(concat(var.cluster_additional_security_group_ids, [local.cluster_security_group_id])))
subnet_ids = coalescelist(var.control_plane_subnet_ids, var.subnet_ids)
@@ -71,14 +84,15 @@ resource "aws_eks_cluster" "this" {
}
tags = merge(
{ terraform-aws-modules = "eks" },
var.tags,
var.cluster_tags,
)
timeouts {
create = lookup(var.cluster_timeouts, "create", null)
update = lookup(var.cluster_timeouts, "update", null)
delete = lookup(var.cluster_timeouts, "delete", null)
create = try(var.cluster_timeouts.create, null)
update = try(var.cluster_timeouts.update, null)
delete = try(var.cluster_timeouts.delete, null)
}
depends_on = [
@@ -109,6 +123,7 @@ resource "aws_cloudwatch_log_group" "this" {
name = "/aws/eks/${var.cluster_name}/cluster"
retention_in_days = var.cloudwatch_log_group_retention_in_days
kms_key_id = var.cloudwatch_log_group_kms_key_id
log_group_class = var.cloudwatch_log_group_class
tags = merge(
var.tags,
@@ -117,6 +132,92 @@ resource "aws_cloudwatch_log_group" "this" {
)
}
################################################################################
# Access Entry
################################################################################
locals {
# This replaces the one time logic from the EKS API with something that can be
# better controlled by users through Terraform
bootstrap_cluster_creator_admin_permissions = {
cluster_creator = {
principal_arn = data.aws_iam_session_context.current.issuer_arn
type = "STANDARD"
policy_associations = {
admin = {
policy_arn = "arn:${local.partition}:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy"
access_scope = {
type = "cluster"
}
}
}
}
}
# Merge the bootstrap behavior with the entries that users provide
merged_access_entries = merge(
{ for k, v in local.bootstrap_cluster_creator_admin_permissions : k => v if var.enable_cluster_creator_admin_permissions },
var.access_entries,
)
# Flatten out entries and policy associations so users can specify the policy
# associations within a single entry
flattened_access_entries = flatten([
for entry_key, entry_val in local.merged_access_entries : [
for pol_key, pol_val in lookup(entry_val, "policy_associations", {}) :
merge(
{
principal_arn = entry_val.principal_arn
kubernetes_groups = lookup(entry_val, "kubernetes_groups", [])
tags = lookup(entry_val, "tags", {})
type = lookup(entry_val, "type", "STANDARD")
user_name = lookup(entry_val, "user_name", null)
},
{ for k, v in {
association_policy_arn = pol_val.policy_arn
association_access_scope_type = pol_val.access_scope.type
association_access_scope_namespaces = lookup(pol_val.access_scope, "namespaces", [])
} : k => v if !contains(["EC2_LINUX", "EC2_WINDOWS", "FARGATE_LINUX"], lookup(entry_val, "type", "STANDARD")) },
{
entry_key = entry_key
pol_key = pol_key
}
)
]
])
}
resource "aws_eks_access_entry" "this" {
for_each = { for k, v in local.flattened_access_entries : "${v.entry_key}_${v.pol_key}" => v if local.create }
cluster_name = aws_eks_cluster.this[0].name
kubernetes_groups = try(each.value.kubernetes_groups, [])
principal_arn = each.value.principal_arn
type = try(each.value.type, "STANDARD")
user_name = try(each.value.user_name, null)
tags = merge(var.tags, try(each.value.tags, {}))
}
resource "aws_eks_access_policy_association" "this" {
for_each = { for k, v in local.flattened_access_entries : "${v.entry_key}_${v.pol_key}" => v if local.create }
access_scope {
namespaces = try(each.value.association_access_scope_namespaces, [])
type = each.value.association_access_scope_type
}
cluster_name = aws_eks_cluster.this[0].name
policy_arn = each.value.association_policy_arn
principal_arn = each.value.principal_arn
depends_on = [
aws_eks_access_entry.this,
]
}
################################################################################
# KMS Key
################################################################################
@@ -148,7 +249,10 @@ module "kms" {
cluster = { name = "eks/${var.cluster_name}" }
}
tags = var.tags
tags = merge(
{ terraform-aws-modules = "eks" },
var.tags,
)
}
################################################################################
@@ -239,7 +343,7 @@ resource "aws_iam_openid_connect_provider" "oidc_provider" {
# Not available on outposts
count = local.create_oidc_provider ? 1 : 0
client_id_list = distinct(compact(concat(["sts.${local.dns_suffix}"], var.openid_connect_audiences)))
client_id_list = distinct(compact(concat(["sts.amazonaws.com"], var.openid_connect_audiences)))
thumbprint_list = concat(local.oidc_root_ca_thumbprint, var.custom_oidc_thumbprints)
url = aws_eks_cluster.this[0].identity[0].oidc[0].issuer
@@ -256,13 +360,9 @@ resource "aws_iam_openid_connect_provider" "oidc_provider" {
locals {
create_iam_role = local.create && var.create_iam_role
iam_role_name = coalesce(var.iam_role_name, "${var.cluster_name}-cluster")
iam_role_policy_prefix = "arn:${data.aws_partition.current.partition}:iam::aws:policy"
iam_role_policy_prefix = "arn:${local.partition}:iam::aws:policy"
cluster_encryption_policy_name = coalesce(var.cluster_encryption_policy_name, "${local.iam_role_name}-ClusterEncryption")
# TODO - hopefully this can be removed once the AWS endpoint is named properly in China
# https://github.com/terraform-aws-modules/terraform-aws-eks/issues/1904
dns_suffix = coalesce(var.cluster_iam_role_dns_suffix, data.aws_partition.current.dns_suffix)
}
data "aws_iam_policy_document" "assume_role_policy" {
@@ -274,7 +374,7 @@ data "aws_iam_policy_document" "assume_role_policy" {
principals {
type = "Service"
identifiers = ["eks.${local.dns_suffix}"]
identifiers = ["eks.amazonaws.com"]
}
dynamic "principals" {
@@ -283,7 +383,7 @@ data "aws_iam_policy_document" "assume_role_policy" {
content {
type = "Service"
identifiers = [
"ec2.${local.dns_suffix}",
"ec2.amazonaws.com",
]
}
}
@@ -394,11 +494,12 @@ resource "aws_eks_addon" "this" {
cluster_name = aws_eks_cluster.this[0].name
addon_name = try(each.value.name, each.key)
addon_version = coalesce(try(each.value.addon_version, null), data.aws_eks_addon_version.this[each.key].version)
configuration_values = try(each.value.configuration_values, null)
preserve = try(each.value.preserve, null)
resolve_conflicts = try(each.value.resolve_conflicts, "OVERWRITE")
service_account_role_arn = try(each.value.service_account_role_arn, null)
addon_version = coalesce(try(each.value.addon_version, null), data.aws_eks_addon_version.this[each.key].version)
configuration_values = try(each.value.configuration_values, null)
preserve = try(each.value.preserve, true)
resolve_conflicts_on_create = try(each.value.resolve_conflicts_on_create, "OVERWRITE")
resolve_conflicts_on_update = try(each.value.resolve_conflicts_on_update, "OVERWRITE")
service_account_role_arn = try(each.value.service_account_role_arn, null)
timeouts {
create = try(each.value.timeouts.create, var.cluster_addons_timeouts.create, null)
@@ -422,11 +523,12 @@ resource "aws_eks_addon" "before_compute" {
cluster_name = aws_eks_cluster.this[0].name
addon_name = try(each.value.name, each.key)
addon_version = coalesce(try(each.value.addon_version, null), data.aws_eks_addon_version.this[each.key].version)
configuration_values = try(each.value.configuration_values, null)
preserve = try(each.value.preserve, null)
resolve_conflicts = try(each.value.resolve_conflicts, "OVERWRITE")
service_account_role_arn = try(each.value.service_account_role_arn, null)
addon_version = coalesce(try(each.value.addon_version, null), data.aws_eks_addon_version.this[each.key].version)
configuration_values = try(each.value.configuration_values, null)
preserve = try(each.value.preserve, true)
resolve_conflicts_on_create = try(each.value.resolve_conflicts_on_create, "OVERWRITE")
resolve_conflicts_on_update = try(each.value.resolve_conflicts_on_update, "OVERWRITE")
service_account_role_arn = try(each.value.service_account_role_arn, null)
timeouts {
create = try(each.value.timeouts.create, var.cluster_addons_timeouts.create, null)
@@ -468,111 +570,3 @@ resource "aws_eks_identity_provider_config" "this" {
tags = var.tags
}
################################################################################
# aws-auth configmap
################################################################################
locals {
node_iam_role_arns_non_windows = distinct(
compact(
concat(
[for group in module.eks_managed_node_group : group.iam_role_arn if group.platform != "windows"],
[for group in module.self_managed_node_group : group.iam_role_arn if group.platform != "windows"],
var.aws_auth_node_iam_role_arns_non_windows,
)
)
)
node_iam_role_arns_windows = distinct(
compact(
concat(
[for group in module.eks_managed_node_group : group.iam_role_arn if group.platform == "windows"],
[for group in module.self_managed_node_group : group.iam_role_arn if group.platform == "windows"],
var.aws_auth_node_iam_role_arns_windows,
)
)
)
fargate_profile_pod_execution_role_arns = distinct(
compact(
concat(
[for group in module.fargate_profile : group.fargate_profile_pod_execution_role_arn],
var.aws_auth_fargate_profile_pod_execution_role_arns,
)
)
)
aws_auth_configmap_data = {
mapRoles = yamlencode(concat(
[for role_arn in local.node_iam_role_arns_non_windows : {
rolearn = role_arn
username = "system:node:{{EC2PrivateDNSName}}"
groups = [
"system:bootstrappers",
"system:nodes",
]
}
],
[for role_arn in local.node_iam_role_arns_windows : {
rolearn = role_arn
username = "system:node:{{EC2PrivateDNSName}}"
groups = [
"eks:kube-proxy-windows",
"system:bootstrappers",
"system:nodes",
]
}
],
# Fargate profile
[for role_arn in local.fargate_profile_pod_execution_role_arns : {
rolearn = role_arn
username = "system:node:{{SessionName}}"
groups = [
"system:bootstrappers",
"system:nodes",
"system:node-proxier",
]
}
],
var.aws_auth_roles
))
mapUsers = yamlencode(var.aws_auth_users)
mapAccounts = yamlencode(var.aws_auth_accounts)
}
}
resource "kubernetes_config_map" "aws_auth" {
count = var.create && var.create_aws_auth_configmap ? 1 : 0
metadata {
name = "aws-auth"
namespace = "kube-system"
}
data = local.aws_auth_configmap_data
lifecycle {
# We are ignoring the data here since we will manage it with the resource below
# This is only intended to be used in scenarios where the configmap does not exist
ignore_changes = [data, metadata[0].labels, metadata[0].annotations]
}
}
resource "kubernetes_config_map_v1_data" "aws_auth" {
count = var.create && var.manage_aws_auth_configmap ? 1 : 0
force = true
metadata {
name = "aws-auth"
namespace = "kube-system"
}
data = local.aws_auth_configmap_data
depends_on = [
# Required for instances where the configmap does not exist yet to avoid race condition
kubernetes_config_map.aws_auth,
]
}