You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Roadmap: N-11 (Now), phase 2 of 2 · Size: M · Milestone: Self-Hosted Design Partners Depends on:#100 (phase 1) — independent changes, but phase 1 is the cheap half and should not wait on this one.
Problem
infra/cloud-stack/stackset/sns.topic.cfn.yaml grants sns:Publish to Principal: AWS: "*" on the parent account's teemops-sns topic. The narrowing condition beside it is commented out — and could never have worked:
sns:Publish supports no message-content condition keys. SNS defines only sns:Endpoint and sns:Protocol, both Subscribe-only. There is nothing for message attributes or body.
CloudFormation's custom-resource publish carries no message attributes at all. All three captured samples in references/samples/ put the entire request in the message body.
So the commented-out sns:MessageAttributes.* line is dead on arrival twice over and should be deleted rather than revived.
Proposed approach: install-scoped filter secret
Mint a GUID once at install time — TopsInstallId — static for the life of that installation, and thread it through three places:
Parent SAM stack takes it as a parameter and sets it in the TopsSubscriber filter policy.
Both child templates (iam.role.child.account.cfn.yamlandiam.role.audit.account.cfn.yaml) take it as a NoEcho parameter and pass it into TopsCustomNotifier, so it lands in the message body.
This upgrades the subscription filter from a shape check — worthless, since the child template is served publicly from <bucket>/templates/* — to a shared secret check. It needs no AWS credentials in the web tier, no topic-policy mutation, no per-account state, and has no ceiling.
State it honestly: this is a speed bump, not an authentication boundary. The value is shared with every account admin onboarded, travels in a URL query string, and lands as a CloudFormation parameter readable via cloudformation:DescribeStacks — a permission the TOPS role itself grants. NoEcho: true masks it in describe output and is worth setting, but does not make it secret. Net effect: raises the bar from anyone on the internet to anyone who has ever been given an onboarding link.
The three things that will bite if not designed in now
Silent failure is the main risk, not the attacker. A mismatched GUID — typo, stale bookmarked link, reinstall, restore-from-backup — means SNS drops the message with no trace. The child stack then hangs until CloudFormation's custom-resource timeout (~1 hour) and rolls back with an unactionable error, and TOPS has nothing to log because the message never arrived. Same failure class CLAUDE.md warns about for scan rules. Mitigation: a quarantine subscription with the inverse filter ({"anything-but": [...]}) so filtered-out traffic is visible and alarmable.
Rotation must be a list from day one — TopsInstallId: ["<current>", "<previous>"]. A non-rotating secret whose leak is install-wide is worse than one that can be rolled. Trivial now, ugly to retrofit.
Persistence and restore. The GUID belongs in generated/teemops.env with the other stack outputs, must be backed up, and must never be regenerated if present. If a re-run of install.sh mints a new one, every issued onboarding link breaks and every existing account's Delete ping is silently filtered — so unlinking stops working with no error.
Unverified — check before relying on it
Payload-based filtering requires a valid JSON body. CloudFormation publishes the request as a JSON string, which should parse, but this has not been tested against a real custom-resource message. A filter that matches nothing is indistinguishable from a working integration. Test on one real account first and watch NumberOfNotificationsFilteredOut-InvalidMessageBody.
Acceptance criteria
Given a fresh install, when the AWS step runs, then a TopsInstallId GUID is generated, persisted to generated/teemops.env, and used in the subscription filter policy
Given an install that already has a TopsInstallId, when install.sh --aws-only is re-run, then the existing value is reused and no onboarding link is invalidated
Given a quick-create URL, when an admin opens it, then the install id is present as a NoEcho parameter and reaches TOPS in the message body
Given a message carrying a wrong or absent install id, when it is published, then it is delivered to the quarantine queue and does not reach teemops_main
Given a message carrying a correct install id, when it is published, then linking completes exactly as it does today — proven end to end against a real AWS account
Given the filter policy, when it is written, then it accepts a list of ids so the value can be rotated without breaking in-flight onboarding
The dead sns:MessageAttributes.* comment is deleted, not amended
Considered and rejected
Option
Why not
Account-ID allowlist in the topic policy (AWS's own APN guidance for this pattern)
Caps the product at SNS's 200-principal / 100-statement / 30 KB policy quota, and requires sns:AddPermission credentials in php-fpm — a tier that deliberately holds none today
aws:PrincipalOrgID
Not rejected — complementary. Worth adding as an opt-in install parameter for the common self-hosted case, where the scanned accounts are in one AWS Organization. Filed separately if this lands first
Shape-only filter policy
The child template is public, so the shape is public
Roadmap: N-11 (Now), phase 2 of 2 · Size: M · Milestone: Self-Hosted Design Partners
Depends on: #100 (phase 1) — independent changes, but phase 1 is the cheap half and should not wait on this one.
Problem
infra/cloud-stack/stackset/sns.topic.cfn.yamlgrantssns:PublishtoPrincipal: AWS: "*"on the parent account'steemops-snstopic. The narrowing condition beside it is commented out — and could never have worked:sns:Publishsupports no message-content condition keys. SNS defines onlysns:Endpointandsns:Protocol, bothSubscribe-only. There is nothing for message attributes or body.references/samples/put the entire request in the message body.So the commented-out
sns:MessageAttributes.*line is dead on arrival twice over and should be deleted rather than revived.Proposed approach: install-scoped filter secret
Mint a GUID once at install time —
TopsInstallId— static for the life of that installation, and thread it through three places:TopsSubscriberfilter policy.iam.role.child.account.cfn.yamlandiam.role.audit.account.cfn.yaml) take it as aNoEchoparameter and pass it intoTopsCustomNotifier, so it lands in the message body.¶m_InstallId=<guid>.This upgrades the subscription filter from a shape check — worthless, since the child template is served publicly from
<bucket>/templates/*— to a shared secret check. It needs no AWS credentials in the web tier, no topic-policy mutation, no per-account state, and has no ceiling.State it honestly: this is a speed bump, not an authentication boundary. The value is shared with every account admin onboarded, travels in a URL query string, and lands as a CloudFormation parameter readable via
cloudformation:DescribeStacks— a permission the TOPS role itself grants.NoEcho: truemasks it in describe output and is worth setting, but does not make it secret. Net effect: raises the bar from anyone on the internet to anyone who has ever been given an onboarding link.The three things that will bite if not designed in now
{"anything-but": [...]}) so filtered-out traffic is visible and alarmable.TopsInstallId: ["<current>", "<previous>"]. A non-rotating secret whose leak is install-wide is worse than one that can be rolled. Trivial now, ugly to retrofit.generated/teemops.envwith the other stack outputs, must be backed up, and must never be regenerated if present. If a re-run ofinstall.shmints a new one, every issued onboarding link breaks and every existing account'sDeleteping is silently filtered — so unlinking stops working with no error.Unverified — check before relying on it
Payload-based filtering requires a valid JSON body. CloudFormation publishes the request as a JSON string, which should parse, but this has not been tested against a real custom-resource message. A filter that matches nothing is indistinguishable from a working integration. Test on one real account first and watch
NumberOfNotificationsFilteredOut-InvalidMessageBody.Acceptance criteria
TopsInstallIdGUID is generated, persisted togenerated/teemops.env, and used in the subscription filter policyTopsInstallId, wheninstall.sh --aws-onlyis re-run, then the existing value is reused and no onboarding link is invalidatedNoEchoparameter and reaches TOPS in the message bodyteemops_mainsns:MessageAttributes.*comment is deleted, not amendedConsidered and rejected
sns:AddPermissioncredentials in php-fpm — a tier that deliberately holds none todayaws:PrincipalOrgIDFull research, sources and the fourth option (pinning the role name so the message carries no authority at all):
docs/features/sns-topic-publish-authorization.md.Source:
docs/roadmap.md— N-11.