Skip to main content

Multi-Account Management

Status: PLANNED — This feature is not yet implemented. The aws_accounts table, the account_discovery collector, and the cross-account collection wrapper (collect_across_accounts) do not exist yet. Currently collectors work with a single configured AWS account. This document is the design specification for future development.

Polo monitors 10-20+ AWS accounts, with new ones added over time. The system discovers which accounts exist, ensures each has the necessary IAM roles, and alerts when an account exists without Polo visibility.

Account Registry: polo.aws_accounts

CREATE TABLE polo.aws_accounts
(
account_id String,
account_name String,
account_email String DEFAULT '',
org_unit String DEFAULT '',
polo_status LowCardinality(String), -- 'active', 'pending_setup', 'no_access', 'excluded'
polo_read_role String DEFAULT '',
polo_action_role String DEFAULT '',
last_successful_scan DateTime64(3),
last_scan_error String DEFAULT '',
account_role LowCardinality(String) DEFAULT '',
owner String DEFAULT '',
discovered_at DateTime64(3),
updated_at DateTime64(3),
_version UInt64
)
ENGINE = ReplacingMergeTree(_version) ORDER BY (account_id);

Status values

StatusMeaning
activePolo has working access, collectors running
pending_setupDiscovered but roles not yet provisioned
no_accessRoles exist but AssumeRole fails
excludedIntentionally excluded from monitoring

Account Discovery

Daily collector calls organizations.list_accounts(). For each account:

  1. Upsert into aws_accounts
  2. New accounts → polo_status = 'pending_setup'
  3. Existing accounts → verify sts.assume_role() still works
  4. Sync into hierarchy_nodes as node_type = 'account'

Coverage Monitoring

SELECT account_id, account_name, polo_status, last_scan_error,
dateDiff('hour', last_successful_scan, now()) AS hours_since_scan
FROM polo.aws_accounts FINAL
WHERE polo_status IN ('pending_setup', 'no_access')
OR last_successful_scan < now() - INTERVAL 24 HOUR;

Powers: UI warning banner, Slack notifications, weekly digest section.

IAM Roles

Two roles per target account, trusting the Polo management account:

{env}-PoloReadRole (for collectors)

Read-only: ec2:Describe*, s3:Get*/List*, elasticloadbalancing:Describe*, sagemaker:List*/Describe*, tag:GetResources, ce:Get*, savingsplans:Describe*, cloudwatch:GetMetricData, logs:DescribeLogGroups, organizations:ListAccounts, iam:ListAccountAliases.

polo-action-role (for action executor)

Write, scoped to supported actions only: ec2:TerminateInstances, ec2:StopInstances, ec2:StartInstances, ec2:ModifyInstanceAttribute, ec2:ModifyVolume, ec2:DeleteVolume, ec2:DeleteSnapshot, ec2:ReleaseAddress, ec2:DeleteNatGateway, sagemaker:StopNotebookInstance, s3:DeleteObject, logs:DeleteLogGroup. With a Deny for polo:protected=true tagged resources.

Role Provisioning

Roles are defined as a CloudFormation StackSet template. Deploy to new accounts via StackSet. The UI will show setup instructions for pending_setup accounts. Infrastructure is managed via AWS CDK in infra/polo/.

See ../operations/new-account.md for the step-by-step process.