IAM Setup
The two IAM identities involved in running Clu — one customer-side, one seller-side — and what each one needs.
Customer-side: in-cluster agent (IRSA)
When an operator installs Clu in their EKS cluster, the agent's pod
ServiceAccount (clu-ops/clu-ops-agent) assumes an IAM role via IRSA
to call AWS APIs on the customer's behalf. The role is created in
the customer's account, trusts the customer's EKS OIDC provider,
and is scoped per active capability.
The recommended pattern: create one IAM policy per capability tier
you've enabled in Helm (see Capabilities), attach it
to the role IRSA assumes, then pass the role ARN to Helm via
serviceAccount.annotations."eks.amazonaws.com/role-arn".
The full install walkthrough — including the trust-policy template
and the aws iam create-policy / aws iam attach-role-policy
commands — lives in Getting started. This
page is the IAM reference: paste the JSON below into the AWS Console
or use it directly with aws iam create-policy --policy-document.
Core (always on)
Required for every Clu install. Lets the pod call Bedrock for inference, read CloudWatch metrics + logs, and register usage with the AWS Marketplace metering service.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "BedrockInvokeAllowedFamilies",
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream",
"bedrock:Converse",
"bedrock:ConverseStream"
],
"Resource": [
"arn:aws:bedrock:*::foundation-model/anthropic.*",
"arn:aws:bedrock:*::foundation-model/meta.llama3-1-*",
"arn:aws:bedrock:*::foundation-model/meta.llama3-3-*",
"arn:aws:bedrock:*::foundation-model/openai.gpt-oss-*",
"arn:aws:bedrock:*::foundation-model/mistral.mistral-large-*",
"arn:aws:bedrock:*:*:inference-profile/*.anthropic.*",
"arn:aws:bedrock:*:*:inference-profile/*.meta.*",
"arn:aws:bedrock:*:*:inference-profile/*.openai.*",
"arn:aws:bedrock:*:*:inference-profile/*.mistral.*",
"arn:aws:bedrock:*:*:inference-profile/anthropic.*",
"arn:aws:bedrock:*:*:inference-profile/meta.*",
"arn:aws:bedrock:*:*:inference-profile/openai.*",
"arn:aws:bedrock:*:*:inference-profile/mistral.*"
]
},
{
"Sid": "CloudWatchRead",
"Effect": "Allow",
"Action": [
"cloudwatch:GetMetricData",
"cloudwatch:GetMetricStatistics",
"cloudwatch:ListMetrics",
"cloudwatch:DescribeAlarms",
"logs:StartQuery",
"logs:GetQueryResults",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams"
],
"Resource": "*"
},
{
"Sid": "MarketplaceMetering",
"Effect": "Allow",
"Action": [
"aws-marketplace:RegisterUsage"
],
"Resource": "*"
}
]
}
The Bedrock Resource list intentionally enumerates four model
families (Anthropic Claude, Llama, gpt-oss, Mistral). Add or remove
families to match what you've opted into via Bedrock's Model Access
console. Inference-profile ARNs are listed twice because the
cross-region profile prefix varies (us.anthropic.* vs raw
anthropic.*); both forms exist depending on AWS region.
Cloud (optional)
Required only when modules.cloud.enabled=true in Helm values.
Adds AWS-side reads so the agent can map IAM roles, RDS clusters,
S3 buckets, ECR repositories, and cost data alongside the cluster
view. Read-only. No mutations.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "BedrockInvoke",
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
],
"Resource": [
"arn:aws:bedrock:*::foundation-model/anthropic.claude-3-*",
"arn:aws:bedrock:*::foundation-model/anthropic.claude-haiku-*",
"arn:aws:bedrock:*::foundation-model/anthropic.claude-sonnet-*"
]
},
{
"Sid": "CloudWatch",
"Effect": "Allow",
"Action": [
"cloudwatch:GetMetricData",
"cloudwatch:GetMetricStatistics",
"cloudwatch:ListMetrics",
"cloudwatch:DescribeAlarms",
"logs:StartQuery",
"logs:GetQueryResults",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams"
],
"Resource": "*"
},
{
"Sid": "Marketplace",
"Effect": "Allow",
"Action": [
"aws-marketplace:RegisterUsage",
"aws-marketplace:MeterUsage",
"aws-marketplace:GetEntitlements"
],
"Resource": "*"
},
{
"Sid": "IAMRead",
"Effect": "Allow",
"Action": [
"iam:ListRoles",
"iam:GetRole",
"iam:ListRolePolicies",
"iam:GetRolePolicy",
"iam:ListAttachedRolePolicies",
"iam:GetPolicy",
"iam:GetPolicyVersion",
"iam:ListPolicies",
"iam:ListOpenIDConnectProviders",
"iam:GetOpenIDConnectProvider",
"iam:SimulatePrincipalPolicy"
],
"Resource": "*"
},
{
"Sid": "EKSRead",
"Effect": "Allow",
"Action": [
"eks:DescribeCluster",
"eks:ListClusters",
"eks:ListAddons",
"eks:DescribeAddon",
"eks:DescribeAddonVersions",
"eks:DescribeAddonConfiguration",
"eks:ListNodegroups",
"eks:DescribeNodegroup",
"eks:ListFargateProfiles",
"eks:DescribeFargateProfile",
"eks:ListIdentityProviderConfigs",
"eks:DescribeIdentityProviderConfig",
"eks:ListPodIdentityAssociations",
"eks:DescribePodIdentityAssociation",
"eks:ListAccessEntries",
"eks:DescribeAccessEntry",
"eks:ListAssociatedAccessPolicies",
"eks:ListAccessPolicies",
"eks:ListInsights",
"eks:DescribeInsight",
"eks:ListUpdates",
"eks:DescribeUpdate",
"eks:ListTagsForResource"
],
"Resource": "*"
},
{
"Sid": "EKSAddonManagement",
"Effect": "Allow",
"Action": [
"eks:CreateAddon",
"eks:UpdateAddon",
"eks:UpdateAddonConfiguration",
"eks:DeleteAddon"
],
"Resource": "*"
},
{
"Sid": "ManagedServicesRead",
"Effect": "Allow",
"Action": [
"rds:DescribeDBInstances",
"rds:DescribeDBClusters",
"rds:ListTagsForResource",
"elasticache:DescribeCacheClusters",
"elasticache:DescribeReplicationGroups",
"elasticache:ListTagsForResource",
"s3:ListAllMyBuckets",
"s3:GetBucketLocation",
"s3:GetBucketTagging",
"s3:GetBucketPolicy",
"s3:GetBucketEncryption",
"ecr:DescribeRepositories",
"ecr:DescribeImages",
"ecr:ListImages",
"ecr:GetRepositoryPolicy"
],
"Resource": "*"
},
{
"Sid": "SecretsMetadata",
"Effect": "Allow",
"Action": [
"secretsmanager:ListSecrets",
"secretsmanager:DescribeSecret"
],
"Resource": "*"
},
{
"Sid": "Networking",
"Effect": "Allow",
"Action": [
"ec2:DescribeVpcs",
"ec2:DescribeSubnets",
"ec2:DescribeRouteTables",
"ec2:DescribeSecurityGroups",
"ec2:DescribeNetworkAcls",
"ec2:DescribeNatGateways",
"ec2:DescribeInternetGateways",
"ec2:DescribeInstances",
"ec2:DescribeNetworkInterfaces"
],
"Resource": "*"
},
{
"Sid": "SavingsDetectors",
"Effect": "Allow",
"Action": [
"ec2:DescribeVolumes",
"elasticloadbalancing:DescribeLoadBalancers",
"elasticloadbalancing:DescribeTargetGroups",
"elasticloadbalancing:DescribeTargetHealth",
"elasticloadbalancing:DescribeTags"
],
"Resource": "*"
},
{
"Sid": "Cost",
"Effect": "Allow",
"Action": [
"ce:GetCostAndUsage",
"ce:GetCostForecast",
"ce:GetTags",
"ce:GetDimensionValues"
],
"Resource": "*"
}
]
}
SecretsMetadata covers list/describe only — no secretsmanager:GetSecretValue, so the agent never sees Secret bodies. The
Cost block is optional in practice; drop it if you don't have
Cost Explorer enabled or don't want cost rollups in the agent's
recommendations.
Core Plus (optional)
Required only when modules.corePlus.enabled=true in Helm values. The
Core Plus's destructive verbs (k8s_apply, k8s_scale,
k8s_restart_rollout, helm_install, etc.) are gated by the
in-cluster RBAC writer ClusterRole — not by IAM. So the IAM
policy here is identical to the Cloud's read scope; the
write authority lives in K8s.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "IdpInheritsCloudReads",
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream",
"cloudwatch:GetMetricData",
"cloudwatch:GetMetricStatistics",
"cloudwatch:ListMetrics",
"cloudwatch:DescribeAlarms",
"logs:StartQuery",
"logs:GetQueryResults",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
"aws-marketplace:RegisterUsage",
"aws-marketplace:MeterUsage",
"aws-marketplace:GetEntitlements",
"iam:ListRoles",
"iam:GetRole",
"iam:ListRolePolicies",
"iam:GetRolePolicy",
"iam:ListAttachedRolePolicies",
"iam:GetPolicy",
"iam:GetPolicyVersion",
"iam:ListPolicies",
"iam:ListOpenIDConnectProviders",
"iam:GetOpenIDConnectProvider",
"rds:DescribeDBInstances",
"rds:DescribeDBClusters",
"rds:ListTagsForResource",
"elasticache:DescribeCacheClusters",
"elasticache:DescribeReplicationGroups",
"elasticache:ListTagsForResource",
"s3:ListAllMyBuckets",
"s3:GetBucketLocation",
"s3:GetBucketTagging",
"s3:GetBucketPolicy",
"s3:GetBucketEncryption",
"ecr:DescribeRepositories",
"ecr:DescribeImages",
"ecr:ListImages",
"ecr:GetRepositoryPolicy",
"secretsmanager:ListSecrets",
"secretsmanager:DescribeSecret",
"ec2:DescribeVpcs",
"ec2:DescribeSubnets",
"ec2:DescribeRouteTables",
"ec2:DescribeSecurityGroups",
"ec2:DescribeNetworkAcls",
"ec2:DescribeNatGateways",
"ec2:DescribeInternetGateways",
"ec2:DescribeInstances",
"ec2:DescribeNetworkInterfaces",
"ce:GetCostAndUsage",
"ce:GetCostForecast",
"ce:GetTags",
"ce:GetDimensionValues"
],
"Resource": "*"
}
]
}
If you've already attached the Cloud policy, you can attach this one in addition to it (the actions are duplicated; AWS dedupes at evaluation time) or merge them into a single policy. Either works; we ship them separately so you can opt out of Cloud while keeping IDP, or vice versa.
Trust policy
The role assumes itself via the customer's EKS OIDC provider. Use this trust-policy template, replacing the placeholder values:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/<OIDC_ISSUER_HOST>"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"<OIDC_ISSUER_HOST>:sub": "system:serviceaccount:clu-ops:clu-ops-agent",
"<OIDC_ISSUER_HOST>:aud": "sts.amazonaws.com"
}
}
}
]
}
Replace <ACCOUNT_ID> with your AWS account number and
<OIDC_ISSUER_HOST> with the host portion of your EKS cluster's
OIDC issuer URL (e.g.
oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE).
The full apply sequence:
# 1. Create one IAM policy per capability you've enabled. Save the
# JSON blocks above to files (cluster.json, cloud.json, idp.json)
# or pipe them inline:
aws iam create-policy --policy-name clu-cluster --policy-document file://cluster.json
aws iam create-policy --policy-name clu-cloud --policy-document file://cloud.json # optional
aws iam create-policy --policy-name clu-idp --policy-document file://idp.json # optional
# 2. Create the role with the trust policy:
aws iam create-role --role-name clu-irsa-role --assume-role-policy-document file://trust.json
# 3. Attach each policy you created:
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
aws iam attach-role-policy --role-name clu-irsa-role \
--policy-arn arn:aws:iam::$ACCOUNT_ID:policy/clu-cluster
aws iam attach-role-policy --role-name clu-irsa-role \
--policy-arn arn:aws:iam::$ACCOUNT_ID:policy/clu-cloud # optional
aws iam attach-role-policy --role-name clu-irsa-role \
--policy-arn arn:aws:iam::$ACCOUNT_ID:policy/clu-idp # optional
# 4. Pass the role ARN to Helm:
helm install clu-ops-agent \
oci://709825985650.dkr.ecr.us-east-1.amazonaws.com/cloudology/clu-ops-agent \
--version 0.1.2 \
--namespace clu-ops --create-namespace \
--set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=arn:aws:iam::$ACCOUNT_ID:role/clu-irsa-role