Connect your AWS account
The scanner is read-only — it only reads cost, metrics and resource metadata. Grant access with read-only keys or a cross-account role. Both Console and CLI steps are below.
Choose a method
| Method | Best for | Stores a secret? | Setup |
|---|---|---|---|
| A. Read-only IAM user (access keys) | Testing / your own account | Yes (encrypted at rest) | Easiest — paste keys |
| B. Cross-account IAM role (AssumeRole) | Production / third-party accounts | No | More secure — paste a Role ARN |
Permissions the optimizer needs
Option 1 — AWS-managed policy (simplest)
Attach ReadOnlyAccess (arn:aws:iam::aws:policy/ReadOnlyAccess). It
covers everything the scanner uses — nothing to maintain.
Option 2 — Minimal custom policy (least privilege)
Grant only what’s used. Create a policy from this document — every action is read-only:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "BillingOptimizerReadOnly",
"Effect": "Allow",
"Action": [
"ce:GetCostAndUsage",
"ce:GetSavingsPlansCoverage",
"ce:GetRightsizingRecommendation",
"ce:GetReservationPurchaseRecommendation",
"ce:GetSavingsPlansPurchaseRecommendation",
"ce:GetAnomalies",
"ec2:Describe*",
"elasticloadbalancing:Describe*",
"rds:DescribeDBInstances",
"rds:DescribeDBSnapshots",
"rds:DescribeDBClusters",
"elasticache:DescribeCacheClusters",
"dynamodb:ListTables",
"dynamodb:DescribeTable",
"redshift:DescribeClusters",
"es:ListDomainNames",
"es:DescribeElasticsearchDomains",
"ecs:ListClusters",
"ecs:ListServices",
"ecs:DescribeServices",
"ecs:DescribeTaskDefinition",
"eks:ListClusters",
"eks:ListNodegroups",
"eks:DescribeNodegroup",
"eks:ListFargateProfiles",
"lambda:ListFunctions",
"lambda:ListProvisionedConcurrencyConfigs",
"s3:ListAllMyBuckets",
"s3:GetBucketLocation",
"s3:GetBucketLifecycleConfiguration",
"cloudwatch:GetMetricStatistics",
"compute-optimizer:GetEC2InstanceRecommendations",
"compute-optimizer:GetEBSVolumeRecommendations",
"compute-optimizer:GetLambdaFunctionRecommendations",
"pricing:GetProducts",
"sts:GetCallerIdentity"
],
"Resource": "*"
}
]
}
Method A — Read-only IAM user with access keys
- IAM → Users → Create user
- User name:
billing-optimizer-readonly - Do NOT tick “Provide user access to the AWS Management Console” — we only need programmatic keys
- Next → Attach policies directly → search
ReadOnlyAccessand select it
(or least-privilege: Create policy → JSON → paste the policy above → name itBillingOptimizerReadOnly→ select it) - Next → Create user
- Open the user → Security credentials → Create access key
- Choose “Application running outside AWS” → Create access key
- Copy the Access Key ID + Secret Access Key (the secret is shown only once)
# 1. create the user
aws iam create-user --user-name billing-optimizer-readonly
# 2a. attach the managed read-only policy (simplest)
aws iam attach-user-policy \
--user-name billing-optimizer-readonly \
--policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
# 2b. (OR) create + attach the minimal custom policy
# save the JSON above as readonly-policy.json first
aws iam create-policy --policy-name BillingOptimizerReadOnly \
--policy-document file://readonly-policy.json
aws iam attach-user-policy --user-name billing-optimizer-readonly \
--policy-arn arn:aws:iam::<YOUR_ACCOUNT_ID>:policy/BillingOptimizerReadOnly
# 3. create the access key (prints AccessKeyId + SecretAccessKey ONCE)
aws iam create-access-key --user-name billing-optimizer-readonly
Dashboard → + Add account → Access keys → paste the Access Key ID + Secret →
leave Regions blank to scan all regions (or type e.g. eu-west-2) → Save.
The app validates the keys via STS and auto-detects your account ID.
Method B — Cross-account IAM role (AssumeRole)
No secret is stored — the app assumes a role to get temporary credentials. You need a Hub account ID (the identity the app runs with) and an External ID (any shared secret).
sts:AssumeRole. On a plain Render deploy with no AWS credentials,
use Method A; use roles when the app runs on AWS (EC2/ECS task role) or with a hub IAM user configured.- IAM → Roles → Create role
- Trusted entity: AWS account → Another AWS account → enter the Hub account ID
- Tick Require external ID → enter your External ID (e.g.
optimizer-9f3c-7a21) - Next → Permissions → attach
ReadOnlyAccess(or your custom policy) - Name it
BillingOptimizerReadOnly→ Create role - Copy the Role ARN (e.g.
arn:aws:iam::111111111111:role/BillingOptimizerReadOnly)
# 1. write the trust policy (who may assume the role)
cat > trust-policy.json <<'JSON'
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::HUB_ACCOUNT_ID:root" },
"Action": "sts:AssumeRole",
"Condition": { "StringEquals": { "sts:ExternalId": "optimizer-9f3c-7a21" } }
}]
}
JSON
# 2. create the role with that trust policy
aws iam create-role --role-name BillingOptimizerReadOnly \
--assume-role-policy-document file://trust-policy.json
# 3. attach read-only permissions
aws iam attach-role-policy --role-name BillingOptimizerReadOnly \
--policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
# 4. print the Role ARN to paste into the app
aws iam get-role --role-name BillingOptimizerReadOnly \
--query 'Role.Arn' --output text
Dashboard → + Add account → Cross-account role → paste the Role ARN and the External ID → Save.
Verify & revoke
The app calls sts:GetCallerIdentity when you add an account and rejects bad
credentials with a clear message. Test keys yourself:
AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... aws sts get-caller-identity
Clean up when you’re done:
# access keys
aws iam delete-access-key --user-name billing-optimizer-readonly --access-key-id AKIA...
aws iam detach-user-policy --user-name billing-optimizer-readonly \
--policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
aws iam delete-user --user-name billing-optimizer-readonly
# role
aws iam detach-role-policy --role-name BillingOptimizerReadOnly \
--policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
aws iam delete-role --role-name BillingOptimizerReadOnly
In the Console you can also just deactivate/delete the access key or delete the role in IAM.
Security notes
- Every action above is read-only — the optimizer can’t change your AWS resources.
- Prefer the role method beyond personal testing — no stored secret, instantly revocable.
- If you use access keys, delete/rotate them when done testing.
- Stored secrets are encrypted at rest and never shown back to the browser.