Skip to content

Commit

Permalink
- Added support for cross account + cross region SNS Lambda Subscript…
Browse files Browse the repository at this point in the history
…ions
  • Loading branch information
gitwater committed Apr 2, 2021
1 parent 71cc056 commit 4c5b5d5
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 27 deletions.
24 changes: 24 additions & 0 deletions src/paco/application/reseng_lambda.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from paco import models
from paco.application.res_engine import ResourceEngine
from paco.core import exception
from paco.core.yaml import YAML
from paco.models import vocabulary
import paco.cftemplates


Expand Down Expand Up @@ -78,9 +80,31 @@ def init_resource(self):
stack_group=self.stack_group,
stack_tags=self.stack_tags
)

self.stack = self.stack_group.add_new_stack(
self.aws_region,
self.resource,
paco.cftemplates.Lambda,
stack_tags=self.stack_tags
)

# Provision Lambda subscriptions in the same region as the SNS Topics
# This is required for cross account + cross region lambda/sns
region_topic_list = {}
for topic in self.resource.sns_topics:
region_name = topic.split('.')[4]
if region_name not in vocabulary.aws_regions.keys():
raise exception.InvalidAWSRegion(f'Invalid SNS Topic region in reference: {region_name}: {topic}')
if region_name not in region_topic_list.keys():
region_topic_list[region_name] = []
region_topic_list[region_name].append(topic)

