InfraSync scans your AWS account with read-only access and generates Terraform plus cost-optimization recommendations. It never creates, modifies, or deletes your AWS resources. This guide covers both ways to grant that access — a cross-account IAM role, or an IAM user with access keys — using the AWS Management Console, the AWS CLI, or AWS CloudFormation.
- Prerequisites
- Choosing a connection method
- Permissions reference
- Method 1 — Cross-account IAM role (recommended)
- Method 2 — IAM user with access keys
- Optional permissions
- Adding the account to InfraSync
- Verifying the connection
- Revoking access
- FAQ
Prerequisites
Before you begin
- An AWS account, and permission to create IAM roles or users in it (
iam:CreateRoleoriam:CreateUser). - An InfraSync account. Sign up free if you do not have one.
- For the cross-account role: the external ID InfraSync generates for your organization. You get it in Method 1, step 1.
- The AWS CLI, if you plan to follow the CLI procedures rather than using the console.
In the commands on this page, replace values written in UPPER_CASE with your own. Everything else can be pasted as written.
Choosing a connection method
Both methods give InfraSync the same read-only access. They differ in what you hand over and how you revoke it.
| Cross-account role | Access keys | |
|---|---|---|
| Credentials shared | None. InfraSync assumes the role for short-lived credentials. | A long-lived access key ID and secret. |
| Stored by InfraSync | Only the role ARN. | The secret, encrypted. |
| Revoking access | Delete the role. | Delete the access key or user. |
| Extra protection | External ID condition on the trust policy. | None beyond the key itself. |
| Setup time | About 5 minutes, or one CloudFormation stack. | About 2 minutes. |
| Recommended for | Production accounts. | Trials and sandbox accounts. |
Use the cross-account role for any account you care about. Nothing long-lived is stored, and deleting the role ends access account-wide and immediately. Access keys exist for speed, not for parity.
Permissions reference
InfraSync reads your infrastructure and never creates, modifies, or deletes your resources. The AWS managed ReadOnlyAccess policy covers every read. The only writes are to a Terraform state bucket you choose — and, if you want the button to work, opting the account into Compute Optimizer.
| Feature | What InfraSync does | Permissions |
|---|---|---|
| Scan and Terraform generation | Reads resource configuration across the 89 supported services. | ReadOnlyAccess |
| Cost optimizer | Reads Cost Explorer, Savings Plans, Compute Optimizer and CloudWatch metrics. | ReadOnlyAccess |
| Terraform state backend | Writes the generated terraform.tfstate to your bucket. Terraform's S3-native lock file is created and removed there too. | State bucket policy — get, put and delete objects in that bucket only |
| Drift fixes as pull requests | Reads your Terraform state and runs a read-only terraform plan (no state lock, no apply) to prove a proposed change does exactly what you approved. | ReadOnlyAccess plus read on the state bucket; KMS decrypt if your state uses your own key |
| Live drift detection | Your account pushes change events to InfraSync. | None — configuration only |
| Enable Compute Optimizer button | Opts the account into AWS Compute Optimizer. | compute-optimizer:UpdateEnrollmentStatus (optional) |
The CloudFormation template in Method 1 sets all of this up in one stack: the role, a private versioned state bucket, and the scoped policies. If you create access by hand, attach ReadOnlyAccess plus the state bucket policy. ReadOnlyAccess stays correct as AWS adds services — a hand-built action list eventually misses one and breaks a scan.
You do not have to get this exactly right up front. InfraSync validates access when you connect and again before every scan, and names the exact action to add if one is missing.
Pull requests reach your repository through the InfraSync GitHub App, which has its own, separate permissions — see GitHub App permissions.
Method 1 — Cross-account IAM role (recommended)
You create a read-only role that InfraSync assumes for short-lived credentials. The role trusts InfraSync's AWS account (529928147507) and is scoped by an external ID — a per-account value that prevents the confused deputy problem.
Step 1 — To get your external ID
- In InfraSync, open Connect your account and choose Amazon Web Services.
- The form opens on IAM role, the recommended method. (Access keys is the other option; see Method 2.)
- Note the two values shown: InfraSync's AWS account ID (
529928147507) and your organization's external ID.
The external ID belongs to your InfraSync organization: every AWS account you connect from it uses the same value. Do not copy one from another organization, and do not invent your own — the trust policy must match the value InfraSync will present.
Then create the role using whichever of the following you prefer. All three produce the same result.
Step 2, option A — To create the role with AWS CloudFormation
Download the template, or copy it from below.
- Open the AWS CloudFormation console and choose Create stack, then With new resources (standard).
- Choose Upload a template file and select
infrasync-readonly-role.yaml. - For ExternalId, enter the external ID from step 1. Leave the other parameters at their defaults unless you need one of these:
- CreateStateBucket —
true(default) creates a private, versioned, encrypted bucket namedinfrasync-tfstate-ACCOUNT_ID-REGIONfor the Terraform state InfraSync generates. It is kept if you delete the stack. - ExistingStateBucket — the name of a state bucket you already use, so InfraSync can read and write state there too.
- StateKmsKeyArn — your KMS key, if that state is encrypted with it.
- AllowSecretValueRead —
false(default). See Secret values before turning it on.
- CreateStateBucket —
- Select I acknowledge that AWS CloudFormation might create IAM resources with custom names, then create the stack.
- When the stack reaches
CREATE_COMPLETE, open the Outputs tab and copy theRoleArnvalue — andStateBucketName, which you enter when you turn on the S3 remote-state backend in InfraSync.
To do the same with the AWS CLI, save the template as infrasync-readonly-role.yaml and run:
aws cloudformation deploy \
--template-file infrasync-readonly-role.yaml \
--stack-name infrasync-readonly-role \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides ExternalId=EXTERNAL_ID
aws cloudformation describe-stacks \
--stack-name infrasync-readonly-role \
--query "Stacks[0].Outputs[?OutputKey=='RoleArn'].OutputValue" \
--output text
The template itself:
AWSTemplateFormatVersion: "2010-09-09"
Description: >-
InfraSync cross-account access. Creates an IAM role that InfraSync (AWS
account 529928147507) assumes via STS, scoped by your per-account External
ID. The role reads your infrastructure (AWS-managed ReadOnlyAccess) and never
creates, modifies or deletes your resources. Optionally it also creates a
private, versioned S3 bucket for the Terraform state InfraSync generates, and
the role may write to that one bucket only.
Parameters:
ExternalId:
Type: String
NoEcho: true
MinLength: 8
Description: The External ID InfraSync showed you for this account.
RoleName:
Type: String
Default: infrasync-readonly-role
AllowedPattern: "[a-zA-Z0-9+=,.@_-]+"
CreateStateBucket:
Type: String
Default: "true"
AllowedValues: ["true", "false"]
Description: >-
Create an S3 bucket for Terraform state (versioned, encrypted, private)
and let InfraSync write state there. Set "false" if you only scan, or
already have a state bucket (then use ExistingStateBucket).
ExistingStateBucket:
Type: String
Default: ""
Description: >-
Optional. The name of a state bucket you already use. InfraSync gets the
same read/write on it as on a bucket this stack creates. Leave empty to
skip.
StateKmsKeyArn:
Type: String
Default: ""
Description: >-
Optional. If your state is encrypted with your own KMS key (SSE-KMS),
the key's ARN. InfraSync may use that key to read and write state, and
for nothing else.
EnableComputeOptimizerOptIn:
Type: String
Default: "true"
AllowedValues: ["true", "false"]
Description: >-
Let InfraSync's "Enable Compute Optimizer" button opt this account in
(compute-optimizer:UpdateEnrollmentStatus). "false" keeps it read-only.
AllowSecretValueRead:
Type: String
Default: "false"
AllowedValues: ["true", "false"]
Description: >-
Off by default. Terraform reads secret VALUES when it plans
aws_secretsmanager_secret_version and encrypted aws_ssm_parameter
resources. Turn on only if your Terraform manages those and you want
InfraSync's plan check to cover them.
Conditions:
MakeStateBucket: !Equals [!Ref CreateStateBucket, "true"]
HasExistingBucket: !Not [!Equals [!Ref ExistingStateBucket, ""]]
AnyStateBucket: !Or [!Condition MakeStateBucket, !Condition HasExistingBucket]
HasKmsKey: !Not [!Equals [!Ref StateKmsKeyArn, ""]]
ComputeOptimizer: !Equals [!Ref EnableComputeOptimizerOptIn, "true"]
SecretValues: !Equals [!Ref AllowSecretValueRead, "true"]
Resources:
# Terraform state: private, versioned (every state version recoverable),
# encrypted, TLS only. Kept when the stack is deleted — state must never
# disappear with the stack.
StateBucket:
Type: AWS::S3::Bucket
Condition: MakeStateBucket
DeletionPolicy: Retain
UpdateReplacePolicy: Retain
Properties:
BucketName: !Sub "infrasync-tfstate-${AWS::AccountId}-${AWS::Region}"
VersioningConfiguration:
Status: Enabled
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
BucketKeyEnabled: true
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
OwnershipControls:
Rules:
- ObjectOwnership: BucketOwnerEnforced
LifecycleConfiguration:
Rules:
- Id: expire-old-state-versions
Status: Enabled
NoncurrentVersionExpiration:
NoncurrentDays: 90
StateBucketPolicy:
Type: AWS::S3::BucketPolicy
Condition: MakeStateBucket
Properties:
Bucket: !Ref StateBucket
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: DenyInsecureTransport
Effect: Deny
Principal: "*"
Action: "s3:*"
Resource:
- !GetAtt StateBucket.Arn
- !Sub "${StateBucket.Arn}/*"
Condition:
Bool:
aws:SecureTransport: "false"
InfraSyncRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Ref RoleName
MaxSessionDuration: 3600
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS: "arn:aws:iam::529928147507:root"
Action: sts:AssumeRole
Condition:
StringEquals:
sts:ExternalId: !Ref ExternalId
# Reads every supported service — scanning, cost analysis, and the
# drift agent's read-only `terraform plan`. No create/modify/delete.
ManagedPolicyArns:
- arn:aws:iam::aws:policy/ReadOnlyAccess
Policies:
- !If
- AnyStateBucket
- PolicyName: infrasync-terraform-state
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: StateBucket
Effect: Allow
Action:
- s3:ListBucket
- s3:GetBucketLocation
- s3:GetBucketVersioning
Resource:
- !If [MakeStateBucket, !GetAtt StateBucket.Arn, !Ref AWS::NoValue]
- !If [HasExistingBucket, !Sub "arn:aws:s3:::${ExistingStateBucket}", !Ref AWS::NoValue]
# Get/Put for state; Delete for Terraform's S3-native lock
# file (<key>.tflock), which is removed after each run.
- Sid: StateObjects
Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:DeleteObject
Resource:
- !If [MakeStateBucket, !Sub "${StateBucket.Arn}/*", !Ref AWS::NoValue]
- !If [HasExistingBucket, !Sub "arn:aws:s3:::${ExistingStateBucket}/*", !Ref AWS::NoValue]
- !Ref AWS::NoValue
- !If
- HasKmsKey
- PolicyName: infrasync-state-kms
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: StateKey
Effect: Allow
Action:
- kms:Decrypt
- kms:Encrypt
- kms:GenerateDataKey
Resource: !Ref StateKmsKeyArn
- !Ref AWS::NoValue
- !If
- ComputeOptimizer
- PolicyName: infrasync-compute-optimizer-optin
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: ComputeOptimizerEnroll
Effect: Allow
Action: compute-optimizer:UpdateEnrollmentStatus
Resource: "*"
- !Ref AWS::NoValue
- !If
- SecretValues
- PolicyName: infrasync-plan-secret-values
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: SecretValuesForPlan
Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource: "*"
- Sid: DecryptOnlyThroughSecretsManagerAndSsm
Effect: Allow
Action: kms:Decrypt
Resource: "*"
Condition:
StringLike:
kms:ViaService:
- "secretsmanager.*.amazonaws.com"
- "ssm.*.amazonaws.com"
- !Ref AWS::NoValue
Outputs:
RoleArn:
Description: Paste this Role ARN into InfraSync.
Value: !GetAtt InfraSyncRole.Arn
StateBucketName:
Condition: MakeStateBucket
Description: Enter this bucket when you turn on the S3 remote-state backend in InfraSync.
Value: !Ref StateBucket
Step 2, option B — To create the role in the AWS Management Console
- Open the IAM console and choose Roles, then Create role.
- For trusted entity type, choose Custom trust policy.
- Paste the trust policy below, replacing
EXTERNAL_IDwith the value from step 1. - Attach the
ReadOnlyAccessmanaged policy. - Name the role — for example
infrasync-readonly-role— and create it. - On the role, choose Add permissions, Create inline policy, and paste the state bucket policy with your bucket name.
- Open the role and copy its Role ARN.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::529928147507:root" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "sts:ExternalId": "EXTERNAL_ID" }
}
}
]
}
Step 2, option C — To create the role with the AWS CLI
# 1. Save the trust policy, using the external ID from step 1
cat > infrasync-trust.json <<'JSON'
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::529928147507:root" },
"Action": "sts:AssumeRole",
"Condition": { "StringEquals": { "sts:ExternalId": "EXTERNAL_ID" } }
}]
}
JSON
# 2. Create the role and attach ReadOnlyAccess
aws iam create-role \
--role-name infrasync-readonly-role \
--assume-role-policy-document file://infrasync-trust.json
aws iam attach-role-policy \
--role-name infrasync-readonly-role \
--policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
# 3. Create the state bucket: private, versioned, encrypted
BUCKET=infrasync-tfstate-ACCOUNT_ID-REGION
aws s3api create-bucket --bucket "$BUCKET" --region REGION \
--create-bucket-configuration LocationConstraint=REGION
aws s3api put-public-access-block --bucket "$BUCKET" \
--public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
aws s3api put-bucket-versioning --bucket "$BUCKET" \
--versioning-configuration Status=Enabled
aws s3api put-bucket-encryption --bucket "$BUCKET" \
--server-side-encryption-configuration '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"},"BucketKeyEnabled":true}]}'
# 4. Let the role read and write state in that bucket only
# (infrasync-state-policy.json = the policy under "State bucket")
aws iam put-role-policy \
--role-name infrasync-readonly-role \
--policy-name infrasync-terraform-state \
--policy-document file://infrasync-state-policy.json
# 5. Print the Role ARN to paste into InfraSync
aws iam get-role --role-name infrasync-readonly-role \
--query "Role.Arn" --output text
In us-east-1, omit --create-bucket-configuration.
Method 2 — IAM user with access keys
Create an IAM user with the ReadOnlyAccess managed policy and an access key you paste into InfraSync. This works immediately and is the fastest way to try a scan. To use the S3 state backend, attach the state bucket policy to the user as well.
An access key is a long-lived credential. If it leaks, it is valid until someone deletes it. For production accounts, use Method 1 instead.
To create the user in the AWS Management Console
- Open the IAM console and choose Users, then Create user.
- Name the user — for example
infrasync-readonly— and do not grant console access. - Choose Attach policies directly and select
ReadOnlyAccess. - Create the user, then open it and choose Security credentials, Create access key.
- For the use case, choose Third-party service.
- Copy both the access key ID and the secret access key.
The secret access key is shown only once. If you lose it, delete the access key and create a new one — it cannot be retrieved later.
To create the user with the AWS CLI
aws iam create-user --user-name infrasync-readonly
aws iam attach-user-policy \
--user-name infrasync-readonly \
--policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
# Optional: the state bucket policy (see "State bucket")
aws iam put-user-policy --user-name infrasync-readonly \
--policy-name infrasync-terraform-state \
--policy-document file://infrasync-state-policy.json
# Prints the access key ID and secret — paste both into InfraSync
aws iam create-access-key --user-name infrasync-readonly \
--query "AccessKey.[AccessKeyId,SecretAccessKey]" --output table
Optional permissions
Each of these is narrowly scoped and added alongside ReadOnlyAccess. None of them touches your infrastructure. The CloudFormation template includes the state bucket by default and the others as parameters.
To let InfraSync enable AWS Compute Optimizer
AWS Compute Optimizer is opt-in per account and returns nothing until enrolled. Grant this to make the Enable Compute Optimizer button work; otherwise enable it yourself in the AWS console and skip this. Everything else works either way.
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "compute-optimizer:UpdateEnrollmentStatus",
"Resource": "*"
}]
}
State bucket — to store Terraform state in your own S3 bucket
The CloudFormation template creates this for you. By hand: use a private, versioned, encrypted bucket, then grant the role or user read and write on that one bucket. InfraSync writes the generated terraform.tfstate there, and Terraform creates and removes its lock file (<key>.tflock) next to it — that is the only reason for s3:DeleteObject.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "StateBucket",
"Effect": "Allow",
"Action": ["s3:ListBucket", "s3:GetBucketLocation", "s3:GetBucketVersioning"],
"Resource": "arn:aws:s3:::TFSTATE_BUCKET"
},
{
"Sid": "StateObjects",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::TFSTATE_BUCKET/*"
}
]
}
State locking is S3-native, so there is no DynamoDB table to create. Keep versioning on: every earlier state is then recoverable.
To read state encrypted with your own KMS key
Only if your state bucket uses SSE-KMS with a customer managed key. ReadOnlyAccess can list the bucket but cannot decrypt the state, so drift fixes and plan checks would fail. Allow the key — and only that key:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["kms:Decrypt", "kms:Encrypt", "kms:GenerateDataKey"],
"Resource": "arn:aws:kms:REGION:ACCOUNT_ID:key/KEY_ID"
}]
}
With CloudFormation, pass the key ARN as StateKmsKeyArn.
Secret values — off by default
When your Terraform manages aws_secretsmanager_secret_version or encrypted aws_ssm_parameter resources, terraform plan reads their values. ReadOnlyAccess deliberately does not allow that, so InfraSync's plan check cannot verify drift that touches them and does not open a pull request for it. If you want them covered, allow value reads — with decryption restricted to Secrets Manager and Parameter Store:
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": "secretsmanager:GetSecretValue", "Resource": "*" },
{
"Effect": "Allow",
"Action": "kms:Decrypt",
"Resource": "*",
"Condition": { "StringLike": { "kms:ViaService": ["secretsmanager.*.amazonaws.com", "ssm.*.amazonaws.com"] } }
}
]
}
With CloudFormation, set AllowSecretValueRead to true. Leaving it off is a reasonable choice: everything else works.
GitHub App permissions
To read your Terraform and propose changes as pull requests, InfraSync uses the InfraSync GitHub App, installed on the repositories you choose. It asks for exactly three permissions:
| Permission | Access | Why |
|---|---|---|
| Metadata | Read | Required by GitHub for every app. |
| Contents | Read and write | Read your Terraform at the exact commit it analyses; push a change to a new infrasync/… branch. It never pushes to your default branch. |
| Pull requests | Write | Open the pull request for that branch, which you review and merge. |
Nothing reaches your default branch without your merge. When the app is installed, GitHub also asks you to authorize it, which lets InfraSync confirm that the person connecting can actually see the installation.
Adding the account to InfraSync
To connect the account
- In InfraSync, open Connect your account and choose Amazon Web Services.
- Leave IAM role selected and paste your role ARN (Method 1), or switch to Access keys and paste your access key ID and secret (Method 2).
- Choose a default region.
- Choose Add account. InfraSync validates the credentials immediately, then offers the next steps: your first scan, or cost optimization for the account.
Verifying the connection
InfraSync validates access when you add the account and again before every scan, so a successful add is already a real check — it performs an sts:GetCallerIdentity against the credentials you gave it and reports the AWS account ID it resolved.
Confirm the account ID InfraSync reports is the account you intended to connect. If it is not, you have pasted credentials for a different account.
To verify a cross-account role yourself before connecting it:
aws sts assume-role \
--role-arn arn:aws:iam::YOUR_ACCOUNT_ID:role/infrasync-readonly-role \
--role-session-name verify \
--external-id EXTERNAL_ID
An AccessDenied here almost always means the external ID does not match the one in the trust policy.
Revoking access
Revocation is immediate and account-wide, with no leftover secrets.
- Cross-account role — delete the role, or remove the
ReadOnlyAccesspolicy from it. If you deployed the CloudFormation stack, deleting the stack removes the role. - Access keys — delete the access key, or the IAM user.
Next steps
- Run your first scan and review the generated Terraform.
- Set up live drift detection to hear about changes seconds after they happen. It needs no additional IAM permission.
- Read how to create a read-only IAM role for the deeper reasoning on least privilege and external IDs.
Read-only access you control.
InfraSync only ever reads your AWS account to generate Terraform and cost insights — and you can revoke its access at any time.
Start a free scanFAQ
What permissions does InfraSync need?
ReadOnlyAccess for everything it reads — scans, cost analysis and the read-only terraform plan behind drift fixes — and InfraSync never creates, modifies, or deletes your resources. Its only writes go to the Terraform state bucket you choose (read and write on that one bucket), plus compute-optimizer:UpdateEnrollmentStatus if you want the Enable Compute Optimizer button. The CloudFormation template sets all of it up — see Permissions reference.
Does InfraSync push to my repository?
Only to a new infrasync/… branch, followed by a pull request you review and merge. The GitHub App reads your Terraform and opens pull requests; it never pushes to your default branch — see GitHub App permissions.
Should I use a role or access keys?
A cross-account role for anything you care about. InfraSync assumes it for short-lived credentials, nothing long-lived is stored, and you revoke access by deleting the role. Access keys are simpler and work immediately, which makes them fine for a trial or a sandbox account.
Does live drift detection need extra permissions?
No. Events are pushed out of your account by EventBridge rather than pulled in, so it adds no IAM permission at all. It does need configuration in your account — see Live drift setup.
Can I revoke access later?
Yes. Delete the IAM user or access key (Method 2), or delete the role (Method 1), and InfraSync immediately loses access — account-wide, no leftover secrets.
What is an external ID and why does it matter?
It is a value InfraSync generates for your account and presents whenever it assumes your role. Your trust policy requires it, so the role cannot be assumed without it — even by InfraSync's own account. It closes the confused deputy problem, where a third party could otherwise be tricked into using its access on someone else's behalf.