A
Arun's Blog
All Posts

AWS Cross-Account Architecture for On-Premises CMDB Integration

|7 min read|
AWSSecurityArchitecture
TL;DR

To enable an on-premises CMDB application to inventory resources across multiple AWS accounts, use cross-account IAM role assumption. Create a single IAM user in a central account with only sts:AssumeRole permissions, then deploy a read-only role to all member accounts using CloudFormation StackSets. This approach eliminates the need for IAM users and access keys in each account.

Introduction

When your organization runs workloads across multiple AWS accounts, keeping your Configuration Management Database (CMDB) up-to-date becomes a challenge. How do you give an on-premises application access to inventory resources in 10, 50, or even hundreds of AWS accounts without creating a security nightmare?

The answer is cross-account IAM role assumption - a pattern that allows a single set of credentials to access multiple accounts securely. This post walks through the architecture, implementation with CloudFormation StackSets, and a complete Python example for your CMDB integration.

Architecture Overview

The key principle is simple: never create IAM users or access keys in each account. Instead, use a single IAM user in a central account with permission only to assume roles, then deploy a read-only role to all member accounts.

Key Principle

The central IAM user has minimal permissions - only sts:AssumeRole. All actual resource access happens through the assumed role in each member account.

The architecture consists of:

  1. On-Premises CMDB Application - Stores credentials for the central account IAM user and assumes roles into each member account
  2. Central Account - Contains the IAM user with only AssumeRole permissions
  3. Member Accounts - Each contains a CMDBReadRole that trusts the central account

Authentication Options

Option 1: IAM User + Cross-Account Roles (Recommended)

Create a single IAM user in your central/management account with minimal permissions (only sts:AssumeRole). The user assumes a read-only role deployed to each member account.

Pros:

  • Simple to set up and understand
  • Only one set of credentials to manage
  • Minimal permissions in central account
  • Easy to audit and rotate

Cons:

  • Long-lived access keys
  • Keys must be stored securely on-prem
  • Manual key rotation required

Option 2: IAM Roles Anywhere (More Secure)

Uses your on-premises PKI/certificates instead of static access keys. Your CMDB app authenticates using X.509 certificates signed by your CA.

Pros:

  • No long-lived access keys
  • Certificate-based authentication
  • Temporary credentials only
  • Leverages existing PKI infrastructure

Cons:

  • More complex setup
  • Requires PKI/CA infrastructure
  • Additional AWS configuration

Implementation Steps

Step 1: Create IAM User in Central Account

Create a service account with minimal permissions - only the ability to assume the CMDBReadRole in other accounts.

IAM User Policy (cmdb-assume-role-policy):

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowAssumeRoleInMemberAccounts",
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "arn:aws:iam::*:role/CMDBReadRole"
        }
    ]
}

AWS CLI Commands:

# Create the IAM user
aws iam create-user --user-name cmdb-service-account

# Create and attach the policy
aws iam create-policy \
    --policy-name cmdb-assume-role-policy \
    --policy-document file://cmdb-assume-role-policy.json

aws iam attach-user-policy \
    --user-name cmdb-service-account \
    --policy-arn arn:aws:iam::CENTRAL_ACCOUNT_ID:policy/cmdb-assume-role-policy

# Create access keys (store these securely!)
aws iam create-access-key --user-name cmdb-service-account

Step 2: Create CloudFormation Template for Cross-Account Role

This template defines the IAM role that will be deployed to each member account.

The template defines a single AWS::IAM::Role named CMDBReadRole, parameterized on TrustedAccountId and TrustedUserName. The trust policy grants sts:AssumeRole to the central IAM user with an sts:ExternalId condition. The role attaches the AWS-managed ReadOnlyAccess policy plus a small inline policy adding organizations:Describe* and organizations:List* so the CMDB can enumerate accounts. MaxSessionDuration is 3600. Outputs export the role ARN for cross-stack reference.

Step 3: Deploy Role to All Accounts Using StackSets

Use CloudFormation StackSets to deploy the role template to all accounts in your organization automatically.

# Enable trusted access for StackSets (run once from management account)
aws organizations enable-aws-service-access \
    --service-principal stacksets.cloudformation.amazonaws.com

# Create the StackSet
aws cloudformation create-stack-set \
    --stack-set-name CMDBReadRole \
    --description "Deploy CMDB read-only role to all member accounts" \
    --template-body file://cmdb-role.yaml \
    --parameters \
        ParameterKey=TrustedAccountId,ParameterValue=123456789012 \
        ParameterKey=TrustedUserName,ParameterValue=cmdb-service-account \
    --permission-model SERVICE_MANAGED \
    --auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false \
    --capabilities CAPABILITY_NAMED_IAM

