Docs

Connect an AWS account

Spendsaw reaches an account through IAM roles you deploy from CloudFormation. You hand over no access keys, and reading and writing are separate roles.

The roles

RoleTemplateDeploy in
CostAgentReadOnlycloudformation/cost-agent-readonly.yamlEvery account you want scanned
CostAgentRemediatorcloudformation/cost-agent-remediator.yamlOnly accounts where you want approved actions to run. Never the management account
CostAgentStateReadercloudformation/cost-agent-state-reader.yamlOnly 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

ParameterMeaning
TrustedAccountIdThe 12-digit account that runs Spendsaw
ExternalIdYour tenant's ExternalId. NoEcho, so CloudFormation never shows it back
TrustedRoleNameOptional. Narrows the trust to one role in the trusted account, for example CostAgentRunner. Recommended
RoleNameDefaults to CostAgentReadOnly
ManagementAccountIdRemediator 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>
  1. The first run generates an ExternalId and prints it.
  2. Repeat in each other account, passing that value with --external-id.
  3. Set it as COST_AGENT_EXTERNAL_ID where 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
  &param_TrustedAccountId=<spendsaw-account-id>
  &param_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/.