github.com/kiali/kiali@v1.84.0/graph/telemetry/istio/appender/security_policy.go (about)

     1  package appender
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/prometheus/common/model"
     8  
     9  	"github.com/kiali/kiali/graph"
    10  	"github.com/kiali/kiali/graph/telemetry/istio/util"
    11  	"github.com/kiali/kiali/log"
    12  	"github.com/kiali/kiali/prometheus"
    13  )
    14  
    15  const (
    16  	SecurityPolicyAppenderName = "securityPolicy"
    17  	policyMTLS                 = "mutual_tls"
    18  )
    19  
    20  // SecurityPolicyAppender is responsible for adding securityPolicy information to the graph.
    21  // The appender currently reports only mutual_tls security although is written in a generic way.
    22  // Name: securityPolicy
    23  type SecurityPolicyAppender struct {
    24  	GraphType          string
    25  	InjectServiceNodes bool
    26  	Namespaces         map[string]graph.NamespaceInfo
    27  	QueryTime          int64 // unix time in seconds
    28  	Rates              graph.RequestedRates
    29  }
    30  
    31  type PolicyRates map[string]float64
    32  
    33  // Name implements Appender
    34  func (a SecurityPolicyAppender) Name() string {
    35  	return SecurityPolicyAppenderName
    36  }
    37  
    38  // IsFinalizer implements Appender
    39  func (a SecurityPolicyAppender) IsFinalizer() bool {
    40  	return false
    41  }
    42  
    43  // AppendGraph implements Appender
    44  func (a SecurityPolicyAppender) AppendGraph(trafficMap graph.TrafficMap, globalInfo *graph.AppenderGlobalInfo, namespaceInfo *graph.AppenderNamespaceInfo) {
    45  	if len(trafficMap) == 0 {
    46  		return
    47  	}
    48  
    49  	if globalInfo.PromClient == nil {
    50  		var err error
    51  		globalInfo.PromClient, err = prometheus.NewClient()
    52  		graph.CheckError(err)
    53  	}
    54  
    55  	a.appendGraph(trafficMap, namespaceInfo.Namespace, globalInfo.PromClient)
    56  }
    57  
    58  func (a SecurityPolicyAppender) appendGraph(trafficMap graph.TrafficMap, namespace string, client *prometheus.Client) {
    59  	log.Tracef("Resolving security policy for namespace [%v], rates [%+v]", namespace, a.Rates)
    60  	duration := a.Namespaces[namespace].Duration
    61  
    62  	// query prometheus for mutual_tls info in two queries (use dest telemetry because it reports the security policy):
    63  	// 1) query for requests originating from a workload outside the namespace.
    64  	groupBy := "source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,source_principal,destination_cluster,destination_service_namespace,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,destination_principal,connection_security_policy"
    65  	var query string
    66  	if a.Rates.Grpc == graph.RateRequests || a.Rates.Http == graph.RateRequests {
    67  		requestsQuery := fmt.Sprintf(`sum(rate(%s{reporter="destination",source_workload_namespace!="%v",destination_service_namespace="%v"}[%vs])) by (%s) > 0`,
    68  			"istio_requests_total",
    69  			namespace,
    70  			namespace,
    71  			int(duration.Seconds()), // range duration for the query
    72  			groupBy)
    73  		query = fmt.Sprintf(`(%s)`, requestsQuery)
    74  	}
    75  	if a.Rates.Grpc == graph.RateSent || a.Rates.Grpc == graph.RateTotal {
    76  		grpcSentQuery := fmt.Sprintf(`sum(rate(%s{reporter="destination",source_workload_namespace!="%v",destination_service_namespace="%v"}[%vs])) by (%s) > 0`,
    77  			"istio_request_messages_total",
    78  			namespace,
    79  			namespace,
    80  			int(duration.Seconds()), // range duration for the query
    81  			groupBy)
    82  		if query == "" {
    83  			query = fmt.Sprintf(`(%s)`, grpcSentQuery)
    84  		} else {
    85  			query = fmt.Sprintf(`%s OR (%s)`, query, grpcSentQuery)
    86  		}
    87  	}
    88  	if a.Rates.Grpc == graph.RateReceived || a.Rates.Grpc == graph.RateTotal {
    89  		grpcReceivedQuery := fmt.Sprintf(`sum(rate(%s{reporter="destination",source_workload_namespace!="%v",destination_service_namespace="%v"}[%vs])) by (%s) > 0`,
    90  			"istio_response_messages_total",
    91  			namespace,
    92  			namespace,
    93  			int(duration.Seconds()), // range duration for the query
    94  			groupBy)
    95  		if query == "" {
    96  			query = fmt.Sprintf(`(%s)`, grpcReceivedQuery)
    97  		} else {
    98  			query = fmt.Sprintf(`%s OR (%s)`, query, grpcReceivedQuery)
    99  		}
   100  	}
   101  	if a.Rates.Tcp == graph.RateSent || a.Rates.Tcp == graph.RateTotal {
   102  		tcpSentQuery := fmt.Sprintf(`sum(rate(%s{reporter="destination",source_workload_namespace!="%v",destination_service_namespace="%v"}[%vs])) by (%s) > 0`,
   103  			"istio_tcp_sent_bytes_total",
   104  			namespace,
   105  			namespace,
   106  			int(duration.Seconds()), // range duration for the query
   107  			groupBy)
   108  		if query == "" {
   109  			query = fmt.Sprintf(`(%s)`, tcpSentQuery)
   110  		} else {
   111  			query = fmt.Sprintf(`%s OR (%s)`, query, tcpSentQuery)
   112  		}
   113  	}
   114  	if a.Rates.Tcp == graph.RateReceived || a.Rates.Tcp == graph.RateTotal {
   115  		tcpReceivedQuery := fmt.Sprintf(`sum(rate(%s{reporter="destination",source_workload_namespace!="%v",destination_service_namespace="%v"}[%vs])) by (%s) > 0`,
   116  			"istio_tcp_received_bytes_total",
   117  			namespace,
   118  			namespace,
   119  			int(duration.Seconds()), // range duration for the query
   120  			groupBy)
   121  		if query == "" {
   122  			query = fmt.Sprintf(`(%s)`, tcpReceivedQuery)
   123  		} else {
   124  			query = fmt.Sprintf(`%s OR (%s)`, query, tcpReceivedQuery)
   125  		}
   126  	}
   127  	outVector := promQuery(query, time.Unix(a.QueryTime, 0), client.GetContext(), client.API(), a)
   128  
   129  	// 2) query for requests originating from a workload inside of the namespace
   130  	query = ""
   131  	if a.Rates.Grpc == graph.RateRequests || a.Rates.Http == graph.RateRequests {
   132  		requestsQuery := fmt.Sprintf(`sum(rate(%s{reporter="destination",source_workload_namespace="%v"}[%vs])) by (%s) > 0`,
   133  			"istio_requests_total",
   134  			namespace,
   135  			int(duration.Seconds()), // range duration for the query
   136  			groupBy)
   137  		query = fmt.Sprintf(`(%s)`, requestsQuery)
   138  	}
   139  	if a.Rates.Grpc == graph.RateSent || a.Rates.Grpc == graph.RateTotal {
   140  		grpcSentQuery := fmt.Sprintf(`sum(rate(%s{reporter="destination",source_workload_namespace="%v"}[%vs])) by (%s) > 0`,
   141  			"istio_request_messages_total",
   142  			namespace,
   143  			int(duration.Seconds()), // range duration for the query
   144  			groupBy)
   145  		if query == "" {
   146  			query = fmt.Sprintf(`(%s)`, grpcSentQuery)
   147  		} else {
   148  			query = fmt.Sprintf(`%s OR (%s)`, query, grpcSentQuery)
   149  		}
   150  	}
   151  	if a.Rates.Grpc == graph.RateReceived || a.Rates.Grpc == graph.RateTotal {
   152  		grpcReceivedQuery := fmt.Sprintf(`sum(rate(%s{reporter="destination",source_workload_namespace="%v"}[%vs])) by (%s) > 0`,
   153  			"istio_response_messages_total",
   154  			namespace,
   155  			int(duration.Seconds()), // range duration for the query
   156  			groupBy)
   157  		if query == "" {
   158  			query = fmt.Sprintf(`(%s)`, grpcReceivedQuery)
   159  		} else {
   160  			query = fmt.Sprintf(`%s OR (%s)`, query, grpcReceivedQuery)
   161  		}
   162  	}
   163  	if a.Rates.Tcp == graph.RateSent || a.Rates.Tcp == graph.RateTotal {
   164  		tcpSentQuery := fmt.Sprintf(`sum(rate(%s{reporter="destination",source_workload_namespace="%v"}[%vs])) by (%s) > 0`,
   165  			"istio_tcp_sent_bytes_total",
   166  			namespace,
   167  			int(duration.Seconds()), // range duration for the query
   168  			groupBy)
   169  		if query == "" {
   170  			query = fmt.Sprintf(`(%s)`, tcpSentQuery)
   171  		} else {
   172  			query = fmt.Sprintf(`%s OR (%s)`, query, tcpSentQuery)
   173  		}
   174  	}
   175  	if a.Rates.Tcp == graph.RateReceived || a.Rates.Tcp == graph.RateTotal {
   176  		tcpReceivedQuery := fmt.Sprintf(`sum(rate(%s{reporter="destination",source_workload_namespace="%v"}[%vs])) by (%s) > 0`,
   177  			"istio_tcp_received_bytes_total",
   178  			namespace,
   179  			int(duration.Seconds()), // range duration for the query
   180  			groupBy)
   181  		if query == "" {
   182  			query = fmt.Sprintf(`(%s)`, tcpReceivedQuery)
   183  		} else {
   184  			query = fmt.Sprintf(`%s OR (%s)`, query, tcpReceivedQuery)
   185  		}
   186  	}
   187  	inVector := promQuery(query, time.Unix(a.QueryTime, 0), client.GetContext(), client.API(), a)
   188  
   189  	// create map to quickly look up securityPolicy
   190  	securityPolicyMap := make(map[string]PolicyRates)
   191  	principalMap := make(map[string]map[graph.MetadataKey]string)
   192  	a.populateSecurityPolicyMap(securityPolicyMap, principalMap, &outVector)
   193  	a.populateSecurityPolicyMap(securityPolicyMap, principalMap, &inVector)
   194  
   195  	applySecurityPolicy(trafficMap, securityPolicyMap, principalMap)
   196  }
   197  
   198  func (a SecurityPolicyAppender) populateSecurityPolicyMap(securityPolicyMap map[string]PolicyRates, principalMap map[string]map[graph.MetadataKey]string, vector *model.Vector) {
   199  	for _, s := range *vector {
   200  		m := s.Metric
   201  		lSourceCluster, sourceClusterOk := m["source_cluster"]
   202  		lSourceWlNs, sourceWlNsOk := m["source_workload_namespace"]
   203  		lSourceWl, sourceWlOk := m["source_workload"]
   204  		lSourceApp, sourceAppOk := m["source_canonical_service"]
   205  		lSourceVer, sourceVerOk := m["source_canonical_revision"]
   206  		lSourcePrincipal, sourcePrincipalOk := m["source_principal"]
   207  		lDestCluster, destClusterOk := m["destination_cluster"]
   208  		lDestSvcNs, destSvcNsOk := m["destination_service_namespace"]
   209  		lDestSvcName, destSvcNameOk := m["destination_service_name"]
   210  		lDestWlNs, destWlNsOk := m["destination_workload_namespace"]
   211  		lDestWl, destWlOk := m["destination_workload"]
   212  		lDestApp, destAppOk := m["destination_canonical_service"]
   213  		lDestVer, destVerOk := m["destination_canonical_revision"]
   214  		lDestPrincipal, destPrincipalOk := m["destination_principal"]
   215  		lCsp, cspOk := m["connection_security_policy"]
   216  
   217  		if !sourceWlNsOk || !sourceWlOk || !sourceAppOk || !sourceVerOk || !destSvcNsOk || !destSvcNameOk || !destWlNsOk || !destWlOk || !destAppOk || !destVerOk || !sourcePrincipalOk || !destPrincipalOk {
   218  			log.Warningf("populateSecurityPolicyMap: Skipping %s, missing expected labels", m.String())
   219  			continue
   220  		}
   221  
   222  		sourceWlNs := string(lSourceWlNs)
   223  		sourceWl := string(lSourceWl)
   224  		sourceApp := string(lSourceApp)
   225  		sourceVer := string(lSourceVer)
   226  		sourcePrincipal := string(lSourcePrincipal)
   227  		destSvcNs := string(lDestSvcNs)
   228  		destSvcName := string(lDestSvcName)
   229  		destWlNs := string(lDestWlNs)
   230  		destWl := string(lDestWl)
   231  		destApp := string(lDestApp)
   232  		destVer := string(lDestVer)
   233  		destPrincipal := string(lDestPrincipal)
   234  		// connection_security_policy is not set on gRPC message metrics
   235  		csp := graph.Unknown
   236  		if cspOk {
   237  			csp = string(lCsp)
   238  		}
   239  
   240  		val := float64(s.Value)
   241  
   242  		// handle clusters
   243  		sourceCluster, destCluster := util.HandleClusters(lSourceCluster, sourceClusterOk, lDestCluster, destClusterOk)
   244  
   245  		// don't inject a service node if any of:
   246  		// - destSvcName is not set
   247  		// - destSvcName is PassthroughCluster (see https://github.com/kiali/kiali/issues/4488)
   248  		// - dest node is already a service node
   249  		inject := false
   250  		if a.InjectServiceNodes && graph.IsOK(destSvcName) && destSvcName != graph.PassthroughCluster {
   251  			_, destNodeType, err := graph.Id(destCluster, destSvcNs, destSvcName, destWlNs, destWl, destApp, destVer, a.GraphType)
   252  			if err != nil {
   253  				log.Warningf("Skipping (sp) %s, %s", m.String(), err)
   254  				continue
   255  			}
   256  			inject = (graph.NodeTypeService != destNodeType)
   257  		}
   258  		if inject {
   259  			a.addSecurityPolicy(securityPolicyMap, csp, val, sourceCluster, sourceWlNs, "", sourceWl, sourceApp, sourceVer, destCluster, destSvcNs, destSvcName, "", "", "", "")
   260  			a.addSecurityPolicy(securityPolicyMap, csp, val, destCluster, destSvcNs, destSvcName, "", "", "", destCluster, destSvcNs, destSvcName, destWlNs, destWl, destApp, destVer)
   261  			a.addPrincipal(principalMap, sourceCluster, sourceWlNs, "", sourceWl, sourceApp, sourceVer, sourcePrincipal, destCluster, destSvcNs, destSvcName, "", "", "", "", destPrincipal)
   262  			a.addPrincipal(principalMap, destCluster, destSvcNs, destSvcName, "", "", "", sourcePrincipal, destCluster, destSvcNs, destSvcName, destWlNs, destWl, destApp, destVer, destPrincipal)
   263  		} else {
   264  			a.addSecurityPolicy(securityPolicyMap, csp, val, sourceCluster, sourceWlNs, "", sourceWl, sourceApp, sourceVer, destCluster, destSvcNs, destSvcName, destWlNs, destWl, destApp, destVer)
   265  			a.addPrincipal(principalMap, sourceCluster, sourceWlNs, "", sourceWl, sourceApp, sourceVer, sourcePrincipal, destCluster, destSvcNs, destSvcName, destWlNs, destWl, destApp, destVer, destPrincipal)
   266  		}
   267  	}
   268  }
   269  
   270  func (a SecurityPolicyAppender) addSecurityPolicy(securityPolicyMap map[string]PolicyRates, csp string, val float64, sourceCluster, sourceNs, sourceSvc, sourceWl, sourceApp, sourceVer, destCluster, destSvcNs, destSvc, destWlNs, destWl, destApp, destVer string) {
   271  	sourceId, _, err := graph.Id(sourceCluster, sourceNs, sourceSvc, sourceNs, sourceWl, sourceApp, sourceVer, a.GraphType)
   272  	if err != nil {
   273  		log.Warningf("Skipping addSecurityPolicy (source), %s", err)
   274  		return
   275  	}
   276  	destId, _, err := graph.Id(destCluster, destSvcNs, destSvc, destWlNs, destWl, destApp, destVer, a.GraphType)
   277  	if err != nil {
   278  		log.Warningf("Skipping addSecurityPolicy (dest), %s", err)
   279  		return
   280  	}
   281  	key := fmt.Sprintf("%s %s", sourceId, destId)
   282  	var policyRates PolicyRates
   283  	var ok bool
   284  	if policyRates, ok = securityPolicyMap[key]; !ok {
   285  		policyRates = make(PolicyRates)
   286  		securityPolicyMap[key] = policyRates
   287  	}
   288  	policyRates[csp] = val
   289  }
   290  
   291  func applySecurityPolicy(trafficMap graph.TrafficMap, securityPolicyMap map[string]PolicyRates, principalMap map[string]map[graph.MetadataKey]string) {
   292  	for _, s := range trafficMap {
   293  		for _, e := range s.Edges {
   294  			key := fmt.Sprintf("%s %s", e.Source.ID, e.Dest.ID)
   295  			if policyRates, ok := securityPolicyMap[key]; ok {
   296  				mtls := 0.0
   297  				other := 0.0
   298  				for policy, rate := range policyRates {
   299  					if policy == policyMTLS {
   300  						mtls = rate
   301  					} else {
   302  						other += rate
   303  					}
   304  				}
   305  				if mtls > 0 {
   306  					e.Metadata[graph.IsMTLS] = mtls / (mtls + other) * 100
   307  				}
   308  			}
   309  			if kPrincipalMap, ok := principalMap[key]; ok {
   310  				e.Metadata[graph.SourcePrincipal] = kPrincipalMap[graph.SourcePrincipal]
   311  				e.Metadata[graph.DestPrincipal] = kPrincipalMap[graph.DestPrincipal]
   312  			}
   313  		}
   314  	}
   315  }
   316  
   317  func (a SecurityPolicyAppender) addPrincipal(principalMap map[string]map[graph.MetadataKey]string, sourceCluster, sourceNs, sourceSvc, sourceWl, sourceApp, sourceVer, sourcePrincipal, destCluster, destSvcNs, destSvc, destWlNs, destWl, destApp, destVer, destPrincipal string) {
   318  	sourceID, _, err := graph.Id(sourceCluster, sourceNs, sourceSvc, sourceNs, sourceWl, sourceApp, sourceVer, a.GraphType)
   319  	if err != nil {
   320  		log.Warningf("Skipping addPrincipal (source), %s", err)
   321  		return
   322  	}
   323  	destID, _, err := graph.Id(destCluster, destSvcNs, destSvc, destWlNs, destWl, destApp, destVer, a.GraphType)
   324  	if err != nil {
   325  		log.Warningf("Skipping addPrincipal (dest), %s", err)
   326  		return
   327  	}
   328  	key := fmt.Sprintf("%s %s", sourceID, destID)
   329  	var ok bool
   330  	if _, ok = principalMap[key]; !ok {
   331  		kPrincipalMap := make(map[graph.MetadataKey]string)
   332  		kPrincipalMap[graph.SourcePrincipal] = sourcePrincipal
   333  		kPrincipalMap[graph.DestPrincipal] = destPrincipal
   334  		principalMap[key] = kPrincipalMap
   335  	}
   336  }