github.com/kiali/kiali@v1.84.0/business/istio_validations.go (about)

     1  package business
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"sync"
     8  
     9  	networking_v1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1"
    10  	security_v1beta "istio.io/client-go/pkg/apis/security/v1beta1"
    11  
    12  	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    13  
    14  	"github.com/kiali/kiali/business/checkers"
    15  	"github.com/kiali/kiali/business/references"
    16  	"github.com/kiali/kiali/config"
    17  	"github.com/kiali/kiali/kubernetes"
    18  	"github.com/kiali/kiali/log"
    19  	"github.com/kiali/kiali/models"
    20  	"github.com/kiali/kiali/observability"
    21  	"github.com/kiali/kiali/prometheus/internalmetrics"
    22  )
    23  
    24  type IstioValidationsService struct {
    25  	userClients   map[string]kubernetes.ClientInterface
    26  	businessLayer *Layer
    27  }
    28  
    29  type ObjectChecker interface {
    30  	Check() models.IstioValidations
    31  }
    32  
    33  type ReferenceChecker interface {
    34  	References() models.IstioReferencesMap
    35  }
    36  
    37  // GetValidations returns an IstioValidations object with all the checks found when running
    38  // all the enabled checkers. If service is "" then the whole namespace is validated.
    39  // If service is not empty string, then all of its associated Istio objects are validated.
    40  func (in *IstioValidationsService) GetValidations(ctx context.Context, cluster, namespace, service, workload string) (models.IstioValidations, error) {
    41  	var end observability.EndFunc
    42  	ctx, end = observability.StartSpan(ctx, "GetValidations",
    43  		observability.Attribute("package", "business"),
    44  		observability.Attribute("cluster", cluster),
    45  		observability.Attribute("namespace", namespace),
    46  		observability.Attribute("service", service),
    47  		observability.Attribute("workload", workload),
    48  	)
    49  	defer end()
    50  
    51  	// Check if user has access to the namespace (RBAC) in cache scenarios and/or
    52  	// if namespace is accessible from Kiali (Deployment.AccessibleNamespaces)
    53  	if namespace != "" {
    54  		if _, err := in.businessLayer.Namespace.GetClusterNamespace(ctx, namespace, cluster); err != nil {
    55  			return nil, err
    56  		}
    57  	}
    58  
    59  	// Ensure the service exists
    60  	if service != "" {
    61  		_, err := in.businessLayer.Svc.GetService(ctx, cluster, namespace, service)
    62  		if err != nil {
    63  			if err != nil {
    64  				log.Warningf("Error invoking GetService %s", err)
    65  			}
    66  			return nil, fmt.Errorf("Service [namespace: %s] [name: %s] doesn't exist for Validations.", namespace, service)
    67  		}
    68  	}
    69  
    70  	// time this function execution so we can capture how long it takes to fully validate this namespace/service
    71  	timer := internalmetrics.GetValidationProcessingTimePrometheusTimer(namespace, service)
    72  	defer timer.ObserveDuration()
    73  
    74  	wg := sync.WaitGroup{}
    75  	errChan := make(chan error, 1)
    76  
    77  	var istioConfigList models.IstioConfigList
    78  	var services models.ServiceList
    79  	var serviceAccounts map[string][]string
    80  	var namespaces models.Namespaces
    81  	var workloadsPerNamespace map[string]models.WorkloadList
    82  	var mtlsDetails kubernetes.MTLSDetails
    83  	var rbacDetails kubernetes.RBACDetails
    84  	var registryServices []*kubernetes.RegistryService
    85  
    86  	wg.Add(4) // We need to add these here to make sure we don't execute wg.Wait() before scheduler has started goroutines
    87  	if service != "" {
    88  		wg.Add(1)
    89  	}
    90  
    91  	// We fetch without target service as some validations will require full-namespace details
    92  	go in.fetchIstioConfigList(ctx, &istioConfigList, &mtlsDetails, &rbacDetails, cluster, namespace, errChan, &wg)
    93  
    94  	if workload != "" {
    95  		// load only requested workload
    96  		go in.fetchWorkload(ctx, &workloadsPerNamespace, cluster, workload, namespace, errChan, &wg)
    97  	} else {
    98  		go in.fetchAllWorkloads(ctx, &workloadsPerNamespace, cluster, &namespaces, errChan, &wg)
    99  	}
   100  
   101  	go in.fetchServiceAccounts(ctx, &serviceAccounts, errChan, &wg)
   102  
   103  	go in.fetchNonLocalmTLSConfigs(&mtlsDetails, cluster, errChan, &wg)
   104  	if service != "" {
   105  		go in.fetchServices(ctx, &services, cluster, namespace, errChan, &wg)
   106  	}
   107  
   108  	criteria := RegistryCriteria{AllNamespaces: true, Cluster: cluster}
   109  	registryServices = in.businessLayer.RegistryStatus.GetRegistryServices(criteria)
   110  
   111  	wg.Wait()
   112  	close(errChan)
   113  	for e := range errChan {
   114  		if e != nil { // Check that default value wasn't returned
   115  			return nil, e
   116  		}
   117  	}
   118  
   119  	objectCheckers := in.getAllObjectCheckers(istioConfigList, workloadsPerNamespace, mtlsDetails, rbacDetails, namespaces, registryServices, cluster, serviceAccounts)
   120  
   121  	// Get group validations for same kind istio objects
   122  	validations := runObjectCheckers(objectCheckers)
   123  
   124  	if service != "" {
   125  		// in.businessLayer.Svc.GetServiceList(criteria) on fetchServices performs the validations on the service
   126  		// No need to re-fetch deployments+pods for this
   127  		validations.MergeValidations(services.Validations)
   128  		validations = validations.FilterBySingleType("service", service)
   129  	} else if workload != "" {
   130  		workloadList := workloadsPerNamespace[namespace]
   131  		validations.MergeValidations(workloadList.Validations)
   132  		validations = validations.FilterBySingleType("workload", workload)
   133  	}
   134  
   135  	return validations, nil
   136  }
   137  
   138  func (in *IstioValidationsService) getAllObjectCheckers(istioConfigList models.IstioConfigList, workloadsPerNamespace map[string]models.WorkloadList, mtlsDetails kubernetes.MTLSDetails, rbacDetails kubernetes.RBACDetails, namespaces []models.Namespace, registryServices []*kubernetes.RegistryService, cluster string, serviceAccounts map[string][]string) []ObjectChecker {
   139  	return []ObjectChecker{
   140  		checkers.NoServiceChecker{Namespaces: namespaces, IstioConfigList: &istioConfigList, WorkloadsPerNamespace: workloadsPerNamespace, AuthorizationDetails: &rbacDetails, RegistryServices: registryServices, PolicyAllowAny: in.isPolicyAllowAny(), Cluster: cluster},
   141  		checkers.VirtualServiceChecker{Namespaces: namespaces, VirtualServices: istioConfigList.VirtualServices, DestinationRules: istioConfigList.DestinationRules, Cluster: cluster},
   142  		checkers.DestinationRulesChecker{Namespaces: namespaces, DestinationRules: istioConfigList.DestinationRules, MTLSDetails: mtlsDetails, ServiceEntries: istioConfigList.ServiceEntries, Cluster: cluster},
   143  		checkers.GatewayChecker{Gateways: istioConfigList.Gateways, WorkloadsPerNamespace: workloadsPerNamespace, IsGatewayToNamespace: in.isGatewayToNamespace(), Cluster: cluster},
   144  		checkers.PeerAuthenticationChecker{PeerAuthentications: mtlsDetails.PeerAuthentications, MTLSDetails: mtlsDetails, WorkloadsPerNamespace: workloadsPerNamespace, Cluster: cluster},
   145  		checkers.ServiceEntryChecker{ServiceEntries: istioConfigList.ServiceEntries, Namespaces: namespaces, WorkloadEntries: istioConfigList.WorkloadEntries, Cluster: cluster},
   146  		checkers.AuthorizationPolicyChecker{AuthorizationPolicies: rbacDetails.AuthorizationPolicies, Namespaces: namespaces, ServiceEntries: istioConfigList.ServiceEntries, WorkloadsPerNamespace: workloadsPerNamespace, MtlsDetails: mtlsDetails, VirtualServices: istioConfigList.VirtualServices, RegistryServices: registryServices, PolicyAllowAny: in.isPolicyAllowAny(), Cluster: cluster, ServiceAccounts: serviceAccounts},
   147  		checkers.SidecarChecker{Sidecars: istioConfigList.Sidecars, Namespaces: namespaces, WorkloadsPerNamespace: workloadsPerNamespace, ServiceEntries: istioConfigList.ServiceEntries, RegistryServices: registryServices, Cluster: cluster},
   148  		checkers.RequestAuthenticationChecker{RequestAuthentications: istioConfigList.RequestAuthentications, WorkloadsPerNamespace: workloadsPerNamespace, Cluster: cluster},
   149  		checkers.WorkloadChecker{AuthorizationPolicies: rbacDetails.AuthorizationPolicies, WorkloadsPerNamespace: workloadsPerNamespace, Cluster: cluster},
   150  		checkers.K8sGatewayChecker{K8sGateways: istioConfigList.K8sGateways, Cluster: cluster, GatewayClasses: in.businessLayer.IstioConfig.GatewayAPIClasses(cluster)},
   151  		checkers.K8sHTTPRouteChecker{K8sHTTPRoutes: istioConfigList.K8sHTTPRoutes, K8sGateways: istioConfigList.K8sGateways, K8sReferenceGrants: istioConfigList.K8sReferenceGrants, Namespaces: namespaces, RegistryServices: registryServices, Cluster: cluster},
   152  		checkers.K8sReferenceGrantChecker{K8sReferenceGrants: istioConfigList.K8sReferenceGrants, Namespaces: namespaces, Cluster: cluster},
   153  		checkers.WasmPluginChecker{WasmPlugins: istioConfigList.WasmPlugins, Namespaces: namespaces},
   154  		checkers.TelemetryChecker{Telemetries: istioConfigList.Telemetries, Namespaces: namespaces},
   155  	}
   156  }
   157  
   158  // GetIstioObjectValidations validates a single Istio object of the given type with the given name found in the given namespace.
   159  func (in *IstioValidationsService) GetIstioObjectValidations(ctx context.Context, cluster, namespace string, objectType string, object string) (models.IstioValidations, models.IstioReferencesMap, error) {
   160  	var end observability.EndFunc
   161  	ctx, end = observability.StartSpan(ctx, "GetIstioObjectValidations",
   162  		observability.Attribute("package", "business"),
   163  		observability.Attribute("cluster", "cluster"),
   164  		observability.Attribute("namespace", namespace),
   165  		observability.Attribute("objectType", objectType),
   166  		observability.Attribute("object", object),
   167  	)
   168  	defer end()
   169  
   170  	var istioConfigList models.IstioConfigList
   171  	var namespaces models.Namespaces
   172  	var workloadsPerNamespace map[string]models.WorkloadList
   173  	var mtlsDetails kubernetes.MTLSDetails
   174  	var rbacDetails kubernetes.RBACDetails
   175  	var registryServices []*kubernetes.RegistryService
   176  	var serviceAccounts map[string][]string
   177  	var err error
   178  	var objectCheckers []ObjectChecker
   179  	var referenceChecker ReferenceChecker
   180  	istioReferences := models.IstioReferencesMap{}
   181  
   182  	istioApiEnabled := config.Get().ExternalServices.Istio.IstioAPIEnabled
   183  
   184  	// Check if user has access to the namespace (RBAC) in cache scenarios and/or
   185  	// if namespace is accessible from Kiali (Deployment.AccessibleNamespaces)
   186  	if _, err = in.businessLayer.Namespace.GetClusterNamespace(ctx, namespace, cluster); err != nil {
   187  		return nil, istioReferences, err
   188  	}
   189  
   190  	// time this function execution so we can capture how long it takes to fully validate this istio object
   191  	timer := internalmetrics.GetSingleValidationProcessingTimePrometheusTimer(namespace, objectType, object)
   192  	defer timer.ObserveDuration()
   193  
   194  	wg := sync.WaitGroup{}
   195  	errChan := make(chan error, 1)
   196  
   197  	// Get all the Istio objects from a Namespace and all gateways from every namespace
   198  	wg.Add(4)
   199  
   200  	go in.fetchIstioConfigList(ctx, &istioConfigList, &mtlsDetails, &rbacDetails, cluster, namespace, errChan, &wg)
   201  	go in.fetchAllWorkloads(ctx, &workloadsPerNamespace, cluster, &namespaces, errChan, &wg)
   202  	go in.fetchServiceAccounts(ctx, &serviceAccounts, errChan, &wg)
   203  	go in.fetchNonLocalmTLSConfigs(&mtlsDetails, cluster, errChan, &wg)
   204  
   205  	if istioApiEnabled {
   206  		criteria := RegistryCriteria{AllNamespaces: true, Cluster: cluster}
   207  		registryServices = in.businessLayer.RegistryStatus.GetRegistryServices(criteria)
   208  	}
   209  
   210  	wg.Wait()
   211  
   212  	noServiceChecker := checkers.NoServiceChecker{Cluster: cluster, Namespaces: namespaces, IstioConfigList: &istioConfigList, WorkloadsPerNamespace: workloadsPerNamespace, AuthorizationDetails: &rbacDetails, RegistryServices: registryServices, PolicyAllowAny: in.isPolicyAllowAny()}
   213  
   214  	switch objectType {
   215  	case kubernetes.Gateways:
   216  		objectCheckers = []ObjectChecker{
   217  			checkers.GatewayChecker{Cluster: cluster, Gateways: istioConfigList.Gateways, WorkloadsPerNamespace: workloadsPerNamespace, IsGatewayToNamespace: in.isGatewayToNamespace()},
   218  		}
   219  		referenceChecker = references.GatewayReferences{Gateways: istioConfigList.Gateways, VirtualServices: istioConfigList.VirtualServices, WorkloadsPerNamespace: workloadsPerNamespace}
   220  	case kubernetes.VirtualServices:
   221  		virtualServiceChecker := checkers.VirtualServiceChecker{Cluster: cluster, Namespaces: namespaces, VirtualServices: istioConfigList.VirtualServices, DestinationRules: istioConfigList.DestinationRules}
   222  		objectCheckers = []ObjectChecker{noServiceChecker, virtualServiceChecker}
   223  		referenceChecker = references.VirtualServiceReferences{Namespace: namespace, Namespaces: namespaces, VirtualServices: istioConfigList.VirtualServices, DestinationRules: istioConfigList.DestinationRules, AuthorizationPolicies: rbacDetails.AuthorizationPolicies}
   224  	case kubernetes.DestinationRules:
   225  		destinationRulesChecker := checkers.DestinationRulesChecker{Cluster: cluster, Namespaces: namespaces, DestinationRules: istioConfigList.DestinationRules, MTLSDetails: mtlsDetails, ServiceEntries: istioConfigList.ServiceEntries}
   226  		objectCheckers = []ObjectChecker{noServiceChecker, destinationRulesChecker}
   227  		referenceChecker = references.DestinationRuleReferences{Namespace: namespace, Namespaces: namespaces, DestinationRules: istioConfigList.DestinationRules, VirtualServices: istioConfigList.VirtualServices, WorkloadsPerNamespace: workloadsPerNamespace, ServiceEntries: istioConfigList.ServiceEntries, RegistryServices: registryServices}
   228  	case kubernetes.ServiceEntries:
   229  		serviceEntryChecker := checkers.ServiceEntryChecker{Cluster: cluster, ServiceEntries: istioConfigList.ServiceEntries, Namespaces: namespaces, WorkloadEntries: istioConfigList.WorkloadEntries}
   230  		objectCheckers = []ObjectChecker{serviceEntryChecker}
   231  		referenceChecker = references.ServiceEntryReferences{AuthorizationPolicies: rbacDetails.AuthorizationPolicies, Namespace: namespace, Namespaces: namespaces, DestinationRules: istioConfigList.DestinationRules, ServiceEntries: istioConfigList.ServiceEntries, Sidecars: istioConfigList.Sidecars, RegistryServices: registryServices}
   232  	case kubernetes.Sidecars:
   233  		sidecarsChecker := checkers.SidecarChecker{
   234  			Cluster: cluster, Sidecars: istioConfigList.Sidecars, Namespaces: namespaces,
   235  			WorkloadsPerNamespace: workloadsPerNamespace, ServiceEntries: istioConfigList.ServiceEntries, RegistryServices: registryServices,
   236  		}
   237  		objectCheckers = []ObjectChecker{sidecarsChecker}
   238  		referenceChecker = references.SidecarReferences{Sidecars: istioConfigList.Sidecars, Namespace: namespace, Namespaces: namespaces, ServiceEntries: istioConfigList.ServiceEntries, RegistryServices: registryServices, WorkloadsPerNamespace: workloadsPerNamespace}
   239  	case kubernetes.AuthorizationPolicies:
   240  		authPoliciesChecker := checkers.AuthorizationPolicyChecker{
   241  			AuthorizationPolicies: rbacDetails.AuthorizationPolicies,
   242  			Cluster:               cluster, Namespaces: namespaces, ServiceEntries: istioConfigList.ServiceEntries, ServiceAccounts: serviceAccounts,
   243  			WorkloadsPerNamespace: workloadsPerNamespace, MtlsDetails: mtlsDetails, VirtualServices: istioConfigList.VirtualServices, RegistryServices: registryServices, PolicyAllowAny: in.isPolicyAllowAny(),
   244  		}
   245  		objectCheckers = []ObjectChecker{authPoliciesChecker}
   246  		referenceChecker = references.AuthorizationPolicyReferences{AuthorizationPolicies: rbacDetails.AuthorizationPolicies, Namespace: namespace, Namespaces: namespaces, VirtualServices: istioConfigList.VirtualServices, ServiceEntries: istioConfigList.ServiceEntries, RegistryServices: registryServices, WorkloadsPerNamespace: workloadsPerNamespace}
   247  	case kubernetes.PeerAuthentications:
   248  		// Validations on PeerAuthentications
   249  		peerAuthnChecker := checkers.PeerAuthenticationChecker{Cluster: cluster, PeerAuthentications: mtlsDetails.PeerAuthentications, MTLSDetails: mtlsDetails, WorkloadsPerNamespace: workloadsPerNamespace}
   250  		objectCheckers = []ObjectChecker{peerAuthnChecker}
   251  		referenceChecker = references.PeerAuthReferences{MTLSDetails: mtlsDetails, WorkloadsPerNamespace: workloadsPerNamespace}
   252  	case kubernetes.WorkloadEntries:
   253  		// Validation on WorkloadEntries are not yet in place
   254  	case kubernetes.WorkloadGroups:
   255  		// Validation on WorkloadGroups are not yet in place
   256  	case kubernetes.RequestAuthentications:
   257  		// Validation on RequestAuthentications are not yet in place
   258  		requestAuthnChecker := checkers.RequestAuthenticationChecker{Cluster: cluster, RequestAuthentications: istioConfigList.RequestAuthentications, WorkloadsPerNamespace: workloadsPerNamespace}
   259  		objectCheckers = []ObjectChecker{requestAuthnChecker}
   260  	case kubernetes.EnvoyFilters:
   261  		// Validation on EnvoyFilters are not yet in place
   262  	case kubernetes.WasmPlugins:
   263  		// Validation on WasmPlugins is not expected
   264  	case kubernetes.Telemetries:
   265  		// Validation on Telemetries is not expected
   266  	case kubernetes.K8sGateways:
   267  		// Validations on K8sGateways
   268  		objectCheckers = []ObjectChecker{
   269  			checkers.K8sGatewayChecker{Cluster: cluster, K8sGateways: istioConfigList.K8sGateways, GatewayClasses: in.businessLayer.IstioConfig.GatewayAPIClasses(cluster)},
   270  		}
   271  		referenceChecker = references.K8sGatewayReferences{K8sGateways: istioConfigList.K8sGateways, K8sHTTPRoutes: istioConfigList.K8sHTTPRoutes}
   272  	case kubernetes.K8sGRPCRoutes:
   273  		// Validation on K8sGRPCRoutes is not expected
   274  	case kubernetes.K8sHTTPRoutes:
   275  		httpRouteChecker := checkers.K8sHTTPRouteChecker{Cluster: cluster, K8sHTTPRoutes: istioConfigList.K8sHTTPRoutes, K8sGateways: istioConfigList.K8sGateways, K8sReferenceGrants: istioConfigList.K8sReferenceGrants, Namespaces: namespaces, RegistryServices: registryServices}
   276  		objectCheckers = []ObjectChecker{noServiceChecker, httpRouteChecker}
   277  		referenceChecker = references.K8sHTTPRouteReferences{K8sHTTPRoutes: istioConfigList.K8sHTTPRoutes, Namespaces: namespaces, K8sReferenceGrants: istioConfigList.K8sReferenceGrants}
   278  	case kubernetes.K8sReferenceGrants:
   279  		objectCheckers = []ObjectChecker{
   280  			checkers.K8sReferenceGrantChecker{Cluster: cluster, K8sReferenceGrants: istioConfigList.K8sReferenceGrants, Namespaces: namespaces},
   281  		}
   282  	case kubernetes.K8sTCPRoutes:
   283  		// Validation on K8sTCPRoutes is not expected
   284  	case kubernetes.K8sTLSRoutes:
   285  		// Validation on K8sTLSRoutes is not expected
   286  	default:
   287  		err = fmt.Errorf("object type not found: %v", objectType)
   288  	}
   289  
   290  	close(errChan)
   291  	for e := range errChan {
   292  		if e != nil { // Check that default value wasn't returned
   293  			return nil, istioReferences, err
   294  		}
   295  	}
   296  
   297  	if referenceChecker != nil {
   298  		istioReferences = runObjectReferenceChecker(referenceChecker)
   299  	}
   300  
   301  	if objectCheckers == nil {
   302  		return models.IstioValidations{}, istioReferences, err
   303  	}
   304  
   305  	return runObjectCheckers(objectCheckers).FilterByKey(models.ObjectTypeSingular[objectType], object), istioReferences, nil
   306  }
   307  
   308  func runObjectCheckers(objectCheckers []ObjectChecker) models.IstioValidations {
   309  	objectTypeValidations := models.IstioValidations{}
   310  
   311  	// Run checks for each IstioObject type
   312  	for _, objectChecker := range objectCheckers {
   313  		objectTypeValidations.MergeValidations(runObjectChecker(objectChecker))
   314  	}
   315  
   316  	objectTypeValidations.StripIgnoredChecks()
   317  
   318  	return objectTypeValidations
   319  }
   320  
   321  func runObjectChecker(objectChecker ObjectChecker) models.IstioValidations {
   322  	// tracking the time it takes to execute the Check
   323  	promtimer := internalmetrics.GetCheckerProcessingTimePrometheusTimer(fmt.Sprintf("%T", objectChecker))
   324  	defer promtimer.ObserveDuration()
   325  	return objectChecker.Check()
   326  }
   327  
   328  func runObjectReferenceChecker(referenceChecker ReferenceChecker) models.IstioReferencesMap {
   329  	// tracking the time it takes to execute the Check
   330  	promtimer := internalmetrics.GetCheckerProcessingTimePrometheusTimer(fmt.Sprintf("%T", referenceChecker))
   331  	defer promtimer.ObserveDuration()
   332  	return referenceChecker.References()
   333  }
   334  
   335  func (in *IstioValidationsService) fetchServices(ctx context.Context, rValue *models.ServiceList, cluster, namespace string, errChan chan error, wg *sync.WaitGroup) {
   336  	defer wg.Done()
   337  	if len(errChan) == 0 {
   338  		var services *models.ServiceList
   339  		var err error
   340  		criteria := ServiceCriteria{
   341  			IncludeHealth: false,
   342  			Namespace:     namespace,
   343  			Cluster:       cluster,
   344  		}
   345  		services, err = in.businessLayer.Svc.GetServiceList(ctx, criteria)
   346  		if err != nil {
   347  			select {
   348  			case errChan <- err:
   349  			default:
   350  			}
   351  		} else {
   352  			*rValue = *services
   353  		}
   354  	}
   355  }
   356  
   357  func (in *IstioValidationsService) fetchAllWorkloads(ctx context.Context, rValue *map[string]models.WorkloadList, cluster string, namespaces *models.Namespaces, errChan chan error, wg *sync.WaitGroup) {
   358  	defer wg.Done()
   359  	if len(errChan) == 0 {
   360  		nss, err := in.businessLayer.Namespace.GetClusterNamespaces(ctx, cluster)
   361  		if err != nil {
   362  			errChan <- err
   363  			return
   364  
   365  		}
   366  		*namespaces = nss
   367  
   368  		allWorkloads := map[string]models.WorkloadList{}
   369  		for _, ns := range nss {
   370  			criteria := WorkloadCriteria{Cluster: cluster, Namespace: ns.Name, IncludeIstioResources: false, IncludeHealth: false}
   371  			workloadList, err := in.businessLayer.Workload.GetWorkloadList(ctx, criteria)
   372  			if err != nil {
   373  				select {
   374  				case errChan <- err:
   375  				default:
   376  				}
   377  			} else {
   378  				allWorkloads[ns.Name] = workloadList
   379  			}
   380  		}
   381  		*rValue = allWorkloads
   382  	}
   383  }
   384  
   385  func (in *IstioValidationsService) fetchWorkload(ctx context.Context, rValue *map[string]models.WorkloadList, cluster, workload, namespace string, errChan chan error, wg *sync.WaitGroup) {
   386  	defer wg.Done()
   387  	if len(errChan) == 0 {
   388  		allWorkloads := map[string]models.WorkloadList{}
   389  		criteria := WorkloadCriteria{Cluster: cluster, WorkloadName: workload, Namespace: namespace, IncludeIstioResources: true, IncludeHealth: false}
   390  		workloadList, err := in.businessLayer.Workload.GetWorkloadList(ctx, criteria)
   391  		if err != nil {
   392  			select {
   393  			case errChan <- err:
   394  			default:
   395  			}
   396  		} else {
   397  			allWorkloads[namespace] = workloadList
   398  		}
   399  		*rValue = allWorkloads
   400  	}
   401  }
   402  
   403  // fetchServiceAccounts returns list of names of the ServiceAccounts retrieved from Registry Services in a map per cluster.
   404  func (in *IstioValidationsService) fetchServiceAccounts(ctx context.Context, rValue *map[string][]string, errChan chan error, wg *sync.WaitGroup) {
   405  	serviceAccounts := map[string][]string{}
   406  
   407  	istioDomain := strings.Replace(config.Get().ExternalServices.Istio.IstioIdentityDomain, "svc.", "", 1)
   408  	defer wg.Done()
   409  	if len(errChan) == 0 {
   410  		for _, cluster := range in.businessLayer.Namespace.GetClusterList() {
   411  			nss, err := in.businessLayer.Namespace.GetClusterNamespaces(ctx, cluster)
   412  			if err != nil {
   413  				errChan <- err
   414  				return
   415  			}
   416  			for _, ns := range nss {
   417  				criteria := WorkloadCriteria{Cluster: cluster, Namespace: ns.Name, IncludeIstioResources: false, IncludeHealth: false}
   418  				workloadList, err := in.businessLayer.Workload.GetWorkloadList(ctx, criteria)
   419  				if err != nil {
   420  					select {
   421  					case errChan <- err:
   422  					default:
   423  					}
   424  				} else {
   425  					for _, wl := range workloadList.Workloads {
   426  						for _, sAccountName := range wl.ServiceAccountNames {
   427  							saFullName := fmt.Sprintf("%s/ns/%s/sa/%s", istioDomain, ns.Name, sAccountName)
   428  							found := false
   429  							if _, ok := serviceAccounts[cluster]; !ok {
   430  								serviceAccounts[cluster] = []string{}
   431  							}
   432  							for _, name := range serviceAccounts[cluster] {
   433  								if name == saFullName {
   434  									found = true
   435  									break
   436  								}
   437  							}
   438  							if !found {
   439  								serviceAccounts[cluster] = append(serviceAccounts[cluster], saFullName)
   440  							}
   441  						}
   442  					}
   443  				}
   444  			}
   445  		}
   446  		*rValue = serviceAccounts
   447  	}
   448  }
   449  
   450  func (in *IstioValidationsService) fetchIstioConfigList(ctx context.Context, rValue *models.IstioConfigList, mtlsDetails *kubernetes.MTLSDetails, rbacDetails *kubernetes.RBACDetails, cluster, namespace string, errChan chan error, wg *sync.WaitGroup) {
   451  	defer wg.Done()
   452  	if len(errChan) > 0 {
   453  		return
   454  	}
   455  
   456  	// all namespaces are necessary to check Ambient mode of each namespace
   457  	nss, err := in.businessLayer.Namespace.GetClusterNamespaces(ctx, cluster)
   458  	if err != nil {
   459  		errChan <- err
   460  		return
   461  	}
   462  
   463  	criteria := IstioConfigCriteria{
   464  		IncludeGateways:               true,
   465  		IncludeDestinationRules:       true,
   466  		IncludeServiceEntries:         true,
   467  		IncludeVirtualServices:        true,
   468  		IncludeSidecars:               true,
   469  		IncludeRequestAuthentications: true,
   470  		IncludeWorkloadEntries:        true,
   471  		IncludeAuthorizationPolicies:  true,
   472  		IncludePeerAuthentications:    true,
   473  		IncludeK8sHTTPRoutes:          true,
   474  		IncludeK8sGateways:            true,
   475  		IncludeK8sReferenceGrants:     true,
   476  	}
   477  	istioConfigMap, err := in.businessLayer.IstioConfig.GetIstioConfigMap(ctx, meta_v1.NamespaceAll, criteria)
   478  	if err != nil {
   479  		errChan <- err
   480  		return
   481  	}
   482  	istioConfigList := istioConfigMap[cluster]
   483  
   484  	// Filter VS
   485  	filteredVSs := in.filterVSExportToNamespaces(nss, namespace, cluster, istioConfigList.VirtualServices)
   486  	rValue.VirtualServices = append(rValue.VirtualServices, filteredVSs...)
   487  
   488  	// Filter DR
   489  	filteredDRs := in.filterDRExportToNamespaces(nss, namespace, cluster, kubernetes.FilterAutogeneratedDestinationRules(istioConfigList.DestinationRules))
   490  	rValue.DestinationRules = append(rValue.DestinationRules, filteredDRs...)
   491  	mtlsDetails.DestinationRules = append(mtlsDetails.DestinationRules, filteredDRs...)
   492  
   493  	// Filter SE
   494  	filteredSEs := in.filterSEExportToNamespaces(nss, namespace, cluster, istioConfigList.ServiceEntries)
   495  	rValue.ServiceEntries = append(rValue.ServiceEntries, filteredSEs...)
   496  
   497  	// All Gateways
   498  	rValue.Gateways = append(rValue.Gateways, kubernetes.FilterAutogeneratedGateways(istioConfigList.Gateways)...)
   499  
   500  	// All K8sGateways
   501  	rValue.K8sGateways = append(rValue.K8sGateways, istioConfigList.K8sGateways...)
   502  
   503  	// All K8sHTTPRoutes
   504  	rValue.K8sHTTPRoutes = append(rValue.K8sHTTPRoutes, istioConfigList.K8sHTTPRoutes...)
   505  
   506  	// All K8sReferenceGrants
   507  	rValue.K8sReferenceGrants = append(rValue.K8sReferenceGrants, istioConfigList.K8sReferenceGrants...)
   508  
   509  	// All Sidecars
   510  	rValue.Sidecars = append(rValue.Sidecars, istioConfigList.Sidecars...)
   511  
   512  	// All RequestAuthentications
   513  	rValue.RequestAuthentications = append(rValue.RequestAuthentications, istioConfigList.RequestAuthentications...)
   514  
   515  	// All WorkloadEntries
   516  	rValue.WorkloadEntries = append(rValue.WorkloadEntries, istioConfigList.WorkloadEntries...)
   517  
   518  	in.filterPeerAuths(namespace, mtlsDetails, istioConfigList.PeerAuthentications)
   519  
   520  	in.filterAuthPolicies(namespace, rbacDetails, istioConfigList.AuthorizationPolicies)
   521  }
   522  
   523  func (in *IstioValidationsService) filterPeerAuths(namespace string, mtlsDetails *kubernetes.MTLSDetails, peerAuths []*security_v1beta.PeerAuthentication) {
   524  	rootNs := config.Get().ExternalServices.Istio.RootNamespace
   525  	for _, pa := range peerAuths {
   526  		if pa.Namespace == rootNs {
   527  			mtlsDetails.MeshPeerAuthentications = append(mtlsDetails.MeshPeerAuthentications, pa)
   528  		}
   529  		if pa.Namespace == namespace || namespace == "" {
   530  			mtlsDetails.PeerAuthentications = append(mtlsDetails.PeerAuthentications, pa)
   531  		}
   532  	}
   533  }
   534  
   535  func (in *IstioValidationsService) filterAuthPolicies(namespace string, rbacDetails *kubernetes.RBACDetails, authPolicies []*security_v1beta.AuthorizationPolicy) {
   536  	for _, ap := range authPolicies {
   537  		if ap.Namespace == namespace || namespace == "" {
   538  			rbacDetails.AuthorizationPolicies = append(rbacDetails.AuthorizationPolicies, ap)
   539  		}
   540  	}
   541  }
   542  
   543  func (in *IstioValidationsService) filterVSExportToNamespaces(allNamespaces models.Namespaces, namespace string, cluster string, vs []*networking_v1beta1.VirtualService) []*networking_v1beta1.VirtualService {
   544  	if namespace == "" {
   545  		return kubernetes.FilterAutogeneratedVirtualServices(vs)
   546  	}
   547  	var result []*networking_v1beta1.VirtualService
   548  	for _, v := range vs {
   549  		if kubernetes.IsAutogenerated(v.Name) {
   550  			continue
   551  		}
   552  		if in.isExportedObjectIncluded(v.Spec.ExportTo, allNamespaces, v.Namespace, namespace, cluster) {
   553  			result = append(result, v)
   554  		}
   555  	}
   556  	return result
   557  }
   558  
   559  func (in *IstioValidationsService) filterDRExportToNamespaces(allNamespaces models.Namespaces, namespace string, cluster string, dr []*networking_v1beta1.DestinationRule) []*networking_v1beta1.DestinationRule {
   560  	if namespace == "" {
   561  		return dr
   562  	}
   563  	var result []*networking_v1beta1.DestinationRule
   564  	for _, d := range dr {
   565  		if in.isExportedObjectIncluded(d.Spec.ExportTo, allNamespaces, d.Namespace, namespace, cluster) {
   566  			result = append(result, d)
   567  		}
   568  	}
   569  	return result
   570  }
   571  
   572  func (in *IstioValidationsService) filterSEExportToNamespaces(allNamespaces models.Namespaces, namespace string, cluster string, se []*networking_v1beta1.ServiceEntry) []*networking_v1beta1.ServiceEntry {
   573  	if namespace == "" {
   574  		return se
   575  	}
   576  	var result []*networking_v1beta1.ServiceEntry
   577  	for _, s := range se {
   578  		if in.isExportedObjectIncluded(s.Spec.ExportTo, allNamespaces, s.Namespace, namespace, cluster) {
   579  			result = append(result, s)
   580  		}
   581  	}
   582  	return result
   583  }
   584  
   585  func (in *IstioValidationsService) isExportedObjectIncluded(exportTo []string, allNamespaces models.Namespaces, objectNamespace, exportedNamespace string, cluster string) bool {
   586  	// Ambient mode namespace does not support ExportTo, so export only to own namespace
   587  	if in.businessLayer.IstioConfig.IsAmbientEnabled(cluster) && allNamespaces.IsNamespaceAmbient(objectNamespace, cluster) {
   588  		return objectNamespace == exportedNamespace
   589  	} else {
   590  		if len(exportTo) > 0 {
   591  			for _, exportToNs := range exportTo {
   592  				// take only namespaces where it is exported to, or if it is exported to all namespaces, or export to own namespace
   593  				if checkExportTo(exportToNs, exportedNamespace, objectNamespace, allNamespaces) {
   594  					return true
   595  				}
   596  			}
   597  		} else {
   598  			// no exportTo field, means object exported to all namespaces
   599  			return true
   600  		}
   601  	}
   602  	return false
   603  }
   604  
   605  func (in *IstioValidationsService) fetchNonLocalmTLSConfigs(mtlsDetails *kubernetes.MTLSDetails, cluster string, errChan chan error, wg *sync.WaitGroup) {
   606  	defer wg.Done()
   607  	if len(errChan) > 0 {
   608  		return
   609  	}
   610  
   611  	cfg := config.Get()
   612  	// TODO: Handle multi-primary instead of only using home cluster.
   613  	kubeCache, err := kialiCache.GetKubeCache(cfg.KubernetesConfig.ClusterName)
   614  	if err != nil {
   615  		return
   616  	}
   617  
   618  	istioConfig, err := kubeCache.GetConfigMap(cfg.IstioNamespace, IstioConfigMapName(*cfg, ""))
   619  	if err != nil {
   620  		errChan <- err
   621  		return
   622  	}
   623  
   624  	icm, err := kubernetes.GetIstioConfigMap(istioConfig)
   625  	if err != nil {
   626  		errChan <- err
   627  	} else {
   628  		mtlsDetails.EnabledAutoMtls = icm.GetEnableAutoMtls()
   629  	}
   630  }
   631  
   632  func (in *IstioValidationsService) isGatewayToNamespace() bool {
   633  	mesh, err := in.businessLayer.Mesh.GetMesh(context.TODO())
   634  	if err != nil {
   635  		log.Errorf("Error getting mesh config: %s", err)
   636  		return false
   637  	}
   638  
   639  	// TODO: Multi-primary support
   640  	for _, controlPlane := range mesh.ControlPlanes {
   641  		if controlPlane.Cluster.IsKialiHome {
   642  			return controlPlane.Config.IsGatewayToNamespace
   643  		}
   644  	}
   645  
   646  	return false
   647  }
   648  
   649  func (in *IstioValidationsService) isPolicyAllowAny() bool {
   650  	allowAny := false
   651  	if in.businessLayer != nil {
   652  		if otp, err := in.businessLayer.Mesh.OutboundTrafficPolicy(); err == nil {
   653  			if otp.Mode == "" || otp.Mode == AllowAny {
   654  				return true
   655  			}
   656  		}
   657  	}
   658  	return allowAny
   659  }
   660  
   661  func checkExportTo(exportToNs string, namespace string, ownNs string, allNamespaces models.Namespaces) bool {
   662  	// check if namespaces where it is exported to, or if it is exported to all namespaces, or export to own namespace
   663  	// when exported to non-existing namespace, consider it to show validation error
   664  	return exportToNs == "*" || exportToNs == namespace || (exportToNs == "." && ownNs == namespace) || (exportToNs != "." && exportToNs != "*" && !allNamespaces.Includes(exportToNs))
   665  }