Skip to content

Commit 7b47b87

Browse files
authored
fix: bug fixes in api resource policies (#1395)
1 parent 3ea32ea commit 7b47b87

27 files changed

+1527
-701
lines changed

examples/2016-10-31/api_resource_policy/template.yaml

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,33 @@
11
AWSTemplateFormatVersion: '2010-09-09'
22
Transform: AWS::Serverless-2016-10-31
3+
Conditions:
4+
C1:
5+
Fn::Equals:
6+
- true
7+
- true
38
Globals:
49
Api:
510
Auth:
611
ResourcePolicy:
7-
CustomStatements: [{
8-
"Effect": "Allow",
9-
"Principal": "*",
10-
"Action": "execute-api:Invoke",
11-
"Resource": "execute-api:/Prod/PUT/get",
12-
"Condition": {
13-
"IpAddress": {
14-
"aws:SourceIp": "1.2.3.4"
15-
}
16-
}
17-
}]
18-
# OR you can use the following, they both do the same thing
19-
IpRangeBlacklist: ['1.2.3.4']
12+
CustomStatements:
13+
Fn::If:
14+
- C1
15+
- Principal: '*'
16+
Action: execute-api:Invoke
17+
Resource:
18+
- execute-api:/Prod/PUT/get
19+
Condition:
20+
IpAddress:
21+
aws:SourceIp: 1.2.3.4
22+
- Principal: '*'
23+
Action: execute-api:Invoke
24+
Resource:
25+
- execute-api:/Prod/PUT/get
26+
Condition:
27+
IpAddress:
28+
aws:SourceIp: 5.6.7.8
29+
# OR you can use the following
30+
# IpRangeBlacklist: ['1.2.3.4']
2031
Resources:
2132
MyFunction:
2233
Type: AWS::Serverless::Function

samtranslator/model/api/api_generator.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,8 @@ def _add_auth(self):
535535
swagger_editor.add_resource_policy(
536536
auth_properties.ResourcePolicy, path, self.logical_id, self.stage_name
537537
)
538+
if auth_properties.ResourcePolicy.get("CustomStatements"):
539+
swagger_editor.add_custom_statements(auth_properties.ResourcePolicy.get("CustomStatements"))
538540

539541
self.definition_body = self._openapi_postprocess(swagger_editor.swagger)
540542

samtranslator/model/eventsources/push.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,7 @@ def to_cloudformation(self, **kwargs):
545545
resources = []
546546

547547
function = kwargs.get("function")
548+
intrinsics_resolver = kwargs.get("intrinsics_resolver")
548549

549550
if not function:
550551
raise TypeError("Missing required keyword argument: function")
@@ -557,7 +558,7 @@ def to_cloudformation(self, **kwargs):
557558

558559
explicit_api = kwargs["explicit_api"]
559560
if explicit_api.get("__MANAGE_SWAGGER"):
560-
self._add_swagger_integration(explicit_api, function)
561+
self._add_swagger_integration(explicit_api, function, intrinsics_resolver)
561562

562563
return resources
563564

@@ -600,7 +601,7 @@ def _get_permission(self, resources_to_link, stage, suffix):
600601

601602
return self._construct_permission(resources_to_link["function"], source_arn=source_arn, suffix=suffix)
602603

603-
def _add_swagger_integration(self, api, function):
604+
def _add_swagger_integration(self, api, function, intrinsics_resolver):
604605
"""Adds the path and method for this Api event source to the Swagger body for the provided RestApi.
605606
606607
:param model.apigateway.ApiGatewayRestApi rest_api: the RestApi to which the path and method should be added.
@@ -639,6 +640,7 @@ def _add_swagger_integration(self, api, function):
639640
if self.Auth:
640641
method_authorizer = self.Auth.get("Authorizer")
641642
api_auth = api.get("Auth")
643+
api_auth = intrinsics_resolver.resolve_parameter_refs(api_auth)
642644

643645
if method_authorizer:
644646
api_authorizers = api_auth and api_auth.get("Authorizers")

samtranslator/model/sam_resources.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,11 @@ def to_cloudformation(self, **kwargs):
168168

169169
try:
170170
resources += self._generate_event_resources(
171-
lambda_function, execution_role, kwargs["event_resources"], lambda_alias=lambda_alias
171+
lambda_function,
172+
execution_role,
173+
kwargs["event_resources"],
174+
intrinsics_resolver,
175+
lambda_alias=lambda_alias,
172176
)
173177
except InvalidEventException as e:
174178
raise InvalidResourceException(self.logical_id, e.message)
@@ -563,7 +567,9 @@ def order_events(event):
563567
return logical_id
564568
return event_dict.get("Properties", {}).get("Path", logical_id)
565569

566-
def _generate_event_resources(self, lambda_function, execution_role, event_resources, lambda_alias=None):
570+
def _generate_event_resources(
571+
self, lambda_function, execution_role, event_resources, intrinsics_resolver, lambda_alias=None
572+
):
567573
"""Generates and returns the resources associated with this function's events.
568574
569575
:param model.lambda_.LambdaFunction lambda_function: generated Lambda function
@@ -591,6 +597,7 @@ def _generate_event_resources(self, lambda_function, execution_role, event_resou
591597
# When Alias is provided, connect all event sources to the alias and *not* the function
592598
"function": lambda_alias or lambda_function,
593599
"role": execution_role,
600+
"intrinsics_resolver": intrinsics_resolver,
594601
}
595602

596603
for name, resource in event_resources[logical_id].items():
@@ -773,7 +780,6 @@ def to_cloudformation(self, **kwargs):
773780
self.BinaryMediaTypes = intrinsics_resolver.resolve_parameter_refs(self.BinaryMediaTypes)
774781
self.Domain = intrinsics_resolver.resolve_parameter_refs(self.Domain)
775782
self.Auth = intrinsics_resolver.resolve_parameter_refs(self.Auth)
776-
777783
redeploy_restapi_parameters = kwargs.get("redeploy_restapi_parameters")
778784

779785
api_generator = ApiGenerator(

samtranslator/swagger/swagger.py

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import re
44
from six import string_types
55

6-
from samtranslator.model.intrinsics import ref
7-
from samtranslator.model.intrinsics import make_conditional, fnSub
6+
from samtranslator.model.intrinsics import ref, is_intrinsic_no_value
7+
from samtranslator.model.intrinsics import make_conditional, fnSub, is_intrinsic_if
88
from samtranslator.model.exceptions import InvalidDocumentException, InvalidTemplateException
99

1010

@@ -804,7 +804,6 @@ def add_resource_policy(self, resource_policy, path, api_id, stage):
804804
ip_range_blacklist = resource_policy.get("IpRangeBlacklist")
805805
source_vpc_whitelist = resource_policy.get("SourceVpcWhitelist")
806806
source_vpc_blacklist = resource_policy.get("SourceVpcBlacklist")
807-
custom_statements = resource_policy.get("CustomStatements")
808807

809808
if aws_account_whitelist is not None:
810809
resource_list = self._get_method_path_uri_list(path, api_id, stage)
@@ -824,16 +823,16 @@ def add_resource_policy(self, resource_policy, path, api_id, stage):
824823

825824
if source_vpc_whitelist is not None:
826825
resource_list = self._get_method_path_uri_list(path, api_id, stage)
827-
for endpoint in source_vpc_whitelist:
828-
self._add_vpc_resource_policy_for_method(endpoint, "StringNotEquals", resource_list)
826+
self._add_vpc_resource_policy_for_method(source_vpc_whitelist, "StringNotEquals", resource_list)
829827

830828
if source_vpc_blacklist is not None:
831829
resource_list = self._get_method_path_uri_list(path, api_id, stage)
832-
for endpoint in source_vpc_blacklist:
833-
self._add_vpc_resource_policy_for_method(endpoint, "StringEquals", resource_list)
830+
self._add_vpc_resource_policy_for_method(source_vpc_blacklist, "StringEquals", resource_list)
834831

835-
if custom_statements is not None:
836-
self._add_custom_statement(custom_statements)
832+
self._doc[self._X_APIGW_POLICY] = self.resource_policy
833+
834+
def add_custom_statements(self, custom_statements):
835+
self._add_custom_statement(custom_statements)
837836

838837
self._doc[self._X_APIGW_POLICY] = self.resource_policy
839838

@@ -932,24 +931,33 @@ def _add_ip_resource_policy_for_method(self, ip_list, conditional, resource_list
932931
statement.extend([deny_statement])
933932
self.resource_policy["Statement"] = statement
934933

935-
def _add_vpc_resource_policy_for_method(self, vpc, conditional, resource_list):
934+
def _add_vpc_resource_policy_for_method(self, endpoint_list, conditional, resource_list):
936935
"""
937936
This method generates a policy statement to grant/deny specific VPC/VPCE access to the API method and
938937
appends it to the swagger under `x-amazon-apigateway-policy`
939938
:raises ValueError: If the conditional passed in does not match the allowed values.
940939
"""
941-
if not vpc:
940+
if not endpoint_list:
942941
return
943942

944943
if conditional not in ["StringNotEquals", "StringEquals"]:
945944
raise ValueError("Conditional must be one of {}".format(["StringNotEquals", "StringEquals"]))
946945

947946
vpce_regex = r"^vpce-"
948-
if not re.match(vpce_regex, vpc):
949-
endpoint = "aws:SourceVpc"
950-
else:
951-
endpoint = "aws:SourceVpce"
952-
947+
vpc_regex = r"^vpc-"
948+
vpc_list = []
949+
vpce_list = []
950+
for endpoint in endpoint_list:
951+
if re.match(vpce_regex, endpoint):
952+
vpce_list.append(endpoint)
953+
if re.match(vpc_regex, endpoint):
954+
vpc_list.append(endpoint)
955+
956+
condition = {}
957+
if vpc_list:
958+
condition["aws:SourceVpc"] = vpc_list
959+
if vpce_list:
960+
condition["aws:SourceVpce"] = vpce_list
953961
self.resource_policy["Version"] = "2012-10-17"
954962
allow_statement = {}
955963
allow_statement["Effect"] = "Allow"
@@ -962,7 +970,7 @@ def _add_vpc_resource_policy_for_method(self, vpc, conditional, resource_list):
962970
deny_statement["Action"] = "execute-api:Invoke"
963971
deny_statement["Resource"] = resource_list
964972
deny_statement["Principal"] = "*"
965-
deny_statement["Condition"] = {conditional: {endpoint: vpc}}
973+
deny_statement["Condition"] = {conditional: condition}
966974

967975
if self.resource_policy.get("Statement") is None:
968976
self.resource_policy["Statement"] = [allow_statement, deny_statement]
@@ -980,16 +988,17 @@ def _add_custom_statement(self, custom_statements):
980988
if custom_statements is None:
981989
return
982990

983-
if not isinstance(custom_statements, list):
984-
custom_statements = [custom_statements]
985-
986991
self.resource_policy["Version"] = "2012-10-17"
987992
if self.resource_policy.get("Statement") is None:
988993
self.resource_policy["Statement"] = custom_statements
989994
else:
995+
if not isinstance(custom_statements, list):
996+
custom_statements = [custom_statements]
997+
990998
statement = self.resource_policy["Statement"]
991999
if not isinstance(statement, list):
9921000
statement = [statement]
1001+
9931002
for s in custom_statements:
9941003
if s not in statement:
9951004
statement.append(s)

tests/swagger/test_swagger.py

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -995,7 +995,7 @@ def test_must_add_custom_statements(self):
995995
]
996996
}
997997

998-
self.editor.add_resource_policy(resourcePolicy, "/foo", "123", "prod")
998+
self.editor.add_custom_statements(resourcePolicy.get("CustomStatements"))
999999

10001000
expected = {
10011001
"Version": "2012-10-17",
@@ -1007,6 +1007,33 @@ def test_must_add_custom_statements(self):
10071007

10081008
self.assertEqual(deep_sort_lists(expected), deep_sort_lists(self.editor.swagger[_X_POLICY]))
10091009

1010+
def test_must_add_fn_if_custom_statements(self):
1011+
1012+
resourcePolicy = {
1013+
"CustomStatements": {
1014+
"Fn::If": [
1015+
"condition",
1016+
{"Action": "execute-api:Invoke", "Resource": ["execute-api:/*/*/*"]},
1017+
{"Action": "execute-api:blah", "Resource": ["execute-api:/*/*/*"]},
1018+
],
1019+
}
1020+
}
1021+
1022+
self.editor.add_custom_statements(resourcePolicy.get("CustomStatements"))
1023+
1024+
expected = {
1025+
"Version": "2012-10-17",
1026+
"Statement": {
1027+
"Fn::If": [
1028+
"condition",
1029+
{"Action": "execute-api:Invoke", "Resource": ["execute-api:/*/*/*"]},
1030+
{"Action": "execute-api:blah", "Resource": ["execute-api:/*/*/*"]},
1031+
]
1032+
},
1033+
}
1034+
1035+
self.assertEqual(deep_sort_lists(expected), deep_sort_lists(self.editor.swagger[_X_POLICY]))
1036+
10101037
def test_must_add_iam_allow(self):
10111038
## fails
10121039
resourcePolicy = {"AwsAccountWhitelist": ["123456"]}
@@ -1140,17 +1167,7 @@ def test_must_add_vpc_allow(self):
11401167
{"Fn::Sub": ["execute-api:/${__Stage__}/GET/foo", {"__Stage__": "prod"}]},
11411168
],
11421169
"Effect": "Deny",
1143-
"Condition": {"StringNotEquals": {"aws:SourceVpc": "vpc-123"}},
1144-
"Principal": "*",
1145-
},
1146-
{
1147-
"Action": "execute-api:Invoke",
1148-
"Resource": [
1149-
{"Fn::Sub": ["execute-api:/${__Stage__}/PUT/foo", {"__Stage__": "prod"}]},
1150-
{"Fn::Sub": ["execute-api:/${__Stage__}/GET/foo", {"__Stage__": "prod"}]},
1151-
],
1152-
"Effect": "Deny",
1153-
"Condition": {"StringNotEquals": {"aws:SourceVpce": "vpce-345"}},
1170+
"Condition": {"StringNotEquals": {"aws:SourceVpc": ["vpc-123"], "aws:SourceVpce": ["vpce-345"]}},
11541171
"Principal": "*",
11551172
},
11561173
],
@@ -1183,7 +1200,7 @@ def test_must_add_vpc_deny(self):
11831200
{"Fn::Sub": ["execute-api:/${__Stage__}/GET/foo", {"__Stage__": "prod"}]},
11841201
],
11851202
"Effect": "Deny",
1186-
"Condition": {"StringEquals": {"aws:SourceVpc": "vpc-123"}},
1203+
"Condition": {"StringEquals": {"aws:SourceVpc": ["vpc-123"]}},
11871204
"Principal": "*",
11881205
},
11891206
],
@@ -1201,6 +1218,7 @@ def test_must_add_iam_allow_and_custom(self):
12011218
}
12021219

