The roles
| Role | Template | Deploy in |
|---|---|---|
CostAgentReadOnly | cloudformation/cost-agent-readonly.yaml | Every account you want scanned |
CostAgentRemediator | cloudformation/cost-agent-remediator.yaml | Only accounts where you want approved actions to run. Never the management account |
CostAgentStateReader | cloudformation/cost-agent-state-reader.yaml | Only the account that owns your Terraform state bucket, and only for Terraform pull requests |
The exact permissions of each role are on the trust page.
The ExternalId
Every role's trust policy requires an sts:ExternalId. It is a value unique to your tenant that Spendsaw must present each time it assumes the role. It stops another party who knows your role ARN from getting Spendsaw to act in your account on their behalf. It must be at least 16 characters, and the same value is used for all of your accounts.
Template parameters
| Parameter | Meaning |
|---|---|
TrustedAccountId | The 12-digit account that runs Spendsaw |
ExternalId | Your tenant's ExternalId. NoEcho, so CloudFormation never shows it back |
TrustedRoleName | Optional. Narrows the trust to one role in the trusted account, for example CostAgentRunner. Recommended |
RoleName | Defaults to CostAgentReadOnly |
ManagementAccountId | Remediator template only. The role is not created when the stack is in this account |
Self-hosted: deploy with the script
This is the path from a checkout. The source repository is not public yet. Run it with credentials for the account being connected.
./setup-org-role.sh --trusted-role CostAgentRunner <tooling-account-id>
- The first run generates an ExternalId and prints it.
- Repeat in each other account, passing that value with
--external-id. - Set it as
COST_AGENT_EXTERNAL_IDwhere Spendsaw runs.
./destroy-org-role.sh removes the stack.
Hosted: the quick-create link
Design partners The hosted service is deployed and scans accounts through this role. There is no sign-up page: we create each tenant by hand and send its link, by invitation. Write to hello@spendsaw.com.
Each tenant gets a CloudFormation quick-create link with the template URL, the trusted account and the tenant's ExternalId already filled in:
https://console.aws.amazon.com/cloudformation/home#/stacks/quickcreate
?templateURL=<template-url>
&stackName=CostAgentReadOnly
¶m_TrustedAccountId=<spendsaw-account-id>
¶m_ExternalId=<your-external-id>
You open it while signed in to the account, review the template, tick the IAM acknowledgement and create the stack. For an AWS Organization there is a service-managed StackSet option that deploys the read-only role to the organizational units you choose. Service-managed StackSets never deploy to the management account, so that account gets the role from the link or the script.
Opt in to remediation
Built, not hosted Remediation is switched off in the hosted service, and the remediator role has not been deployed in any live account yet. The hosted service needs only the read-only role.
Deploy the remediator only where you want approved actions to run:
aws cloudformation deploy --stack-name CostAgentRemediator \
--template-file cloudformation/cost-agent-remediator.yaml \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides TrustedAccountId=<tooling> ExternalId=<external-id> \
ManagementAccountId=<management> TrustedRoleName=CostAgentRunner
Then list the account in REMEDIATION_ACCOUNT_IDS (self-hosted). An account that is not listed cannot have anything executed in it, whatever role exists there.
What you get without the optional roles
- Read-only role alone: findings, prices, recommendations and the commands as text. Nothing can be changed.
- Without the state reader and a linked repository, ownership is unknown for most resources and findings stay recommendations.
Sources: infra/README.md, setup-org-role.sh, cloudformation/, infra/terraform/modules/tenant/, infra/terraform/modules/onboarding/.