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  }