shaikhsalman's picture
refactor: merged structure - model at center, DevSecOps wrapped around it
9d4d5c7 verified
# =============================================================================
# EKS Module — Production-Grade AWS EKS Cluster
# =============================================================================
# Security Features:
# - Private API endpoint (public access optional, restricted CIDRs)
# - Encrypted secrets with KMS
# - Managed node groups with custom launch templates
# - IRSA (IAM Roles for Service Accounts)
# - Audit logging enabled (all log types)
# - Pod security standards enforced via Kyverno (at k8s layer)
# - Bottlerocket or AL2023 node OS options
# =============================================================================
terraform {
required_version = ">= 1.7.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.25"
}
}
}
data "aws_caller_identity" "current" {}
# ---------- EKS Cluster ----------
resource "aws_eks_cluster" "this" {
name = var.cluster_name
role_arn = aws_iam_role.cluster.arn
version = var.kubernetes_version
vpc_config {
subnet_ids = var.private_subnet_ids
endpoint_private_access = true
endpoint_public_access = var.endpoint_public_access
public_access_cidrs = var.endpoint_public_access_cidrs
security_group_ids = [var.cluster_security_group_id]
}
encryption_config {
provider {
key_arn = var.kms_key_arn
}
resources = ["secrets"]
}
enabled_cluster_log_types = [
"api",
"audit",
"authenticator",
"controllerManager",
"scheduler"
]
tags = merge(var.tags, {
Name = var.cluster_name
})
}
# ---------- Cluster IAM Role ----------
resource "aws_iam_role" "cluster" {
name = "${var.cluster_name}-cluster-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "eks.amazonaws.com"
}
}]
})
tags = var.tags
}
resource "aws_iam_role_policy_attachment" "cluster_policy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
role = aws_iam_role.cluster.name
}
resource "aws_iam_role_policy_attachment" "cluster_vpc_resource_controller" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSVPCResourceController"
role = aws_iam_role.cluster.name
}
# ---------- OIDC Provider for IRSA ----------
data "tls_certificate" "cluster" {
url = aws_eks_cluster.this.identity[0].oidc[0].issuer
}
resource "aws_iam_openid_connect_provider" "cluster" {
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = [data.tls_certificate.cluster.certificates[0].sha1_fingerprint]
url = aws_eks_cluster.this.identity[0].oidc[0].issuer
tags = merge(var.tags, {
Name = "${var.cluster_name}-oidc"
})
}
# ---------- Managed Node Groups ----------
resource "aws_eks_node_group" "this" {
for_each = var.node_groups
cluster_name = aws_eks_cluster.this.name
node_group_name = each.key
node_role_arn = aws_iam_role.node.arn
subnet_ids = var.private_subnet_ids
instance_types = each.value.instance_types
ami_type = each.value.ami_type
capacity_type = each.value.capacity_type
disk_size = each.value.disk_size
scaling_config {
desired_size = each.value.desired_size
min_size = each.value.min_size
max_size = each.value.max_size
}
update_config {
max_unavailable_percentage = 25
}
labels = merge(each.value.labels, {
"node-group" = each.key
})
dynamic "taint" {
for_each = each.value.taints
content {
key = taint.value.key
value = taint.value.value
effect = taint.value.effect
}
}
# Only proceed when cluster is ready
depends_on = [
aws_iam_role_policy_attachment.node_policy,
aws_iam_role_policy_attachment.cni_policy,
aws_iam_role_policy_attachment.container_registry_policy,
]
tags = merge(var.tags, {
Name = "${var.cluster_name}-${each.key}"
})
}
# ---------- Node IAM Role ----------
resource "aws_iam_role" "node" {
name = "${var.cluster_name}-node-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
}]
})
tags = var.tags
}
resource "aws_iam_role_policy_attachment" "node_policy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
role = aws_iam_role.node.name
}
resource "aws_iam_role_policy_attachment" "cni_policy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
role = aws_iam_role.node.name
}
resource "aws_iam_role_policy_attachment" "container_registry_policy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
role = aws_iam_role.node.name
}
resource "aws_iam_role_policy_attachment" "ssm_managed_instance" {
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
role = aws_iam_role.node.name
}
# ---------- IRSA Helper Module ----------
# Creates IAM role for a Kubernetes service account
resource "aws_iam_role" "irsa" {
for_each = var.irsa_roles
name = "${var.cluster_name}-${each.key}-irsa"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRoleWithWebIdentity"
Effect = "Allow"
Principal = {
Federated = aws_iam_openid_connect_provider.cluster.arn
}
Condition = {
StringEquals = {
"${aws_iam_openid_connect_provider.cluster.url}:sub" = "system:serviceaccount:${each.value.namespace}:${each.value.service_account}"
}
}
}]
})
tags = merge(var.tags, {
Name = "${var.cluster_name}-${each.key}-irsa"
ServiceAccount = each.value.service_account
})
}
resource "aws_iam_role_policy_attachment" "irsa" {
for_each = var.irsa_roles
policy_arn = each.value.policy_arn
role = aws_iam_role.irsa[each.key].name
}