10000 Fix multiple AWS::Lambda::Permission for single function (#7832) · localstack/localstack@4914f48 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4914f48

Browse files
Fix multiple AWS::Lambda::Permission for single function (#7832)
1 parent b197e44 commit 4914f48

File tree

5 files changed

+149
-12
lines changed

5 files changed

+149
-12
lines changed

localstack/services/cloudformation/engine/template_deployer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,6 +1161,7 @@ def get_change_config(self, action, resource, change_set_id=None):
11611161

11621162
def resource_config_differs(self, resource_new):
11631163
"""Return whether the given resource properties differ from the existing config (for stack updates)."""
1164+
# TODO: this is broken for default fields when they're added to the properties in the model
11641165
resource_id = resource_new["LogicalResourceId"]
11651166
resource_old = self.resources[resource_id]
11661167
props_old = resource_old["Properties"]

localstack/services/cloudformation/models/awslambda.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def add_defaults(resource, stack_name: str):
8686

8787
@staticmethod
8888
def get_lambda_code_param(params, _include_arch=False, **kwargs):
89-
code = params.get("Code", {})
89+
code = params.get("Code", {}).copy()
9090
zip_file = code.get("ZipFile")
9191
if zip_file and not is_base64(zip_file) and not is_zip_file(to_bytes(zip_file)):
9292
tmp_dir = new_tmp_dir()
@@ -153,7 +153,6 @@ def result_handler(result, resource_id, resources, resource_type):
153153
"VpcConfig": "VpcConfig"
154154
# TODO add missing fields
155155
},
156-
"defaults": {"Role": "test_role"},
157156
"types": {"Timeout": int, "MemorySize": int},
158157
"result_handler": result_handler,
159158
},
@@ -243,26 +242,34 @@ def cloudformation_type():
243242
return "AWS::Lambda::Permission"
244243

245244
def fetch_state(self, stack_name, resources):
245+
if not self.physical_resource_id:
246+
return None
247+
246248
props = self.props
247249
func_name = props.get("FunctionName")
248250
lambda_client = aws_stack.connect_to_service("lambda")
249-
return lambda_client.get_policy(FunctionName=func_name)
251+
policy = lambda_client.get_policy(FunctionName=func_name)
252+
if not policy:
253+
return None
250254

251-
def get_physical_resource_id(self, attribute=None, **kwargs):
252-
# return statement ID here to indicate that the resource has been deployed
253-
return self.props.get("Sid")
255+
loaded_policy = json.loads(policy["Policy"])
256+
statements = loaded_policy.get("Statement", [])
257+
matched_statements = [s for s in statements if s["Sid"] == self.physical_resource_id]
258+
if not matched_statements:
259+
return None
260+
261+
return statements[0]
254262

255263
def update_resource(self, new_resource, stack_name, resources):
256264
props = new_resource["Properties"]
257265
parameters_to_select = ["FunctionName", "Action", "Principal", "SourceArn"]
258266
update_config_props = select_attributes(props, parameters_to_select)
259267

260268
client = aws_stack.connect_to_service("lambda")
261-
sid = new_resource["PhysicalResourceId"]
262-
263-
client.remove_permission(FunctionName=update_config_props["FunctionName"], StatementId=sid)
264-
265-
return client.add_permission(StatementId=sid, **update_config_props)
269+
client.remove_permission(
270+
FunctionName=update_config_props["FunctionName"], StatementId=self.physical_resource_id
271+
)
272+
return client.add_permission(StatementId=self.physical_resource_id, **update_config_props)
266273

267274
@staticmethod
268275
def get_deploy_templates():
@@ -435,7 +442,7 @@ def get_physical_resource_id(self, attribute=None, **kwargs):
435442

436443
def get_cfn_attribute(self, attribute_name):
437444
if attribute_name == "CodeSigningConfigId":
438-
return self.props()["CodeSigningConfigId"]
445+
return self.props["CodeSigningConfigId"]
439446

440447
return self.physical_resource_id
441448

