Skip to main content

Adding a New AWS Account to Polo

Status: PLANNED — The automated account discovery system described here is not yet implemented. The account_discovery collector, aws_accounts table, and collect_across_accounts() wrapper do not exist yet. Currently collectors work with a single configured AWS account. This document is the design specification for the multi-account onboarding flow.

When a new AWS account is created in the Marqo organization, Polo needs two things: IAM roles in the account and a registry entry.

Automatic Discovery

The account_discovery collector runs daily and calls organizations.list_accounts(). New accounts automatically appear in polo.aws_accounts with polo_status = 'pending_setup'.

The UI's System Status page shows accounts in pending_setup or no_access status as a warning banner.

Step 1: Deploy IAM Roles

If the CloudFormation StackSet is configured, deploying roles to a new account is:

aws cloudformation update-stack-instances \
--stack-set-name polo-roles \
--accounts '["NEW_ACCOUNT_ID"]' \
--regions '["us-east-1"]'

This deploys both {env}-PoloReadRole and polo-action-role.

Option B: Manual

Apply the CloudFormation template directly in the new account. Infrastructure is managed via AWS CDK in infra/polo/.

What the roles provide

{env}-PoloReadRole (read-only, for collectors):

  • ec2:Describe*, s3:Get*/List*, elasticloadbalancing:Describe*
  • sagemaker:List*/Describe*, tag:GetResources
  • ce:Get*, savingsplans:Describe*, cloudwatch:GetMetricData
  • logs:DescribeLogGroups, iam:ListAccountAliases

polo-action-role (write, for action executor):

  • Scoped to the 11 supported action types only
  • Deny on polo:protected=true tagged resources

Both trust the Polo management account for sts:AssumeRole.

Step 2: Register the Account

If the account was auto-discovered, update its status:

ALTER TABLE polo.aws_accounts
UPDATE polo_status = 'active',
polo_read_role = 'arn:aws:iam::NEW_ACCOUNT_ID:role/{env}-PoloReadRole',
polo_action_role = 'arn:aws:iam::NEW_ACCOUNT_ID:role/polo-action-role',
account_role = 'customer', -- or 'testing', 'sales', 'internal'
updated_at = now(),
_version = _version + 1
WHERE account_id = 'NEW_ACCOUNT_ID';

Also add to hierarchy_nodes (this table exists):

INSERT INTO polo.hierarchy_nodes
(node_id, node_type, node_name, parent_id, hierarchy, metadata, created_at, updated_at, _version)
VALUES (
'account:NEW_ACCOUNT_ID', 'account', 'account-name', '', 'aws_account',
{'role': 'customer', 'owner': 'platform-team'},
now(), now(), 1
);

Reload the dictionary: SYSTEM RELOAD DICTIONARY polo.hierarchy_dict

Step 3: Verify

# Check that Polo can assume the role
aws sts assume-role \
--role-arn arn:aws:iam::NEW_ACCOUNT_ID:role/{env}-PoloReadRole \
--role-session-name polo-verify

# Run a collector against the new account
python -c "
from components.collectors.config_ec2.handler import collect
events = collect(account_id='NEW_ACCOUNT_ID', region='us-east-1')
print(f'Found {len(events)} instances')
"

The next scheduled collector run will pick up the account automatically. Check collector_runs and aws_accounts.last_successful_scan to confirm.

Excluding an Account

To intentionally exclude an account from monitoring:

ALTER TABLE polo.aws_accounts
UPDATE polo_status = 'excluded', updated_at = now(), _version = _version + 1
WHERE account_id = 'ACCOUNT_TO_EXCLUDE';

Excluded accounts are skipped by collect_across_accounts().