diff --git a/README.md b/README.md index 67d4c1d..e19631b 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ A Terraform module for creating and managing Amazon EKS (Elastic Kubernetes Serv - **IRSA Support**: OIDC provider setup for IAM Roles for Service Accounts - **EKS Addons**: Flexible addon configuration (CoreDNS, VPC CNI, Kube-proxy, Pod Identity Agent, EBS CSI Driver) - **EKS Capabilities**: Support for ACK, KRO, and ArgoCD capabilities -- **AWS Load Balancer Controller**: Optional IAM role creation for AWS Load Balancer Controller (IRSA) +- **AWS Load Balancer Controller**: Optional IAM role creation for AWS Load Balancer Controller (IRSA). The IRSA role expects the controller's service account in the **`aws-load-balancer-controller`** namespace (e.g. when using the [official Helm chart](https://kubernetes-sigs.github.io/aws-load-balancer-controller/), deploy into that namespace or set the Helm chart namespace accordingly). - **Security**: KMS encryption, IMDSv2 enforcement, security groups - **CloudWatch Log Group**: Optional log group for EKS control plane logs; set `cloudwatch_log_group_force_destroy = true` to allow the log group to be deleted on `terraform destroy` (default is to protect it). @@ -200,7 +200,7 @@ No modules. | [create\_cloudwatch\_log\_group](#input\_create\_cloudwatch\_log\_group) | Whether to create a CloudWatch log group for EKS cluster logs | `bool` | `true` | no | | [create\_kms\_key](#input\_create\_kms\_key) | Controls if a KMS key for cluster encryption should be created | `bool` | `true` | no | | [eks\_managed\_node\_groups](#input\_eks\_managed\_node\_groups) | Map of EKS managed node group configurations |
map(object({
name = optional(string)
ami_type = optional(string, "AL2023_x86_64_STANDARD")
instance_types = optional(list(string), ["t3.medium"])
min_size = optional(number, 1)
max_size = optional(number, 3)
desired_size = optional(number, 2)
disk_size = optional(number, 20)
subnet_ids = optional(list(string))
enable_bootstrap_user_data = optional(bool, true)
metadata_options = optional(object({
http_endpoint = optional(string, "enabled")
http_tokens = optional(string, "required")
http_put_response_hop_limit = optional(number, 1)
}))
labels = optional(map(string), {})
tags = optional(map(string), {})
}))
| `{}` | no | -| [enable\_aws\_load\_balancer\_controller](#input\_enable\_aws\_load\_balancer\_controller) | Whether to create IAM role for AWS Load Balancer Controller (IRSA) | `bool` | `false` | no | +| [enable\_aws\_load\_balancer\_controller](#input\_enable\_aws\_load\_balancer\_controller) | Whether to create IAM role for AWS Load Balancer Controller (IRSA). Role expects the controller's service account in the `aws-load-balancer-controller` namespace. | `bool` | `false` | no | | [enable\_cluster\_creator\_admin\_permissions](#input\_enable\_cluster\_creator\_admin\_permissions) | Indicates whether or not to add the cluster creator (the identity used by Terraform) as an administrator via access entry | `bool` | `false` | no | | [enable\_external\_dns](#input\_enable\_external\_dns) | Whether to create IAM role for ExternalDNS (IRSA) | `bool` | `false` | no | | [enabled\_cluster\_log\_types](#input\_enabled\_cluster\_log\_types) | List of control plane logging types to enable | `list(string)` |
[
"api",
"audit",
"authenticator"
]
| no | diff --git a/aws-lb-controller-iam.tf b/aws-lb-controller-iam.tf index 8435145..98baf67 100644 --- a/aws-lb-controller-iam.tf +++ b/aws-lb-controller-iam.tf @@ -20,7 +20,7 @@ data "aws_iam_policy_document" "aws_lb_controller_assume_role" { condition { test = "StringEquals" variable = "${replace(aws_eks_cluster.this.identity[0].oidc[0].issuer, "https://", "")}:sub" - values = ["system:serviceaccount:kube-system:aws-load-balancer-controller"] + values = ["system:serviceaccount:aws-load-balancer-controller:aws-load-balancer-controller"] } condition { @@ -46,64 +46,289 @@ resource "aws_iam_role" "aws_lb_controller" { } # Attach AWS managed policy for Elastic Load Balancing -resource "aws_iam_role_policy_attachment" "aws_lb_controller" { - for_each = var.enable_aws_load_balancer_controller ? { - elastic_load_balancing = "arn:${data.aws_partition.current.partition}:iam::aws:policy/ElasticLoadBalancingFullAccess" - ec2 = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonEC2FullAccess" - } : {} +data "aws_iam_policy_document" "aws_lb_controller" { + count = var.enable_aws_load_balancer_controller ? 1 : 0 - role = aws_iam_role.aws_lb_controller[0].name - policy_arn = each.value -} + statement { + sid = "CreateServiceLinkedRole" + effect = "Allow" + actions = ["iam:CreateServiceLinkedRole"] + resources = ["*"] + condition { + test = "StringEquals" + variable = "iam:AWSServiceName" + values = ["elasticloadbalancing.amazonaws.com"] + } + } -# IAM policy document for AWS Load Balancer Controller WAF, WAF Regional, and Shield permissions -data "aws_iam_policy_document" "aws_lb_controller_waf" { - count = var.enable_aws_load_balancer_controller ? 1 : 0 + statement { + sid = "Describe" + effect = "Allow" + actions = [ + "ec2:DescribeAccountAttributes", + "ec2:DescribeAddresses", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeInternetGateways", + "ec2:DescribeVpcs", + "ec2:DescribeVpcPeeringConnections", + "ec2:DescribeSubnets", + "ec2:DescribeSecurityGroups", + "ec2:DescribeInstances", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeTags", + "ec2:GetCoipPoolUsage", + "ec2:DescribeCoipPools", + "ec2:GetSecurityGroupsForVpc", + "ec2:DescribeIpamPools", + "ec2:DescribeRouteTables", + "elasticloadbalancing:DescribeLoadBalancers", + "elasticloadbalancing:DescribeLoadBalancerAttributes", + "elasticloadbalancing:DescribeListeners", + "elasticloadbalancing:DescribeListenerCertificates", + "elasticloadbalancing:DescribeSSLPolicies", + "elasticloadbalancing:DescribeRules", + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:DescribeTargetGroupAttributes", + "elasticloadbalancing:DescribeTargetHealth", + "elasticloadbalancing:DescribeTags", + "elasticloadbalancing:DescribeTrustStores", + "elasticloadbalancing:DescribeListenerAttributes", + "elasticloadbalancing:DescribeCapacityReservation", + ] + resources = ["*"] + } statement { - sid = "WAFv2Permissions" + sid = "CognitoAcmIamWafShield" effect = "Allow" actions = [ + "cognito-idp:DescribeUserPoolClient", + "acm:ListCertificates", + "acm:DescribeCertificate", + "iam:ListServerCertificates", + "iam:GetServerCertificate", + "waf-regional:GetWebACL", + "waf-regional:GetWebACLForResource", + "waf-regional:AssociateWebACL", + "waf-regional:DisassociateWebACL", "wafv2:GetWebACL", "wafv2:GetWebACLForResource", "wafv2:AssociateWebACL", "wafv2:DisassociateWebACL", - "wafv2:ListWebACLs" + "wafv2:ListWebACLs", + "shield:GetSubscriptionState", + "shield:DescribeProtection", + "shield:CreateProtection", + "shield:DeleteProtection", ] resources = ["*"] } statement { - sid = "WAFRegionalPermissions" + sid = "SecurityGroupIngressEgress" + effect = "Allow" + actions = ["ec2:AuthorizeSecurityGroupIngress", "ec2:RevokeSecurityGroupIngress"] + resources = ["*"] + } + + statement { + sid = "CreateSecurityGroup" + effect = "Allow" + actions = ["ec2:CreateSecurityGroup"] + resources = ["*"] + } + + statement { + sid = "CreateTagsSecurityGroup" + effect = "Allow" + actions = ["ec2:CreateTags"] + resources = ["arn:${data.aws_partition.current.partition}:ec2:*:*:security-group/*"] + condition { + test = "StringEquals" + variable = "ec2:CreateAction" + values = ["CreateSecurityGroup"] + } + condition { + test = "Null" + variable = "aws:RequestTag/elbv2.k8s.aws/cluster" + values = ["false"] + } + } + + statement { + sid = "CreateDeleteTagsSecurityGroup" + effect = "Allow" + actions = ["ec2:CreateTags", "ec2:DeleteTags"] + resources = ["arn:${data.aws_partition.current.partition}:ec2:*:*:security-group/*"] + condition { + test = "Null" + variable = "aws:RequestTag/elbv2.k8s.aws/cluster" + values = ["true"] + } + condition { + test = "Null" + variable = "aws:ResourceTag/elbv2.k8s.aws/cluster" + values = ["false"] + } + } + + statement { + sid = "SecurityGroupManageTagged" effect = "Allow" actions = [ - "waf-regional:GetWebACL", - "waf-regional:GetWebACLForResource", - "waf-regional:AssociateWebACL", - "waf-regional:DisassociateWebACL", - "waf-regional:ListWebACLs" + "ec2:AuthorizeSecurityGroupIngress", + "ec2:RevokeSecurityGroupIngress", + "ec2:DeleteSecurityGroup", ] resources = ["*"] + condition { + test = "Null" + variable = "aws:ResourceTag/elbv2.k8s.aws/cluster" + values = ["false"] + } } statement { - sid = "ShieldPermissions" + sid = "CreateLoadBalancerTargetGroup" effect = "Allow" actions = [ - "shield:GetSubscriptionState", - "shield:DescribeProtection", - "shield:CreateProtection", - "shield:DeleteProtection" + "elasticloadbalancing:CreateLoadBalancer", + "elasticloadbalancing:CreateTargetGroup", + ] + resources = ["*"] + condition { + test = "Null" + variable = "aws:RequestTag/elbv2.k8s.aws/cluster" + values = ["false"] + } + } + + statement { + sid = "ListenerRule" + effect = "Allow" + actions = [ + "elasticloadbalancing:CreateListener", + "elasticloadbalancing:DeleteListener", + "elasticloadbalancing:CreateRule", + "elasticloadbalancing:DeleteRule", + ] + resources = ["*"] + } + + statement { + sid = "AddRemoveTagsLbTargetGroup" + effect = "Allow" + actions = ["elasticloadbalancing:AddTags", "elasticloadbalancing:RemoveTags"] + resources = [ + "arn:${data.aws_partition.current.partition}:elasticloadbalancing:*:*:targetgroup/*/*", + "arn:${data.aws_partition.current.partition}:elasticloadbalancing:*:*:loadbalancer/net/*/*", + "arn:${data.aws_partition.current.partition}:elasticloadbalancing:*:*:loadbalancer/app/*/*", + ] + condition { + test = "Null" + variable = "aws:RequestTag/elbv2.k8s.aws/cluster" + values = ["true"] + } + condition { + test = "Null" + variable = "aws:ResourceTag/elbv2.k8s.aws/cluster" + values = ["false"] + } + } + + statement { + sid = "AddRemoveTagsListener" + effect = "Allow" + actions = ["elasticloadbalancing:AddTags", "elasticloadbalancing:RemoveTags"] + resources = [ + "arn:${data.aws_partition.current.partition}:elasticloadbalancing:*:*:listener/net/*/*/*", + "arn:${data.aws_partition.current.partition}:elasticloadbalancing:*:*:listener/app/*/*/*", + "arn:${data.aws_partition.current.partition}:elasticloadbalancing:*:*:listener-rule/net/*/*/*", + "arn:${data.aws_partition.current.partition}:elasticloadbalancing:*:*:listener-rule/app/*/*/*", + ] + } + + statement { + sid = "ModifyDeleteLbTargetGroup" + effect = "Allow" + actions = [ + "elasticloadbalancing:ModifyLoadBalancerAttributes", + "elasticloadbalancing:SetIpAddressType", + "elasticloadbalancing:SetSecurityGroups", + "elasticloadbalancing:SetSubnets", + "elasticloadbalancing:DeleteLoadBalancer", + "elasticloadbalancing:ModifyTargetGroup", + "elasticloadbalancing:ModifyTargetGroupAttributes", + "elasticloadbalancing:DeleteTargetGroup", + "elasticloadbalancing:ModifyListenerAttributes", + "elasticloadbalancing:ModifyCapacityReservation", + "elasticloadbalancing:ModifyIpPools" + ] + resources = ["*"] + condition { + test = "Null" + variable = "aws:ResourceTag/elbv2.k8s.aws/cluster" + values = ["false"] + } + } + + statement { + sid = "AddTagsOnCreate" + effect = "Allow" + actions = ["elasticloadbalancing:AddTags"] + resources = [ + "arn:${data.aws_partition.current.partition}:elasticloadbalancing:*:*:targetgroup/*/*", + "arn:${data.aws_partition.current.partition}:elasticloadbalancing:*:*:loadbalancer/net/*/*", + "arn:${data.aws_partition.current.partition}:elasticloadbalancing:*:*:loadbalancer/app/*/*", + ] + condition { + test = "StringEquals" + variable = "elasticloadbalancing:CreateAction" + values = ["CreateTargetGroup", "CreateLoadBalancer"] + } + condition { + test = "Null" + variable = "aws:RequestTag/elbv2.k8s.aws/cluster" + values = ["false"] + } + } + + statement { + sid = "RegisterTargets" + effect = "Allow" + actions = ["elasticloadbalancing:RegisterTargets", "elasticloadbalancing:DeregisterTargets"] + resources = ["arn:${data.aws_partition.current.partition}:elasticloadbalancing:*:*:targetgroup/*/*"] + } + + statement { + sid = "SetWebAclModifyListenerRule" + effect = "Allow" + actions = [ + "elasticloadbalancing:SetWebAcl", + "elasticloadbalancing:ModifyListener", + "elasticloadbalancing:AddListenerCertificates", + "elasticloadbalancing:RemoveListenerCertificates", + "elasticloadbalancing:ModifyRule", + "elasticloadbalancing:SetRulePriorities" ] resources = ["*"] } } -# IAM policy for AWS Load Balancer Controller WAF permissions -resource "aws_iam_role_policy" "aws_lb_controller_waf" { +resource "aws_iam_policy" "aws_lb_controller" { + count = var.enable_aws_load_balancer_controller ? 1 : 0 + + name = "${var.name}-aws-lb-controller-policy" + path = "/" + description = "Least-privilege permissions for AWS Load Balancer Controller" + policy = data.aws_iam_policy_document.aws_lb_controller[0].json + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "aws_lb_controller" { count = var.enable_aws_load_balancer_controller ? 1 : 0 - name = "${var.name}-aws-lb-controller-waf-policy" - role = aws_iam_role.aws_lb_controller[0].id - policy = data.aws_iam_policy_document.aws_lb_controller_waf[0].json + role = aws_iam_role.aws_lb_controller[0].name + policy_arn = aws_iam_policy.aws_lb_controller[0].arn }