for region_name in region_topic_list.keys():
topic_list = region_topic_list[region_name]
self.stack = self.stack_group.add_new_stack(
region_name,
self.resource,
paco.cftemplates.LambdaSNSSubscriptions,
stack_tags=self.stack_tags,
extra_context={'sns_topic_ref_list': topic_list}
)
1 change: 1 addition & 0 deletions src/paco/cftemplates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from paco.cftemplates.kms import KMS
from paco.cftemplates.cw_alarms import CWAlarms
from paco.cftemplates.lambda_function import Lambda
from paco.cftemplates.lambda_function import LambdaSNSSubscriptions
from paco.cftemplates.eventsrule import EventsRule
from paco.cftemplates.snstopics import SNSTopics
from paco.cftemplates.sns import SNS
Expand Down
2 changes: 1 addition & 1 deletion src/paco/cftemplates/cloudfront.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def __init__(self, stack, paco_ctx, factory_name):
# force the certificate to be in us-east-1, as that's the only CloudFront region
certificate = get_model_obj_from_ref(cloudfront_config.viewer_certificate.certificate, self.paco_ctx.project)
if certificate.region != 'us-east-1':
raise InvalidCloudFrontCertificateRegion(f'Certficate region is: {certificate.region}')
raise InvalidCloudFrontCertificateRegion(f'Certficate region is: {certificate.region}: {certificate.paco_ref}')
viewer_certificate_param = self.create_cfn_parameter(
name='ViewerCertificateArn',
description="ACM Viewer Certificate ARN",
Expand Down
65 changes: 54 additions & 11 deletions src/paco/cftemplates/lambda_function.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from awacs.aws import Action, Allow, Statement, Policy
from troposphere import awslambda
from paco.aws_api.awslambda.code import init_lambda_code
from paco.cftemplates.cftemplates import StackTemplate
from paco.cftemplates.eventsrule import create_event_rule_name
Expand Down Expand Up @@ -287,16 +286,6 @@ def __init__(
SourceArn=troposphere.Ref(param_name),
)

# SNS Topic subscription
sns_topic = get_model_obj_from_ref(sns_topic_ref, self.paco_ctx.project)
troposphere.sns.SubscriptionResource(
title=param_name + 'Subscription',
template=self.template,
Endpoint=troposphere.GetAtt(self.awslambda_resource, 'Arn'),
Protocol='lambda',
TopicArn=troposphere.Ref(param_name),
Region=sns_topic.region_name
)
idx += 1

# Lambda permissions for connected Paco resources
Expand Down Expand Up @@ -578,3 +567,57 @@ def add_log_group(self, loggroup_name, logical_name=None):
self.template.add_output(loggroup_output)
return loggroup_resource


class LambdaSNSSubscriptions(StackTemplate):
def __init__(
self,
stack,
paco_ctx,
sns_topic_ref_list
):
super().__init__(
stack,
paco_ctx,
iam_capabilities=["CAPABILITY_NAMED_IAM"],
)
account_ctx = stack.account_ctx
aws_region = stack.aws_region
self.set_aws_name('LambdaSNSSubs', self.resource_group_name, self.resource_name)
awslambda = self.awslambda = self.stack.resource
self.init_template('Lambda SNS Subscriptions')

# if not enabled finish with only empty placeholder
if not self.awslambda.is_enabled(): return

# Permissions
# SNS Topic Lambda permissions and subscription

lambda_arn_param = self.create_cfn_parameter(
name='LambdaFunctionArn',
param_type='String',
description='An SNS Topic ARN to grant permission to.',
value=self.awslambda.paco_ref + '.arn'
)
idx = 1
for sns_topic_ref in sns_topic_ref_list:
# SNS Topic Arn parameters
param_name = 'SNSTopicArn%d' % idx
self.create_cfn_parameter(
name=param_name,
param_type='String',
description='An SNS Topic ARN to grant permission to.',
value=sns_topic_ref + '.arn'
)

# SNS Topic subscription
sns_topic = get_model_obj_from_ref(sns_topic_ref, self.paco_ctx.project)
troposphere.sns.SubscriptionResource(
title=param_name + 'Subscription',
template=self.template,
Endpoint=troposphere.Ref(lambda_arn_param),
Protocol='lambda',
TopicArn=troposphere.Ref(param_name),
Region=sns_topic.region_name
)
idx += 1

2 changes: 1 addition & 1 deletion src/paco/cftemplates/notification_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def __init__(self, stack, paco_ctx, app_name, env_name):
def create_notification_param(self, group):
"Create a CFN Parameter for a Notification Group"
if registry.CODESTAR_NOTIFICATION_RULE_HOOK != None:
notification_ref = registry.CODESTAR_NOTIFICATION_RULE_HOOK(self.resource, self.aws_region)
notification_ref = registry.CODESTAR_NOTIFICATION_RULE_HOOK(self.resource, self.account_ctx.name, self.aws_region)
else:
notification_ref = self.paco_ctx.project['resource']['sns'].computed[self.account_ctx.name][self.stack.aws_region][group].paco_ref + '.arn'

Expand Down
2 changes: 1 addition & 1 deletion src/paco/cftemplates/sns.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def __init__(
Statement(
Effect = Allow,
Principal = Principal("AWS", "*"),
Action = [ awacs.sns.Publish ],
Action = [ awacs.sns.Publish, awacs.sns.Subscribe ],
Resource = [troposphere.Ref(topic_resource) ],
Condition = Condition(
StringEquals({
Expand Down
30 changes: 17 additions & 13 deletions src/paco/cftemplates/snstopics.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,25 +118,29 @@ def __init__(
account_id_list = [
account.account_id for account in self.paco_ctx.project.accounts.values()
]
statement_list = []
for account_id in account_id_list:
statement = Statement(
Effect = Allow,
Sid = self.create_cfn_logical_id(account_id),
Principal = Principal("AWS", f'arn:aws:iam::{account_id}:root'),
Action = [ awacs.sns.Publish, awacs.sns.Subscribe ],
Resource = topics_ref_cross_list,
#Condition = Condition(
# StringEquals({
# 'AWS:SourceOwner': account_id_list,
# })
#)
)
statement_list.append(statement)

topic_policy_resource = troposphere.sns.TopicPolicy(
'TopicPolicyCrossAccountPacoProject',
Topics = topics_ref_cross_list,
PolicyDocument = Policy(
Version = '2012-10-17',
Id = "CrossAccountPublish",
Statement=[
Statement(
Effect = Allow,
Principal = Principal("AWS", "*"),
Action = [ awacs.sns.Publish ],
Resource = topics_ref_cross_list,
Condition = Condition(
StringEquals({
'AWS:SourceOwner': account_id_list,
})
)
)
]
Statement=statement_list
)
)
template.add_resource(topic_policy_resource)
3 changes: 3 additions & 0 deletions src/paco/core/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,6 @@ class LambdaInvocationError(PacoBaseException):

class InvalidCloudFrontCertificateRegion(PacoBaseException):
title = "The CloudFront certificate must be in us-east-1"

class InvalidAWSRegion(PacoBaseException):
title = "The region supplied is not a valid AWS region"

0 comments on commit 4c5b5d5

Please sign in to comment.