Skip to content

4-Profile AWS Credential ContractΒΆ

Operational rule: Every runbook command routes to exactly one of four profiles. Using the wrong profile for an API call either fails silently (zero results) or raises AccessDeniedException. This page defines the contract so ops team members pick the right profile every time.

The Four ProfilesΒΆ

Profile Env Var Account Role APIs Authorised Example Commands
$AWS_MANAGEMENT_PROFILE AWS Organizations management account organizations:List*, organizations:Describe*, organizations:ListPolicies* list-enabled-services, list-delegated-administrators, list-org-policies, list-org-accounts
$AWS_OPERATIONS_PROFILE Centralised-ops shared-services resource-groups:List*, tag:GetResources, ec2:Describe* (centralised VPC) list-resource-groups, vpc flow-logs, vpc security-groups
$AWS_BILLING_PROFILE Cost management / payer ce:GetCostAndUsage, budgets:ViewBudget, billing:View* enrich-costs, validate-costs
Per-workload profile Individual workload account ec2:Describe*, rds:Describe*, s3:List*, lambda:List* resource-explorer, enrich-ec2, discover-rds

Management Profile β€” $AWS_MANAGEMENT_PROFILEΒΆ

Used for: AWS Organizations API (accounts, policies, enabled services, delegated admins) Account: Management/Root account (hub β€” no workload resources)

What this unlocks for business leaders: Complete visibility into which AWS services are enabled org-wide, who administers what, and which governance policies are active.

export AWS_MANAGEMENT_PROFILE=<your-management-profile>
runbooks inventory list-enabled-services --profile $AWS_MANAGEMENT_PROFILE

Billing Profile β€” $AWS_BILLING_PROFILEΒΆ

Used for: Cost Explorer, FinOps reports, budget alerts Account: Payer / billing account (consolidated billing data)

What this unlocks for business leaders: Real-time $NZD spend vs budget, cost allocation by bc:* tags, and CFO-ready savings reports β€” all from a single READONLY API call.

export AWS_BILLING_PROFILE=<your-billing-profile>
runbooks finops enrich-costs --profile $AWS_BILLING_PROFILE

Operations Profile β€” $AWS_OPERATIONS_PROFILEΒΆ

Used for: Resource Groups, centralised-ops shared services, cross-account inventory Account: Centralised-ops account (shared services hub)

What this unlocks for business leaders: Single inventory view across all accounts via AWS Config Aggregator, centralised CloudWatch, and shared-services resource groups.

export AWS_OPERATIONS_PROFILE=<your-operations-profile>
runbooks inventory list-resource-groups --profile $AWS_OPERATIONS_PROFILE

Workload Profile β€” $AWS_PROFILEΒΆ

Used for: Per-workload EC2/RDS/S3 queries in individual accounts Account: Specific workload account (contains actual compute/data resources)

What this unlocks for business leaders: Resource-level cost attribution, decommission signal collection, and per-application CMDB data for the service map.

export AWS_PROFILE=<your-workload-profile>
runbooks inventory resource-explorer --profile $AWS_PROFILE

Profile Semantics (ADLC Rule)ΒΆ

Profile names in ~/.aws/config follow this naming convention on the b2b-energy tenant:

# ~/.aws/config (never commit β€” gitignored)
[profile <tenant>Management]    # β†’ $AWS_MANAGEMENT_PROFILE
[profile <tenant>Operations]    # β†’ $AWS_OPERATIONS_PROFILE  
[profile <tenant>Billing]       # β†’ $AWS_BILLING_PROFILE
[profile <tenant>-<AccountName>] # β†’ per-workload

Export these in your shell session before running any runbook:

export AWS_MANAGEMENT_PROFILE=<tenant>Management
export AWS_OPERATIONS_PROFILE=<tenant>Operations
export AWS_BILLING_PROFILE=<tenant>Billing
export AWS_DEFAULT_REGION=<your-aws-region>

Pre-Flight VerificationΒΆ

Run this before any discovery session to confirm profile routing is correct:

# Management account β€” must return organization master account ID
aws sts get-caller-identity --profile $AWS_MANAGEMENT_PROFILE
aws organizations describe-organization --profile $AWS_MANAGEMENT_PROFILE \
    | jq '.Organization.MasterAccountId'

# Operations account β€” must return centralised-ops account ID
aws sts get-caller-identity --profile $AWS_OPERATIONS_PROFILE

# Billing account β€” must return payer account ID
aws sts get-caller-identity --profile $AWS_BILLING_PROFILE

Expected: all three get-caller-identity calls return different account IDs. If two return the same ID, the profiles are mapped to the same account β€” confirm with your AWS admin.

Cross-Account Silent Zero ProblemΒΆ

Do not interpret zero results as 'no resources exist'

AWS Organizations APIs sometimes return an empty result when the profile lacks cross-account permission, rather than raising AccessDeniedException. Before concluding a resource type does not exist:

  1. Confirm sts:GetCallerIdentity returns the expected account for that API family
  2. Check CloudTrail for AccessDenied events in the last 5 minutes
  3. Re-run with --verbose to surface the raw API response

Anti-pattern blocked: CROSS_ACCOUNT_SILENT_ZERO

Organizations API Routing (Why Management Profile)ΒΆ

AWS Organizations APIs (ListPolicies, ListDelegatedAdministrators, ListAWSServiceAccessForOrganization) must execute from either:

  • The management account (unconditional access), or
  • A delegated administrator account (requires explicit delegation via RegisterDelegatedAdministrator)

Using $AWS_OPERATIONS_PROFILE (operations account) for these APIs will return empty results silently on most calls. This is not a permissions error β€” it is AWS Organizations account-scoping behavior.

Resource Groups API Routing (Why Operations Profile)ΒΆ

AWS Resource Groups is account-scoped and region-scoped. Each AWS account maintains its own Resource Groups independently. The operations account ($AWS_OPERATIONS_PROFILE) holds the centralised shared-services resources and is the correct target for cross-team resource grouping queries.

An empty Groups array from list-resource-groups is a valid result β€” it means the operations account has no Resource Groups configured in that region, not that the API failed.