In September 2019, AWS announced the ability to map IAM Roles to Kubernetes Service accounts (IRSA). This enables a finer grain of control for pods running on EC2 instances. Previously, customers had to deploy and configure kube2iam to wrap pods with IAM credentials. However, this was confusing and not deeply integrated with the platform. Without kube2iam, pods inherited the underlying permissions of the EC2 host which means pods could potentially have more privileges than they should.

IRSA enables users to deploy a service like the ALB Ingress Controller with the least amount of privilege possible. In this example, I will use the eksctl command line tool to provision the cluster and configure a service account for the ALB Ingress Controller with the appropriate IAM permissions attached.

Below is an example yaml configuration for eksctl. This configuration deploys a demo cluster with a single node group. It will also wire up IAM Roles for Service Accounts with the OIDC Provider. Finally, it will create a Kubernetes Service Account with an IAM role containing the permissions for the controller. The permissions for the ALB Ingress Controller can be found in the documentation.

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: alb-demo-cluster
  region: us-east-2

vpc:
  cidr: "10.1.0.0/16"

nodeGroups:
  - name: NodeGroup1
    instanceType: t3.large
    desiredCapacity: 3
    privateNetworking: true

iam:
  withOIDC: true
  serviceAccounts:
    - metadata:
        name: alb-ingress-controller
        namespace: aws-system
      attachPolicy:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Resource: "*"
            Action:
              - "acm:DescribeCertificate"
              - "acm:ListCertificates"
              - "acm:GetCertificate"
              - "ec2:AuthorizeSecurityGroupIngress"
              - "ec2:CreateSecurityGroup"
              - "ec2:CreateTags"
              - "ec2:DeleteTags"
              - "ec2:DeleteSecurityGroup"
              - "ec2:DescribeAccountAttributes"
              - "ec2:DescribeAddresses"
              - "ec2:DescribeInstances"
              - "ec2:DescribeInstanceStatus"
              - "ec2:DescribeInternetGateways"
              - "ec2:DescribeNetworkInterfaces"
              - "ec2:DescribeSecurityGroups"
              - "ec2:DescribeSubnets"
              - "ec2:DescribeTags"
              - "ec2:DescribeVpcs"
              - "ec2:ModifyInstanceAttribute"
              - "ec2:ModifyNetworkInterfaceAttribute"
              - "ec2:RevokeSecurityGroupIngress"
              - "elasticloadbalancing:AddListenerCertificates"
              - "elasticloadbalancing:AddTags"
              - "elasticloadbalancing:CreateListener"
              - "elasticloadbalancing:CreateLoadBalancer"
              - "elasticloadbalancing:CreateRule"
              - "elasticloadbalancing:CreateTargetGroup"
              - "elasticloadbalancing:DeleteListener"
              - "elasticloadbalancing:DeleteLoadBalancer"
              - "elasticloadbalancing:DeleteRule"
              - "elasticloadbalancing:DeleteTargetGroup"
              - "elasticloadbalancing:DeregisterTargets"
              - "elasticloadbalancing:DescribeListenerCertificates"
              - "elasticloadbalancing:DescribeListeners"
              - "elasticloadbalancing:DescribeLoadBalancers"
              - "elasticloadbalancing:DescribeLoadBalancerAttributes"
              - "elasticloadbalancing:DescribeRules"
              - "elasticloadbalancing:DescribeSSLPolicies"
              - "elasticloadbalancing:DescribeTags"
              - "elasticloadbalancing:DescribeTargetGroups"
              - "elasticloadbalancing:DescribeTargetGroupAttributes"
              - "elasticloadbalancing:DescribeTargetHealth"
              - "elasticloadbalancing:ModifyListener"
              - "elasticloadbalancing:ModifyLoadBalancerAttributes"
              - "elasticloadbalancing:ModifyRule"
              - "elasticloadbalancing:ModifyTargetGroup"
              - "elasticloadbalancing:ModifyTargetGroupAttributes"
              - "elasticloadbalancing:RegisterTargets"
              - "elasticloadbalancing:RemoveListenerCertificates"
              - "elasticloadbalancing:RemoveTags"
              - "elasticloadbalancing:SetIpAddressType"
              - "elasticloadbalancing:SetSecurityGroups"
              - "elasticloadbalancing:SetSubnets"
              - "elasticloadbalancing:SetWebACL"
              - "iam:CreateServiceLinkedRole"
              - "iam:GetServerCertificate"
              - "iam:ListServerCertificates"
              - "waf-regional:GetWebACLForResource"
              - "waf-regional:GetWebACL"
              - "waf-regional:AssociateWebACL"
              - "waf-regional:DisassociateWebACL"
              - "tag:GetResources"
              - "tag:TagResources"
              - "waf:GetWebACL"

Once our cluster is configured, we need to deploy the ALB Ingress Components using our new Service Account. The easiest way to do this is via the Helm Chart. First, we need to add the Chart Repository to our list of repositories.

helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator

Next, we need to run the alb-ingress-controller Helm Chart on our cluster. Make sure you replace the configuration settings if you made any changes to the above configuration.

helm upgrade -i aws-alb incubator/aws-alb-ingress-controller \
  --namespace aws-system \
  --set clusterName=alb-demo-cluster \
  --set awsRegion=us-east-2 \
  --set awsVpcID=<vpc id> \
  --set image.tag=v1.1.4 \
  --set rbac.create=true \
  --set rbac.serviceAccountName=alb-ingress-controller

When this is deployed, it will pick up the service account that we already created in the aws-system namespace. Now, we can specify the following annotation on our Ingress Resources within our AWS cluster to deploy an ALB.

annotations:
    kubernetes.io/ingress.class: alb

In conclusion, by using IAM Roles for Services Accounts along with the ALB Ingress Controller we can ensure we are following the policy of least privilege within our cluster. Only the Ingress Controller can create and remove ALBs within our environment. This even removes the need for Kubernetes Administrators to have access to manage network resources.