Adding a New AWS Account to Polo
Status: PLANNED — The automated account discovery system described here is not yet implemented. The
account_discoverycollector,aws_accountstable, andcollect_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
Option A: StackSet (recommended)
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:GetResourcesce:Get*,savingsplans:Describe*,cloudwatch:GetMetricDatalogs:DescribeLogGroups,iam:ListAccountAliases
polo-action-role (write, for action executor):
- Scoped to the 11 supported action types only
- Deny on
polo:protected=truetagged 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().