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 }