Skip to content

Commit 3b718e5

Browse files
Jon Beilkejamesls
authored andcommitted
Update planner to include source account for S3 events
1 parent f7d6fa9 commit 3b718e5

File tree

4 files changed

+58
-28
lines changed

4 files changed

+58
-28
lines changed

chalice/awsclient.py

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1522,24 +1522,27 @@ def _merge_s3_notification_config(self, existing_config, new_config):
15221522
final_config.append(new_config)
15231523
return final_config
15241524

1525-
def add_permission_for_s3_event(self, bucket, function_arn):
1526-
# type: (str, str) -> None
1525+
def add_permission_for_s3_event(self, bucket, function_arn, account_id):
1526+
# type: (str, str, str) -> None
15271527
bucket_arn = 'arn:{partition}:s3:::{bucket}'.format(
15281528
partition=self.partition_name, bucket=bucket)
15291529
self._add_lambda_permission_if_needed(
15301530
source_arn=bucket_arn,
15311531
function_arn=function_arn,
15321532
service_name='s3',
1533+
source_account=account_id,
15331534
)
15341535

1535-
def remove_permission_for_s3_event(self, bucket, function_arn):
1536-
# type: (str, str) -> None
1536+
def remove_permission_for_s3_event(self, bucket, function_arn,
1537+
account_id):
1538+
# type: (str, str, str) -> None
15371539
bucket_arn = 'arn:{partition}:s3:::{bucket}'.format(
15381540
partition=self.partition_name, bucket=bucket)
15391541
self._remove_lambda_permission_if_needed(
15401542
source_arn=bucket_arn,
15411543
function_arn=function_arn,
15421544
service_name='s3',
1545+
source_account=account_id,
15431546
)
15441547

15451548
def disconnect_s3_bucket_from_lambda(self, bucket, function_arn):
@@ -1562,22 +1565,25 @@ def disconnect_s3_bucket_from_lambda(self, bucket, function_arn):
15621565
)
15631566

15641567
def _add_lambda_permission_if_needed(self, source_arn, function_arn,
1565-
service_name):
1566-
# type: (str, str, str) -> None
1568+
service_name, source_account=None):
1569+
# type: (str, str, str, Optional[str]) -> None
15671570
policy = self.get_function_policy(function_arn)
15681571
if self._policy_gives_access(policy, source_arn, service_name):
15691572
return
15701573
random_id = self._random_id()
15711574
dns_suffix = self.endpoint_dns_suffix_from_arn(source_arn)
1572-
self._client('lambda').add_permission(
1573-
Action='lambda:InvokeFunction',
1574-
FunctionName=function_arn,
1575-
StatementId=random_id,
1576-
Principal=self.service_principal(service_name,
1577-
self.region_name,
1578-
dns_suffix),
1579-
SourceArn=source_arn,
1580-
)
1575+
kwargs = {
1576+
'Action': 'lambda:InvokeFunction',
1577+
'FunctionName': function_arn,
1578+
'StatementId': random_id,
1579+
'Principal': self.service_principal(service_name,
1580+
self.region_name,
1581+
dns_suffix),
1582+
'SourceArn': source_arn,
1583+
}
1584+
if source_account is not None:
1585+
kwargs['SourceAccount'] = source_account
1586+
self._client('lambda').add_permission(**kwargs)
15811587

15821588
def _policy_gives_access(self, policy, source_arn, service_name):
15831589
# type: (Dict[str, Any], str, str) -> bool
@@ -1610,8 +1616,9 @@ def _policy_gives_access(self, policy, source_arn, service_name):
16101616
return True
16111617
return False
16121618

