> ## Documentation Index
> Fetch the complete documentation index at: https://support.lilt.com/llms.txt
> Use this file to discover all available pages before exploring further.

# AWS Access Requirements

> Executive summary of the AWS IAM permissions and roles to grant LILT before a managed Amazon EKS deployment — written for review by your cloud security team.

## Executive summary

To run LILT in your own AWS account, LILT's deployment automation provisions a
standard set of AWS resources — a **managed Amazon EKS cluster with EKS-managed
node groups**, an S3 bucket, an RDS MySQL database, an optional SQS queue, the
supporting network, and the encryption keys that protect them — and then runs
the LILT platform on that cluster. AWS manages the Kubernetes control plane and
the worker-node instance lifecycle; this is not a self-managed cluster running
on hand-operated virtual machines.

This document is the **access request**: it lists exactly what your security
team needs to grant, why each item is needed, how it is scoped, and how long it
is required. There are two grants:

<CardGroup cols={2}>
  <Card title="1 · Deployment role" icon="key">
    One IAM role, used **only while creating or updating infrastructure**. Grant
    it the [deployment permissions](#grant-1-deployment-role) below. It can be
    disabled or removed between deployments — it is not needed for day-to-day
    operation.
  </Card>

  <Card title="2 · Runtime roles" icon="gears">
    A set of narrowly-scoped roles the deployment **creates for you** and that
    the cluster's workloads assume at runtime. Your team's role here is to
    **review and approve** them — see [runtime roles](#grant-2-runtime-roles).
  </Card>
</CardGroup>

### What you are granting, at a glance

| Grant                                    | Purpose                                                                                          | Scope                                                               | Lifetime                                               |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- | ------------------------------------------------------ |
| **Deployment role**                      | Create/update/delete the EKS cluster, S3, SQS, RDS, networking, IAM roles, and KMS keys for LILT | AWS account (services listed below); tightenable to a naming prefix | Only during install/upgrade — can be removed afterward |
| **Runtime — EKS service roles**          | Let the control plane and worker nodes operate (standard AWS-managed policies)                   | The LILT cluster                                                    | Life of the cluster                                    |
| **Runtime — application & add-on roles** | Let LILT pods use their own S3 bucket, KMS key, DNS zone, and (when configured) SQS queue        | The specific bucket / queue / key / hosted zone LILT owns           | Life of the cluster                                    |

### Security posture

What your team can rely on when granting this:

* **Least privilege.** Every policy lists only the actions the component needs.
  Runtime roles are additionally scoped to the specific resource LILT owns (its
  bucket, queue, key, or DNS zone) — they cannot touch anything else in the
  account.
* **No long-lived keys.** Access is brokered by short-lived, federated
  credentials (EKS IRSA / Pod Identity for workloads; your choice of federation
  for the deployment role). LILT does not require static IAM access keys.
* **Time-boxed deployment access.** The broad create/delete permissions live on
  the deployment role, which is only needed during install or upgrade and can be
  detached the rest of the time.
* **Encryption by default.** S3 and RDS are encrypted with dedicated KMS keys;
  the RDS database is private (no public endpoint) and reachable only from within
  the VPC.
* **Auditable.** The runtime policies are fixed and enumerated in this document,
  so a granted deployment produces a known, reviewable set of standing roles.

<Note>
  **Placeholders.** The policies below use <code>\<ACCOUNT\_ID></code>, <code>\<REGION></code>,
  <code>\<CLUSTER\_NAME></code>, <code>\<PREFIX></code> (an environment name prefix, e.g. `prod`), and the
  <code>\<\*\_ARN></code> / <code>\<HOSTED\_ZONE\_ID></code> / <code>\<OIDC\_PROVIDER></code> values — substitute the ones
  for your environment. This request assumes a single-region deployment;
  multi-region follows the same model per region.
</Note>

***

## Grant 1 · Deployment role

Create one IAM role for LILT's deployment identity and attach the permissions
below. This is the complete set of `create`, `describe`, `modify`, `tag`, and
`delete` actions required to stand up **and later tear down** the resources LILT
manages. It is needed only while provisioning; you may detach it between changes.

**Why each service is needed:**

| Service                                                | What it is used for                                                                                           |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- |
| **EKS**                                                | Create and manage the Kubernetes cluster, node groups, add-ons, and admin access entries                      |
| **EC2 / VPC**                                          | Create the private subnets, NAT, route tables, security groups, and node launch templates the cluster runs in |
| **S3**                                                 | Create the object-storage bucket (application assets and the container registry backend)                      |
| **SQS**                                                | Create the queues used for S3 event notifications and node-lifecycle handling                                 |
| **RDS**                                                | Create and configure the MySQL database                                                                       |
| **KMS**                                                | Create the encryption keys that protect S3 and RDS                                                            |
| **IAM**                                                | Create the runtime roles in Grant 2 and the cluster's OIDC identity provider                                  |
| **Route 53**                                           | Create the DNS zone and records for the application endpoints                                                 |
| **Secrets Manager, CloudWatch Logs, EventBridge, SSM** | Store the DB master secret, cluster log groups, and node-lifecycle event rules                                |

<Tip>
  The statements are split into four documents purely to stay under the AWS
  6,144-character managed-policy size limit — attach all four to the same role.
</Tip>

<AccordionGroup>
  <Accordion title="Policy 1 — STS, ECR, Secrets Manager, S3">
    ```json theme={null}
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "STSAuth",
          "Effect": "Allow",
          "Action": "sts:GetCallerIdentity",
          "Resource": "*"
        },
        {
          "Sid": "ECRManagement",
          "Effect": "Allow",
          "Action": [
            "ecr:CreateRepository", "ecr:DeleteRepository", "ecr:DescribeRepositories",
            "ecr:GetRepositoryPolicy", "ecr:SetRepositoryPolicy", "ecr:DeleteRepositoryPolicy",
            "ecr:TagResource", "ecr:UntagResource", "ecr:ListTagsForResource",
            "ecr:CreateLifecyclePolicy", "ecr:DeleteLifecyclePolicy", "ecr:GetLifecyclePolicy",
            "ecr:PutLifecyclePolicy", "ecr:GetAuthorizationToken"
          ],
          "Resource": "*"
        },
        {
          "Sid": "SecretsManager",
          "Effect": "Allow",
          "Action": [
            "secretsmanager:CreateSecret", "secretsmanager:DeleteSecret", "secretsmanager:UpdateSecret",
            "secretsmanager:PutSecretValue", "secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret",
            "secretsmanager:TagResource", "secretsmanager:UntagResource",
            "secretsmanager:ListSecretVersionIds", "secretsmanager:RestoreSecret"
          ],
          "Resource": "arn:aws:secretsmanager:*:<ACCOUNT_ID>:secret:*"
        },
        {
          "Sid": "S3",
          "Effect": "Allow",
          "Action": [
            "s3:CreateBucket", "s3:DeleteBucket", "s3:ListBucket", "s3:ListAllMyBuckets",
            "s3:GetBucketLocation", "s3:GetBucketAcl", "s3:PutBucketAcl",
            "s3:GetBucketCORS", "s3:PutBucketCORS", "s3:GetBucketLogging", "s3:PutBucketLogging",
            "s3:GetBucketNotification", "s3:PutBucketNotification",
            "s3:GetBucketPolicy", "s3:PutBucketPolicy", "s3:DeleteBucketPolicy",
            "s3:GetBucketPublicAccessBlock", "s3:PutBucketPublicAccessBlock",
            "s3:GetBucketTagging", "s3:PutBucketTagging",
            "s3:GetBucketVersioning", "s3:PutBucketVersioning",
            "s3:GetEncryptionConfiguration", "s3:PutEncryptionConfiguration",
            "s3:GetLifecycleConfiguration", "s3:PutLifecycleConfiguration",
            "s3:GetReplicationConfiguration", "s3:PutReplicationConfiguration",
            "s3:GetBucketObjectLockConfiguration", "s3:PutBucketObjectLockConfiguration",
            "s3:GetBucketRequestPayment", "s3:PutBucketRequestPayment",
            "s3:GetIntelligentTieringConfiguration", "s3:PutIntelligentTieringConfiguration"
          ],
          "Resource": "*"
        }
      ]
    }
    ```
  </Accordion>

  <Accordion title="Policy 2 — SQS, RDS, EKS">
    ```json theme={null}
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "SQS",
          "Effect": "Allow",
          "Action": [
            "sqs:CreateQueue", "sqs:DeleteQueue", "sqs:GetQueueAttributes", "sqs:SetQueueAttributes",
            "sqs:GetQueueUrl", "sqs:ListQueues", "sqs:TagQueue", "sqs:UntagQueue", "sqs:ListQueueTags"
          ],
          "Resource": "*"
        },
        {
          "Sid": "RDS",
          "Effect": "Allow",
          "Action": [
            "rds:CreateDBCluster", "rds:DeleteDBCluster", "rds:DescribeDBClusters", "rds:ModifyDBCluster",
            "rds:CreateDBInstance", "rds:DeleteDBInstance", "rds:DescribeDBInstances", "rds:ModifyDBInstance",
            "rds:RebootDBInstance",
            "rds:CreateDBSubnetGroup", "rds:DeleteDBSubnetGroup", "rds:DescribeDBSubnetGroups", "rds:ModifyDBSubnetGroup",
            "rds:CreateDBParameterGroup", "rds:DeleteDBParameterGroup", "rds:DescribeDBParameterGroups", "rds:ModifyDBParameterGroup",
            "rds:CreateDBClusterParameterGroup", "rds:DeleteDBClusterParameterGroup", "rds:DescribeDBClusterParameterGroups", "rds:ModifyDBClusterParameterGroup",
            "rds:ListTagsForResource", "rds:AddTagsToResource", "rds:RemoveTagsFromResource",
            "rds:DescribeDBEngineVersions", "rds:DescribeOrderableDBInstanceOptions",
            "rds:CreateDBSnapshot", "rds:DeleteDBSnapshot", "rds:DescribeDBSnapshots",
            "rds:CreateDBClusterSnapshot", "rds:DeleteDBClusterSnapshot", "rds:DescribeDBClusterSnapshots",
            "rds:DescribeCertificates"
          ],
          "Resource": "*"
        },
        {
          "Sid": "EKS",
          "Effect": "Allow",
          "Action": [
            "eks:CreateCluster", "eks:DeleteCluster", "eks:DescribeCluster", "eks:ListClusters",
            "eks:UpdateClusterConfig", "eks:UpdateClusterVersion",
            "eks:CreateNodegroup", "eks:DeleteNodegroup", "eks:DescribeNodegroup", "eks:ListNodegroups",
            "eks:UpdateNodegroupConfig", "eks:UpdateNodegroupVersion",
            "eks:CreateAddon", "eks:DeleteAddon", "eks:DescribeAddon", "eks:UpdateAddon",
            "eks:ListAddons", "eks:DescribeAddonVersions",
            "eks:CreateAccessEntry", "eks:DeleteAccessEntry", "eks:DescribeAccessEntry",
            "eks:AssociateAccessPolicy", "eks:DisassociateAccessPolicy",
            "eks:ListAccessEntries", "eks:ListAccessPolicies", "eks:ListAssociatedAccessPolicies",
            "eks:TagResource", "eks:UntagResource"
          ],
          "Resource": "*"
        }
      ]
    }
    ```
  </Accordion>

  <Accordion title="Policy 3 — EC2 / VPC, IAM, KMS">
    ```json theme={null}
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "EC2ForEKSAndRDS",
          "Effect": "Allow",
          "Action": [
            "ec2:CreateVpc", "ec2:DeleteVpc", "ec2:DescribeVpcs", "ec2:ModifyVpcAttribute",
            "ec2:CreateSubnet", "ec2:DeleteSubnet", "ec2:DescribeSubnets", "ec2:ModifySubnetAttribute",
            "ec2:CreateInternetGateway", "ec2:DeleteInternetGateway", "ec2:DescribeInternetGateways",
            "ec2:AttachInternetGateway", "ec2:DetachInternetGateway",
            "ec2:CreateNatGateway", "ec2:DeleteNatGateway", "ec2:DescribeNatGateways",
            "ec2:AllocateAddress", "ec2:ReleaseAddress", "ec2:DescribeAddresses",
            "ec2:CreateRouteTable", "ec2:DeleteRouteTable", "ec2:DescribeRouteTables",
            "ec2:CreateRoute", "ec2:DeleteRoute", "ec2:AssociateRouteTable", "ec2:DisassociateRouteTable",
            "ec2:CreateSecurityGroup", "ec2:DeleteSecurityGroup", "ec2:DescribeSecurityGroups",
            "ec2:AuthorizeSecurityGroupIngress", "ec2:RevokeSecurityGroupIngress",
            "ec2:AuthorizeSecurityGroupEgress", "ec2:RevokeSecurityGroupEgress",
            "ec2:CreateLaunchTemplate", "ec2:DeleteLaunchTemplate", "ec2:DescribeLaunchTemplates",
            "ec2:DescribeLaunchTemplateVersions", "ec2:CreateLaunchTemplateVersion", "ec2:DeleteLaunchTemplateVersions",
            "ec2:DescribeAvailabilityZones", "ec2:DescribeKeyPairs", "ec2:DescribeInstances",
            "ec2:DescribeInstanceTypes", "ec2:DescribeImages", "ec2:DescribeNetworkInterfaces",
            "ec2:CreateTags", "ec2:DeleteTags", "ec2:DescribeTags"
          ],
          "Resource": "*"
        },
        {
          "Sid": "IAM",
          "Effect": "Allow",
          "Action": [
            "iam:CreatePolicy", "iam:DeletePolicy", "iam:GetPolicy", "iam:GetPolicyVersion",
            "iam:ListPolicyVersions", "iam:CreatePolicyVersion", "iam:DeletePolicyVersion",
            "iam:TagPolicy", "iam:UntagPolicy", "iam:ListEntitiesForPolicy",
            "iam:CreateRole", "iam:DeleteRole", "iam:GetRole", "iam:UpdateRole",
            "iam:UpdateAssumeRolePolicy", "iam:AttachRolePolicy", "iam:DetachRolePolicy",
            "iam:ListAttachedRolePolicies", "iam:ListRolePolicies", "iam:TagRole", "iam:UntagRole",
            "iam:PassRole",
            "iam:CreateInstanceProfile", "iam:DeleteInstanceProfile", "iam:GetInstanceProfile",
            "iam:AddRoleToInstanceProfile", "iam:RemoveRoleFromInstanceProfile",
            "iam:CreateOpenIDConnectProvider", "iam:DeleteOpenIDConnectProvider", "iam:GetOpenIDConnectProvider",
            "iam:UpdateOpenIDConnectProviderThumbprint", "iam:TagOpenIDConnectProvider",
            "iam:UntagOpenIDConnectProvider", "iam:ListOpenIDConnectProviders"
          ],
          "Resource": "*"
        },
        {
          "Sid": "KMS",
          "Effect": "Allow",
          "Action": [
            "kms:CreateKey", "kms:DescribeKey",
            "kms:GetKeyPolicy", "kms:PutKeyPolicy", "kms:GetKeyRotationStatus",
            "kms:EnableKeyRotation", "kms:DisableKeyRotation",
            "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion",
            "kms:ListKeys", "kms:ListAliases", "kms:CreateAlias", "kms:DeleteAlias", "kms:UpdateAlias",
            "kms:TagResource", "kms:UntagResource",
            "kms:CreateGrant", "kms:ListGrants", "kms:RevokeGrant"
          ],
          "Resource": "*"
        }
      ]
    }
    ```
  </Accordion>

  <Accordion title="Policy 4 — Route 53, extended EC2, CloudWatch Logs, EKS Pod Identity, EventBridge, SSM">
    ```json theme={null}
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "Route53",
          "Effect": "Allow",
          "Action": [
            "route53:CreateHostedZone", "route53:DeleteHostedZone", "route53:GetHostedZone",
            "route53:ListHostedZones", "route53:ListHostedZonesByName",
            "route53:ChangeResourceRecordSets", "route53:ListResourceRecordSets", "route53:GetChange",
            "route53:ChangeTagsForResource", "route53:ListTagsForResource"
          ],
          "Resource": "*"
        },
        {
          "Sid": "EC2Extended",
          "Effect": "Allow",
          "Action": [
            "ec2:RunInstances", "ec2:TerminateInstances", "ec2:StopInstances", "ec2:StartInstances",
            "ec2:CreateNetworkAcl", "ec2:DeleteNetworkAcl", "ec2:CreateNetworkAclEntry",
            "ec2:DeleteNetworkAclEntry", "ec2:ReplaceNetworkAclAssociation", "ec2:DescribeNetworkAcls",
            "ec2:CreateNetworkInterface", "ec2:DeleteNetworkInterface",
            "ec2:DescribeNetworkInterfaceAttribute", "ec2:ModifyNetworkInterfaceAttribute",
            "ec2:DescribeInstanceAttribute", "ec2:DescribeInstanceCreditSpecifications",
            "ec2:ModifyInstanceAttribute", "ec2:DescribeVolumes"
          ],
          "Resource": "*"
        },
        {
          "Sid": "CloudWatchLogs",
          "Effect": "Allow",
          "Action": [
            "logs:CreateLogGroup", "logs:DeleteLogGroup", "logs:DescribeLogGroups",
            "logs:PutRetentionPolicy", "logs:DeleteRetentionPolicy",
            "logs:TagResource", "logs:UntagResource", "logs:ListTagsForResource"
          ],
          "Resource": "*"
        },
        {
          "Sid": "EKSPodIdentity",
          "Effect": "Allow",
          "Action": [
            "eks:CreatePodIdentityAssociation", "eks:DeletePodIdentityAssociation",
            "eks:DescribePodIdentityAssociation", "eks:ListPodIdentityAssociations"
          ],
          "Resource": "*"
        },
        {
          "Sid": "EventBridgeRules",
          "Effect": "Allow",
          "Action": [
            "events:PutRule", "events:DeleteRule", "events:DescribeRule",
            "events:EnableRule", "events:DisableRule",
            "events:PutTargets", "events:RemoveTargets", "events:ListTargetsByRule",
            "events:TagResource", "events:UntagResource", "events:ListTagsForResource"
          ],
          "Resource": "arn:aws:events:*:<ACCOUNT_ID>:rule/*"
        },
        {
          "Sid": "SSMReadServiceParams",
          "Effect": "Allow",
          "Action": ["ssm:GetParameter", "ssm:GetParameters"],
          "Resource": "arn:aws:ssm:*::parameter/aws/service/eks/*"
        }
      ]
    }
    ```

    <Note>
      For an EKS **managed node group** cluster like LILT's reference deployment:

      * The `eks:*PodIdentityAssociation` actions (`EKSPodIdentity`) **are
        required** — LILT binds every workload to its IAM role via EKS Pod
        Identity, so the deployment creates these associations. Do **not** drop
        them.
      * `EventBridgeRules` and the EKS-service `ssm:GetParameter` read are only
        needed for node auto-provisioning (Karpenter); they can be dropped here.
      * The instance-lifecycle actions in `EC2Extended` — `ec2:RunInstances`,
        `ec2:TerminateInstances`, `ec2:StopInstances`, `ec2:StartInstances` — are
        only needed for **self-managed nodes or Karpenter**. With EKS-managed node
        groups the EKS service manages instance lifecycle, so the deployment role
        does not use them; the remaining `EC2Extended` network-interface / ACL /
        volume actions are still required.
    </Note>
  </Accordion>
</AccordionGroup>

***

## Grant 2 · Runtime roles

These roles are **created for you** by the deployment (using Grant 1) and are
assumed by the cluster and its workloads at runtime. You do not hand-craft them;
they are enumerated here so your team can review the standing access that will
exist in the account after install.

### EKS service roles (AWS-managed policies)

The cluster and its worker nodes use roles built entirely from **AWS's own
managed policies** — AWS's least-privilege definitions for these service roles.
No custom permissions are involved.

| Role                              | Assumed by                            | AWS-managed policies attached                                                                                                                              |
| --------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Cluster role**                  | the EKS control plane                 | `AmazonEKSClusterPolicy`, `AmazonEKSVPCResourceController`                                                                                                 |
| **Node role**                     | worker EC2 instances                  | `AmazonEKSWorkerNodePolicy`, `AmazonEKS_CNI_Policy`, `AmazonEC2ContainerRegistryReadOnly`, `AmazonEKSVPCResourceController`, `CloudWatchAgentServerPolicy` |
| **Node role (auto-provisioning)** | as above                              | additionally `AmazonSSMManagedInstanceCore`                                                                                                                |
| **EBS CSI driver**                | the storage driver (EKS Pod Identity) | `service-role/AmazonEBSCSIDriverPolicy`                                                                                                                    |

<Note>
  LILT's reference cluster also attaches the legacy `AmazonEKSServicePolicy` to the
  cluster role. On EKS 1.24+ its permissions are folded into
  `AmazonEKSClusterPolicy`, so it is **optional** on new clusters.
</Note>

<Note>
  LILT runs on **EKS-managed node groups**: the worker and GPU nodes are EC2
  instances whose lifecycle Amazon EKS manages for you (creation, replacement,
  and scaling via the node group's `min`/`max`/`desired` config). There are no
  self-managed EC2 instances or Auto Scaling groups to operate. As a result the
  **Cluster Autoscaler** and **Karpenter** roles below, and the "auto-provisioning"
  node-role variant above (`AmazonSSMManagedInstanceCore`), are **not used** in
  this model — they are documented only for deployments that opt into node
  auto-provisioning.
</Note>

### Application & add-on roles (custom, least-privilege)

Each of these is a role with a small inline policy, scoped to the specific
resource LILT owns and assumed by one named Kubernetes workload via short-lived
federated credentials (EKS IRSA or Pod Identity). None of them can access
resources outside the ones LILT created.

<AccordionGroup>
  <Accordion title="LILT application — its own S3 bucket, SQS queue, KMS key" icon="star">
    The core LILT platform. Reads/writes objects in the LILT bucket, uses that
    bucket's KMS key, and consumes/produces on the LILT SQS queue — nothing else.

    ```json theme={null}
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowS3Access",
          "Effect": "Allow",
          "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket", "s3:GetBucketLocation"],
          "Resource": ["<S3_BUCKET_ARN>", "<S3_BUCKET_ARN>/*"]
        },
        {
          "Sid": "AllowKMSForS3",
          "Effect": "Allow",
          "Action": ["kms:Decrypt", "kms:Encrypt", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:DescribeKey"],
          "Resource": "<S3_KMS_KEY_ARN>"
        },
        {
          "Sid": "AllowSQSAccess",
          "Effect": "Allow",
          "Action": ["sqs:SendMessage", "sqs:ReceiveMessage", "sqs:DeleteMessage", "sqs:GetQueueAttributes", "sqs:GetQueueUrl"],
          "Resource": "<SQS_QUEUE_ARN>"
        }
      ]
    }
    ```

    <Note>
      In LILT's reference terraform this single Pod Identity role (bound to the
      cluster service account) also grants the same S3 object/list and KMS access
      to a **second bucket used by Argo Workflows** (<code>\<CLUSTER\_NAME>-bucket-argo</code>),
      encrypted with the same S3 KMS key — so `Resource` lists both
      <code>\<S3\_BUCKET\_ARN></code> and <code>\<ARGO\_S3\_BUCKET\_ARN></code> (and their `/*`). SQS is only
      present when the deployment provisions an SQS queue; the reference module
      does not, so the `AllowSQSAccess` statement is omitted there.
    </Note>
  </Accordion>

  <Accordion title="Container registry (Harbor) — S3 under its own prefix">
    LILT's in-cluster image registry stores layers under the `harbor/` prefix of
    the LILT bucket. Object access and bucket listing are both scoped to that
    prefix — it cannot see the rest of the bucket.

    ```json theme={null}
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowObjectAccessUnderHarborPrefix",
          "Effect": "Allow",
          "Action": [
            "s3:GetObject", "s3:PutObject", "s3:DeleteObject",
            "s3:AbortMultipartUpload", "s3:ListMultipartUploadParts"
          ],
          "Resource": "<S3_BUCKET_ARN>/harbor/*"
        },
        {
          "Sid": "AllowListBucketScopedToHarborPrefix",
          "Effect": "Allow",
          "Action": ["s3:ListBucket", "s3:ListBucketMultipartUploads"],
          "Resource": "<S3_BUCKET_ARN>",
          "Condition": { "StringLike": { "s3:prefix": ["harbor/*"] } }
        },
        {
          "Sid": "AllowBucketLocation",
          "Effect": "Allow",
          "Action": ["s3:GetBucketLocation"],
          "Resource": "<S3_BUCKET_ARN>"
        },
        {
          "Sid": "AllowKMSForS3",
          "Effect": "Allow",
          "Action": ["kms:Decrypt", "kms:Encrypt", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:DescribeKey"],
          "Resource": "<S3_KMS_KEY_ARN>"
        }
      ]
    }
    ```
  </Accordion>

  <Accordion title="ExternalDNS — the LILT DNS zone only">
    Keeps DNS records in sync for the application endpoints, scoped to LILT's
    hosted zone.

    ```json theme={null}
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowChangeRecords",
          "Effect": "Allow",
          "Action": ["route53:ChangeResourceRecordSets"],
          "Resource": "arn:aws:route53:::hostedzone/<HOSTED_ZONE_ID>"
        },
        {
          "Sid": "AllowListZones",
          "Effect": "Allow",
          "Action": ["route53:ListHostedZones", "route53:ListResourceRecordSets", "route53:ListTagsForResource"],
          "Resource": "*"
        }
      ]
    }
    ```
  </Accordion>

  <Accordion title="cert-manager — TLS certificate issuance (DNS-01)">
    Proves domain control for automated TLS certificates, scoped to LILT's
    hosted zone.

    ```json theme={null}
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowGetChange",
          "Effect": "Allow",
          "Action": ["route53:GetChange"],
          "Resource": "arn:aws:route53:::change/*"
        },
        {
          "Sid": "AllowDNS01Challenge",
          "Effect": "Allow",
          "Action": ["route53:ChangeResourceRecordSets", "route53:ListResourceRecordSets"],
          "Resource": "arn:aws:route53:::hostedzone/<HOSTED_ZONE_ID>"
        },
        {
          "Sid": "AllowListZones",
          "Effect": "Allow",
          "Action": ["route53:ListHostedZonesByName"],
          "Resource": "*"
        }
      ]
    }
    ```
  </Accordion>

  <Accordion title="Cluster Autoscaler — scaling the LILT cluster's node groups">
    Only present if the cluster scales via managed node groups. Scaling actions
    are gated on the cluster-ownership tag.

    ```json theme={null}
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowDescribe",
          "Effect": "Allow",
          "Action": [
            "autoscaling:DescribeAutoScalingGroups", "autoscaling:DescribeAutoScalingInstances",
            "autoscaling:DescribeLaunchConfigurations", "autoscaling:DescribeScalingActivities",
            "autoscaling:DescribeTags",
            "ec2:DescribeImages", "ec2:DescribeInstanceTypes", "ec2:DescribeLaunchTemplateVersions",
            "ec2:GetInstanceTypesFromInstanceRequirements", "eks:DescribeNodegroup"
          ],
          "Resource": "*"
        },
        {
          "Sid": "AllowScale",
          "Effect": "Allow",
          "Action": ["autoscaling:SetDesiredCapacity", "autoscaling:TerminateInstanceInAutoScalingGroup"],
          "Resource": "*",
          "Condition": { "StringEquals": { "aws:ResourceTag/kubernetes.io/cluster/<CLUSTER_NAME>": "owned" } }
        }
      ]
    }
    ```
  </Accordion>

  <Accordion title="Node auto-provisioning (Karpenter) — cluster-tagged EC2 only">
    Only present if node auto-provisioning is enabled. Launches and terminates
    nodes for the LILT cluster; every mutating action is gated on the cluster
    tag and region. This is the upstream Karpenter controller policy.

    ```json theme={null}
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowScopedEC2InstanceAccessActions",
          "Effect": "Allow",
          "Resource": [
            "arn:aws:ec2:<REGION>::image/*", "arn:aws:ec2:<REGION>::snapshot/*",
            "arn:aws:ec2:<REGION>:*:security-group/*", "arn:aws:ec2:<REGION>:*:subnet/*",
            "arn:aws:ec2:<REGION>:*:capacity-reservation/*"
          ],
          "Action": ["ec2:RunInstances", "ec2:CreateFleet"]
        },
        {
          "Sid": "AllowRegionalReadActions",
          "Effect": "Allow",
          "Resource": "*",
          "Action": [
            "ec2:DescribeCapacityReservations", "ec2:DescribeImages", "ec2:DescribeInstances",
            "ec2:DescribeInstanceStatus", "ec2:DescribeInstanceTypeOfferings", "ec2:DescribeInstanceTypes",
            "ec2:DescribeLaunchTemplates", "ec2:DescribePlacementGroups", "ec2:DescribeSecurityGroups",
            "ec2:DescribeSpotPriceHistory", "ec2:DescribeSubnets"
          ],
          "Condition": { "StringEquals": { "aws:RequestedRegion": "<REGION>" } }
        },
        {
          "Sid": "AllowSSMReadActions",
          "Effect": "Allow",
          "Resource": "arn:aws:ssm:<REGION>::parameter/aws/service/*",
          "Action": "ssm:GetParameter"
        },
        {
          "Sid": "AllowPricingReadActions",
          "Effect": "Allow",
          "Resource": "*",
          "Action": "pricing:GetProducts"
        },
        {
          "Sid": "AllowInterruptionQueueActions",
          "Effect": "Allow",
          "Resource": "<KARPENTER_SQS_QUEUE_ARN>",
          "Action": ["sqs:DeleteMessage", "sqs:GetQueueUrl", "sqs:ReceiveMessage"]
        },
        {
          "Sid": "AllowPassingInstanceRole",
          "Effect": "Allow",
          "Resource": "<NODE_ROLE_ARN>",
          "Action": "iam:PassRole",
          "Condition": { "StringEquals": { "iam:PassedToService": ["ec2.amazonaws.com"] } }
        },
        {
          "Sid": "AllowAPIServerEndpointDiscovery",
          "Effect": "Allow",
          "Resource": "arn:aws:eks:<REGION>:<ACCOUNT_ID>:cluster/<CLUSTER_NAME>",
          "Action": "eks:DescribeCluster"
        }
      ]
    }
    ```

    <Note>
      Abbreviated to the core statements. The full policy additionally scopes
      `ec2:CreateFleet` / `RunInstances` / `CreateLaunchTemplate` / `CreateTags` /
      `TerminateInstances` and the `iam:*InstanceProfile` actions by the
      <code>kubernetes.io/cluster/\<CLUSTER\_NAME>: owned</code> and `karpenter.sh/nodepool`
      tags — use the current published
      [Karpenter controller policy](https://karpenter.sh/docs/reference/cloudformation/)
      for your Karpenter version.
    </Note>
  </Accordion>

  <Accordion title="AWS Load Balancer Controller — load balancers it creates">
    Provisions the ALB/NLB load balancers for the application's public
    endpoints. Management actions are gated on the controller's ownership tag.

    ```json theme={null}
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowCreateServiceLinkedRole",
          "Effect": "Allow",
          "Action": ["iam:CreateServiceLinkedRole"],
          "Resource": "*",
          "Condition": { "StringEquals": { "iam:AWSServiceName": "elasticloadbalancing.amazonaws.com" } }
        },
        {
          "Sid": "AllowDescribe",
          "Effect": "Allow",
          "Action": [
            "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",
            "elasticloadbalancing:Describe*"
          ],
          "Resource": "*"
        },
        {
          "Sid": "AllowManageOwnedResources",
          "Effect": "Allow",
          "Action": [
            "elasticloadbalancing:CreateLoadBalancer", "elasticloadbalancing:CreateTargetGroup",
            "elasticloadbalancing:CreateListener", "elasticloadbalancing:DeleteListener",
            "elasticloadbalancing:CreateRule", "elasticloadbalancing:DeleteRule",
            "elasticloadbalancing:ModifyLoadBalancerAttributes", "elasticloadbalancing:SetSubnets",
            "elasticloadbalancing:SetSecurityGroups", "elasticloadbalancing:DeleteLoadBalancer",
            "elasticloadbalancing:ModifyTargetGroup", "elasticloadbalancing:ModifyTargetGroupAttributes",
            "elasticloadbalancing:DeleteTargetGroup", "elasticloadbalancing:RegisterTargets",
            "elasticloadbalancing:DeregisterTargets", "elasticloadbalancing:ModifyListener",
            "elasticloadbalancing:AddTags", "elasticloadbalancing:RemoveTags",
            "ec2:CreateSecurityGroup", "ec2:AuthorizeSecurityGroupIngress",
            "ec2:RevokeSecurityGroupIngress", "ec2:CreateTags", "ec2:DeleteTags"
          ],
          "Resource": "*",
          "Condition": { "Null": { "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" } }
        }
      ]
    }
    ```

    <Note>
      Condensed for readability. Deploy the exact, tag-condition-complete policy
      from the
      [AWS Load Balancer Controller install guide](https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/deploy/installation/)
      matching your controller version.
    </Note>
  </Accordion>

  <Accordion title="External Secrets Operator (optional) — read the LILT-managed secrets only">
    Only present when the deployment uses the External Secrets Operator to sync
    AWS Secrets Manager into the cluster; some deployments do not, in which case
    this role is absent. When used, it syncs a fixed set of secrets (the RDS
    credentials, the ingress TLS secret, and the SMTP secret): read-only and
    scoped to exactly those secret ARNs — it cannot enumerate or read any other
    secret in the account. Assumed by the `external-secrets-sa` service account
    in the `external-secrets` namespace via EKS Pod Identity.

    ```json theme={null}
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowReadLiltManagedSecrets",
          "Effect": "Allow",
          "Action": [
            "secretsmanager:GetResourcePolicy", "secretsmanager:GetSecretValue",
            "secretsmanager:DescribeSecret", "secretsmanager:ListSecretVersionIds"
          ],
          "Resource": ["<RDS_SECRET_ARN>", "<SSL_SECRET_ARN>", "<SMTP_SECRET_ARN>"]
        }
      ]
    }
    ```
  </Accordion>

  <Accordion title="Bastion host (optional) — describe the cluster + Session Manager">
    Only present when a bastion is enabled (`enable_bastion`). An EC2 instance
    role that lets operators run `aws eks describe-cluster` /
    `update-kubeconfig` against this one cluster and reach the private bastion
    over AWS Systems Manager Session Manager (no public IP or open SSH port
    required). It attaches the AWS-managed `AmazonSSMManagedInstanceCore` policy
    plus the inline statement below.

    ```json theme={null}
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": ["eks:DescribeCluster"],
          "Resource": "arn:aws:eks:<REGION>:<ACCOUNT_ID>:cluster/<CLUSTER_NAME>"
        }
      ]
    }
    ```
  </Accordion>
</AccordionGroup>

***

## Cluster service account & Pod Identity wiring

The runtime roles above are not attached to pods directly. Each is bound to a
named **Kubernetes service account (SA)**, and an **EKS Pod Identity
association** maps that SA to the IAM role. This section describes how LILT's
terraform sets that up so your team can see exactly which in-cluster identity
receives which AWS permissions.

### The LILT cluster service account

LILT's application does **not** spread its AWS access across many service
accounts. A single service account — configured via the `cluster_service_account`
variable and created in the deployment namespace (`namespace`, default `lilt`) —
is the identity used for **all** of the application's AWS access (S3, and any
other application-level AWS resources). It is a plain service account with **no
IRSA `eks.amazonaws.com/role-arn` annotation**: the role binding is done outside
the SA object, by the Pod Identity association.

```hcl theme={null}
# The single SA the LILT application runs under
resource "kubernetes_service_account" "cluster_sa" {
  metadata {
    name      = var.cluster_service_account   # e.g. "lilt-sa"
    namespace = var.namespace                 # default "lilt"
  }
}

# Bind that SA to the S3/application IAM role via EKS Pod Identity
resource "aws_eks_pod_identity_association" "s3_attach_to_sa" {
  cluster_name    = <cluster>
  namespace       = var.namespace
  service_account = var.cluster_service_account
  role_arn        = <s3_pod_role ARN>          # the LILT application role
}
```

### How the pieces fit together

Three objects have to agree for a pod to receive AWS permissions:

1. **The Kubernetes service account** — the identity the pod runs as
   (`namespace` + `name`).
2. **The IAM role** — carries the least-privilege policy (e.g. the S3 + KMS
   permissions) and has a **trust policy that allows `pods.eks.amazonaws.com` to
   assume it** (`sts:AssumeRole` + `sts:TagSession`). This is what makes it a
   Pod Identity role rather than an IRSA/OIDC role.
3. **The Pod Identity association** — the AWS-side record that says "SA
   *namespace/name* on *this cluster* may assume *this role*."

At runtime the **EKS Pod Identity Agent** (installed as a cluster add-on) sees a
pod running under that service account, matches it to the association, and hands
the pod **short-lived, automatically-rotated credentials** for the mapped role.
No static keys and no OIDC provider are involved — the SA name/namespace pair
*is* the binding key.

<Warning>
  Because the association is keyed on `namespace` + `service account name`, those
  must match on both sides. If the SA is renamed, moved to another namespace, or
  recreated in a different namespace without updating the association, the pod
  loses its AWS access. Some add-on service accounts additionally have **fixed
  names** required by their controllers — e.g. the EBS CSI driver expects
  `ebs-csi-controller-sa` and the External Secrets Operator expects
  `external-secrets-sa` — and cannot be renamed.
</Warning>

### Service-account-to-role map

| Kubernetes service account                 | Namespace                    | Bound IAM role (Grant 2)          | Grants                                   |
| ------------------------------------------ | ---------------------------- | --------------------------------- | ---------------------------------------- |
| `cluster_service_account` (e.g. `lilt-sa`) | `namespace` (default `lilt`) | LILT application role             | S3 (LILT + Argo buckets) and its KMS key |
| `aws-load-balancer-controller-sa`          | `kube-system`                | AWS Load Balancer Controller role | Manage the cluster's ALBs/NLBs           |
| `ebs-csi-controller-sa`                    | `kube-system`                | EBS CSI driver role               | `AmazonEBSCSIDriverPolicy`               |
| `external-secrets-sa` (if used)            | `external-secrets`           | External Secrets role             | Read the RDS / SSL / SMTP secrets        |

<Note>
  The LILT cluster service account also carries in-cluster **RBAC** (Kubernetes
  `Role`/`RoleBinding`s for the application and Argo Workflows components). That
  RBAC governs access to the Kubernetes API *inside* the cluster and is entirely
  separate from the AWS IAM permissions granted through the Pod Identity
  association described here — the same SA simply serves both roles.
</Note>

***

## How access is brokered

No component uses a long-lived IAM access key. Each principal obtains
short-lived credentials via a federated trust:

| Principal                                                                      | Mechanism                                     | How it is trusted                                                                                                                                                                                                                                                                                          |
| ------------------------------------------------------------------------------ | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| App, registry, ExternalDNS, cert-manager, autoscaler, Karpenter, LB controller | **EKS IRSA** (OIDC) *or* **EKS Pod Identity** | With IRSA, scoped to one Kubernetes service account (<code>\<OIDC\_PROVIDER>:sub = system:serviceaccount:\<namespace>:\<sa-name></code>); with Pod Identity, the same SA is bound via an association. LILT's reference terraform uses **Pod Identity** for the app and LB controller — see the note below. |
| External Secrets Operator (if used)                                            | **EKS Pod Identity**                          | The `external-secrets-sa` service account in the `external-secrets` namespace                                                                                                                                                                                                                              |
| EBS CSI storage driver                                                         | **EKS Pod Identity**                          | The EKS Pod Identity service on your cluster                                                                                                                                                                                                                                                               |
| Worker nodes                                                                   | **EC2 instance profile**                      | The node role, assumed by EC2                                                                                                                                                                                                                                                                              |
| Bastion host (if enabled)                                                      | **EC2 instance profile**                      | The bastion role, assumed by EC2                                                                                                                                                                                                                                                                           |
| Control plane                                                                  | **EKS service role**                          | Assumed by the EKS service                                                                                                                                                                                                                                                                                 |
| **Deployment role (Grant 1)**                                                  | **Your choice**                               | You decide who may assume it — a federated CI identity or a named administrator, per your own access standards                                                                                                                                                                                             |

<Note>
  LILT's reference terraform implements **all** of its workload roles (the LILT
  application/S3, the AWS Load Balancer Controller, the EBS CSI driver, and — when
  used — the External Secrets Operator) via **EKS Pod Identity** rather than IRSA,
  and therefore provisions **no OIDC identity provider**. Either mechanism is
  supported; if your deployment uses Pod Identity throughout, the OIDC provider
  and its IAM permissions are not required, and each workload role trusts
  `pods.eks.amazonaws.com` instead of a web-identity subject.
</Note>

<Warning>
  The deployment role's **trust policy is yours to define** — this document does
  not prescribe who may assume it. Grant the Grant 1 permissions to whichever
  identity your team authorizes to run the deployment, following your federation
  and access-control standards.
</Warning>
