# Subimagesec Skills Connect AWS

> Connect AWS to SubImage

- Skill: `tomevault-io/subimagesec-skills-connect-aws` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/subimagesec-skills-connect-aws`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/subimagesec-skills-connect-aws/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/subimagesec-skills-connect-aws

---


# Connect AWS to SubImage

## What this does

Deploys a read-only IAM role named `SubImageScanRole` in one or more AWS accounts so SubImage can assume it and inventory the account. Walks the user through three paths and asks for the values it needs before running anything.

## When to use

✅ User wants to onboard an AWS account, an Organization, or the management account into SubImage.
✅ User asks for the IAM role definition, StackSet template, or Terraform for SubImage scanning.
✅ User is in their IaC repo and wants to commit the setup as code.

❌ User wants to scan EKS clusters but already has the AWS module connected: use `subimage-setup:connect-kubernetes-outpost` (and the EKS RBAC steps reachable from there) instead.
❌ User wants to remove an account: this skill only adds; deletion goes through their CloudFormation/Terraform tooling.

## Required inputs

Before generating any commands or HCL, collect these values. **If any are missing, ask the user explicitly.** Do not invent, guess, or paste a literal placeholder string into a command.

| Value | Where to find it | If missing, ask |
|---|---|---|
| `<TENANT_ACCOUNT_ID>` | The AWS account number SubImage runs under for this tenant. Visible at **Settings → Modules → AWS** in the SubImage UI, in the principal ARN line. | "What is your SubImage tenant AWS account ID? You can find it in Settings → Modules → AWS, in the principal ARN. Format: 12 digits." |
| `<TENANT_ID>` | The customer's SubImage tenant slug (e.g. `acme`). Same screen as above. | "What is your SubImage tenant ID (the slug, e.g. `acme`)? It is part of the principal ARN at Settings → Modules → AWS." |
| `<AWS_ACCOUNT_IDS>` | The AWS account(s) to onboard. | "Which AWS account IDs should SubImage scan? Comma-separated list of 12-digit IDs." |
| Path choice | A, B, or C below. | "Which deployment path do you want? **A** CloudFormation StackSet (recommended for AWS Organizations), **B** Terraform (recommended for an IaC repo), or **C** manual `aws-cli` (one-off accounts)." |

The SubImage principal ARN that the trust policy must allow is:

```
arn:aws:iam::<TENANT_ACCOUNT_ID>:role/<TENANT_ID>-subimage-readonly
```

## Permissions baseline

`SubImageScanRole` needs:

- AWS managed policy `arn:aws:iam::aws:policy/SecurityAudit`
- Inline policies for SSO read, EKS identity read, ECR read (full JSON in path A and B below)

`SecurityAudit` already covers most discovery actions including `eks:DescribeCluster` and `eks:ListAccessEntries`. The inline additions cover SSO assignments, EKS identity provider configs, and ECR image pulls used by the image scanner.

## Gotchas

Read these before generating any commands; they correct the most common wrong assumptions.

- **Service-managed StackSets skip the management account.** Targeting the org root is not enough. Deploy a standalone stack on the management account separately if you want it scanned.
- **`SecurityAudit` is broad but not complete.** It covers `eks:DescribeCluster` and `eks:ListAccessEntries`. The inline `AllowEKSIdentityRead` adds only the three actions that are missing (`DescribeAccessEntry`, `ListIdentityProviderConfigs`, `DescribeIdentityProviderConfig`). Do not duplicate or you make the policy harder to audit.
- **Principal ARN format is non-obvious.** It is `arn:aws:iam::<TENANT_ACCOUNT_ID>:role/<TENANT_ID>-subimage-readonly`. The role name is `<TENANT_ID>-subimage-readonly`, NOT `subimage-readonly` or `<tenant>-readonly`. Copying the wrong form means the trust policy passes `terraform plan` but every sync fails with `AccessDenied`.
- **Service-managed StackSets need org-level prerequisites.** AWS Organizations must be set up with all-features enabled and trusted access for CloudFormation StackSets. If `create-stack-set --permission-model SERVICE_MANAGED` fails with "trusted access is not enabled", run `aws organizations enable-aws-service-access --service-principal=stacksets.cloudformation.amazonaws.com` first.
- **IAM is global; pick one StackSet region.** The role gets created once per account regardless of how many regions you target. Use `us-east-1` and stop. Multi-region targeting on an IAM-only stack just multiplies the work.
- **Do not pass the placeholder strings.** `<TENANT_ACCOUNT_ID>` and `<TENANT_ID>` are typed in this skill so it is obvious you must substitute. AWS will accept the literal string in the trust policy and the trust will silently never resolve.

## Path A: CloudFormation StackSet (recommended for AWS Organizations)

Deploys the role into every existing and future account in the organization. Service-managed StackSets do **not** deploy to the management account. Run a standalone stack there if you want it scanned too.

1. Save this template as `subimage-scan-role.yaml`. Substitute `<TENANT_ACCOUNT_ID>` and `<TENANT_ID>` first; do not pass the angle-bracket form to AWS.

   ```yaml
   AWSTemplateFormatVersion: '2010-09-09'
   Description: IAM role used by SubImage to inventory this AWS account.
   Resources:
     SubImageScanRole:
       Type: AWS::IAM::Role
       Properties:
         RoleName: SubImageScanRole
         AssumeRolePolicyDocument:
           Version: '2012-10-17'
           Statement:
             - Effect: Allow
               Principal:
                 AWS:
                   - 'arn:aws:iam::<TENANT_ACCOUNT_ID>:role/<TENANT_ID>-subimage-readonly'
               Action: sts:AssumeRole
         ManagedPolicyArns:
           - arn:aws:iam::aws:policy/SecurityAudit
         Policies:
           - PolicyName: AllowEKSIdentityRead
             PolicyDocument:
               Version: '2012-10-17'
               Statement:
                 - Effect: Allow
                   Action:
                     - eks:DescribeAccessEntry
                     - eks:ListIdentityProviderConfigs
                     - eks:DescribeIdentityProviderConfig
                   Resource: '*'
           - PolicyName: AllowECRRead
             PolicyDocument:
               Version: '2012-10-17'
               Statement:
                 - Effect: Allow
                   Action:
                     - ecr:GetAuthorizationToken
                     - ecr:BatchCheckLayerAvailability
                     - ecr:GetDownloadUrlForLayer
                     - ecr:BatchGetImage
                   Resource: '*'
           - PolicyName: AllowSSORead
             PolicyDocument:
               Version: '2012-10-17'
               Statement:
                 - Effect: Allow
                   Action:
                     - sso:Describe*
                     - sso:Get*
                     - sso:List*
                   Resource: '*'
   Outputs:
     SubImageScanRoleArn:
       Description: ARN of the created SubImage scanning IAM Role.
       Value: !GetAtt SubImageScanRole.Arn
   ```

   The full per-action SSO list is also valid; the wildcard form above is functionally equivalent for read-only and easier to maintain. If your organization disallows wildcard SSO actions, use the explicit list at https://app.subimage.io/docs/modules/aws.

2. Create the StackSet with service-managed permissions:

   ```bash
   aws cloudformation create-stack-set \
     --stack-set-name SubImageScanRole \
     --template-body file://subimage-scan-role.yaml \
     --permission-model SERVICE_MANAGED \
     --auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false \
     --capabilities CAPABILITY_NAMED_IAM
   ```

3. Target every account and every region:

   ```bash
   aws cloudformation create-stack-instances \
     --stack-set-name SubImageScanRole \
     --deployment-targets OrganizationalUnitIds=<root-ou-id> \
     --regions us-east-1
   ```

   Replace `<root-ou-id>` with your org root or specific OU IDs (`aws organizations list-roots` returns the root id).

4. **For the management account**, deploy the same template as a standalone stack:

   ```bash
   aws cloudformation create-stack \
     --stack-name SubImageScanRole \
     --template-body file://subimage-scan-role.yaml \
     --capabilities CAPABILITY_NAMED_IAM
   ```

## Path B: Terraform

Use this when the IAM is owned by IaC. For a single account, drop the file in your existing module. For org-wide deployment, wrap the same template in `aws_cloudformation_stack_set` plus `aws_cloudformation_stack_set_instance`.

```hcl
# subimage_scan_role.tf
resource "aws_iam_role" "subimage_scan_role" {
  name = "SubImageScanRole"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Principal = {
        AWS = [
          "arn:aws:iam::<TENANT_ACCOUNT_ID>:role/<TENANT_ID>-subimage-readonly",
        ]
      }
      Action = "sts:AssumeRole"
    }]
  })
}

