github.com/kiali/kiali@v1.84.0/business/checkers/authorization/principals_checker.go (about) 1 package authorization 2 3 import ( 4 "fmt" 5 "regexp" 6 "strings" 7 8 api_security_v1beta "istio.io/api/security/v1beta1" 9 security_v1beta "istio.io/client-go/pkg/apis/security/v1beta1" 10 11 "github.com/kiali/kiali/models" 12 ) 13 14 type PrincipalsChecker struct { 15 AuthorizationPolicy *security_v1beta.AuthorizationPolicy 16 Cluster string 17 ServiceAccounts map[string][]string 18 } 19 20 const ( 21 wildCardMatch = "*" 22 ) 23 24 func (pc PrincipalsChecker) Check() ([]*models.IstioCheck, bool) { 25 checks, valid := make([]*models.IstioCheck, 0), true 26 27 for ruleIdx, rule := range pc.AuthorizationPolicy.Spec.Rules { 28 if rule == nil { 29 continue 30 } 31 if len(rule.From) > 0 { 32 toChecks, toValid := pc.validateFromField(ruleIdx, rule.From) 33 checks = append(checks, toChecks...) 34 valid = valid && toValid 35 } 36 } 37 return checks, valid 38 } 39 40 func (pc PrincipalsChecker) validateFromField(ruleIdx int, from []*api_security_v1beta.Rule_From) ([]*models.IstioCheck, bool) { 41 if len(from) == 0 { 42 return nil, true 43 } 44 45 checks, valid := make([]*models.IstioCheck, 0, len(from)), true 46 for fromIdx, f := range from { 47 if f == nil { 48 continue 49 } 50 51 if f.Source == nil { 52 continue 53 } 54 55 if len(f.Source.Principals) == 0 { 56 continue 57 } 58 59 for i, p := range f.Source.Principals { 60 if !pc.hasMatchingServiceAccount(pc.ServiceAccounts[pc.Cluster], p) { 61 path := fmt.Sprintf("spec/rules[%d]/from[%d]/source/principals[%d]", ruleIdx, fromIdx, i) 62 valid = false 63 if pc.hasMatchingRemoteServiceAccount(p) { 64 validation := models.Build("authorizationpolicy.source.principalremote", path) 65 checks = append(checks, &validation) 66 } else { 67 validation := models.Build("authorizationpolicy.source.principalnotfound", path) 68 checks = append(checks, &validation) 69 } 70 } 71 } 72 } 73 74 return checks, valid 75 } 76 77 func (pc PrincipalsChecker) hasMatchingServiceAccount(serviceAccounts []string, principal string) bool { 78 if principal == wildCardMatch { 79 return true 80 } 81 82 for _, sa := range serviceAccounts { 83 if (strings.HasPrefix(principal, wildCardMatch) || strings.HasSuffix(principal, wildCardMatch)) && regexpFromPrincipal(principal).MatchString(sa) { 84 // Prefix match: “abc*” will match on value “abc” and “abcd”. 85 // Suffix match: “*abc” will match on value “abc” and “xabc”. 86 return true 87 } else if sa == principal { 88 return true 89 } 90 } 91 92 return false 93 } 94 95 func (pc PrincipalsChecker) hasMatchingRemoteServiceAccount(principal string) bool { 96 // should check among remote cluster's service accounts 97 for cluster, sas := range pc.ServiceAccounts { 98 if cluster != pc.Cluster && pc.hasMatchingServiceAccount(sas, principal) { 99 return true 100 } 101 } 102 return false 103 } 104 105 func regexpFromPrincipal(principal string) *regexp.Regexp { 106 // Replace '*' from principal with regexp '.*' 107 escaped := strings.Replace(principal, "*", ".*", -1) 108 109 // We anchor the beginning and end of the string when it's 110 // to be used as a regex, so that we don't get spurious 111 // substring matches, e.g., "example.com" matching 112 // "foo.example.com". 113 anchored := strings.Join([]string{"^", escaped, "$"}, "") 114 115 return regexp.MustCompile(anchored) 116 }