Skip to content

Commit

Permalink
ingress/gateway-api: ordered envoy filterchain for TLS listener
Browse files Browse the repository at this point in the history
[ upstream commit 305ea74 ]

Currently, while translating K8s Ingress or Gateway API resources into Envoy resources,
the filterchain for TLS listeners is in random order. This leads to situations (especially in combination
with Shared Ingress) where the order of the filterchains isn't guaranteed -
resulting in unnecessary reconciliations.

Therefore, this commit orders the filterchains within a Envoy Listener by the name of the backends.
This makes the translation deterministic.

Signed-off-by: Marco Hofstetter <marco.hofstetter@isovalent.com>
Signed-off-by: Tam Mach <tam.mach@cilium.io>
  • Loading branch information
mhofstetter authored and sayboras committed Mar 26, 2024
1 parent 31fe8af commit 63b73d3
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 4 deletions.
6 changes: 5 additions & 1 deletion operator/pkg/model/translation/envoy_listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,11 @@ func NewSNIListenerWithDefaults(name string, backendsForHost map[string][]string
func NewSNIListener(name string, backendsForHost map[string][]string, mutatorFunc ...ListenerMutator) (ciliumv2.XDSResource, error) {
var filterChains []*envoy_config_listener.FilterChain

for backend, hostNames := range backendsForHost {
orderedBackends := maps.Keys(backendsForHost)
goslices.Sort(orderedBackends)

for _, backend := range orderedBackends {
hostNames := backendsForHost[backend]
filterChains = append(filterChains, &envoy_config_listener.FilterChain{
FilterChainMatch: toFilterChainMatch(hostNames),
Filters: []*envoy_config_listener.Filter{
Expand Down
49 changes: 46 additions & 3 deletions operator/pkg/model/translation/envoy_listener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,17 @@ func TestNewHTTPListener(t *testing.T) {

func TestNewSNIListener(t *testing.T) {
t.Run("normal SNI listener", func(t *testing.T) {
res, err := NewSNIListener("dummy-name", map[string][]string{"dummy-namespace/dummy-service:443": {"example.org", "example.com"}})
res, err := NewSNIListener("dummy-name",
map[string][]string{
"foo-namespace/dummy-service:443": {
"foo.bar",
},
"dummy-namespace/dummy-service:443": {
"example.org",
"example.com",
},
},
)
require.Nil(t, err)

listener := &envoy_config_listener.Listener{}
Expand All @@ -146,8 +156,41 @@ func TestNewSNIListener(t *testing.T) {

require.Equal(t, "dummy-name", listener.Name)
require.Len(t, listener.GetListenerFilters(), 1)
require.Len(t, listener.GetFilterChains(), 1)
require.Len(t, listener.GetFilterChains()[0].FilterChainMatch.ServerNames, 2)
require.Len(t, listener.GetFilterChains(), 2)
require.Equal(t, []string{"example.com", "example.org"}, listener.GetFilterChains()[0].FilterChainMatch.ServerNames)
require.Equal(t, []string{"foo.bar"}, listener.GetFilterChains()[1].FilterChainMatch.ServerNames)
})

t.Run("SNI listener with stable filterchain sort-order", func(t *testing.T) {
res1, err1 := NewSNIListener("dummy-name",
map[string][]string{
"dummy-namespace/dummy-service:443": {
"example.org",
"example.com",
},
"foo-namespace/dummy-service:443": {
"foo.bar",
},
},
)
res2, err2 := NewSNIListener("dummy-name",
map[string][]string{
"foo-namespace/dummy-service:443": {
"foo.bar",
},
"dummy-namespace/dummy-service:443": {
"example.org",
"example.com",
},
},
)
require.Nil(t, err1)
require.Nil(t, err2)

diffOutput := cmp.Diff(res1, res2, protocmp.Transform())
if len(diffOutput) != 0 {
t.Errorf("Listeners did not match:\n%s\n", diffOutput)
}
})

t.Run("normal SNI listener with Proxy Protocol", func(t *testing.T) {
Expand Down

0 comments on commit 63b73d3

Please sign in to comment.