1613-
def _statement_gives_arn_access(self, statement, source_arn, service_name):
1614-
# type: (Dict[str, Any], str, str) -> bool
1619+
def _statement_gives_arn_access(self, statement, source_arn,
1620+
service_name, source_account=None):
1621+
# type: (Dict[str, Any], str, str, Optional[str]) -> bool
16151622
dns_suffix = self.endpoint_dns_suffix_from_arn(source_arn)
16161623
principal = self.service_principal(service_name,
16171624
self.region_name,
@@ -1623,19 +1630,29 @@ def _statement_gives_arn_access(self, statement, source_arn, service_name):
16231630
return False
16241631
if statement.get('Principal', {}).get('Service', '') != principal:
16251632
return False
1633+
if source_account is not None:
1634+
if statement.get('Condition', {}).get('StringEquals', {}).get(
1635+
'AWS:SourceAccount', '') != source_account:
1636+
return False
16261637
# We're not checking the "Resource" key because we're assuming
16271638
# that lambda.get_policy() is returning the policy for the particular
16281639
# resource in question.
16291640
return True
16301641

16311642
def _remove_lambda_permission_if_needed(self, source_arn, function_arn,
1632-
service_name):
1633-
# type: (str, str, str) -> None
1643+
service_name, source_account=None):
1644+
# type: (str, str, str, Optional[str]) -> None
16341645
client = self._client('lambda')
16351646
policy = self.get_function_policy(function_arn)
16361647
for statement in policy.get('Statement', []):
1637-
if self._statement_gives_arn_access(statement, source_arn,
1638-
service_name):
1648+
kwargs = {
1649+
'statement': statement,
1650+
'source_arn': source_arn,
1651+
'service_name': service_name,
1652+
}
1653+
if source_account is not None:
1654+
kwargs['source_account'] = source_account
1655+
if self._statement_gives_arn_access(**kwargs):
16391656
client.remove_permission(
16401657
FunctionName=function_arn,
16411658
StatementId=statement['Sid'],

chalice/deploy/planner.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -877,7 +877,8 @@ def _plan_s3bucketnotification(self, resource):
877877
models.APICall(
878878
method_name='add_permission_for_s3_event',
879879
params={'bucket': resource.bucket,
880-
'function_arn': function_arn},
880+
'function_arn': function_arn,
881+
'account_id': Variable('account_id')},
881882
),
882883
(models.APICall(
883884
method_name='connect_s3_bucket_to_lambda',

tests/functional/test_awsclient.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
from chalice.awsclient import ReadTimeout
2020

2121

22-
def create_policy_statement(source_arn, service_name, statement_id):
23-
return {
22+
def create_policy_statement(source_arn, service_name, statement_id,
23+
account_id=None):
24+
policy_statement = {
2425
'Action': 'lambda:InvokeFunction',
2526
'Condition': {
2627
'ArnLike': {
@@ -32,6 +33,11 @@ def create_policy_statement(source_arn, service_name, statement_id):
3233
'Resource': 'function-arn',
3334
'Sid': statement_id,
3435
}
36+
if account_id is not None:
37+
policy_statement['Condition']['StringEquals'] = {
38+
'AWS:SourceAccount': account_id,
39+
}
40+
return policy_statement
3541

3642

3743
def test_region_name_is_exposed(stubbed_session):
@@ -3256,12 +3262,13 @@ def test_add_permission_for_s3_event(stubbed_session):
32563262
StatementId=stub.ANY,
32573263
Principal='s3.amazonaws.com',
32583264
SourceArn='arn:aws:s3:::mybucket',
3265+
SourceAccount='12345',
32593266
).returns({})
32603267
stubbed_session.activate_stubs()
32613268

32623269
awsclient = TypedAWSClient(stubbed_session)
32633270
awsclient.add_permission_for_s3_event(
3264-
'mybucket', 'function-arn')
3271+
'mybucket', 'function-arn', '12345')
32653272
stubbed_session.verify_stubs()
32663273

32673274

@@ -3272,6 +3279,9 @@ def test_skip_if_permission_already_granted_to_s3(stubbed_session):
32723279
'Statement': [{
32733280
'Action': 'lambda:InvokeFunction',
32743281
'Condition': {
3282+
'StringEquals': {
3283+
'AWS:SourceAccount': '12345',
3284+
},
32753285
'ArnLike': {
32763286
'AWS:SourceArn': 'arn:aws:s3:::mybucket',
32773287
}
@@ -3288,7 +3298,7 @@ def test_skip_if_permission_already_granted_to_s3(stubbed_session):
32883298
stubbed_session.activate_stubs()
32893299
awsclient = TypedAWSClient(stubbed_session)
32903300
awsclient.add_permission_for_s3_event(
3291-
'mybucket', 'function-arn')
3301+
'mybucket', 'function-arn', '12345')
32923302
stubbed_session.verify_stubs()
32933303

32943304

@@ -3480,7 +3490,8 @@ def test_can_remove_s3_permission(stubbed_session):
34803490
'Id': 'default',
34813491
'Statement': [create_policy_statement('arn:aws:s3:::mybucket',
34823492
service_name='s3',
3483-
statement_id='12345')],
3493+
statement_id='12345',
3494+
account_id='67890')],
34843495
'Version': '2012-10-17'
34853496
}
34863497
lambda_stub = stubbed_session.stub('lambda')
@@ -3495,7 +3506,7 @@ def test_can_remove_s3_permission(stubbed_session):
34953506
stubbed_session.activate_stubs()
34963507
client = TypedAWSClient(stubbed_session)
34973508
client.remove_permission_for_s3_event(
3498-
'mybucket', 'name')
3509+
'mybucket', 'name', '67890')
34993510
stubbed_session.verify_stubs()
35003511

35013512

tests/unit/deploy/test_planner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,7 @@ def test_can_plan_s3_event(self):
819819
params={
820820
'bucket': 'mybucket',
821821
'function_arn': Variable('function_name_lambda_arn'),
822+
'account_id': Variable('account_id'),
822823
},
823824
)
824825
)

0 commit comments

Comments
 (0)