resource "aws_iam_role_policy_attachment" "subimage_security_audit" {
  role       = aws_iam_role.subimage_scan_role.name
  policy_arn = "arn:aws:iam::aws:policy/SecurityAudit"
}

resource "aws_iam_role_policy" "subimage_eks_identity_read" {
  name = "AllowEKSIdentityRead"
  role = aws_iam_role.subimage_scan_role.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Action = [
        "eks:DescribeAccessEntry",
        "eks:ListIdentityProviderConfigs",
        "eks:DescribeIdentityProviderConfig",
      ]
      Resource = "*"
    }]
  })
}

resource "aws_iam_role_policy" "subimage_ecr_read" {
  name = "AllowECRRead"
  role = aws_iam_role.subimage_scan_role.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Action = [
        "ecr:GetAuthorizationToken",
        "ecr:BatchCheckLayerAvailability",
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage",
      ]
      Resource = "*"
    }]
  })
}

resource "aws_iam_role_policy" "subimage_sso_read" {
  name = "AllowSSORead"
  role = aws_iam_role.subimage_scan_role.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Action = [
        "sso:Describe*",
        "sso:Get*",
        "sso:List*",
      ]
      Resource = "*"
    }]
  })
}

output "subimage_scan_role_arn" {
  value = aws_iam_role.subimage_scan_role.arn
}
```

Substitute `<TENANT_ACCOUNT_ID>` and `<TENANT_ID>` before `terraform apply`. If the user wants the values pulled from variables, suggest:

```hcl
variable "subimage_tenant_account_id" { type = string }
variable "subimage_tenant_id"         { type = string }
```

and reference `var.subimage_tenant_account_id`, `var.subimage_tenant_id` in the principal ARN.

## Path C: Manual aws-cli

For a one-off account or environments without IaC. Run with credentials in the target account.

```bash
TRUST_POLICY=$(cat <<EOF
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "AWS": "arn:aws:iam::<TENANT_ACCOUNT_ID>:role/<TENANT_ID>-subimage-readonly" },
    "Action": "sts:AssumeRole"
  }]
}
EOF
)

