diff --git a/aws-platform-ui-storage/ci_user.tf b/aws-platform-ui-storage/ci_user.tf index 2b042df..8932590 100644 --- a/aws-platform-ui-storage/ci_user.tf +++ b/aws-platform-ui-storage/ci_user.tf @@ -8,7 +8,10 @@ module "luthername_env_admin_role" { } locals { - env_admin_access_principals = concat(var.external_access_principals, [aws_iam_role.ci_role.arn]) + env_admin_access_principals = concat( + var.external_access_principals, + local.create_ci_role ? [aws_iam_role.ci_role[0].arn] : [] + ) } data "aws_iam_policy_document" "env_admin_assume_role" { @@ -38,14 +41,18 @@ resource "aws_iam_role" "env_admin_role" { } output "env_admin_role_name" { - value = try(aws_iam_role.env_admin_role[0].name, null) + value = var.has_env_admin ? aws_iam_role.env_admin_role[0].name : null } output "env_admin_role_arn" { - value = try(aws_iam_role.env_admin_role[0].arn, null) + value = var.has_env_admin ? aws_iam_role.env_admin_role[0].arn : null } resource "aws_iam_openid_connect_provider" "github" { + count = local.has_github ? 1 : 0 + + # TODO: this is globally unique across the account. + # we need to handle multiple deployments in a single account. url = "https://token.actions.githubusercontent.com" client_id_list = [ @@ -56,15 +63,18 @@ resource "aws_iam_openid_connect_provider" "github" { } locals { - # TODO generalize this by creating a new var that is an array of ojects with these values, and construct - # the sub strs from these objects + has_github = var.has_github && length(var.ci_github_repos) > 0 + ci_github_sub_strs = [for sub in var.ci_github_repos : "repo:${sub.org}/${sub.repo}:environment:${sub.env}"] + + create_ci_role = local.has_github || length(var.external_access_principals) > 0 } data "aws_iam_policy_document" "ci_assume_role" { dynamic "statement" { for_each = length(var.external_access_principals) == 0 ? [] : [1] + content { sid = "allowAdminAssumeCIRoleAccess" @@ -78,14 +88,14 @@ data "aws_iam_policy_document" "ci_assume_role" { } dynamic "statement" { - for_each = var.has_github ? [1] : [] + for_each = local.has_github ? [1] : [] content { sid = "allowGitHubOIDCAssumeRole" effect = "Allow" principals { type = "Federated" - identifiers = [aws_iam_openid_connect_provider.github.arn] + identifiers = [aws_iam_openid_connect_provider.github[0].arn] } actions = ["sts:AssumeRoleWithWebIdentity"] @@ -115,6 +125,8 @@ module "luthername_ci_role" { } resource "aws_iam_role" "ci_role" { + count = local.create_ci_role ? 1 : 0 + name = "${module.luthername_ci_role.name}-role" description = "Provides CI level access" assume_role_policy = data.aws_iam_policy_document.ci_assume_role.json @@ -123,11 +135,11 @@ resource "aws_iam_role" "ci_role" { } output "ci_role_name" { - value = aws_iam_role.ci_role.name + value = local.create_ci_role ? aws_iam_role.ci_role[0].name : null } output "ci_role_arn" { - value = aws_iam_role.ci_role.arn + value = local.create_ci_role ? aws_iam_role.ci_role[0].arn : null } data "aws_iam_policy_document" "ecr_push_ci" { @@ -158,7 +170,7 @@ data "aws_iam_policy_document" "ecr_push_ci" { } resource "aws_iam_policy" "ecr_push_ci" { - count = length(var.ci_ecr_push_arns) == 0 ? 0 : 1 + count = local.create_ci_role && length(var.ci_ecr_push_arns) > 0 ? 1 : 0 name = "${module.luthername_ci_role.name}-ecr-rw" path = "/" @@ -166,8 +178,8 @@ resource "aws_iam_policy" "ecr_push_ci" { } resource "aws_iam_role_policy_attachment" "ecr_push_ci" { - count = length(var.ci_ecr_push_arns) == 0 ? 0 : 1 + count = local.create_ci_role && length(var.ci_ecr_push_arns) > 0 ? 1 : 0 - role = aws_iam_role.ci_role.name + role = aws_iam_role.ci_role[0].name policy_arn = aws_iam_policy.ecr_push_ci[0].arn } diff --git a/aws-platform-ui-storage/s3_buckets.tf b/aws-platform-ui-storage/s3_buckets.tf index 6c4ef75..0f61530 100644 --- a/aws-platform-ui-storage/s3_buckets.tf +++ b/aws-platform-ui-storage/s3_buckets.tf @@ -24,7 +24,10 @@ resource "aws_s3_bucket_policy" "static" { locals { s3_access_principals = compact( - concat(var.external_access_principals, var.ci_static_access ? [aws_iam_role.ci_role.arn] : []) + concat( + var.external_access_principals, + (var.ci_static_access && local.create_ci_role) ? [aws_iam_role.ci_role[0].arn] : [] + ) ) } diff --git a/aws-platform-ui-storage/vars.tf b/aws-platform-ui-storage/vars.tf index 19a267c..a895527 100644 --- a/aws-platform-ui-storage/vars.tf +++ b/aws-platform-ui-storage/vars.tf @@ -53,6 +53,7 @@ variable "ci_github_repos" { repo = string env = string })) + default = [] } variable "ci_ecr_push_arns" { diff --git a/eks-vpc/eks_workers.tf b/eks-vpc/eks_workers.tf index 62d0efe..5053eeb 100644 --- a/eks-vpc/eks_workers.tf +++ b/eks-vpc/eks_workers.tf @@ -62,15 +62,21 @@ locals { docker_config_json = var.awslogs_driver ? jsonencode(local.docker_config_awslogs) : jsonencode(local.docker_config) - user_data_vars = { - docker_config_json = local.docker_config_json - endpoint = aws_eks_cluster.app.endpoint, - cluster_ca = aws_eks_cluster.app.certificate_authority[0].data, - cluster_name = aws_eks_cluster.app.name, + userdata_vars = { + cluster_ca = aws_eks_cluster.app.certificate_authority[0].data + cluster_name = aws_eks_cluster.app.name + endpoint = aws_eks_cluster.app.endpoint + cluster_cidr = aws_eks_cluster.app.kubernetes_network_config[0].service_ipv4_cidr common_userdata = module.common_userdata.user_data + docker_config_json = local.docker_config_json } - user_data = templatefile("${path.module}/files/userdata.sh.tmpl", local.user_data_vars) + user_data = ( + local.is_al2023 + ? templatefile("${path.module}/files/userdata_al2023.sh.tmpl", local.userdata_vars) + : templatefile("${path.module}/files/userdata_al2.sh.tmpl", local.userdata_vars) + ) + } # Docker log options are output so they can be used to configure a @@ -217,7 +223,6 @@ resource "aws_launch_template" "eks_worker" { create_before_destroy = true ignore_changes = [ key_name, - #image_id, ] } diff --git a/eks-vpc/files/userdata.sh.tmpl b/eks-vpc/files/userdata_al2.sh.tmpl similarity index 100% rename from eks-vpc/files/userdata.sh.tmpl rename to eks-vpc/files/userdata_al2.sh.tmpl diff --git a/eks-vpc/files/userdata_al2023.sh.tmpl b/eks-vpc/files/userdata_al2023.sh.tmpl new file mode 100644 index 0000000..f363c77 --- /dev/null +++ b/eks-vpc/files/userdata_al2023.sh.tmpl @@ -0,0 +1,32 @@ +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="BOUNDARY" + +--BOUNDARY +Content-Type: application/node.eks.aws + +--- +apiVersion: node.eks.aws/v1alpha1 +kind: NodeConfig +spec: + cluster: + name: ${cluster_name} + apiServerEndpoint: ${endpoint} + certificateAuthority: ${cluster_ca} + cidr: ${cluster_cidr} + +--BOUNDARY +Content-Type: text/x-shellscript; charset="us-ascii" + +#!/bin/bash +set -euxo pipefail + +# Install latest security updates +yum update --security -y + +# Disable root SSH +sed -i 's/#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config +systemctl reload sshd + +${common_userdata} + +--BOUNDARY-- diff --git a/eks-vpc/vars.tf b/eks-vpc/vars.tf index 38ea413..90863be 100644 --- a/eks-vpc/vars.tf +++ b/eks-vpc/vars.tf @@ -194,6 +194,8 @@ variable "cni_addon_version" { "1.29" = "v1.18.2-eksbuild.1" "1.30" = "v1.18.3-eksbuild.1" "1.31" = "v1.18.3-eksbuild.3" + "1.32" = "v1.19.2-eksbuild.1" + "1.33" = "v1.19.5-eksbuild.3" } } @@ -212,6 +214,8 @@ variable "csi_addon_version" { "1.29" = "v1.31.0-eksbuild.1" "1.30" = "v1.31.0-eksbuild.1" "1.31" = "v1.35.0-eksbuild.1" + "1.32" = "v1.45.0-eksbuild.2" + "1.33" = "v1.45.0-eksbuild.2" } } @@ -229,6 +233,8 @@ variable "kubeproxy_addon_version" { "1.29" = "v1.29.0-eksbuild.2" "1.30" = "v1.30.0-eksbuild.3" "1.31" = "v1.31.0-eksbuild.5" + "1.32" = "v1.32.0-eksbuild.2" + "1.33" = "v1.33.0-eksbuild.2" } } @@ -246,6 +252,8 @@ variable "coredns_addon_version" { "1.29" = "v1.11.1-eksbuild.4" "1.30" = "v1.11.1-eksbuild.9" "1.31" = "v1.11.3-eksbuild.1" + "1.32" = "v1.11.4-eksbuild.2" + "1.33" = "v1.12.1-eksbuild.2" } } diff --git a/eks-vpc/worker_ami.tf b/eks-vpc/worker_ami.tf index c310874..3aff28e 100644 --- a/eks-vpc/worker_ami.tf +++ b/eks-vpc/worker_ami.tf @@ -1,33 +1,75 @@ -# -# Only update the AMI when k8s version has changed. This avoids accidently +# # Only update the AMI when k8s version or instance type has changed. This avoids accidently # refreshing the nodes due to a new AMI added to AWS. -# locals { - image_id = terraform_data.worker_ami.output - - # Determine if the instance type is Graviton (ARM architecture) - is_graviton = contains(["a1", "c6g", "m6g", "r6g", "t4g"], substr(var.worker_instance_type, 0, 3)) - - # Set the AMI filter based on the instance type's architecture - ami_name_filter = local.is_graviton ? "amazon-eks-arm64-node-${aws_eks_cluster.app.version}-v*" : "amazon-eks-node-${aws_eks_cluster.app.version}-v*" + # Detect architecture from instance type + core = substr(var.worker_instance_type, 0, 3) + is_graviton = contains(["a1", "c6g", "m6g", "r6g", "t4g"], local.core) + arch = local.is_graviton ? "arm64" : "x86_64" + k8s_version = aws_eks_cluster.app.version } + data "aws_ami" "eks_worker" { + most_recent = true + owners = ["amazon"] filter { - name = "name" - values = [local.ami_name_filter] + name = "name" + values = [ + "amazon-eks-node-al2023-${local.arch}-standard-${local.k8s_version}-v*", + local.is_graviton ? "amazon-eks-${local.arch}-node-${local.k8s_version}-v*" : "amazon-eks-node-${local.k8s_version}-v*" + ] } +} - most_recent = true - owners = ["amazon"] + +locals { + # Prefer AL2023 since they are newer, fallback to AL2 + selected_image_id = data.aws_ami.eks_worker.id } -resource "terraform_data" "worker_ami" { - input = data.aws_ami.eks_worker.id +resource "terraform_data" "image_id" { + input = local.selected_image_id lifecycle { ignore_changes = [input] } - triggers_replace = [aws_eks_cluster.app.version, var.worker_instance_type] + # pull latest AMI if user data version changes + triggers_replace = [aws_eks_cluster.app.version, var.worker_instance_type, var.custom_instance_userdata_version] +} + +locals { + image_id = terraform_data.image_id.output +} + +resource "null_resource" "fail_if_no_ami" { + count = local.selected_image_id == null ? 1 : 0 + provisioner "local-exec" { + command = "echo 'ERROR: No EKS worker AMI found via filters!' && exit 1" + } +} + +data "aws_ami" "selected" { + most_recent = true + owners = ["amazon"] + filter { + name = "image-id" + values = [local.selected_image_id] + } +} + +locals { + is_al2023 = can(regex("al2023", lower(data.aws_ami.selected.name))) +} + +output "is_al2023" { + value = local.is_al2023 +} + +output "worker_ami_id" { + value = data.aws_ami.selected.id +} + +output "worker_ami_name" { + value = data.aws_ami.selected.name }