github.com/kiali/kiali@v1.84.0/models/config_dump.go (about)

     1  package models
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"github.com/kiali/kiali/kubernetes"
     9  )
    10  
    11  type EnvoyProxyDump struct {
    12  	ConfigDump *kubernetes.ConfigDump `json:"config_dump,omitempty"`
    13  	Bootstrap  *Bootstrap             `json:"bootstrap,omitempty"`
    14  	Clusters   *Clusters              `json:"clusters,omitempty"`
    15  	Listeners  *Listeners             `json:"listeners,omitempty"`
    16  	Routes     *Routes                `json:"routes,omitempty"`
    17  }
    18  
    19  type Listeners []*Listener
    20  type Listener struct {
    21  	Address     string  `json:"address"`
    22  	Port        float64 `json:"port"`
    23  	Match       string  `json:"match"`
    24  	Destination string  `json:"destination"`
    25  }
    26  
    27  type Clusters []*Cluster
    28  type Cluster struct {
    29  	ServiceFQDN     kubernetes.Host `json:"service_fqdn"`
    30  	Port            int             `json:"port"`
    31  	Subset          string          `json:"subset"`
    32  	Direction       string          `json:"direction"`
    33  	Type            string          `json:"type"`
    34  	DestinationRule string          `json:"destination_rule"`
    35  }
    36  
    37  type Routes []*Route
    38  type Route struct {
    39  	Name           string          `json:"name"`
    40  	Domains        kubernetes.Host `json:"domains"`
    41  	Match          string          `json:"match"`
    42  	VirtualService string          `json:"virtual_service"`
    43  }
    44  
    45  type Bootstrap struct {
    46  	Bootstrap map[string]interface{} `json:"bootstrap,inline"`
    47  }
    48  
    49  func (ls *Listeners) Parse(dump *kubernetes.ConfigDump) error {
    50  	listenersDump, err := dump.GetListeners()
    51  	if err != nil {
    52  		return err
    53  	}
    54  	listeners := make([]kubernetes.EnvoyListener, 0, len(listenersDump.StaticListeners)+len(listenersDump.DynamicListeners))
    55  	for _, dynamicListener := range listenersDump.DynamicListeners {
    56  		listeners = append(listeners, dynamicListener.ActiveState.Listener)
    57  	}
    58  	for _, staticListener := range listenersDump.StaticListeners {
    59  		listeners = append(listeners, staticListener.Listener)
    60  	}
    61  
    62  	for _, listener := range listeners {
    63  		for _, match := range listenerMatches(listener) {
    64  			*ls = append(*ls, &Listener{
    65  				Address:     listener.Address.SocketAddress.Address,
    66  				Port:        listener.Address.SocketAddress.PortValue,
    67  				Match:       match["match"].(string),
    68  				Destination: match["destination"].(string),
    69  			})
    70  		}
    71  	}
    72  	return nil
    73  }
    74  
    75  func listenerMatches(listener kubernetes.EnvoyListener) []map[string]interface{} {
    76  	chains := listener.FilterChains
    77  	if listener.DefaultFilterChain != nil {
    78  		chains = append(chains, *listener.DefaultFilterChain)
    79  	}
    80  
    81  	matches := make([]map[string]interface{}, 0, len(chains))
    82  	for _, chain := range chains {
    83  		descriptors := make([]string, 0)
    84  
    85  		match := chain.FilterChainMatch
    86  		if match == nil {
    87  			match = &kubernetes.FilterChainMatch{}
    88  		}
    89  
    90  		if len(match.ServerNames) > 0 {
    91  			descriptors = append(descriptors, fmt.Sprintf("SNI: %s", strings.Join(match.ServerNames, ", ")))
    92  		}
    93  
    94  		if len(match.TransportProtocol) > 0 {
    95  			descriptors = append(descriptors, fmt.Sprintf("Trans: %s", match.TransportProtocol))
    96  		}
    97  
    98  		if apd := getAppDescriptor(match); apd != "" {
    99  			descriptors = append(descriptors, apd)
   100  		}
   101  
   102  		port := ""
   103  		if match.DestinationPort != nil {
   104  			port = fmt.Sprintf(":%d", *match.DestinationPort)
   105  		}
   106  
   107  		if len(match.PrefixRanges) > 0 {
   108  			pfs := []string{}
   109  			for _, p := range match.PrefixRanges {
   110  				pfs = append(pfs, fmt.Sprintf("%s/%d", p.AddressPrefix, p.PrefixLen))
   111  			}
   112  			descriptors = append(descriptors, fmt.Sprintf("Addr: %s%s", strings.Join(pfs, ", "), port))
   113  		} else if port != "" {
   114  			descriptors = append(descriptors, fmt.Sprintf("Addr: *%s", port))
   115  		}
   116  
   117  		if len(descriptors) == 0 {
   118  			descriptors = []string{"ALL"}
   119  		}
   120  
   121  		matches = append(matches, map[string]interface{}{
   122  			"match":       strings.Join(descriptors, "; "),
   123  			"destination": getListenerDestination(chain.Filters),
   124  		})
   125  	}
   126  	return matches
   127  }
   128  
   129  func getListenerDestination(filters []kubernetes.EnvoyListenerFilter) string {
   130  	if len(filters) == 0 {
   131  		return ""
   132  	}
   133  
   134  	for _, filter := range filters {
   135  		if filter.Name == "envoy.filters.network.http_connection_manager" {
   136  			typedConfig := filter.TypedConfig
   137  			if typedConfig.RouteConfig != nil {
   138  				if cluster := getMatchAllCluster(typedConfig.RouteConfig); cluster != "" {
   139  					return cluster
   140  				}
   141  				vhosts := []string{}
   142  				for _, vh := range typedConfig.RouteConfig.VirtualHosts {
   143  					if describeDomains(vh) == "" {
   144  						vhosts = append(vhosts, describeRoutes(vh))
   145  					} else {
   146  						vhosts = append(vhosts, fmt.Sprintf("%s %s", describeDomains(vh), describeRoutes(vh)))
   147  					}
   148  				}
   149  				return fmt.Sprintf("Inline Route: %s", strings.Join(vhosts, "; "))
   150  			}
   151  
   152  			if typedConfig.Rds != nil && typedConfig.Rds.RouteConfigName != "" {
   153  				return fmt.Sprintf("Route: %s", typedConfig.Rds.RouteConfigName)
   154  			}
   155  
   156  			return "HTTP"
   157  		} else if filter.Name == "envoy.filters.network.tcp_proxy" {
   158  			typedConfig := filter.TypedConfig
   159  			if strings.Contains(typedConfig.Cluster, "Cluster") {
   160  				return typedConfig.Cluster
   161  			} else {
   162  				return fmt.Sprintf("Cluster: %s", typedConfig.Cluster)
   163  			}
   164  		}
   165  	}
   166  	return "Non-HTTP/Non-TCP"
   167  }
   168  
   169  func describeDomains(vh kubernetes.VirtualHostFilter) string {
   170  	domains := vh.Domains
   171  	if len(domains) == 1 && domains[0] == "*" {
   172  		return ""
   173  	}
   174  	return strings.Join(domains, "/")
   175  }
   176  
   177  func describeRoutes(vh kubernetes.VirtualHostFilter) string {
   178  	routes := make([]string, 0, len(vh.Routes))
   179  	for _, route := range vh.Routes {
   180  		routes = append(routes, matchSummary(route.Match))
   181  	}
   182  	return strings.Join(routes, ", ")
   183  }
   184  
   185  func getMatchAllCluster(route *kubernetes.RouteConfig) string {
   186  	vhs := route.VirtualHosts
   187  	if len(vhs) != 1 {
   188  		return ""
   189  	}
   190  
   191  	vh := vhs[0]
   192  	domains := vh.Domains
   193  	if !(len(domains) == 1 && domains[0] == "*") {
   194  		return ""
   195  	}
   196  
   197  	if len(vh.Routes) != 1 {
   198  		return ""
   199  	}
   200  
   201  	r := vh.Routes[0]
   202  	if prefix, found := r.Match["prefix"]; found && prefix.(string) != "/" {
   203  		return ""
   204  	}
   205  
   206  	if r.Route == nil || len(r.Route.Cluster) == 0 {
   207  		return ""
   208  	}
   209  
   210  	if strings.Contains(r.Route.Cluster, "Cluster") {
   211  		return r.Route.Cluster
   212  	}
   213  
   214  	return fmt.Sprintf("Cluster: %s", r.Route.Cluster)
   215  }
   216  
   217  func getAppDescriptor(chainMatch *kubernetes.FilterChainMatch) string {
   218  	plainText := []string{"http/1.0", "http/1.1", "h2c"}
   219  	istioPlainText := []string{"istio", "istio-http/1.0", "istio-http/1.1", "istio-h2"}
   220  	httpTLS := []string{"http/1.0", "http/1.1", "h2c", "istio-http/1.0", "istio-http/1.1", "istio-h2"}
   221  	tcpTLS := []string{"istio-peer-exchange", "istio"}
   222  
   223  	protocolMap := map[string][]string{
   224  		"App: HTTP TLS":         httpTLS,
   225  		"App: Istio HTTP Plain": istioPlainText,
   226  		"App: TCP TLS":          tcpTLS,
   227  		"App: HTTP":             plainText,
   228  	}
   229  
   230  	if len(chainMatch.ApplicationProtocols) == 0 {
   231  		return ""
   232  	}
   233  
   234  	for label, protList := range protocolMap {
   235  		if len(protList) == len(chainMatch.ApplicationProtocols) {
   236  			same := true
   237  			for i, prot := range protList {
   238  				same = same && prot == chainMatch.ApplicationProtocols[i]
   239  			}
   240  			if same {
   241  				return label
   242  			}
   243  		}
   244  	}
   245  
   246  	return ""
   247  }
   248  
   249  func (css *Clusters) Parse(dump *kubernetes.ConfigDump) error {
   250  	clusterDump, err := dump.GetClusters()
   251  	if err != nil {
   252  		return err
   253  	}
   254  
   255  	for _, clusterSet := range [][]kubernetes.EnvoyClusterWrapper{clusterDump.DynamicClusters, clusterDump.StaticClusters} {
   256  		for _, cluster := range clusterSet {
   257  			cs := &Cluster{}
   258  			cs.Parse(cluster.Cluster)
   259  			*css = append(*css, cs)
   260  		}
   261  	}
   262  
   263  	return nil
   264  }
   265  
   266  func (cs *Cluster) Parse(cluster kubernetes.EnvoyCluster) {
   267  	cs.ServiceFQDN = kubernetes.Host{Service: cluster.Name}
   268  	cs.Type = cluster.Type
   269  	cs.Port = 0
   270  	cs.Subset = ""
   271  	cs.Direction = ""
   272  	cs.DestinationRule = ""
   273  
   274  	parts := strings.Split(cluster.Name, "|")
   275  	if len(parts) > 3 {
   276  		cs.ServiceFQDN = kubernetes.ParseHost(parts[3], "")
   277  		cs.Port, _ = strconv.Atoi(strings.TrimSuffix(parts[1], "_"))
   278  		cs.Subset = parts[2]
   279  		cs.Direction = strings.TrimSuffix(parts[0], "_")
   280  		cs.DestinationRule = istioMetadata(cluster.Metadata)
   281  	}
   282  }
   283  
   284  func (rs *Routes) Parse(dump *kubernetes.ConfigDump, namespaces []string) error {
   285  	routesDump, err := dump.GetRoutes()
   286  	if err != nil {
   287  		return err
   288  	}
   289  
   290  	for _, routeSet := range [][]kubernetes.EnvoyRouteConfig{routesDump.DynamicRouteConfigs, routesDump.StaticRouteConfigs} {
   291  		for _, route := range routeSet {
   292  			rc := route.RouteConfig
   293  
   294  			for _, vhs := range rc.VirtualHosts {
   295  				for _, r := range vhs.Routes {
   296  					if r.Route != nil && r.Route.Cluster != "PassthroughCluster" {
   297  						*rs = append(*rs, &Route{
   298  							Name:           rc.Name,
   299  							Domains:        bestDomainMatch(vhs.Domains, namespaces),
   300  							Match:          matchSummary(r.Match),
   301  							VirtualService: istioMetadata(r.Metadata),
   302  						})
   303  					}
   304  				}
   305  
   306  				if len(vhs.Routes) == 0 {
   307  					*rs = append(*rs, &Route{
   308  						Name:           rc.Name,
   309  						Domains:        bestDomainMatch(vhs.Domains, namespaces),
   310  						Match:          "/*",
   311  						VirtualService: "404",
   312  					})
   313  				}
   314  			}
   315  		}
   316  	}
   317  
   318  	return nil
   319  }
   320  
   321  func (bd *Bootstrap) Parse(dump *kubernetes.ConfigDump) error {
   322  	bd.Bootstrap = dump.GetConfig("type.googleapis.com/envoy.admin.v3.BootstrapConfigDump")
   323  	return nil
   324  }
   325  
   326  func matchSummary(match map[string]interface{}) string {
   327  	conds := []string{}
   328  	if prefixRaw, found := match["prefix"]; found && prefixRaw.(string) != "" {
   329  		conds = append(conds, fmt.Sprintf("%s*", prefixRaw))
   330  	}
   331  	if pathRaw, found := match["path"]; found && pathRaw.(string) != "" {
   332  		conds = append(conds, fmt.Sprintf("%s", pathRaw))
   333  	}
   334  
   335  	if safeRegex, found := match["safe_regex"]; found {
   336  		conds = append(conds, fmt.Sprintf("regex %s", safeRegex))
   337  	}
   338  	// Ignore headers
   339  	return strings.Join(conds, " ")
   340  }
   341  
   342  func bestDomainMatch(domains []string, namespaces []string) kubernetes.Host {
   343  	if len(domains) == 0 {
   344  		return kubernetes.Host{Service: ""}
   345  	}
   346  
   347  	if len(domains) == 1 {
   348  		return kubernetes.GetHost(domains[0], "", namespaces)
   349  	}
   350  
   351  	bestMatch := domains[0]
   352  	for _, domain := range domains {
   353  		if len(domain) == 0 {
   354  			continue
   355  		}
   356  
   357  		firstChar := domain[0]
   358  		if firstChar >= '1' && firstChar <= '9' {
   359  			continue
   360  		}
   361  
   362  		if len(bestMatch) > len(domain) {
   363  			bestMatch = domain
   364  		}
   365  	}
   366  	return kubernetes.GetHost(bestMatch, "", namespaces)
   367  }
   368  
   369  func istioMetadata(metadata *kubernetes.EnvoyMetadata) string {
   370  	if metadata == nil || metadata.FilterMetadata == nil || metadata.FilterMetadata.Istio == nil {
   371  		return ""
   372  	}
   373  
   374  	return renderConfig(metadata.FilterMetadata.Istio.Config)
   375  }
   376  
   377  func renderConfig(configPath string) string {
   378  	if strings.HasPrefix(configPath, "/apis/networking.istio.io/v1alpha3/namespaces/") {
   379  		parts := strings.Split(configPath, "/")
   380  		if len(parts) != 8 {
   381  			return ""
   382  		}
   383  		return fmt.Sprintf("%s.%s", parts[7], parts[5])
   384  	}
   385  	return ""
   386  }