aws iam create-role \
  --role-name SubImageScanRole \
  --assume-role-policy-document "$TRUST_POLICY"

aws iam attach-role-policy \
  --role-name SubImageScanRole \
  --policy-arn arn:aws:iam::aws:policy/SecurityAudit

aws iam put-role-policy \
  --role-name SubImageScanRole \
  --policy-name AllowEKSIdentityRead \
  --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["eks:DescribeAccessEntry","eks:ListIdentityProviderConfigs","eks:DescribeIdentityProviderConfig"],"Resource":"*"}]}'

aws iam put-role-policy \
  --role-name SubImageScanRole \
  --policy-name AllowECRRead \
  --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["ecr:GetAuthorizationToken","ecr:BatchCheckLayerAvailability","ecr:GetDownloadUrlForLayer","ecr:BatchGetImage"],"Resource":"*"}]}'

aws iam put-role-policy \
  --role-name SubImageScanRole \
  --policy-name AllowSSORead \
  --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["sso:Describe*","sso:Get*","sso:List*"],"Resource":"*"}]}'
```

## Register the accounts in SubImage

After the role exists, the user must add the accounts to the AWS module:

1. Open SubImage → **Modules → Add → aws** (or edit if already present).
2. Set `aws_account_ids` to a comma-separated list of every account ID that has the role deployed (include the management account if you ran the standalone stack there).
3. Optional: set `aws_resource_functions` to a subset (e.g. `s3,iam,ssm`) for faster targeted scans. Leave empty for all collectors.
4. Save. Hit **Run Sync** to trigger immediately, or wait for the hourly schedule.

## Verification

Run from a host that has SubImage's tenant credentials, or call the MCP tool from a connected client:

```bash
aws sts assume-role \
  --role-arn arn:aws:iam::<one-account-id>:role/SubImageScanRole \
  --role-session-name verify
```

A successful response with `Credentials` confirms the trust policy. Then in any MCP-connected AI client:

```
subimageListModules()
```

Look for `aws` with `status: synced` and a recent `lastSyncEndedAt`.

## Troubleshooting

- **`AccessDenied` on `sts:AssumeRole`**: trust policy does not list the SubImage principal ARN, or you copied a placeholder literal. Re-run path A/B/C with the substituted `<TENANT_ACCOUNT_ID>` / `<TENANT_ID>`.
- **`AccessDenied` on a service action**: managed policy missing or inline policy missing. Confirm `SecurityAudit` is attached and the inline policies above are present.
- **Management account missing from scans, StackSet otherwise healthy**: expected. Service-managed StackSets skip the management account; deploy the standalone stack there.

## References

- Canonical doc: https://app.subimage.io/docs/modules/aws
- StackSet limitations: AWS docs on service-managed permissions.

---
> Source: [subimagesec/skills](https://github.com/subimagesec/skills) — distributed by [TomeVault](https://tomevault.io).
<!-- tomevault:4.0:skill_md:2026-05-22 -->

