import%20marimo%0A%0A__generated_with%20%3D%20%220.18.1%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20return%20(mo%2C)%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20SNS%20Publish%20Permissions%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20When%20Bad%20Thing%20happens%2C%20aws%20puts%20an%20event%20onto%20our%20event%20bus.%0A%20%20%20%20We%20have%20an%20eventbridge%20rule%20to%20publish%20the%20event%20to%20an%20sns%20topic%20so%20that%20we%20get%20emailed.%0A%20%20%20%20But%20no%20email%20arrived.%0A%20%20%20%20How%20come%3F%0A%0A%20%20%20%20The%20rule's%20metrics%20show%20a%20failed%20invocation.%0A%20%20%20%20So%20the%20event%20was%20emitted%20and%20matched%20the%20rule%2C%0A%20%20%20%20but%20the%20rule%20failed%20to%20publish%20to%20the%20topic.%0A%0A%20%20%20%20We%20have%20a%20cloudwatch%20alarm%20which%20publishes%20to%20that%20topic%20too.%0A%20%20%20%20We%20_do_%20get%20emails%20when%20the%20alarm%20goes%20off.%0A%20%20%20%20So%20the%20alarm%20is%20working%20as%20expected%20but%20not%20the%20rule.%0A%0A%20%20%20%20We%20set%20up%20the%20alarm%20and%20the%20rule%20via%20cdk.%0A%20%20%20%20We%20import%20the%20topic%2C%20created%20elsewhere%2C%20into%20the%20stack%20via%20the%20%60from_topic_arn%60%20method.%0A%20%20%20%20We%20configure%20the%20rule%20to%20publish%20to%20the%20topic%20via%20the%20%60Rule%60%20construct's%20%60add_target%60%20method.%0A%20%20%20%20We%20configure%20the%20alarm%20to%20publish%20to%20the%20topic%20via%20the%20%60Alarm%60%20construct's%20%60add_alarm_action%60%20method.%0A%20%20%20%20Similar%2C%20yet%20the%20alarm%20works%20and%20the%20rule%20doesn't.%0A%0A%20%20%20%20Maybe%20a%20permissions%20issue%3F%0A%20%20%20%20Does%20it%20matter%20that%20the%20topic%20was%20imported%2C%20not%20created%3F%0A%20%20%20%20What%20are%20the%20default%20permissions%3F%0A%20%20%20%20How%20do%20the%20cdk%20methods%20modify%20them%3F%0A%0A%20%20%20%20I'll%20investigate%20using%20a%20simple%20stack.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Stack%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.mermaid(%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20graph%20LR%0A%20%20%20%20%20%20%20%20Z1%3A%3A%3Ahidden%20--%3E%7Cinvoking%7C%20A%5Bnoop%20%CE%BB%5D%0A%20%20%20%20%20%20%20%20A%20--%3E%7Ctriggers%7C%20B%5Balarm%5D%0A%20%20%20%20%20%20%20%20B%20--%3E%7Cpublishes%20to%7C%20C%5Bpre-existing%20topic%5D%0A%20%20%20%20%20%20%20%20C%20--%3E%7Cinvokes%7C%20D%5Btarget%20%CE%BB%5D%0A%0A%20%20%20%20%20%20%20%20Z2%3A%3A%3Ahidden%20--%3E%7Cputting%7C%20E%5Bevent%5D%20%0A%20%20%20%20%20%20%20%20E%20--%3E%7Cmatches%7C%20F%5Brule%5D%0A%20%20%20%20%20%20%20%20F%20--%3E%7Cpublishes%20to%7C%20G%5Btopic%5D%0A%20%20%20%20%20%20%20%20G%20--%3E%7Cinvokes%7C%20H%5Btarget%20%CE%BB%5D%0A%0A%20%20%20%20%20%20%20%20B%20--%3E%7Cpublishes%20to%7C%20G%0A%20%20%20%20%20%20%20%20F%20--%3E%7Cpublishes%20to%7C%20C%0A%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Investigation%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20from%20datetime%20import%20datetime%2C%20timezone%0A%0A%20%20%20%20import%20boto3%0A%0A%20%20%20%20lambda_%20%3D%20boto3.client(%22lambda%22)%0A%20%20%20%20events%20%3D%20boto3.client(%22events%22)%0A%20%20%20%20return%20boto3%2C%20datetime%2C%20events%2C%20lambda_%2C%20timezone%0A%0A%0A%40app.cell%0Adef%20_(datetime%2C%20events%2C%20lambda_%2C%20timezone)%3A%0A%20%20%20%20start_time%20%3D%20datetime.now(timezone.utc)%0A%20%20%20%20invoke_response%20%3D%20lambda_.invoke(FunctionName%3D%22noop%22)%0A%20%20%20%20put_events_response%20%3D%20events.put_events(%0A%20%20%20%20%20%20%20%20Entries%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22Source%22%3A%20%22my_source%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22DetailType%22%3A%20%22my_detail_type%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22Detail%22%3A%20%22%7B%7D%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20)%0A%20%20%20%20return%20invoke_response%2C%20put_events_response%2C%20start_time%0A%0A%0A%40app.cell%0Adef%20_(start_time)%3A%0A%20%20%20%20start_time%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(invoke_response)%3A%0A%20%20%20%20assert%20invoke_response%5B%22StatusCode%22%5D%20%3D%3D%20200%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(put_events_response)%3A%0A%20%20%20%20assert%20put_events_response%5B%22FailedEntryCount%22%5D%20%3D%3D%200%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Which%20topics%20did%20the%20alarm%20and%20rule%20successfully%20publish%20to%3F%0A%20%20%20%20We'll%20wait%20a%20bit%20then%20check%20the%20target%20lambdas.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20time%0A%0A%20%20%20%20time.sleep(4%20*%2060)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(boto3)%3A%0A%20%20%20%20cloudwatch%20%3D%20boto3.client(%22cloudwatch%22)%0A%20%20%20%20return%20(cloudwatch%2C)%0A%0A%0A%40app.cell%0Adef%20_(cloudwatch%2C%20datetime%2C%20start_time%2C%20timezone)%3A%0A%20%20%20%20cloudwatch.get_metric_statistics(%0A%20%20%20%20%20%20%20%20MetricName%3D%22Invocations%22%2C%0A%20%20%20%20%20%20%20%20Namespace%3D%22AWS%2FLambda%22%2C%0A%20%20%20%20%20%20%20%20Dimensions%3D%5B%7B%22Name%22%3A%20%22FunctionName%22%2C%20%22Value%22%3A%20%22pre_existing_topic_target%22%7D%5D%2C%0A%20%20%20%20%20%20%20%20StartTime%3Dstart_time%2C%0A%20%20%20%20%20%20%20%20EndTime%3Ddatetime.now(timezone.utc)%2C%0A%20%20%20%20%20%20%20%20Statistics%3D%5B%22Sum%22%5D%2C%0A%20%20%20%20%20%20%20%20Period%3D60%2C%0A%20%20%20%20)%5B%22Datapoints%22%5D%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(cloudwatch%2C%20datetime%2C%20start_time%2C%20timezone)%3A%0A%20%20%20%20cloudwatch.get_metric_statistics(%0A%20%20%20%20%20%20%20%20MetricName%3D%22Invocations%22%2C%0A%20%20%20%20%20%20%20%20Namespace%3D%22AWS%2FLambda%22%2C%0A%20%20%20%20%20%20%20%20Dimensions%3D%5B%7B%22Name%22%3A%20%22FunctionName%22%2C%20%22Value%22%3A%20%22topic_target%22%7D%5D%2C%0A%20%20%20%20%20%20%20%20StartTime%3Dstart_time%2C%0A%20%20%20%20%20%20%20%20EndTime%3Ddatetime.now(timezone.utc)%2C%0A%20%20%20%20%20%20%20%20Statistics%3D%5B%22Sum%22%5D%2C%0A%20%20%20%20%20%20%20%20Period%3D60%2C%0A%20%20%20%20)%5B%22Datapoints%22%5D%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20For%20the%20pre-existing%20topic%20and%20for%20the%20new%20topic%2C%20one%20publish%20succeeded%20and%20the%20other%20failed.%20Which%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(cloudwatch%2C%20datetime%2C%20start_time%2C%20timezone)%3A%0A%20%20%20%20cloudwatch.describe_alarm_history(%0A%20%20%20%20%20%20%20%20StartDate%3Dstart_time%2C%0A%20%20%20%20%20%20%20%20EndDate%3Ddatetime.now(timezone.utc)%2C%0A%20%20%20%20%20%20%20%20AlarmName%3D%22noop_lambda_invocation_alarm%22%2C%0A%20%20%20%20)%5B%22AlarmHistoryItems%22%5D%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Cloudwatch%20published%20ok%20to%20the%20pre-existing%20topic%20but%20failed%20to%20publish%20to%20the%20new%20topic%20because%0A%0A%20%20%20%20%3E%20CloudWatch%20Alarms%20is%20not%20authorized%20to%20perform%3A%20SNS%3APublish%20on%20resource%3Aarn%3Aaws%3Asns%3Aeu-west-2%3A872115063659%3Amy_topic%0A%0A%20%20%20%20That%20means%20eventbridge%20published%20ok%20to%20the%20new%20topic%20but%20not%20to%20the%20pre-existing%20topic.%0A%0A%20%20%20%20What%20are%20the%20topic%20permissions%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(boto3)%3A%0A%20%20%20%20import%20json%0A%20%20%20%20from%20os%20import%20environ%0A%0A%20%20%20%20sns%20%3D%20boto3.client(%22sns%22)%0A%20%20%20%20return%20environ%2C%20json%2C%20sns%0A%0A%0A%40app.cell%0Adef%20_(environ%2C%20json%2C%20sns)%3A%0A%20%20%20%20_%20%3D%20sns.get_topic_attributes(TopicArn%3Df%22arn%3Aaws%3Asns%3A%7Benviron%5B'REGION'%5D%7D%3A%7Benviron%5B'ACCOUNT_ID'%5D%7D%3Apre_existing_topic%22)%0A%20%20%20%20json.loads(_%5B%22Attributes%22%5D%5B%22Policy%22%5D)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Based%20on%20the%20%60Id%60%2C%20this%20must%20be%20a%20topic's%20default%20access%20policy.%0A%0A%20%20%20%20And%20it%20must%20be%20that%20cloudwatch%20satisfies%20the%20%60AWS%3ASourceOwner%60%20condition%20and%20eventbridge%20doesn't.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(environ%2C%20json%2C%20sns)%3A%0A%20%20%20%20_%20%3D%20sns.get_topic_attributes(TopicArn%3Df%22arn%3Aaws%3Asns%3A%7Benviron%5B'REGION'%5D%7D%3A%7Benviron%5B'ACCOUNT_ID'%5D%7D%3Amy_topic%22)%0A%20%20%20%20json.loads(_%5B%22Attributes%22%5D%5B%22Policy%22%5D)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Ok%2C%20that's%20clear%3A%20eventbridge%20has%20permission%20to%20publish%20to%20the%20new%20topic%20and%20cloudwatch%20does%20not.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20How%20come%20these%20permissions%3F%0A%20%20%20%20Here's%20what%20I%20think%20has%20happened.%0A%0A%20%20%20%20I%20created%20the%20new%20topic%20in%20the%20stack%2C%0A%20%20%20%20and%20added%20subscriptions%20via%20%60add_target%60%20and%20%60add_alarm_action%60.%0A%20%20%20%20%60add_target%60%20modifies%20the%20access%20policy%2C%20permitting%20eventbridge.%0A%20%20%20%20But%20%60add_alarm_action%60%20does%20not%3A%0A%20%20%20%20you%20have%20to%20give%20cloudwatch%20permission%20yourself.%0A%20%20%20%20That's%20the%20**first%20surprise**.%0A%0A%20%20%20%20The%20**second%20surprise**%20is%20that%20%60add_target%60%20doesn't%20add%20permissions%0A%20%20%20%20on%20top%20of%20the%20default%20%60AWS%3ASourceOwner%60%20policy.%0A%20%20%20%20Instead%2C%20it%20replaces%20the%20default%20policy.%0A%20%20%20%20The%20docs%20make%20this%20clear%20for%20%60add_to_resource_policy%60%3A%0A%0A%20%20%20%20%3E%20If%20this%20topic%20was%20created%20in%20this%20stack%20(%60new%20Topic%60)%2C%20a%20topic%20policy%20will%20be%20automatically%20created%20upon%20the%20first%20call%20to%20%60addToResourcePolicy%60.%0A%0A%20%20%20%20and%20I%20suppose%20it's%20implicit%20that%20the%20same%20applies%20to%20other%20permissions-changing%20methods%2C%0A%20%20%20%20such%20as%20%60add_target%60.%0A%0A%20%20%20%20I%20created%20the%20pre-existing%20topic%20in%20another%20stack%2C%20with%20all%20defaults.%0A%20%20%20%20It%20has%20the%20default%20policy%20based%20on%20%60AWS%3ASourceOwner%60.%0A%20%20%20%20I%20imported%20the%20topic%20into%20the%20stack%20via%20%60from_topic_arn%60%2C%0A%20%20%20%20then%20added%20subscriptions%20via%20%60add_target%60%20and%20%60add_alarm_action%60.%0A%20%20%20%20Because%20the%20topic%20was%20imported%2C%0A%20%20%20%20neither%20method%20changed%20the%20access%20policy.%0A%20%20%20%20But%20%E2%80%94%20**third%20surprise**%20%E2%80%94%20cloudwatch%20passes%20the%20policy%20and%20events%20doesn't.%0A%0A%20%20%20%20A%20**fourth%20surprise**.%0A%20%20%20%20It%20makes%20sense%20that%20the%20cdk%20methods%20didn't%20change%20the%20pre-existing%20topic's%20policy.%0A%20%20%20%20It%20would%20be%20confusing%20if%20stacks%20could%20modify%20resources%20they%20didn't%20create.%0A%20%20%20%20Except%20sometimes%20they%20do!%0A%20%20%20%20For%20example%2C%20in%20our%20stack%20we%20_were_%20able%20to%20add%20the%20target%20lambda%20subscription%20to%20the%20pre-existing%20topic.%0A%20%20%20%20So%20cdk%20_doesn't_%20strictly%20observe%20the%20%22stacks%20can't%20modify%20resources%20they%20didn't%20create%22%20rule.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20References%0A%0A%20%20%20%20%5B1%5D%20https%3A%2F%2Fdocs.aws.amazon.com%2Fsns%2Flatest%2Fdg%2Fsns-access-policy-use-cases.html%23sns-allow-specified-service-to-publish-to-topic%0A%0A%20%20%20%20Confirms%20that%20cloudwatch%20supports%20%60AWS%3ASourceOwner%60%20and%20eventbridge%20does%20not.%0A%0A%20%20%20%20Warns%20that%0A%0A%20%20%20%20%3E%20%60aws%3ASourceOwner%60%20is%20deprecated%20and%20new%20services%20can%20integrate%20with%20Amazon%20SNS%0A%20%20%20%20%3E%20only%20through%20%60aws%3ASourceArn%60%20and%20%60aws%3ASourceAccount%60.%0A%20%20%20%20%3E%20Amazon%20SNS%20still%20maintains%20backward%20compatibility%20for%20existing%20services%0A%20%20%20%20%3E%20that%20are%20currently%20supporting%20%60aws%3ASourceOwner%60.%0A%0A%20%20%20%20%22Maintains%20backward%20compatibility%22%20seems%20to%20go%20as%20far%20as%20using%20%60aws%3ASourceOwner%60%20in%20the%20default%20policy.%0A%0A%20%20%20%20%5B2%5D%20https%3A%2F%2Fgithub.com%2Fawsdocs%2Fiam-user-guide%2Fissues%2F111%0A%0A%20%20%20%20AWS%20say%20they're%20not%20going%20to%20document%20%60SourceOwner%60%20because%20deprecated.%0A%20%20%20%20Ok%2C%20but%20then%2C%20as%20one%20of%20the%20comments%20says%2C%20why%20keep%20using%20it%20in%20the%20default%20policy%3F%0A%0A%20%20%20%20%5B3%5D%20https%3A%2F%2Fdocs.aws.amazon.com%2Fcdk%2Fapi%2Fv2%2Fpython%2Faws_cdk.aws_sns%2FTopic.html%23aws_cdk.aws_sns.Topic.add_to_resource_policy%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
daee21e23e3cca925165a6673e97534e