# IAM Module - Roles and IRSA # RFC 0039: ADR-Compliant Foundation Infrastructure # # Creates IAM roles for: # - Karpenter node provisioning # - EBS CSI Driver # - EFS CSI Driver # - AWS Load Balancer Controller # - External DNS # - cert-manager locals { oidc_issuer = replace(var.cluster_oidc_issuer_url, "https://", "") } # Karpenter Controller IAM Role resource "aws_iam_role" "karpenter_controller" { name = "${var.cluster_name}-karpenter-controller" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Principal = { Federated = var.cluster_oidc_provider_arn } Action = "sts:AssumeRoleWithWebIdentity" Condition = { StringEquals = { "${local.oidc_issuer}:aud" = "sts.amazonaws.com" "${local.oidc_issuer}:sub" = "system:serviceaccount:karpenter:karpenter" } } } ] }) tags = var.tags } resource "aws_iam_role_policy" "karpenter_controller" { name = "${var.cluster_name}-karpenter-controller" role = aws_iam_role.karpenter_controller.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Sid = "Karpenter" Effect = "Allow" Action = [ "ec2:CreateLaunchTemplate", "ec2:CreateFleet", "ec2:CreateTags", "ec2:DescribeLaunchTemplates", "ec2:DescribeImages", "ec2:DescribeInstances", "ec2:DescribeSecurityGroups", "ec2:DescribeSubnets", "ec2:DescribeInstanceTypes", "ec2:DescribeInstanceTypeOfferings", "ec2:DescribeAvailabilityZones", "ec2:DescribeSpotPriceHistory", "ec2:DeleteLaunchTemplate", "ec2:RunInstances", "ec2:TerminateInstances", ] Resource = "*" }, { Sid = "PassNodeRole" Effect = "Allow" Action = "iam:PassRole" Resource = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${var.cluster_name}-node" }, { Sid = "EKSClusterEndpointLookup" Effect = "Allow" Action = "eks:DescribeCluster" Resource = "arn:aws:eks:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:cluster/${var.cluster_name}" }, { Sid = "KarpenterInterruptionQueue" Effect = "Allow" Action = [ "sqs:DeleteMessage", "sqs:GetQueueUrl", "sqs:GetQueueAttributes", "sqs:ReceiveMessage" ] Resource = aws_sqs_queue.karpenter_interruption.arn }, { Sid = "PricingAPI" Effect = "Allow" Action = "pricing:GetProducts" Resource = "*" }, { Sid = "SSM" Effect = "Allow" Action = "ssm:GetParameter" Resource = "arn:aws:ssm:${data.aws_region.current.name}::parameter/aws/service/*" } ] }) } # Karpenter Interruption Queue (for Spot instance termination notices) resource "aws_sqs_queue" "karpenter_interruption" { name = "${var.cluster_name}-karpenter-interruption" message_retention_seconds = 300 tags = var.tags } resource "aws_sqs_queue_policy" "karpenter_interruption" { queue_url = aws_sqs_queue.karpenter_interruption.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Sid = "EC2InterruptionPolicy" Effect = "Allow" Principal = { Service = [ "events.amazonaws.com", "sqs.amazonaws.com" ] } Action = "sqs:SendMessage" Resource = aws_sqs_queue.karpenter_interruption.arn } ] }) } # EventBridge rules for Karpenter interruption handling resource "aws_cloudwatch_event_rule" "spot_interruption" { name = "${var.cluster_name}-spot-interruption" description = "Spot instance interruption handler for Karpenter" event_pattern = jsonencode({ source = ["aws.ec2"] detail-type = ["EC2 Spot Instance Interruption Warning"] }) tags = var.tags } resource "aws_cloudwatch_event_target" "spot_interruption" { rule = aws_cloudwatch_event_rule.spot_interruption.name target_id = "KarpenterInterruptionQueue" arn = aws_sqs_queue.karpenter_interruption.arn } resource "aws_cloudwatch_event_rule" "instance_rebalance" { name = "${var.cluster_name}-instance-rebalance" description = "Instance rebalance recommendation for Karpenter" event_pattern = jsonencode({ source = ["aws.ec2"] detail-type = ["EC2 Instance Rebalance Recommendation"] }) tags = var.tags } resource "aws_cloudwatch_event_target" "instance_rebalance" { rule = aws_cloudwatch_event_rule.instance_rebalance.name target_id = "KarpenterInterruptionQueue" arn = aws_sqs_queue.karpenter_interruption.arn } resource "aws_cloudwatch_event_rule" "instance_state_change" { name = "${var.cluster_name}-instance-state-change" description = "Instance state change handler for Karpenter" event_pattern = jsonencode({ source = ["aws.ec2"] detail-type = ["EC2 Instance State-change Notification"] }) tags = var.tags } resource "aws_cloudwatch_event_target" "instance_state_change" { rule = aws_cloudwatch_event_rule.instance_state_change.name target_id = "KarpenterInterruptionQueue" arn = aws_sqs_queue.karpenter_interruption.arn } # EBS CSI Driver IAM Role resource "aws_iam_role" "ebs_csi" { name = "${var.cluster_name}-ebs-csi" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Principal = { Federated = var.cluster_oidc_provider_arn } Action = "sts:AssumeRoleWithWebIdentity" Condition = { StringEquals = { "${local.oidc_issuer}:aud" = "sts.amazonaws.com" "${local.oidc_issuer}:sub" = "system:serviceaccount:kube-system:ebs-csi-controller-sa" } } } ] }) tags = var.tags } resource "aws_iam_role_policy_attachment" "ebs_csi" { policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy" role = aws_iam_role.ebs_csi.name } # Additional EBS CSI policy for encryption resource "aws_iam_role_policy" "ebs_csi_kms" { name = "${var.cluster_name}-ebs-csi-kms" role = aws_iam_role.ebs_csi.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "kms:CreateGrant", "kms:ListGrants", "kms:RevokeGrant", "kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:DescribeKey" ] Resource = "*" } ] }) } # EFS CSI Driver IAM Role resource "aws_iam_role" "efs_csi" { name = "${var.cluster_name}-efs-csi" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Principal = { Federated = var.cluster_oidc_provider_arn } Action = "sts:AssumeRoleWithWebIdentity" Condition = { StringEquals = { "${local.oidc_issuer}:aud" = "sts.amazonaws.com" "${local.oidc_issuer}:sub" = "system:serviceaccount:kube-system:efs-csi-controller-sa" } } } ] }) tags = var.tags } resource "aws_iam_role_policy_attachment" "efs_csi" { policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEFSCSIDriverPolicy" role = aws_iam_role.efs_csi.name } # AWS Load Balancer Controller IAM Role resource "aws_iam_role" "aws_load_balancer_controller" { name = "${var.cluster_name}-aws-load-balancer-controller" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Principal = { Federated = var.cluster_oidc_provider_arn } Action = "sts:AssumeRoleWithWebIdentity" Condition = { StringEquals = { "${local.oidc_issuer}:aud" = "sts.amazonaws.com" "${local.oidc_issuer}:sub" = "system:serviceaccount:kube-system:aws-load-balancer-controller" } } } ] }) tags = var.tags } resource "aws_iam_role_policy" "aws_load_balancer_controller" { name = "${var.cluster_name}-aws-load-balancer-controller" role = aws_iam_role.aws_load_balancer_controller.id policy = file("${path.module}/policies/aws-load-balancer-controller.json") } # cert-manager IAM Role resource "aws_iam_role" "cert_manager" { name = "${var.cluster_name}-cert-manager" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Principal = { Federated = var.cluster_oidc_provider_arn } Action = "sts:AssumeRoleWithWebIdentity" Condition = { StringEquals = { "${local.oidc_issuer}:aud" = "sts.amazonaws.com" "${local.oidc_issuer}:sub" = "system:serviceaccount:cert-manager:cert-manager" } } } ] }) tags = var.tags } resource "aws_iam_role_policy" "cert_manager" { name = "${var.cluster_name}-cert-manager" role = aws_iam_role.cert_manager.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "route53:GetChange" ] Resource = "arn:aws:route53:::change/*" }, { Effect = "Allow" Action = [ "route53:ChangeResourceRecordSets", "route53:ListResourceRecordSets" ] Resource = "arn:aws:route53:::hostedzone/*" }, { Effect = "Allow" Action = "route53:ListHostedZonesByName" Resource = "*" } ] }) } # External DNS IAM Role resource "aws_iam_role" "external_dns" { name = "${var.cluster_name}-external-dns" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Principal = { Federated = var.cluster_oidc_provider_arn } Action = "sts:AssumeRoleWithWebIdentity" Condition = { StringEquals = { "${local.oidc_issuer}:aud" = "sts.amazonaws.com" "${local.oidc_issuer}:sub" = "system:serviceaccount:external-dns:external-dns" } } } ] }) tags = var.tags } resource "aws_iam_role_policy" "external_dns" { name = "${var.cluster_name}-external-dns" role = aws_iam_role.external_dns.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "route53:ChangeResourceRecordSets" ] Resource = "arn:aws:route53:::hostedzone/*" }, { Effect = "Allow" Action = [ "route53:ListHostedZones", "route53:ListResourceRecordSets" ] Resource = "*" } ] }) } data "aws_caller_identity" "current" {} data "aws_region" "current" {}