| // Copyright Istio Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package xdstest |
| |
| import ( |
| "strings" |
| "testing" |
| ) |
| |
| import ( |
| cluster "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" |
| endpoint "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" |
| listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" |
| route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" |
| "github.com/google/go-cmp/cmp" |
| "google.golang.org/protobuf/testing/protocmp" |
| "k8s.io/apimachinery/pkg/util/sets" |
| ) |
| |
| import ( |
| xdsfilters "github.com/apache/dubbo-go-pixiu/pilot/pkg/xds/filters" |
| ) |
| |
| func ValidateListeners(t testing.TB, ls []*listener.Listener) { |
| t.Helper() |
| found := sets.String{} |
| for _, l := range ls { |
| if found.Has(l.Name) { |
| t.Errorf("duplicate listener name %v", l.Name) |
| } |
| found.Insert(l.Name) |
| ValidateListener(t, l) |
| } |
| } |
| |
| func ValidateListener(t testing.TB, l *listener.Listener) { |
| t.Helper() |
| if err := l.Validate(); err != nil { |
| t.Errorf("listener %v is invalid: %v", l.Name, err) |
| } |
| validateInspector(t, l) |
| validateListenerTLS(t, l) |
| validateFilterChainMatch(t, l) |
| validateInboundListener(t, l) |
| } |
| |
| func validateInboundListener(t testing.TB, l *listener.Listener) { |
| if l.GetAddress().GetSocketAddress().GetPortValue() != 15006 { |
| // Not an inbound port |
| return |
| } |
| for i, fc := range l.GetFilterChains() { |
| if fc.FilterChainMatch == nil { |
| t.Errorf("nil filter chain %d", i) |
| continue |
| } |
| if fc.FilterChainMatch.TransportProtocol == "" && fc.FilterChainMatch.GetDestinationPort().GetValue() != 15006 { |
| // Not setting transport protocol may lead to unexpected matching behavior due to https://github.com/istio/istio/issues/26079 |
| // This is not *always* a bug, just a guideline - the 15006 blocker filter chain doesn't follow this rule and is exluced. |
| t.Errorf("filter chain %d had no transport protocol set", i) |
| } |
| } |
| } |
| |
| func validateFilterChainMatch(t testing.TB, l *listener.Listener) { |
| t.Helper() |
| |
| // Check for duplicate filter chains, to avoid "multiple filter chains with the same matching rules are defined" error |
| for i1, l1 := range l.FilterChains { |
| for i2, l2 := range l.FilterChains { |
| if i1 == i2 { |
| continue |
| } |
| // We still create virtual inbound listeners before merging into single inbound |
| // This hack skips these ones, as they will be processed later |
| if hcm := ExtractHTTPConnectionManager(t, l1); strings.HasPrefix(hcm.GetStatPrefix(), "inbound_") && l.Name != "virtualInbound" { |
| continue |
| } |
| if cmp.Equal(l1.FilterChainMatch, l2.FilterChainMatch, protocmp.Transform()) { |
| fcms := []string{} |
| for _, fc := range l.FilterChains { |
| fcms = append(fcms, Dump(t, fc.GetFilterChainMatch())) |
| } |
| t.Errorf("overlapping filter chains %d and %d:\n%v\n Full listener: %v", i1, i2, strings.Join(fcms, ",\n"), Dump(t, l)) |
| } |
| } |
| } |
| |
| // Due to the trie based logic of FCM, an unset field is only a wildcard if no |
| // other FCM sets it. Therefore, we should ensure we explicitly set the FCM on |
| // all match clauses if its set on any other match clause See |
| // https://github.com/envoyproxy/envoy/issues/12572 for details |
| destPorts := sets.NewInt() |
| for _, fc := range l.FilterChains { |
| if fc.GetFilterChainMatch().GetDestinationPort() != nil { |
| destPorts.Insert(int(fc.GetFilterChainMatch().GetDestinationPort().GetValue())) |
| } |
| } |
| for _, p := range destPorts.List() { |
| hasTLSInspector := false |
| for _, fc := range l.FilterChains { |
| if p == int(fc.GetFilterChainMatch().GetDestinationPort().GetValue()) && fc.GetFilterChainMatch().GetTransportProtocol() != "" { |
| hasTLSInspector = true |
| } |
| } |
| if hasTLSInspector { |
| for _, fc := range l.FilterChains { |
| if p == int(fc.GetFilterChainMatch().GetDestinationPort().GetValue()) && fc.GetFilterChainMatch().GetTransportProtocol() == "" { |
| // Note: matches [{transport=tls},{}] and [{transport=tls},{transport=buffer}] |
| // are equivalent, so technically this error is overly sensitive. However, for |
| // more complicated use cases its generally best to be explicit rather than |
| // assuming that {} will be treated as wildcard when in reality it may not be. |
| // Instead, we should explicitly double the filter chain (one for raw buffer, one |
| // for TLS) |
| t.Errorf("filter chain should have transport protocol set for port %v: %v", p, Dump(t, fc)) |
| } |
| } |
| } |
| } |
| } |
| |
| func validateListenerTLS(t testing.TB, l *listener.Listener) { |
| t.Helper() |
| for _, fc := range l.FilterChains { |
| m := fc.FilterChainMatch |
| if m == nil { |
| continue |
| } |
| // if we are matching TLS traffic and doing HTTP traffic, we must terminate the TLS |
| if m.TransportProtocol == xdsfilters.TLSTransportProtocol && fc.TransportSocket == nil && ExtractHTTPConnectionManager(t, fc) != nil { |
| t.Errorf("listener %v is invalid: tls traffic may not be terminated: %v", l.Name, Dump(t, fc)) |
| } |
| } |
| } |
| |
| // Validate a tls inspect filter is added whenever it is needed |
| // matches logic in https://github.com/envoyproxy/envoy/blob/22683a0a24ffbb0cdeb4111eec5ec90246bec9cb/source/server/listener_impl.cc#L41 |
| func validateInspector(t testing.TB, l *listener.Listener) { |
| t.Helper() |
| for _, lf := range l.ListenerFilters { |
| if lf.Name == xdsfilters.TLSInspector.Name { |
| return |
| } |
| } |
| for _, fc := range l.FilterChains { |
| m := fc.FilterChainMatch |
| if fc.FilterChainMatch == nil { |
| continue |
| } |
| if m.TransportProtocol == xdsfilters.TLSTransportProtocol { |
| t.Errorf("transport protocol set, but missing tls inspector: %v", Dump(t, l)) |
| } |
| if m.TransportProtocol == "" && len(m.ServerNames) > 0 { |
| t.Errorf("server names set, but missing tls inspector: %v", Dump(t, l)) |
| } |
| // This is a bit suspect; I suspect this could be done with just http inspector without tls inspector, |
| // but this mirrors Envoy validation logic |
| if m.TransportProtocol == "" && len(m.ApplicationProtocols) > 0 { |
| t.Errorf("application protocol set, but missing tls inspector: %v", Dump(t, l)) |
| } |
| } |
| } |
| |
| func ValidateClusters(t testing.TB, ls []*cluster.Cluster) { |
| found := sets.String{} |
| for _, l := range ls { |
| if found.Has(l.Name) { |
| t.Errorf("duplicate cluster name %v", l.Name) |
| } |
| found.Insert(l.Name) |
| ValidateCluster(t, l) |
| } |
| } |
| |
| func ValidateCluster(t testing.TB, c *cluster.Cluster) { |
| if err := c.Validate(); err != nil { |
| t.Errorf("cluster %v is invalid: %v", c.Name, err) |
| } |
| validateClusterTLS(t, c) |
| } |
| |
| func validateClusterTLS(t testing.TB, c *cluster.Cluster) { |
| if c.TransportSocket != nil && c.TransportSocketMatches != nil { |
| t.Errorf("both transport_socket and transport_socket_matches set for %v", c) |
| } |
| } |
| |
| func ValidateRoutes(t testing.TB, ls []*route.Route) { |
| for _, l := range ls { |
| ValidateRoute(t, l) |
| } |
| } |
| |
| func ValidateRoute(t testing.TB, r *route.Route) { |
| if err := r.Validate(); err != nil { |
| t.Errorf("route %v is invalid: %v", r.Name, err) |
| } |
| } |
| |
| func ValidateRouteConfigurations(t testing.TB, ls []*route.RouteConfiguration) { |
| found := sets.String{} |
| for _, l := range ls { |
| if found.Has(l.Name) { |
| t.Errorf("duplicate route config name %v", l.Name) |
| } |
| found.Insert(l.Name) |
| ValidateRouteConfiguration(t, l) |
| } |
| } |
| |
| func ValidateRouteConfiguration(t testing.TB, l *route.RouteConfiguration) { |
| t.Helper() |
| if err := l.Validate(); err != nil { |
| t.Errorf("route configuration %v is invalid: %v", l.Name, err) |
| } |
| validateRouteConfigurationDomains(t, l) |
| } |
| |
| func validateRouteConfigurationDomains(t testing.TB, l *route.RouteConfiguration) { |
| t.Helper() |
| |
| vhosts := sets.String{} |
| domains := sets.String{} |
| for _, vhost := range l.VirtualHosts { |
| if vhosts.Has(vhost.Name) { |
| t.Errorf("duplicate virtual host found %s", vhost.Name) |
| } |
| vhosts.Insert(vhost.Name) |
| for _, domain := range vhost.Domains { |
| if domains.Has(domain) { |
| t.Errorf("duplicate virtual host domain found %s", domain) |
| } |
| domains.Insert(domain) |
| } |
| } |
| } |
| |
| func ValidateClusterLoadAssignments(t testing.TB, ls []*endpoint.ClusterLoadAssignment) { |
| for _, l := range ls { |
| ValidateClusterLoadAssignment(t, l) |
| } |
| } |
| |
| func ValidateClusterLoadAssignment(t testing.TB, l *endpoint.ClusterLoadAssignment) { |
| if err := l.Validate(); err != nil { |
| t.Errorf("cluster load assignment %v is invalid: %v", l.ClusterName, err) |
| } |
| } |