Skip to content

Commit 948e8f8

Browse files
authored
Merge pull request #1207 from mtulio/fix-1206-update-sg
fix/lb SG rule update failures when service port is changed
2 parents 4fe0aaf + 421639c commit 948e8f8

File tree

2 files changed

+272
-4
lines changed

2 files changed

+272
-4
lines changed

pkg/providers/v1/sets_ippermissions.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,18 +141,24 @@ func (s IPPermissionSet) Equal(s2 IPPermissionSet) bool {
141141
return len(s) == len(s2) && s.IsSuperset(s2)
142142
}
143143

144-
// Difference returns a set of objects that are not in s2
144+
// Difference returns a set of objects that are not in s2.
145145
// For example:
146146
// s1 = {a1, a2, a3}
147147
// s2 = {a1, a2, a4, a5}
148148
// s1.Difference(s2) = {a3}
149149
// s2.Difference(s1) = {a4, a5}
150150
func (s IPPermissionSet) Difference(s2 IPPermissionSet) IPPermissionSet {
151151
result := NewIPPermissionSet()
152-
for k, v := range s {
153-
_, found := s2[k]
152+
for _, desired := range s.List() {
153+
found := false
154+
for _, existing := range s2.List() {
155+
if ipPermissionExists(&desired, &existing, false) {
156+
found = true
157+
break
158+
}
159+
}
154160
if !found {
155-
result[k] = v
161+
result.Insert(desired)
156162
}
157163
}
158164
return result

pkg/providers/v1/sets_ippermissions_test.go

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/aws/aws-sdk-go-v2/aws"
77
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
8+
"github.com/stretchr/testify/assert"
89
)
910

1011
func TestUngroup(t *testing.T) {
@@ -156,3 +157,264 @@ func TestUngroup(t *testing.T) {
156157
})
157158
}
158159
}
160+
161+
func TestIPPermissionSetDifferenceCriticalScenarios(t *testing.T) {
162+
t.Run("real_world_nlb_security_group_scenario", func(t *testing.T) {
163+
// Scenario:
164+
// Desired: tcp:80, tcp:81, icmp:3-4
165+
// Actual: tcp:80, icmp:3-4
166+
// Expected: add tcp:81 only, remove nothing
167+
168+
desired := NewIPPermissionSet(
169+
ec2types.IpPermission{
170+
IpProtocol: aws.String("tcp"),
171+
FromPort: aws.Int32(80),
172+
ToPort: aws.Int32(80),
173+
IpRanges: []ec2types.IpRange{
174+
{CidrIp: aws.String("0.0.0.0/0")},
175+
},
176+
},
177+
ec2types.IpPermission{
178+
IpProtocol: aws.String("tcp"),
179+
FromPort: aws.Int32(81),
180+
ToPort: aws.Int32(81),
181+
IpRanges: []ec2types.IpRange{
182+
{CidrIp: aws.String("0.0.0.0/0")},
183+
},
184+
},
185+
ec2types.IpPermission{
186+
IpProtocol: aws.String("icmp"),
187+
FromPort: aws.Int32(3),
188+
ToPort: aws.Int32(4),
189+
IpRanges: []ec2types.IpRange{
190+
{CidrIp: aws.String("0.0.0.0/0")},
191+
},
192+
},
193+
)
194+
195+
actual := NewIPPermissionSet(
196+
ec2types.IpPermission{
197+
IpProtocol: aws.String("tcp"),
198+
FromPort: aws.Int32(80),
199+
ToPort: aws.Int32(80),
200+
IpRanges: []ec2types.IpRange{
201+
{CidrIp: aws.String("0.0.0.0/0")},
202+
},
203+
},
204+
ec2types.IpPermission{
205+
IpProtocol: aws.String("icmp"),
206+
FromPort: aws.Int32(3),
207+
ToPort: aws.Int32(4),
208+
IpRanges: []ec2types.IpRange{
209+
{CidrIp: aws.String("0.0.0.0/0")},
210+
},
211+
},
212+
)
213+
214+
// Calculate what should be added and removed
215+
add := desired.Difference(actual)
216+
remove := actual.Difference(desired)
217+
218+
// Verify correct results
219+
assert.Equal(t, 1, add.Len(), "Should add exactly one permission (tcp:81)")
220+
assert.Equal(t, 0, remove.Len(), "Should remove no permissions")
221+
222+
// Verify the added permission is tcp:81
223+
addList := add.List()
224+
if len(addList) > 0 {
225+
perm := addList[0]
226+
assert.Equal(t, "tcp", aws.ToString(perm.IpProtocol))
227+
assert.Equal(t, int32(81), aws.ToInt32(perm.FromPort))
228+
assert.Equal(t, int32(81), aws.ToInt32(perm.ToPort))
229+
}
230+
})
231+
232+
t.Run("empty_sets_and_edge_cases", func(t *testing.T) {
233+
// Test edge cases with empty sets and nil scenarios
234+
235+
emptySet := NewIPPermissionSet()
236+
nonEmptySet := NewIPPermissionSet(
237+
ec2types.IpPermission{
238+
IpProtocol: aws.String("tcp"),
239+
FromPort: aws.Int32(443),
240+
ToPort: aws.Int32(443),
241+
IpRanges: []ec2types.IpRange{
242+
{CidrIp: aws.String("10.0.0.0/8")},
243+
},
244+
},
245+
)
246+
247+
// Empty - NonEmpty should return empty
248+
diff1 := emptySet.Difference(nonEmptySet)
249+
assert.Equal(t, 0, diff1.Len(), "Empty set difference with non-empty should be empty")
250+
251+
// NonEmpty - Empty should return all from NonEmpty
252+
diff2 := nonEmptySet.Difference(emptySet)
253+
assert.Equal(t, 1, diff2.Len(), "Non-empty set difference with empty should return all permissions")
254+
255+
// Empty - Empty should return empty
256+
diff3 := emptySet.Difference(emptySet)
257+
assert.Equal(t, 0, diff3.Len(), "Empty set difference with empty should be empty")
258+
})
259+
260+
t.Run("initialization_issue_prevention", func(t *testing.T) {
261+
// Test that demonstrates the importance of proper initialization
262+
// This prevents the bug where variables were declared as `var add IPPermissionSet`
263+
264+
sourceSet := NewIPPermissionSet(
265+
ec2types.IpPermission{
266+
IpProtocol: aws.String("tcp"),
267+
FromPort: aws.Int32(80),
268+
ToPort: aws.Int32(80),
269+
IpRanges: []ec2types.IpRange{
270+
{CidrIp: aws.String("0.0.0.0/0")},
271+
},
272+
},
273+
)
274+
275+
// Test with properly initialized empty set
276+
emptySet := NewIPPermissionSet()
277+
diff := sourceSet.Difference(emptySet)
278+
assert.Equal(t, 1, diff.Len(), "Difference with properly initialized empty set should work")
279+
280+
// Test that uninitialized set doesn't cause panic in Difference operation
281+
var uninitializedSet IPPermissionSet
282+
defer func() {
283+
if r := recover(); r != nil {
284+
t.Errorf("Difference operation should not panic with uninitialized set: %v", r)
285+
}
286+
}()
287+
288+
// This should not panic (though behavior may be undefined)
289+
_ = sourceSet.Difference(uninitializedSet)
290+
})
291+
292+
t.Run("multiple_ip_ranges_scenario", func(t *testing.T) {
293+
// Test complex permissions with multiple IP ranges to ensure
294+
// the Difference function handles them correctly
295+
296+
desired := NewIPPermissionSet(
297+
// Permission with multiple IP ranges
298+
ec2types.IpPermission{
299+
IpProtocol: aws.String("tcp"),
300+
FromPort: aws.Int32(443),
301+
ToPort: aws.Int32(443),
302+
IpRanges: []ec2types.IpRange{
303+
{CidrIp: aws.String("10.0.0.0/8")},
304+
{CidrIp: aws.String("172.16.0.0/12")},
305+
{CidrIp: aws.String("192.168.0.0/16")},
306+
},
307+
},
308+
// Single IP range permission
309+
ec2types.IpPermission{
310+
IpProtocol: aws.String("tcp"),
311+
FromPort: aws.Int32(80),
312+
ToPort: aws.Int32(80),
313+
IpRanges: []ec2types.IpRange{
314+
{CidrIp: aws.String("0.0.0.0/0")},
315+
},
316+
},
317+
)
318+
319+
actual := NewIPPermissionSet(
320+
// Same permission with multiple IP ranges (should match)
321+
ec2types.IpPermission{
322+
IpProtocol: aws.String("tcp"),
323+
FromPort: aws.Int32(443),
324+
ToPort: aws.Int32(443),
325+
IpRanges: []ec2types.IpRange{
326+
{CidrIp: aws.String("10.0.0.0/8")},
327+
{CidrIp: aws.String("172.16.0.0/12")},
328+
{CidrIp: aws.String("192.168.0.0/16")},
329+
},
330+
},
331+
// Different permission with multiple IP ranges
332+
ec2types.IpPermission{
333+
IpProtocol: aws.String("tcp"),
334+
FromPort: aws.Int32(8080),
335+
ToPort: aws.Int32(8080),
336+
IpRanges: []ec2types.IpRange{
337+
{CidrIp: aws.String("10.0.0.0/8")},
338+
{CidrIp: aws.String("172.16.0.0/12")},
339+
},
340+
},
341+
)
342+
343+
// Calculate differences
344+
add := desired.Difference(actual)
345+
remove := actual.Difference(desired)
346+
347+
// Should add tcp:80 (not present in actual)
348+
assert.Equal(t, 1, add.Len(), "Should add exactly one permission (tcp:80)")
349+
350+
// Should remove tcp:8080 (not present in desired)
351+
assert.Equal(t, 1, remove.Len(), "Should remove exactly one permission (tcp:8080)")
352+
353+
// Verify what's being added
354+
addList := add.List()
355+
if len(addList) > 0 {
356+
perm := addList[0]
357+
assert.Equal(t, "tcp", aws.ToString(perm.IpProtocol))
358+
assert.Equal(t, int32(80), aws.ToInt32(perm.FromPort))
359+
assert.Equal(t, int32(80), aws.ToInt32(perm.ToPort))
360+
assert.Equal(t, 1, len(perm.IpRanges), "Should have one IP range")
361+
assert.Equal(t, "0.0.0.0/0", aws.ToString(perm.IpRanges[0].CidrIp))
362+
}
363+
364+
// Verify what's being removed
365+
removeList := remove.List()
366+
if len(removeList) > 0 {
367+
perm := removeList[0]
368+
assert.Equal(t, "tcp", aws.ToString(perm.IpProtocol))
369+
assert.Equal(t, int32(8080), aws.ToInt32(perm.FromPort))
370+
assert.Equal(t, int32(8080), aws.ToInt32(perm.ToPort))
371+
assert.Equal(t, 2, len(perm.IpRanges), "Should have two IP ranges")
372+
}
373+
})
374+
375+
t.Run("identical_permissions_different_ip_range_order", func(t *testing.T) {
376+
// Test that permissions with same IP ranges but in different order
377+
// are treated as identical (this tests the robustness of the key generation)
378+
379+
perm1 := ec2types.IpPermission{
380+
IpProtocol: aws.String("tcp"),
381+
FromPort: aws.Int32(443),
382+
ToPort: aws.Int32(443),
383+
IpRanges: []ec2types.IpRange{
384+
{CidrIp: aws.String("10.0.0.0/8")},
385+
{CidrIp: aws.String("172.16.0.0/12")},
386+
{CidrIp: aws.String("192.168.0.0/16")},
387+
},
388+
}
389+
390+
perm2 := ec2types.IpPermission{
391+
IpProtocol: aws.String("tcp"),
392+
FromPort: aws.Int32(443),
393+
ToPort: aws.Int32(443),
394+
IpRanges: []ec2types.IpRange{
395+
{CidrIp: aws.String("192.168.0.0/16")}, // Different order
396+
{CidrIp: aws.String("10.0.0.0/8")},
397+
{CidrIp: aws.String("172.16.0.0/12")},
398+
},
399+
}
400+
401+
set1 := NewIPPermissionSet(perm1)
402+
set2 := NewIPPermissionSet(perm2)
403+
404+
// These should be different due to different order in JSON marshaling
405+
// (This tests the current behavior - if this fails, it indicates the key generation
406+
// doesn't account for order, which might be the root cause of issues)
407+
diff := set1.Difference(set2)
408+
409+
// Log the result to understand current behavior
410+
t.Logf("Difference between permissions with same IP ranges in different order: %d", diff.Len())
411+
412+
// The current implementation might treat these as different due to JSON marshaling
413+
// This test documents the current behavior and will help identify if this is the issue
414+
if diff.Len() == 0 {
415+
t.Log("Permissions with different IP range order are treated as identical")
416+
} else {
417+
t.Log("Permissions with different IP range order are treated as different")
418+
}
419+
})
420+
}

0 commit comments

Comments
 (0)