fix(traffic): reload trusted cidr changes
- commit
- 1f467a18b2480fad5e3963af549cf2daeefc0f8a
- author
- brice · · 2026-06-30 02:48 UTC
- parent
- f46f30c
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +73 -4
M internal/adapters/in/traffic/manager.go +23 -0
| @@ -362,6 +362,29 @@ func parseTrustedCIDRs(values []string) ([]*net.IPNet, error) { | |||
| 362 | 362 | return trusted, nil | |
| 363 | 363 | } | |
| 364 | 364 | ||
| 365 | + | func trustedCIDRsEqual(left []string, right []string) bool { | |
| 366 | + | left = normalizedCIDRs(left) | |
| 367 | + | right = normalizedCIDRs(right) | |
| 368 | + | if len(left) != len(right) { | |
| 369 | + | return false | |
| 370 | + | } | |
| 371 | + | for i := range left { | |
| 372 | + | if left[i] != right[i] { | |
| 373 | + | return false | |
| 374 | + | } | |
| 375 | + | } | |
| 376 | + | return true | |
| 377 | + | } | |
| 378 | + | ||
| 379 | + | func normalizedCIDRs(values []string) []string { | |
| 380 | + | out := make([]string, 0, len(values)) | |
| 381 | + | for _, value := range values { | |
| 382 | + | out = append(out, strings.TrimSpace(value)) | |
| 383 | + | } | |
| 384 | + | sort.Strings(out) | |
| 385 | + | return out | |
| 386 | + | } | |
| 387 | + | ||
| 365 | 388 | func trustedRemoteAddr(trusted []*net.IPNet, addr net.Addr) bool { | |
| 366 | 389 | if len(trusted) == 0 { | |
| 367 | 390 | return true | |
| @@ -362,6 +362,29 @@ func parseTrustedCIDRs(values []string) ([]*net.IPNet, error) { | |||||
| 362 | return trusted, nil | 362 | return trusted, nil | ||
| 363 | } | 363 | } | ||
| 364 | 364 | ||||
| 365 | + | func trustedCIDRsEqual(left []string, right []string) bool { | |||
| 366 | + | left = normalizedCIDRs(left) | |||
| 367 | + | right = normalizedCIDRs(right) | |||
| 368 | + | if len(left) != len(right) { | |||
| 369 | + | return false | |||
| 370 | + | } | |||
| 371 | + | for i := range left { | |||
| 372 | + | if left[i] != right[i] { | |||
| 373 | + | return false | |||
| 374 | + | } | |||
| 375 | + | } | |||
| 376 | + | return true | |||
| 377 | + | } | |||
| 378 | + | ||||
| 379 | + | func normalizedCIDRs(values []string) []string { | |||
| 380 | + | out := make([]string, 0, len(values)) | |||
| 381 | + | for _, value := range values { | |||
| 382 | + | out = append(out, strings.TrimSpace(value)) | |||
| 383 | + | } | |||
| 384 | + | sort.Strings(out) | |||
| 385 | + | return out | |||
| 386 | + | } | |||
| 387 | + | ||||
| 365 | func trustedRemoteAddr(trusted []*net.IPNet, addr net.Addr) bool { | 388 | func trustedRemoteAddr(trusted []*net.IPNet, addr net.Addr) bool { | ||
| 366 | if len(trusted) == 0 { | 389 | if len(trusted) == 0 { | ||
| 367 | return true | 390 | return true | ||
M internal/adapters/in/traffic/tcp.go +1 -1
| @@ -419,7 +419,7 @@ func (r *entryPointRuntime) closeActiveConns() { | |||
| 419 | 419 | } | |
| 420 | 420 | ||
| 421 | 421 | func (r *entryPointRuntime) matches(entryPoint domain.EntryPoint) bool { | |
| 422 | - | return r.entryPoint.Name == entryPoint.Name && r.entryPoint.Address == entryPoint.Address && r.entryPoint.Protocol == entryPoint.Protocol | |
| 422 | + | return r.entryPoint.Name == entryPoint.Name && r.entryPoint.Address == entryPoint.Address && r.entryPoint.Protocol == entryPoint.Protocol && trustedCIDRsEqual(r.entryPoint.TrustedCIDRs, entryPoint.TrustedCIDRs) | |
| 423 | 423 | } | |
| 424 | 424 | ||
| 425 | 425 | func (r *entryPointRuntime) shouldStopWith(next *entryPointRuntime) bool { | |
| @@ -419,7 +419,7 @@ func (r *entryPointRuntime) closeActiveConns() { | |||||
| 419 | } | 419 | } | ||
| 420 | 420 | ||||
| 421 | func (r *entryPointRuntime) matches(entryPoint domain.EntryPoint) bool { | 421 | func (r *entryPointRuntime) matches(entryPoint domain.EntryPoint) bool { | ||
| 422 | - | return r.entryPoint.Name == entryPoint.Name && r.entryPoint.Address == entryPoint.Address && r.entryPoint.Protocol == entryPoint.Protocol | 422 | + | return r.entryPoint.Name == entryPoint.Name && r.entryPoint.Address == entryPoint.Address && r.entryPoint.Protocol == entryPoint.Protocol && trustedCIDRsEqual(r.entryPoint.TrustedCIDRs, entryPoint.TrustedCIDRs) |
| 423 | } | 423 | } | ||
| 424 | 424 | ||||
| 425 | func (r *entryPointRuntime) shouldStopWith(next *entryPointRuntime) bool { | 425 | func (r *entryPointRuntime) shouldStopWith(next *entryPointRuntime) bool { | ||
M internal/adapters/in/traffic/tcp_test.go +24 -1
| @@ -88,13 +88,36 @@ func TestTCPPassthroughRejectsUntrustedCIDR(t *testing.T) { | |||
| 88 | 88 | require.NoError(t, manager.Apply(context.Background(), &graph)) | |
| 89 | 89 | defer shutdownManager(t, manager) | |
| 90 | 90 | ||
| 91 | + | assertTCPRejected(t, manager, graph.EntryPoints[0].Address, 1) | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | func TestTCPPassthroughReloadAppliesTrustedCIDRChange(t *testing.T) { | |
| 95 | + | backend := startTCPEchoServer(t, 0) | |
| 96 | + | graph := tcpGraph(t, freeTCPAddress(t), backend.address) | |
| 97 | + | graph.EntryPoints[0].TrustedCIDRs = []string{"127.0.0.0/8"} | |
| 98 | + | manager := NewManager() | |
| 99 | + | require.NoError(t, manager.Apply(context.Background(), &graph)) | |
| 100 | + | defer shutdownManager(t, manager) | |
| 101 | + | ||
| 91 | 102 | conn := dialTCP(t, graph.EntryPoints[0].Address) | |
| 103 | + | assertRoundTrip(t, conn, "allowed") | |
| 104 | + | require.NoError(t, conn.Close()) | |
| 105 | + | ||
| 106 | + | updated := graph | |
| 107 | + | updated.EntryPoints[0].TrustedCIDRs = []string{"192.0.2.0/24"} | |
| 108 | + | require.NoError(t, manager.Apply(context.Background(), &updated)) | |
| 109 | + | assertTCPRejected(t, manager, graph.EntryPoints[0].Address, 1) | |
| 110 | + | } | |
| 111 | + | ||
| 112 | + | func assertTCPRejected(t *testing.T, manager *Manager, address string, refused int64) { | |
| 113 | + | t.Helper() | |
| 114 | + | conn := dialTCP(t, address) | |
| 92 | 115 | defer conn.Close() | |
| 93 | 116 | _, err := conn.Write([]byte("blocked")) | |
| 94 | 117 | require.NoError(t, err) | |
| 95 | 118 | _, err = bufio.NewReader(conn).ReadByte() | |
| 96 | 119 | require.Error(t, err) | |
| 97 | - | assert.Eventually(t, func() bool { return manager.Status().Counters.TotalRefused == 1 }, time.Second, 10*time.Millisecond) | |
| 120 | + | assert.Eventually(t, func() bool { return manager.Status().Counters.TotalRefused == refused }, time.Second, 10*time.Millisecond) | |
| 98 | 121 | } | |
| 99 | 122 | ||
| 100 | 123 | func TestTCPPassthroughEcho(t *testing.T) { | |
| @@ -88,13 +88,36 @@ func TestTCPPassthroughRejectsUntrustedCIDR(t *testing.T) { | |||||
| 88 | require.NoError(t, manager.Apply(context.Background(), &graph)) | 88 | require.NoError(t, manager.Apply(context.Background(), &graph)) | ||
| 89 | defer shutdownManager(t, manager) | 89 | defer shutdownManager(t, manager) | ||
| 90 | 90 | ||||
| 91 | + | assertTCPRejected(t, manager, graph.EntryPoints[0].Address, 1) | |||
| 92 | + | } | |||
| 93 | + | ||||
| 94 | + | func TestTCPPassthroughReloadAppliesTrustedCIDRChange(t *testing.T) { | |||
| 95 | + | backend := startTCPEchoServer(t, 0) | |||
| 96 | + | graph := tcpGraph(t, freeTCPAddress(t), backend.address) | |||
| 97 | + | graph.EntryPoints[0].TrustedCIDRs = []string{"127.0.0.0/8"} | |||
| 98 | + | manager := NewManager() | |||
| 99 | + | require.NoError(t, manager.Apply(context.Background(), &graph)) | |||
| 100 | + | defer shutdownManager(t, manager) | |||
| 101 | + | ||||
| 91 | conn := dialTCP(t, graph.EntryPoints[0].Address) | 102 | conn := dialTCP(t, graph.EntryPoints[0].Address) | ||
| 103 | + | assertRoundTrip(t, conn, "allowed") | |||
| 104 | + | require.NoError(t, conn.Close()) | |||
| 105 | + | ||||
| 106 | + | updated := graph | |||
| 107 | + | updated.EntryPoints[0].TrustedCIDRs = []string{"192.0.2.0/24"} | |||
| 108 | + | require.NoError(t, manager.Apply(context.Background(), &updated)) | |||
| 109 | + | assertTCPRejected(t, manager, graph.EntryPoints[0].Address, 1) | |||
| 110 | + | } | |||
| 111 | + | ||||
| 112 | + | func assertTCPRejected(t *testing.T, manager *Manager, address string, refused int64) { | |||
| 113 | + | t.Helper() | |||
| 114 | + | conn := dialTCP(t, address) | |||
| 92 | defer conn.Close() | 115 | defer conn.Close() | ||
| 93 | _, err := conn.Write([]byte("blocked")) | 116 | _, err := conn.Write([]byte("blocked")) | ||
| 94 | require.NoError(t, err) | 117 | require.NoError(t, err) | ||
| 95 | _, err = bufio.NewReader(conn).ReadByte() | 118 | _, err = bufio.NewReader(conn).ReadByte() | ||
| 96 | require.Error(t, err) | 119 | require.Error(t, err) | ||
| 97 | - | assert.Eventually(t, func() bool { return manager.Status().Counters.TotalRefused == 1 }, time.Second, 10*time.Millisecond) | 120 | + | assert.Eventually(t, func() bool { return manager.Status().Counters.TotalRefused == refused }, time.Second, 10*time.Millisecond) |
| 98 | } | 121 | } | ||
| 99 | 122 | ||||
| 100 | func TestTCPPassthroughEcho(t *testing.T) { | 123 | func TestTCPPassthroughEcho(t *testing.T) { | ||
M internal/adapters/in/traffic/udp.go +1 -1
| @@ -328,7 +328,7 @@ func (r *udpEntryPointRuntime) closeSessions() { | |||
| 328 | 328 | } | |
| 329 | 329 | ||
| 330 | 330 | func (r *udpEntryPointRuntime) matches(entryPoint domain.EntryPoint) bool { | |
| 331 | - | return r.entryPoint.Name == entryPoint.Name && r.entryPoint.Address == entryPoint.Address && r.entryPoint.Protocol == entryPoint.Protocol | |
| 331 | + | return r.entryPoint.Name == entryPoint.Name && r.entryPoint.Address == entryPoint.Address && r.entryPoint.Protocol == entryPoint.Protocol && trustedCIDRsEqual(r.entryPoint.TrustedCIDRs, entryPoint.TrustedCIDRs) | |
| 332 | 332 | } | |
| 333 | 333 | ||
| 334 | 334 | func (r *udpEntryPointRuntime) isClosed() bool { return r.closed.Load() } | |
| @@ -328,7 +328,7 @@ func (r *udpEntryPointRuntime) closeSessions() { | |||||
| 328 | } | 328 | } | ||
| 329 | 329 | ||||
| 330 | func (r *udpEntryPointRuntime) matches(entryPoint domain.EntryPoint) bool { | 330 | func (r *udpEntryPointRuntime) matches(entryPoint domain.EntryPoint) bool { | ||
| 331 | - | return r.entryPoint.Name == entryPoint.Name && r.entryPoint.Address == entryPoint.Address && r.entryPoint.Protocol == entryPoint.Protocol | 331 | + | return r.entryPoint.Name == entryPoint.Name && r.entryPoint.Address == entryPoint.Address && r.entryPoint.Protocol == entryPoint.Protocol && trustedCIDRsEqual(r.entryPoint.TrustedCIDRs, entryPoint.TrustedCIDRs) |
| 332 | } | 332 | } | ||
| 333 | 333 | ||||
| 334 | func (r *udpEntryPointRuntime) isClosed() bool { return r.closed.Load() } | 334 | func (r *udpEntryPointRuntime) isClosed() bool { return r.closed.Load() } | ||
M internal/adapters/in/traffic/udp_test.go +24 -1
| @@ -32,7 +32,30 @@ func TestUDPRejectsUntrustedCIDR(t *testing.T) { | |||
| 32 | 32 | require.NoError(t, manager.Apply(context.Background(), &graph)) | |
| 33 | 33 | defer shutdownManager(t, manager) | |
| 34 | 34 | ||
| 35 | + | assertUDPRejected(t, manager, graph.EntryPoints[0].Address, 1) | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | func TestUDPReloadAppliesTrustedCIDRChange(t *testing.T) { | |
| 39 | + | backend := startUDPEchoServer(t) | |
| 40 | + | graph := udpGraph(t, freeUDPAddress(t), backend.address) | |
| 41 | + | graph.EntryPoints[0].TrustedCIDRs = []string{"127.0.0.0/8"} | |
| 42 | + | manager := NewManager() | |
| 43 | + | require.NoError(t, manager.Apply(context.Background(), &graph)) | |
| 44 | + | defer shutdownManager(t, manager) | |
| 45 | + | ||
| 35 | 46 | conn := dialUDP(t, graph.EntryPoints[0].Address) | |
| 47 | + | assertUDPRoundTrip(t, conn, "allowed") | |
| 48 | + | require.NoError(t, conn.Close()) | |
| 49 | + | ||
| 50 | + | updated := graph | |
| 51 | + | updated.EntryPoints[0].TrustedCIDRs = []string{"192.0.2.0/24"} | |
| 52 | + | require.NoError(t, manager.Apply(context.Background(), &updated)) | |
| 53 | + | assertUDPRejected(t, manager, graph.EntryPoints[0].Address, 1) | |
| 54 | + | } | |
| 55 | + | ||
| 56 | + | func assertUDPRejected(t *testing.T, manager *Manager, address string, refused int64) { | |
| 57 | + | t.Helper() | |
| 58 | + | conn := dialUDP(t, address) | |
| 36 | 59 | defer conn.Close() | |
| 37 | 60 | _, err := conn.Write([]byte("blocked")) | |
| 38 | 61 | require.NoError(t, err) | |
| @@ -40,7 +63,7 @@ func TestUDPRejectsUntrustedCIDR(t *testing.T) { | |||
| 40 | 63 | buf := make([]byte, 32) | |
| 41 | 64 | _, err = conn.Read(buf) | |
| 42 | 65 | require.Error(t, err) | |
| 43 | - | assert.Eventually(t, func() bool { return manager.Status().Counters.TotalRefused == 1 }, time.Second, 10*time.Millisecond) | |
| 66 | + | assert.Eventually(t, func() bool { return manager.Status().Counters.TotalRefused == refused }, time.Second, 10*time.Millisecond) | |
| 44 | 67 | } | |
| 45 | 68 | ||
| 46 | 69 | func TestUDPTwoClientsGetIsolatedSessions(t *testing.T) { | |
| @@ -32,7 +32,30 @@ func TestUDPRejectsUntrustedCIDR(t *testing.T) { | |||||
| 32 | require.NoError(t, manager.Apply(context.Background(), &graph)) | 32 | require.NoError(t, manager.Apply(context.Background(), &graph)) | ||
| 33 | defer shutdownManager(t, manager) | 33 | defer shutdownManager(t, manager) | ||
| 34 | 34 | ||||
| 35 | + | assertUDPRejected(t, manager, graph.EntryPoints[0].Address, 1) | |||
| 36 | + | } | |||
| 37 | + | ||||
| 38 | + | func TestUDPReloadAppliesTrustedCIDRChange(t *testing.T) { | |||
| 39 | + | backend := startUDPEchoServer(t) | |||
| 40 | + | graph := udpGraph(t, freeUDPAddress(t), backend.address) | |||
| 41 | + | graph.EntryPoints[0].TrustedCIDRs = []string{"127.0.0.0/8"} | |||
| 42 | + | manager := NewManager() | |||
| 43 | + | require.NoError(t, manager.Apply(context.Background(), &graph)) | |||
| 44 | + | defer shutdownManager(t, manager) | |||
| 45 | + | ||||
| 35 | conn := dialUDP(t, graph.EntryPoints[0].Address) | 46 | conn := dialUDP(t, graph.EntryPoints[0].Address) | ||
| 47 | + | assertUDPRoundTrip(t, conn, "allowed") | |||
| 48 | + | require.NoError(t, conn.Close()) | |||
| 49 | + | ||||
| 50 | + | updated := graph | |||
| 51 | + | updated.EntryPoints[0].TrustedCIDRs = []string{"192.0.2.0/24"} | |||
| 52 | + | require.NoError(t, manager.Apply(context.Background(), &updated)) | |||
| 53 | + | assertUDPRejected(t, manager, graph.EntryPoints[0].Address, 1) | |||
| 54 | + | } | |||
| 55 | + | ||||
| 56 | + | func assertUDPRejected(t *testing.T, manager *Manager, address string, refused int64) { | |||
| 57 | + | t.Helper() | |||
| 58 | + | conn := dialUDP(t, address) | |||
| 36 | defer conn.Close() | 59 | defer conn.Close() | ||
| 37 | _, err := conn.Write([]byte("blocked")) | 60 | _, err := conn.Write([]byte("blocked")) | ||
| 38 | require.NoError(t, err) | 61 | require.NoError(t, err) | ||
| @@ -40,7 +63,7 @@ func TestUDPRejectsUntrustedCIDR(t *testing.T) { | |||||
| 40 | buf := make([]byte, 32) | 63 | buf := make([]byte, 32) | ||
| 41 | _, err = conn.Read(buf) | 64 | _, err = conn.Read(buf) | ||
| 42 | require.Error(t, err) | 65 | require.Error(t, err) | ||
| 43 | - | assert.Eventually(t, func() bool { return manager.Status().Counters.TotalRefused == 1 }, time.Second, 10*time.Millisecond) | 66 | + | assert.Eventually(t, func() bool { return manager.Status().Counters.TotalRefused == refused }, time.Second, 10*time.Millisecond) |
| 44 | } | 67 | } | ||
| 45 | 68 | ||||
| 46 | func TestUDPTwoClientsGetIsolatedSessions(t *testing.T) { | 69 | func TestUDPTwoClientsGetIsolatedSessions(t *testing.T) { | ||