Skip to content

Trim the child IAM role to the permissions TOPS actually uses #111

Description

@kiwifellows

Size: S · Found 2026-08-02 while writing user-docs/aws-accounts/what-the-iam-role-can-do.md. Scope agreed 2026-08-03.

Problem

templates/iam.role.child.account.cfn.yaml asks every customer for write access across eight AWS services. Nothing in TOPS uses any of it.

  • TOPS instantiates only StsClient, SnsClient and SqsClient. There is no Security Hub, GuardDuty, Macie, Inspector, Config or CloudTrail client code in the repository.
  • All 36 operations declared across app/rules/tasks/*/tasks.json are list, get or describe. Zero mutate anything.

So the role is provisioned for operational features that do not exist. This is the largest remaining objection in a vendor review: a reviewer reaches securityhub:* on Resource: "*" and stops reading, and we have no answer beyond "we might build something one day".

Agreed scope

Service Decision
CloudFormation Keep as-is
CloudTrail Keep as-is
CloudWatch Logs Read only
AWS Config Read only
Security Hub Remove
GuardDuty Remove
Macie Remove
Inspector Remove

What that works out to

21 inline policies → 15. 66 actions → 34 kept, 32 dropped. Six policies disappear entirely.

Removed outrightTopsSecurityHub, TopsGuardDuty, TopsMacie, TopsInspector (securityhub:*, guardduty:*, macie:*, inspector:*, all Resource: "*"), plus TopsCloudWatchLogEvent (logs:PutLogEvents, nothing left after trimming).

Trimmed to read — drop the write half of each, keep the Describe:

Policy Drop
TopsCloudWatchLogGroup logs:CreateLogGroup, logs:DeleteLogGroup
TopsCloudWatchLogStream logs:CreateLogStream, logs:DeleteLogStream
TopsCloudWatchLogMetricFilter logs:PutMetricFilter, logs:DeleteMetricFilter
TopsConfigRule config:PutConfigRule, config:DeleteConfigRule
TopsConfigConfigurationRecorder config:PutConfigurationRecorder, config:DeleteConfigurationRecorder
TopsConfigDeliveryChannel config:PutDeliveryChannel, config:DeleteDeliveryChannel
TopsConfigConfigurationAggregator config:PutConfigurationAggregator, config:DeleteConfigurationAggregator
TopsConfigConfigurationSnapshot config:StartConfigurationRecorder, config:StopConfigurationRecorder
TopsConfigConformancePack config:PutConformancePack, config:DeleteConformancePack
TopsConfigRemediationConfiguration config:PutRemediationConfigurations, config:DeleteRemediationConfigurations
TopsConfigRemediationExceptions config:PutRemediationExceptions, config:DeleteRemediationExceptions

UnchangedTopsPassRole, TopsCloudFormationAccess, TopsCloudFormationListAccess, TopsCloudTrailTrail, and the separate TopsCWEventRole.

Do it properly: those 11 policies probably vanish too

After trimming, each Logs and Config policy retains exactly one Describe action — and ReadOnlyAccess is already attached to this role. If it grants logs:Describe* and config:Describe*, all 11 are dead weight and the inline policies should be deleted entirely rather than left as one-action husks.

Check before writing the trimmed version:

aws iam get-policy-version \
  --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess \
  --version-id "$(aws iam get-policy --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess --query 'Policy.DefaultVersionId' --output text)" \
  --query 'PolicyVersion.Document' | grep -oE '"(logs|config):[A-Za-z*]+"' | sort -u

If they are covered, 21 policies → 4, which is a much better story than 15.

Three decisions this exposes

  1. TopsS3Bucket is probably orphaned. Its comment says it exists "to create an S3 bucket for Config delivery". With Config read-only there is no delivery channel to create, so s3:CreateBucket / s3:DeleteBucket on arn:aws:s3:::tops-config-* have no remaining purpose. Remove?
  2. TopsPassRole may be orphaned for the same reason. iam:PassRole on arn:aws:iam::*:role/tops* exists to hand a service role to Config's recorder or delivery channel. Nothing else passes a role. Remove?
  3. CloudTrail keeps StopLogging and DeleteTrail. Agreed to keep, and noting it once rather than relitigating: these two are what a reviewer flags hardest, because anyone who can assume the role can disable audit logging in every linked account. If the trails are only ever read, the same read-only treatment would apply. Worth a deliberate yes.

Acceptance criteria

  • Given the trimmed template, when a child stack is created, then the role carries no securityhub, guardduty, macie or inspector permission at all
  • Given the trimmed template, when a child stack is created, then no logs: or config: write action is granted
  • Given each retained read action, when checked against ReadOnlyAccess, then it is deleted if already covered rather than left in place
  • Given an existing linked account, when the stack is updated to the new template, then it updates cleanly rather than requiring a delete and re-link
  • Given a scan against an account on the trimmed role, then it completes and returns the same findings as before — this is the test that proves nothing depended on the write half
  • tests/install-messaging.test.sh still passes
  • Docs updated in the same change: user-docs/aws-accounts/what-the-iam-role-can-do.md and the permissions section of user-docs/start-here/how-tops-connects-to-aws.md both currently say nothing uses the write half; after this they describe what is actually granted

Note on existing installs

The template is served from each operator's own S3 bucket, so trimming it does not change any role already created. Existing linked accounts keep the wider role until their stack is updated. Worth saying explicitly in the release notes — an operator who read the old docs may reasonably assume the change is retroactive.

Metadata

Metadata

Assignees

No one assigned

    Labels

    roadmap:nextQueued behind the current milestonesecuritySecurity-relevant changesize/SA day or two

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions