Skip to content

Commit ece83c8

Browse files
authored
Feature: filtering for ListFirewallRules in Route53Resolver (#11742)
1 parent 2a9fdec commit ece83c8

File tree

4 files changed

+360
-17
lines changed

4 files changed

+360
-17
lines changed

localstack-core/localstack/services/route53resolver/provider.py

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,12 @@ def create_firewall_rule_group(
121121
) -> CreateFirewallRuleGroupResponse:
122122
"""Create a Firewall Rule Group."""
123123
store = self.get_store(context.account_id, context.region)
124-
id = get_route53_resolver_firewall_rule_group_id()
125-
arn = arns.route53_resolver_firewall_rule_group_arn(id, context.account_id, context.region)
124+
firewall_rule_group_id = get_route53_resolver_firewall_rule_group_id()
125+
arn = arns.route53_resolver_firewall_rule_group_arn(
126+
firewall_rule_group_id, context.account_id, context.region
127+
)
126128
firewall_rule_group = FirewallRuleGroup(
127-
Id=id,
129+
Id=firewall_rule_group_id,
128130
Arn=arn,
129131
Name=name,
130132
RuleCount=0,
@@ -136,7 +138,8 @@ def create_firewall_rule_group(
136138
CreationTime=datetime.now(timezone.utc).isoformat(),
137139
ModificationTime=datetime.now(timezone.utc).isoformat(),
138140
)
139-
store.firewall_rule_groups[id] = firewall_rule_group
141+
store.firewall_rule_groups[firewall_rule_group_id] = firewall_rule_group
142+
store.firewall_rules[firewall_rule_group_id] = {}
140143
route53resolver_backends[context.account_id][context.region].tagger.tag_resource(
141144
arn, tags or []
142145
)
@@ -342,11 +345,9 @@ def create_firewall_rule(
342345
FirewallDomainRedirectionAction=firewall_domain_redirection_action,
343346
Qtype=qtype,
344347
)
345-
if store.firewall_rules.get(firewall_rule_group_id):
346-
store.firewall_rules[firewall_rule_group_id][firewall_domain_list_id] = firewall_rule
347-
else:
348-
store.firewall_rules[firewall_rule_group_id] = {}
348+
if firewall_rule_group_id in store.firewall_rules:
349349
store.firewall_rules[firewall_rule_group_id][firewall_domain_list_id] = firewall_rule
350+
# TODO: handle missing firewall-rule-group-id
350351
return CreateFirewallRuleResponse(FirewallRule=firewall_rule)
351352

352353
def delete_firewall_rule(
@@ -377,19 +378,29 @@ def list_firewall_rules(
377378
next_token: NextToken = None,
378379
**kwargs,
379380
) -> ListFirewallRulesResponse:
380-
"""List all the firewall rules in a firewall rule group."""
381-
# TODO: implement priority and action filtering
381+
"""List firewall rules in a firewall rule group.
382+
383+
Rules will be filtered by priority and action if values for these params are provided.
384+
385+
Raises:
386+
ResourceNotFound: If a firewall group by the provided id does not exist.
387+
"""
382388
store = self.get_store(context.account_id, context.region)
383-
firewall_rules = []
384-
for firewall_rule in store.firewall_rules.get(firewall_rule_group_id, {}).values():
385-
firewall_rules.append(FirewallRule(firewall_rule))
386-
if len(firewall_rules) == 0:
389+
firewall_rule_group = store.firewall_rules.get(firewall_rule_group_id)
390+
if firewall_rule_group is None:
387391
raise ResourceNotFoundException(
388392
f"Can't find the resource with ID '{firewall_rule_group_id}'. Trace Id: '{localstack.services.route53resolver.utils.get_trace_id()}'"
389393
)
390-
return ListFirewallRulesResponse(
391-
FirewallRules=firewall_rules,
392-
)
394+
395+
firewall_rules = [
396+
FirewallRule(rule)
397+
for rule in firewall_rule_group.values()
398+
if (action is None or action == rule["Action"])
399+
and (priority is None or priority == rule["Priority"])
400+
]
401+
402+
# TODO: implement max_results filtering and next_token handling
403+
return ListFirewallRulesResponse(FirewallRules=firewall_rules)
393404

394405
def update_firewall_rule(
395406
self,

tests/aws/services/route53resolver/test_route53resolver.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import pytest
55

66
from localstack.aws.api.route53resolver import (
7+
Action,
78
ListResolverEndpointsResponse,
89
ListResolverQueryLogConfigsResponse,
910
ListResolverRuleAssociationsResponse,
@@ -23,6 +24,27 @@ def route53resolver_api_snapshot_transformer(snapshot):
2324
snapshot.add_transformer(snapshot.transform.route53resolver_api())
2425

2526

27+
@pytest.fixture
28+
def create_firewall_rule(aws_client: ServiceLevelClientFactory):
29+
rules = []
30+
31+
def inner(**kwargs):
32+
kwargs.setdefault("Name", f"rule-name-{short_uid()}")
33+
rule_group_id = kwargs["FirewallRuleGroupId"]
34+
domain_list_id = kwargs["FirewallDomainListId"]
35+
response = aws_client.route53resolver.create_firewall_rule(**kwargs)
36+
rules.append((rule_group_id, domain_list_id))
37+
return response
38+
39+
yield inner
40+
41+
for rule_group_id, domain_list_id in rules[::-1]:
42+
aws_client.route53resolver.delete_firewall_rule(
43+
FirewallRuleGroupId=rule_group_id,
44+
FirewallDomainListId=domain_list_id,
45+
)
46+
47+
2648
# TODO: extract this somewhere so that we can reuse it in other places
2749
def _cleanup_vpc(aws_client: ServiceLevelClientFactory, vpc_id: str):
2850
"""
@@ -721,3 +743,125 @@ def test_list_firewall_domain_lists(self, cleanups, snapshot, aws_client):
721743

722744
tag_result = aws_client.route53resolver.list_tags_for_resource(ResourceArn=arn)
723745
snapshot.match("list-tags-for-resource", tag_result)
746+
747+
@markers.aws.validated
748+
@markers.snapshot.skip_snapshot_verify(paths=["$..Message"])
749+
def test_list_firewall_rules_for_missing_rule_group(self, snapshot, aws_client):
750+
"""Test listing firewall rules for a non-existing rule-group."""
751+
with pytest.raises(
752+
aws_client.route53resolver.exceptions.ResourceNotFoundException
753+
) as resource_not_found:
754+
aws_client.route53resolver.list_firewall_rules(FirewallRuleGroupId="missing-id")
755+
756+
snapshot.add_transformer(
757+
snapshot.transform.regex(r"\d{1}-[a-f0-9]{8}-[a-f0-9]{24}", "trace-id")
758+
)
759+
snapshot.match("missing-firewall-rule-group-id", resource_not_found.value.response)
760+
761+
@markers.aws.validated
762+
def test_list_firewall_rules_for_empty_rule_group(self, cleanups, snapshot, aws_client):
763+
snapshot.add_transformer(snapshot.transform.key_value("Name"))
764+
765+
rule_group_response = aws_client.route53resolver.create_firewall_rule_group(
766+
Name=f"empty-{short_uid()}"
767+
)
768+
cleanups.append(
769+
lambda: aws_client.route53resolver.delete_firewall_rule_group(
770+
FirewallRuleGroupId=rule_group_response["FirewallRuleGroup"]["Id"]
771+
)
772+
)
773+
snapshot.match("create-firewall-rule-group", rule_group_response)
774+
775+
response = aws_client.route53resolver.list_firewall_rules(
776+
FirewallRuleGroupId=rule_group_response["FirewallRuleGroup"]["Id"]
777+
)
778+
snapshot.match("empty-firewall-rule-group", response)
779+
780+
@markers.aws.validated
781+
@markers.snapshot.skip_snapshot_verify(paths=["$..FirewallDomainRedirectionAction"])
782+
def test_list_firewall_rules(
783+
self,
784+
cleanups,
785+
snapshot,
786+
aws_client,
787+
create_firewall_rule,
788+
):
789+
"""Test listing firewall rules.
790+
791+
We test listing:
792+
- all rules in the rule-group
793+
- rules filtered by priority
794+
- rules filtered by action
795+
- rules filtered by priority and action
796+
"""
797+
798+
snapshot.add_transformer(
799+
[
800+
snapshot.transform.key_value("Name"),
801+
snapshot.transform.key_value("FirewallRuleGroupId"),
802+
snapshot.transform.key_value("FirewallDomainListId"),
803+
]
804+
)
805+
806+
firewall_rule_group_name = f"fw-rule-group-{short_uid()}"
807+
rule_group_response = aws_client.route53resolver.create_firewall_rule_group(
808+
Name=firewall_rule_group_name
809+
)
810+
cleanups.append(
811+
lambda rule_group_id=rule_group_response["FirewallRuleGroup"][
812+
"Id"
813+
]: aws_client.route53resolver.delete_firewall_rule_group(
814+
FirewallRuleGroupId=rule_group_id
815+
)
816+
)
817+
# Parameters for creating resources
818+
priorities = [1, 2, 3, 4]
819+
actions = [Action.ALLOW, Action.ALERT, Action.ALERT, Action.ALLOW]
820+
821+
for action, priority in zip(actions, priorities):
822+
domain_list_response = aws_client.route53resolver.create_firewall_domain_list(
823+
Name=f"fw-domain-list-{short_uid()}"
824+
)
825+
cleanups.append(
826+
lambda domain_list_id=domain_list_response["FirewallDomainList"][
827+
"Id"
828+
]: aws_client.route53resolver.delete_firewall_domain_list(
829+
FirewallDomainListId=domain_list_id
830+
)
831+
)
832+
create_firewall_rule(
833+
FirewallRuleGroupId=rule_group_response["FirewallRuleGroup"]["Id"],
834+
FirewallDomainListId=domain_list_response["FirewallDomainList"]["Id"],
835+
Priority=priority,
836+
Action=action,
837+
)
838+
839+
# Check list filtering
840+
list_all_response = aws_client.route53resolver.list_firewall_rules(
841+
FirewallRuleGroupId=rule_group_response["FirewallRuleGroup"]["Id"]
842+
)
843+
snapshot.match("firewall-rules-list-all", list_all_response)
844+
845+
filter_by_priority_response = aws_client.route53resolver.list_firewall_rules(
846+
FirewallRuleGroupId=rule_group_response["FirewallRuleGroup"]["Id"], Priority=1
847+
)
848+
snapshot.match("firewall-rules-list-by-priority", filter_by_priority_response)
849+
850+
filter_by_action_response = aws_client.route53resolver.list_firewall_rules(
851+
FirewallRuleGroupId=rule_group_response["FirewallRuleGroup"]["Id"], Action=Action.ALLOW
852+
)
853+
snapshot.match("firewall-rules-list-by-action", filter_by_action_response)
854+
855+
action_and_priority_response = aws_client.route53resolver.list_firewall_rules(
856+
FirewallRuleGroupId=rule_group_response["FirewallRuleGroup"]["Id"],
857+
Action=Action.ALLOW,
858+
Priority=4,
859+
)
860+
snapshot.match("firewall-rules-list-by-action-and-priority", action_and_priority_response)
861+
862+
filter_empty_response = aws_client.route53resolver.list_firewall_rules(
863+
FirewallRuleGroupId=rule_group_response["FirewallRuleGroup"]["Id"],
864+
Action=Action.ALLOW,
865+
Priority=0, # 0 catches cases when integers pose as booleans
866+
)
867+
snapshot.match("firewall-rules-list-no-match", filter_empty_response)

0 commit comments

Comments
 (0)