12031220
self.editor.add_resource_policy(resourcePolicy, "/foo", "123", "prod")
1221+
self.editor.add_custom_statements(resourcePolicy.get("CustomStatements"))
12041222

12051223
expected = {
12061224
"Version": "2012-10-17",
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
Conditions:
2+
C1:
3+
Fn::Equals:
4+
- true
5+
- true
6+
Resources:
7+
ExplicitApi:
8+
Type: AWS::Serverless::Api
9+
Properties:
10+
StageName: Prod
11+
Auth:
12+
ResourcePolicy:
13+
CustomStatements:
14+
Fn::If: [
15+
C1,
16+
{
17+
Action: 'execute-api:Invoke',
18+
Resource: ['execute-api:/*/*/*']
19+
},
20+
{
21+
Ref: 'AWS::NoValue'
22+
},
23+
]
24+
25+
ExplicitApiFunction:
26+
Type: AWS::Serverless::Function
27+
Properties:
28+
CodeUri: s3://sam-demo-bucket/member_portal.zip
29+
Handler: index.gethtml
30+
Runtime: nodejs12.x
31+
Events:
32+
GetHtml:
33+
Type: Api
34+
Properties:
35+
RestApiId:
36+
Ref: ExplicitApi
37+
Path: /one
38+
Method: get
39+
PostHtml:
40+
Type: Api
41+
Properties:
42+
RestApiId:
43+
Ref: ExplicitApi
44+
Path: /two
45+
Method: post
46+
PutHtml:
47+
Type: Api
48+
Properties:
49+
RestApiId:
50+
Ref: ExplicitApi
51+
Path: /three
52+
Method: put
53+
54+

0 commit comments

Comments
 (0)