tests/integration/cloudformation/resources/test_lambda.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,34 @@ def test_update_lambda_permissions(deploy_cfn_template, lambda_client, sts_clien
338338
assert new_principal in principal
339339

340340

341+
@pytest.mark.skip_snapshot_verify(
342+
condition=is_old_provider, paths=["$..PolicyArn", "$..PolicyName", "$..RevisionId"]
343+
)
344+
@pytest.mark.aws_validated
345+
def test_multiple_lambda_permissions_for_singlefn(
346+
deploy_cfn_template, cfn_client, lambda_client, snapshot
347+
):
348+
deploy = deploy_cfn_template(
349+
template_path=os.path.join(
350+
os.path.dirname(__file__), "../../templates/cfn_lambda_permission_multiple.yaml"
351+
),
352+
max_wait=240,
353+
)
354+
fn_name = deploy.outputs["LambdaName"]
355+
p1_sid = deploy.outputs["PermissionLambda"]
356+
p2_sid = deploy.outputs["PermissionStates"]
357+
358+
snapshot.add_transformer(snapshot.transform.regex(p1_sid, "<p1-sid>"))
359+
snapshot.add_transformer(snapshot.transform.regex(p2_sid, "<p2-sid>"))
360+
snapshot.add_transformer(snapshot.transform.regex(fn_name, "<fn-name>"))
361+
snapshot.add_transformer(SortingTransformer("Statement", lambda s: s["Sid"]))
362+
363+
policy = lambda_client.get_policy(FunctionName=fn_name)
364+
# load the policy json, so we can properly snapshot it
365+
policy["Policy"] = json.loads(policy["Policy"])
366+
snapshot.match("policy", policy)
367+
368+
341369
class TestCfnLambdaIntegrations:
342370
@pytest.mark.skip_snapshot_verify(
343371
paths=[

tests/integration/cloudformation/resources/test_lambda.snapshot.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1355,5 +1355,41 @@
13551355
}
13561356
}
13571357
}
1358+
},
1359+
"tests/integration/cloudformation/resources/test_lambda.py::test_multiple_lambda_permissions_for_singlefn": {
1360+
"recorded-date": "09-03-2023, 22:07:56",
1361+
"recorded-content": {
1362+
"policy": {
1363+
"Policy": {
1364+
"Id": "default",
1365+
"Statement": [
1366+
{
1367+
"Action": "lambda:InvokeFunction",
1368+
"Effect": "Allow",
1369+
"Principal": {
1370+
"Service": "lambda.amazonaws.com"
1371+
},
1372+
"Resource": "arn:aws:lambda:<region>:111111111111:function:<fn-name>",
1373+
"Sid": "<p1-sid>"
1374+
},
1375+
{
1376+
"Action": "lambda:InvokeFunction",
1377+
"Effect": "Allow",
1378+
"Principal": {
1379+
"Service": "states.amazonaws.com"
1380+
},
1381+
"Resource": "arn:aws:lambda:<region>:111111111111:function:<fn-name>",
1382+
"Sid": "<p2-sid>"
1383+
}
1384+
],
1385+
"Version": "2012-10-17"
1386+
},
1387+
"RevisionId": "<uuid:1>",
1388+
"ResponseMetadata": {
1389+
"HTTPHeaders": {},
1390+
"HTTPStatusCode": 200
1391+
}
1392+
}
1393+
}
13581394
}
13591395
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
Resources:
2+
FunctionServiceRole675BB04A:
3+
Type: AWS::IAM::Role
4+
Properties:
5+
AssumeRolePolicyDocument:
6+
Statement:
7+
- Action: sts:AssumeRole
8+
Effect: Allow
9+
Principal:
10+
Service: lambda.amazonaws.com
11+
Version: "2012-10-17"
12+
ManagedPolicyArns:
13+
- Fn::Join:
14+
- ""
15+
- - "arn:"
16+
- Ref: AWS::Partition
17+
- :iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
18+
Function76856677:
19+
Type: AWS::Lambda::Function
20+
Properties:
21+
Code:
22+
ZipFile: |
23+
def handler(event, context):
24+
return {"hello": "world"}
25+
Role:
26+
Fn::GetAtt:
27+
- FunctionServiceRole675BB04A
28+
- Arn
A851 29+
Handler: index.handler
30+
Runtime: python3.9
31+
DependsOn:
32+
- FunctionServiceRole675BB04A
33+
FunctionInvoketnlpEgfIKpZyEIAINyVhXFSIfV5EJ7IaMp0cNGOcC4227DF5:
34+
Type: AWS::Lambda::Permission
35+
Properties:
36+
Action: lambda:InvokeFunction
37+
FunctionName:
38+
Fn::GetAtt:
39+
- Function76856677
40+
- Arn
41+
Principal: states.amazonaws.com
42+
FunctionInvokearOgSVH4Ts0qbAOHNp0VMi6g5gzMbVQFH2pumFohAQA578E6CA:
43+
Type: AWS::Lambda::Permission
44+
Properties:
45+
Action: lambda:InvokeFunction
46+
FunctionName:
47+
Fn::GetAtt:
48+
- Function76856677
49+
- Arn
50+
Principal: lambda.amazonaws.com
51+
Outputs:
52+
LambdaName:
53+
Value:
54+
Ref: Function76856677
55+
LambdaArn:
56+
Value:
57+
Fn::GetAtt:
58+
- Function76856677
< 5968 /td>
59+
- Arn
60+
PermissionLambda:
61+
Value:
62+
Ref: FunctionInvokearOgSVH4Ts0qbAOHNp0VMi6g5gzMbVQFH2pumFohAQA578E6CA
63+
PermissionStates:
64+
Value:
65+
Ref: FunctionInvoketnlpEgfIKpZyEIAINyVhXFSIfV5EJ7IaMp0cNGOcC4227DF5

0 commit comments

Comments
 (0)
0