# Connect AWS

> Create the VizTopoReadOnly IAM policy and an IAM user with an access key, then optionally add Cost Explorer, tag-fenced changes and SSH through EC2 Instance Connect.

About 5 minutes in the AWS console. VizTopo signs in to your account as a dedicated IAM user whose policy can only describe EC2 resources, read CloudWatch CPU metrics and look up prices. You paste its access key into VizTopo once: it's stored encrypted on the server and never sent back to your browser.

## 1. Create the VizTopoReadOnly policy

In the IAM console: **Policies → Create policy → JSON**. Replace everything in the editor with this policy, choose **Next**, name it `VizTopoReadOnly` and choose **Create policy**.

IAM policy (JSON):

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VizTopoInventory",
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeInstances",
        "ec2:DescribeInstanceStatus",
        "ec2:DescribeInstanceTypes",
        "ec2:DescribeVpcs",
        "ec2:DescribeSubnets",
        "ec2:DescribeSecurityGroups",
        "ec2:DescribeSecurityGroupRules",
        "ec2:DescribeNetworkInterfaces",
        "ec2:DescribeVolumes",
        "ec2:DescribeRegions",
        "cloudwatch:GetMetricData",
        "pricing:GetProducts",
        "ec2:DescribeAddresses",
        "ec2:DescribeNatGateways",
        "ec2:DescribeSnapshots",
        "elasticloadbalancing:DescribeLoadBalancers",
        "s3:ListAllMyBuckets",
        "ec2:DescribeRouteTables",
        "ec2:DescribeNetworkAcls",
        "ec2:DescribeVpcEndpoints"
      ],
      "Resource": "*"
    }
  ]
}
```

To compare VizTopo's estimates with what AWS actually billed, add Cost Explorer: one more statement allowing `ce:GetCostAndUsage`.

Or with the AWS CLI (an admin profile):

```sh
cat > viztopo-readonly.json <<'JSON'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VizTopoInventory",
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeInstances",
        "ec2:DescribeInstanceStatus",
        "ec2:DescribeInstanceTypes",
        "ec2:DescribeVpcs",
        "ec2:DescribeSubnets",
        "ec2:DescribeSecurityGroups",
        "ec2:DescribeSecurityGroupRules",
        "ec2:DescribeNetworkInterfaces",
        "ec2:DescribeVolumes",
        "ec2:DescribeRegions",
        "cloudwatch:GetMetricData",
        "pricing:GetProducts",
        "ec2:DescribeAddresses",
        "ec2:DescribeNatGateways",
        "ec2:DescribeSnapshots",
        "elasticloadbalancing:DescribeLoadBalancers",
        "s3:ListAllMyBuckets",
        "ec2:DescribeRouteTables",
        "ec2:DescribeNetworkAcls",
        "ec2:DescribeVpcEndpoints"
      ],
      "Resource": "*"
    }
  ]
}
JSON
aws iam create-policy --policy-name VizTopoReadOnly --policy-document file://viztopo-readonly.json
```

## 2. Create the IAM user viztopo and its access key

**Users → Create user**. User name `viztopo`, console access off, **Next**. Choose **Attach policies directly**, tick `VizTopoReadOnly`, **Next → Create user**.

Then open the user → **Security credentials → Create access key**, use case **Application running outside AWS**. AWS shows the secret only once: paste it into VizTopo in the next step, and nowhere else.

Or with the AWS CLI:

```sh
aws iam create-user --user-name viztopo
aws iam attach-user-policy --user-name viztopo \
  --policy-arn "arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):policy/VizTopoReadOnly"