# Deploy to all accounts in the organization
aws cloudformation create-stack-instances \
    --stack-set-name CMDBReadRole \
    --deployment-targets OrganizationalUnitIds=ou-xxxx-xxxxxxxx \
    --regions us-east-1 \
    --operation-preferences \
        FailureTolerancePercentage=10,MaxConcurrentPercentage=25
Note

Replace ou-xxxx-xxxxxxxx with your root OU ID to deploy to all accounts, or specify individual OU IDs to target specific accounts. New accounts added to the OU will automatically receive the role.

Step 4: Configure CMDB Application

Configure your on-premises CMDB application to use the credentials and assume roles in each account.

Python sketch: The script pulls the central account credentials from a secrets manager, calls organizations.list_accounts to enumerate active accounts, then for each one calls sts.assume_role with the role ARN, an ExternalId, and a session name. The returned temporary credentials seed a new boto3 client per service/region, and from there it's standard describe_vpcs, describe_subnets, describe_instances calls that get flattened into your CMDB schema. The gotcha worth highlighting: paginate everything (list_accounts, describe_instances) and wrap assume_role in try/except for accounts where the role hasn't deployed yet.

creds = sts.assume_role(
    RoleArn=f'arn:aws:iam::{account_id}:role/CMDBReadRole',
    RoleSessionName='CMDBInventorySession',
    ExternalId=EXTERNAL_ID,
    DurationSeconds=3600,
)['Credentials']

Security Best Practices

Practice Description
Use External ID Add an external ID to the role trust policy to prevent confused deputy attacks
Minimal Permissions The central IAM user should ONLY have sts:AssumeRole permission - nothing else
Secure Key Storage Store access keys in a secrets manager (HashiCorp Vault, CyberArk, etc.), never in code or config files
Regular Key Rotation Rotate access keys at least every 90 days. Automate this process if possible
ReadOnly Access The CMDBReadRole should only have read permissions. Never grant write access for inventory purposes
Session Duration Keep assumed role session duration short (1 hour). The app can re-assume as needed
CloudTrail Logging Ensure CloudTrail is enabled to audit all AssumeRole calls and API activity
IP Restrictions Consider adding IP conditions to the role trust policy to only allow assumption from known on-prem IPs

Optional: IP-Restricted Trust Policy

For additional security, restrict role assumption to specific IP addresses:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::CENTRAL_ACCOUNT_ID:user/cmdb-service-account"
            },
            "Action": "sts:AssumeRole",
            "Condition": {
                "StringEquals": {
                    "sts:ExternalId": "cmdb-external-id-change-me"
                },
                "IpAddress": {
                    "aws:SourceIp": [
                        "203.0.113.0/24",
                        "198.51.100.10/32"
                    ]
                }
            }
        }
    ]
}

Monitoring & Troubleshooting

Check StackSet Deployment Status

# List stack instances
aws cloudformation list-stack-instances \
    --stack-set-name CMDBReadRole

# Check for failures
aws cloudformation list-stack-instances \
    --stack-set-name CMDBReadRole \
    --filters Name=DETAILED_STATUS,Values=FAILED

Test Role Assumption

# Test assuming role from CLI
aws sts assume-role \
    --role-arn arn:aws:iam::MEMBER_ACCOUNT_ID:role/CMDBReadRole \
    --role-session-name TestSession \
    --external-id cmdb-external-id-change-me \
    --profile central-account-profile

CloudTrail Query for AssumeRole Events

-- Athena query for AssumeRole events
SELECT
    eventtime,
    useridentity.arn as caller_arn,
    requestparameters.rolearn as assumed_role,
    sourceipaddress,
    errorcode,
    errormessage
FROM cloudtrail_logs
WHERE eventsource = 'sts.amazonaws.com'
  AND eventname = 'AssumeRole'
  AND requestparameters.rolearn LIKE '%CMDBReadRole%'
ORDER BY eventtime DESC
LIMIT 100;

Conclusion

This architecture provides a secure, scalable way for your on-premises CMDB application to access all AWS accounts with minimal credential management:

  • One IAM user in a central account with minimal permissions
  • One set of access keys to manage and rotate
  • Roles deployed automatically via CloudFormation StackSets
  • New accounts automatically included through StackSet auto-deployment

The key takeaway: resist the temptation to create IAM users in each account. Cross-account role assumption is the AWS-recommended pattern for this use case, and combined with StackSets, it scales to hundreds of accounts with minimal operational overhead.

Pro Tip

For even stronger security, consider using IAM Roles Anywhere with your existing PKI infrastructure to eliminate long-lived access keys entirely.

Want Help With This?

If you're working on something similar and want a second set of eyes, or you'd like to talk through how this applies to your environment, reach out via the contact form. Happy to help.

Related Articles