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 }