aws iam create-access-key --user-name viztopo   # prints AccessKeyId and SecretAccessKey once
```

## 3. Add the connection

In VizTopo: **Connections → Add connection → Amazon Web Services → Guide me**. Paste the access key ID and secret, pick the regions to read, and **Test connection**: it checks every permission, and fails on root account keys. Nothing is saved until you choose **Save connection**.

## 4. Optional: allow changes on tagged resources

This policy allows start, stop and reboot, security group rules, changing an instance's groups and creating groups, only on resources tagged `viztopo-control` = `true`. Attach it to the same user, tag what VizTopo may change, then turn on write mode for the connection by typing its name.

Control policy (JSON):

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VizTopoPowerTaggedOnly",
      "Effect": "Allow",
      "Action": [
        "ec2:StartInstances",
        "ec2:StopInstances",
        "ec2:RebootInstances"
      ],
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": {
        "StringEquals": {
          "aws:ResourceTag/viztopo-control": "true"
        }
      }
    },
    {
      "Sid": "VizTopoPortsTaggedOnly",
      "Effect": "Allow",
      "Action": [
        "ec2:AuthorizeSecurityGroupIngress",
        "ec2:RevokeSecurityGroupIngress",
        "ec2:AuthorizeSecurityGroupEgress",
        "ec2:RevokeSecurityGroupEgress"
      ],
      "Resource": "arn:aws:ec2:*:*:security-group/*",
      "Condition": {
        "StringEquals": {
          "aws:ResourceTag/viztopo-control": "true"
        }
      }
    },
    {
      "Sid": "VizTopoInstanceGroupsTaggedOnly",
      "Effect": "Allow",
      "Action": "ec2:ModifyInstanceAttribute",
      "Resource": [
        "arn:aws:ec2:*:*:instance/*",
        "arn:aws:ec2:*:*:security-group/*"
      ],
      "Condition": {
        "StringEquals": {
          "aws:ResourceTag/viztopo-control": "true"
        }
      }
    },
    {
      "Sid": "VizTopoNewGroupsInTaggedVpcs",
      "Effect": "Allow",
      "Action": "ec2:CreateSecurityGroup",
      "Resource": "arn:aws:ec2:*:*:vpc/*",
      "Condition": {
        "StringEquals": {
          "aws:ResourceTag/viztopo-control": "true"
        }
      }
    },
    {
      "Sid": "VizTopoNewGroupsAreTagged",
      "Effect": "Allow",
      "Action": "ec2:CreateSecurityGroup",
      "Resource": "arn:aws:ec2:*:*:security-group/*",
      "Condition": {
        "StringEquals": {
          "aws:RequestTag/viztopo-control": "true"
        }
      }
    },
    {
      "Sid": "VizTopoTagOnlyWhenCreating",
      "Effect": "Allow",
      "Action": "ec2:CreateTags",
      "Resource": "arn:aws:ec2:*:*:security-group/*",
      "Condition": {
        "StringEquals": {
          "ec2:CreateAction": "CreateSecurityGroup"
        }
      }
    }
  ]
}
```

## 5. Optional: SSH without a key

VizTopo can open a terminal to an instance the way EC2 Instance Connect does: it uploads a one-time public key that the instance accepts for 60 seconds. It needs this policy, the instance tagged `viztopo-control` = `true`, the EC2 Instance Connect agent on the instance, and its SSH port open to VizTopo's server address.

Instance Connect policy (JSON):

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VizTopoInstanceConnectTaggedOnly",
      "Effect": "Allow",
      "Action": "ec2-instance-connect:SendSSHPublicKey",
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": {
        "StringEquals": {
          "aws:ResourceTag/viztopo-control": "true"
        }
      }
    }
  ]
}
```

## Questions

### Why an IAM user and not a role?

viztopo.com signs in from outside your account, so on viztopo.com VizTopo uses a dedicated IAM user's access key. Its policy only reads, and you can deactivate the key in IAM at any time.

### Does VizTopo keep my secret access key?

Yes, encrypted with AES-256-GCM on the server, so it can keep reading. It's never sent back to your browser and never logged. Remove the connection or deactivate the key to stop it.

### Can VizTopo terminate instances?

No. None of these policies allow it, and VizTopo has no such action.

---

Source: https://viztopo.com/docs/connect-aws (updated 2026-10-21). Start free: https://viztopo.com/signup
