Skip to content

Commit 07be700

Browse files
authored
Fix NetworkACL default rules sync (#247)
Filter out AWS default rules (rule #32767) from both desired and actual states during synchronization Implement clear and documented behavior for default rules: 1. If default rules are explicitly defined in spec -> they remain in spec 2. If default rules are not defined in spec -> they are ignored during sync but preserved in AWS
1 parent 25104bd commit 07be700

File tree

6 files changed

+188
-15
lines changed

6 files changed

+188
-15
lines changed

apis/v1alpha1/ack-generate-metadata.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
ack_generate_info:
2-
build_date: "2025-02-20T17:55:49Z"
2+
build_date: "2025-02-26T00:29:35Z"
33
build_hash: a326346bd3a6973254d247c9ab2dc76790c36241
44
go_version: go1.24.0
55
version: v0.43.2

pkg/resource/network_acl/hooks.go

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,14 @@ func (rm *resourceManager) upsertNewAssociations(
303303
return nil
304304
}
305305

306+
// The function filters out AWS-managed default rules (rule #32767) from both desired
307+
// and latest states to prevent interference with AWS's automatic management of these
308+
// rules and to maintain GitOps compatibility.
309+
//
310+
// Default rules behavior:
311+
// - If default rules (rule #32767) are explicitly defined in the spec, they will remain in the spec
312+
// - If default rules are not defined in the spec, they will be ignored during sync
313+
// operations and not be added to the spec
306314
func (rm *resourceManager) syncEntries(
307315
ctx context.Context,
308316
desired *resource,
@@ -316,6 +324,19 @@ func (rm *resourceManager) syncEntries(
316324
toDelete := []*svcapitypes.NetworkACLEntry{}
317325
toUpdate := []*svcapitypes.NetworkACLEntry{}
318326

327+
// Filter out AWS default rules (rule #32767) from desired entries to ensure
328+
// they don't interfere with GitOps workflows. These rules are automatically
329+
// managed by AWS and should not be included in the desired state.
330+
filteredDesiredEntries := []*svcapitypes.NetworkACLEntry{}
331+
for _, entry := range desired.ko.Spec.Entries {
332+
if entry.RuleNumber != nil && *entry.RuleNumber == int64(DefaultRuleNumber) {
333+
continue
334+
}
335+
filteredDesiredEntries = append(filteredDesiredEntries, entry)
336+
}
337+
desired.ko.Spec.Entries = filteredDesiredEntries
338+
339+
// Check for duplicate rule numbers within the same direction (egress/ingress)
319340
uniqEntries := lo.UniqBy(desired.ko.Spec.Entries, func(entry *svcapitypes.NetworkACLEntry) string {
320341
return strconv.FormatBool(*entry.Egress) + strconv.Itoa(int(*entry.RuleNumber))
321342
})
@@ -324,25 +345,26 @@ func (rm *resourceManager) syncEntries(
324345
return errors.New("multple rules with the same rule number and Egress in the desired spec")
325346
}
326347

348+
// Identify new entries that need to be created
327349
for _, desiredEntry := range desired.ko.Spec.Entries {
328-
329-
if *((*desiredEntry).RuleNumber) == int64(DefaultRuleNumber) {
330-
// no-op for default route
331-
continue
332-
}
333-
334350
if latest != nil && !containsEntry(latest.ko.Spec.Entries, desiredEntry) {
335-
// a desired rule is not in the latest resource; therefore, create
336351
toAdd = append(toAdd, desiredEntry)
337352
}
338353
}
339354

340355
if latest != nil {
341-
for _, latestEntry := range latest.ko.Spec.Entries {
342-
if *((*latestEntry).RuleNumber) == int64(DefaultRuleNumber) {
343-
// no-op for default route
356+
// Filter out AWS default rules from latest entries before comparison
357+
// to ensure consistent state management between desired and actual
358+
filteredLatestEntries := []*svcapitypes.NetworkACLEntry{}
359+
for _, entry := range latest.ko.Spec.Entries {
360+
if entry.RuleNumber != nil && *entry.RuleNumber == int64(DefaultRuleNumber) {
344361
continue
345362
}
363+
filteredLatestEntries = append(filteredLatestEntries, entry)
364+
}
365+
366+
// Identify entries that need to be deleted (exist in latest but not in desired)
367+
for _, latestEntry := range filteredLatestEntries {
346368
if !containsEntry(desired.ko.Spec.Entries, latestEntry) {
347369
// entry is in latest resource, but not in desired resource; therefore, delete
348370
toDelete = append(toDelete, latestEntry)

pkg/resource/network_acl/sdk.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

templates/hooks/network_acl/sdk_create_post_set_output.go.tpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212

1313
if len(desired.ko.Spec.Entries) > 0 {
1414
//desired rules are overwritten by NetworkACL's default rules
15-
ko.Spec.Entries = append(ko.Spec.Entries, desired.ko.Spec.Entries...)
15+
ko.Spec.Entries = desired.ko.Spec.Entries
1616
copy := ko.DeepCopy()
1717
if err := rm.createEntries(ctx, &resource{copy}); err != nil {
18-
rlog.Debug("Error while syncing routes", err)
18+
rlog.Debug("Error while syncing entries", err)
1919
}
2020
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
apiVersion: ec2.services.k8s.aws/v1alpha1
2+
kind: NetworkACL
3+
metadata:
4+
name: $NETWORK_ACL_NAME
5+
spec:
6+
entries:
7+
# Default egress rule
8+
- cidrBlock: 0.0.0.0/0
9+
egress: true
10+
protocol: "-1"
11+
ruleAction: deny
12+
ruleNumber: 32767
13+
# Default ingress rule
14+
- cidrBlock: 0.0.0.0/0
15+
egress: false
16+
protocol: "-1"
17+
ruleAction: deny
18+
ruleNumber: 32767
19+
# Custom rule
20+
- cidrBlock: $CIDR_BLOCK
21+
egress: true
22+
portRange:
23+
from: 443
24+
to: 443
25+
protocol: "6"
26+
ruleAction: allow
27+
ruleNumber: 100
28+
vpcID: $VPC_ID

test/e2e/tests/test_network_acl.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,55 @@ def simple_network_acl(request):
9393
except:
9494
pass
9595

96+
@pytest.fixture
97+
def network_acl_with_default_rules(request):
98+
resource_name = random_suffix_name("network-acl-default-rules", 32)
99+
resource_file = "network_acl_with_default_rules"
100+
resources = get_bootstrap_resources()
101+
102+
replacements = REPLACEMENT_VALUES.copy()
103+
replacements["NETWORK_ACL_NAME"] = resource_name
104+
replacements["VPC_ID"] = resources.SharedTestVPC.vpc_id
105+
replacements["CIDR_BLOCK"] = "10.0.0.0/24"
106+
107+
marker = request.node.get_closest_marker("resource_data")
108+
if marker is not None:
109+
data = marker.args[0]
110+
if 'vpc_id' in data:
111+
replacements["VPC_ID"] = data['vpc_id']
112+
if 'cidr_block' in data:
113+
replacements["CIDR_BLOCK"] = data['cidr_block']
114+
if 'resource_file' in data:
115+
resource_file = data['resource_file']
116+
117+
# Load NetworkACL CR with default rules
118+
resource_data = load_ec2_resource(
119+
resource_file,
120+
additional_replacements=replacements,
121+
)
122+
logging.debug(resource_data)
123+
124+
# Create k8s resource
125+
ref = k8s.CustomResourceReference(
126+
CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL,
127+
resource_name, namespace="default",
128+
)
129+
k8s.create_custom_resource(ref, resource_data)
130+
time.sleep(CREATE_WAIT_AFTER_SECONDS)
131+
132+
cr = k8s.wait_resource_consumed_by_controller(ref)
133+
assert cr is not None
134+
assert k8s.get_resource_exists(ref)
135+
136+
yield (ref, cr)
137+
138+
# Try to delete, if doesn't already exist
139+
try:
140+
_, deleted = k8s.delete_custom_resource(ref, 3, 10)
141+
assert deleted
142+
except:
143+
pass
144+
96145
@service_marker
97146
@pytest.mark.canary
98147
class TestNetworkACLs:
@@ -324,3 +373,77 @@ def test_crud_tags(self, ec2_client, simple_network_acl):
324373

325374
# Check networkAcl no longer exists in AWS
326375
ec2_validator.assert_network_acl(resource_id, exists=False)
376+
377+
def test_default_rules_not_duplicated_on_create(self, ec2_client, network_acl_with_default_rules):
378+
(ref, cr) = network_acl_with_default_rules
379+
network_acl_id = cr["status"]["id"]
380+
381+
# Check NetworkACL exists in AWS
382+
ec2_validator = EC2Validator(ec2_client)
383+
ec2_validator.assert_network_acl(network_acl_id)
384+
385+
resource = k8s.get_resource(ref)
386+
387+
# Count how many rules with number 32767 exist in the spec
388+
default_rule_count = 0
389+
for entry in resource["spec"]["entries"]:
390+
if entry.get("ruleNumber") == 32767:
391+
default_rule_count += 1
392+
393+
# default rules are no op
394+
assert default_rule_count == 2, "Default rules should not be added to spec when not explicitly defined"
395+
396+
# Verify custom rule
397+
custom_rule_exists = False
398+
for entry in resource["spec"]["entries"]:
399+
if entry.get("ruleNumber") == 100:
400+
custom_rule_exists = True
401+
break
402+
403+
assert custom_rule_exists, "Custom rule with ruleNumber 100 not found in spec"
404+
405+
# Clean up
406+
_, deleted = k8s.delete_custom_resource(ref)
407+
assert deleted is True
408+
409+
time.sleep(DELETE_WAIT_AFTER_SECONDS)
410+
411+
# Verify the NetworkACL was deleted
412+
ec2_validator.assert_network_acl(network_acl_id, exists=False)
413+
414+
def test_default_rules_not_added_to_spec(self, ec2_client, simple_network_acl):
415+
(ref, cr) = simple_network_acl
416+
network_acl_id = cr["status"]["id"]
417+
418+
# Check NetworkACL exists in AWS
419+
ec2_validator = EC2Validator(ec2_client)
420+
ec2_validator.assert_network_acl(network_acl_id)
421+
422+
resource = k8s.get_resource(ref)
423+
424+
# Count how many rules with number 32767 exist in the spec
425+
default_rule_count = 0
426+
for entry in resource["spec"]["entries"]:
427+
if entry.get("ruleNumber") == 32767:
428+
default_rule_count += 1
429+
430+
# Verify no default rules were added to spec
431+
assert default_rule_count == 0, "Default rules should not be added to spec when not explicitly defined"
432+
433+
# Verify custom rule exists
434+
custom_rule_exists = False
435+
for entry in resource["spec"]["entries"]:
436+
if entry.get("ruleNumber") == 100:
437+
custom_rule_exists = True
438+
break
439+
440+
assert custom_rule_exists, "Custom rule with ruleNumber 100 not found in spec"
441+
442+
# Clean up
443+
_, deleted = k8s.delete_custom_resource(ref)
444+
assert deleted is True
445+
446+
time.sleep(DELETE_WAIT_AFTER_SECONDS)
447+
448+
# Verify the NetworkACL was deleted
449+
ec2_validator.assert_network_acl(network_acl_id, exists=False)

0 commit comments

Comments
 (0)