istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/serviceregistry/kube/controller/ambient/authorization.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package ambient
    16  
    17  import (
    18  	"fmt"
    19  	"net/netip"
    20  	"strconv"
    21  	"strings"
    22  
    23  	"istio.io/api/security/v1beta1"
    24  	securityclient "istio.io/client-go/pkg/apis/security/v1beta1"
    25  	"istio.io/istio/pilot/pkg/model"
    26  	"istio.io/istio/pkg/config/schema/kind"
    27  	"istio.io/istio/pkg/log"
    28  	"istio.io/istio/pkg/util/sets"
    29  	"istio.io/istio/pkg/workloadapi/security"
    30  )
    31  
    32  const (
    33  	staticStrictPolicyName = "istio_converted_static_strict" // use '_' character since those are illegal in k8s names
    34  )
    35  
    36  func (a *index) Policies(requested sets.Set[model.ConfigKey]) []model.WorkloadAuthorization {
    37  	// TODO: use many Gets instead of List?
    38  	cfgs := a.authorizationPolicies.List()
    39  	l := len(cfgs)
    40  	if len(requested) > 0 {
    41  		l = len(requested)
    42  	}
    43  	res := make([]model.WorkloadAuthorization, 0, l)
    44  	for _, cfg := range cfgs {
    45  		k := model.ConfigKey{
    46  			Kind:      kind.AuthorizationPolicy,
    47  			Name:      cfg.Authorization.Name,
    48  			Namespace: cfg.Authorization.Namespace,
    49  		}
    50  
    51  		if len(requested) > 0 && !requested.Contains(k) {
    52  			continue
    53  		}
    54  		res = append(res, cfg)
    55  	}
    56  	return res
    57  }
    58  
    59  // convertedSelectorPeerAuthentications returns a list of keys corresponding to one or both of:
    60  // [static STRICT policy, port-level STRICT policy] based on the effective PeerAuthentication policy
    61  func convertedSelectorPeerAuthentications(rootNamespace string, configs []*securityclient.PeerAuthentication) []string {
    62  	var meshCfg, namespaceCfg, workloadCfg *securityclient.PeerAuthentication
    63  	for _, cfg := range configs {
    64  		spec := &cfg.Spec
    65  		if spec.Selector == nil || len(spec.Selector.MatchLabels) == 0 {
    66  			// Namespace-level or mesh-level policy
    67  			if cfg.Namespace == rootNamespace {
    68  				if meshCfg == nil || cfg.CreationTimestamp.Before(&meshCfg.CreationTimestamp) {
    69  					log.Debugf("Switch selected mesh policy to %s.%s (%v)", cfg.Name, cfg.Namespace, cfg.CreationTimestamp)
    70  					meshCfg = cfg
    71  				}
    72  			} else {
    73  				if namespaceCfg == nil || cfg.CreationTimestamp.Before(&namespaceCfg.CreationTimestamp) {
    74  					log.Debugf("Switch selected namespace policy to %s.%s (%v)", cfg.Name, cfg.Namespace, cfg.CreationTimestamp)
    75  					namespaceCfg = cfg
    76  				}
    77  			}
    78  		} else if cfg.Namespace != rootNamespace {
    79  			if workloadCfg == nil || cfg.CreationTimestamp.Before(&workloadCfg.CreationTimestamp) {
    80  				log.Debugf("Switch selected workload policy to %s.%s (%v)", cfg.Name, cfg.Namespace, cfg.CreationTimestamp)
    81  				workloadCfg = cfg
    82  			}
    83  		}
    84  	}
    85  
    86  	// Whether it comes from a mesh-wide, namespace-wide, or workload-specific policy
    87  	// if the effective policy is STRICT, then reference our static STRICT policy
    88  	var isEffectiveStrictPolicy bool
    89  	// Only 1 per port workload policy can be effective at a time. In the case of a conflict
    90  	// the oldest policy wins.
    91  	var effectivePortLevelPolicyKey string
    92  
    93  	// Process in mesh, namespace, workload order to resolve inheritance (UNSET)
    94  	if meshCfg != nil {
    95  		if !isMtlsModeUnset(meshCfg.Spec.Mtls) {
    96  			isEffectiveStrictPolicy = isMtlsModeStrict(meshCfg.Spec.Mtls)
    97  		}
    98  	}
    99  
   100  	if namespaceCfg != nil {
   101  		if !isMtlsModeUnset(namespaceCfg.Spec.Mtls) {
   102  			isEffectiveStrictPolicy = isMtlsModeStrict(namespaceCfg.Spec.Mtls)
   103  		}
   104  	}
   105  
   106  	if workloadCfg == nil {
   107  		return effectivePeerAuthenticationKeys(rootNamespace, isEffectiveStrictPolicy, "")
   108  	}
   109  
   110  	workloadSpec := &workloadCfg.Spec
   111  
   112  	// Regardless of if we have port-level overrides, if the workload policy is STRICT, then we need to reference our static STRICT policy
   113  	if isMtlsModeStrict(workloadSpec.Mtls) {
   114  		isEffectiveStrictPolicy = true
   115  	}
   116  
   117  	// Regardless of if we have port-level overrides, if the workload policy is PERMISSIVE or DISABLE, then we shouldn't send our static STRICT policy
   118  	if isMtlsModePermissive(workloadSpec.Mtls) || isMtlsModeDisable(workloadSpec.Mtls) {
   119  		isEffectiveStrictPolicy = false
   120  	}
   121  
   122  	if workloadSpec.PortLevelMtls != nil {
   123  		switch workloadSpec.GetMtls().GetMode() {
   124  		case v1beta1.PeerAuthentication_MutualTLS_STRICT:
   125  			foundPermissive := false
   126  			for _, portMtls := range workloadSpec.PortLevelMtls {
   127  				if isMtlsModePermissive(portMtls) || isMtlsModeDisable(portMtls) {
   128  					foundPermissive = true
   129  					break
   130  				}
   131  			}
   132  
   133  			if foundPermissive {
   134  				// If we found a non-strict policy, we need to reference this workload policy to see the port level exceptions
   135  				effectivePortLevelPolicyKey = workloadCfg.Namespace + "/" + model.GetAmbientPolicyConfigName(model.ConfigKey{
   136  					Name:      workloadCfg.Name,
   137  					Kind:      kind.PeerAuthentication,
   138  					Namespace: workloadCfg.Namespace,
   139  				})
   140  				isEffectiveStrictPolicy = false // don't send our static STRICT policy since the converted form of this policy will include the default STRICT mode
   141  			}
   142  		case v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE, v1beta1.PeerAuthentication_MutualTLS_DISABLE:
   143  			foundStrict := false
   144  			for _, portMtls := range workloadSpec.PortLevelMtls {
   145  				if isMtlsModeStrict(portMtls) {
   146  					foundStrict = true
   147  					break
   148  				}
   149  			}
   150  
   151  			// There's a STRICT port mode, so we need to reference this policy in the workload
   152  			if foundStrict {
   153  				effectivePortLevelPolicyKey = workloadCfg.Namespace + "/" + model.GetAmbientPolicyConfigName(model.ConfigKey{
   154  					Name:      workloadCfg.Name,
   155  					Kind:      kind.PeerAuthentication,
   156  					Namespace: workloadCfg.Namespace,
   157  				})
   158  			}
   159  		default: // Unset
   160  			if isEffectiveStrictPolicy {
   161  				// Strict mesh or namespace policy
   162  				foundPermissive := false
   163  				for _, portMtls := range workloadSpec.PortLevelMtls {
   164  					if isMtlsModePermissive(portMtls) {
   165  						foundPermissive = true
   166  						break
   167  					}
   168  				}
   169  
   170  				if foundPermissive {
   171  					// If we found a non-strict policy, we need to reference this workload policy to see the port level exceptions
   172  					effectivePortLevelPolicyKey = workloadCfg.Namespace + "/" + model.GetAmbientPolicyConfigName(model.ConfigKey{
   173  						Name:      workloadCfg.Name,
   174  						Kind:      kind.PeerAuthentication,
   175  						Namespace: workloadCfg.Namespace,
   176  					})
   177  				}
   178  			} else {
   179  				// Permissive mesh or namespace policy
   180  				isEffectiveStrictPolicy = false // any ports that aren't specified will be PERMISSIVE so this workload isn't effectively under a STRICT policy
   181  				foundStrict := false
   182  				for _, portMtls := range workloadSpec.PortLevelMtls {
   183  					if isMtlsModeStrict(portMtls) {
   184  						foundStrict = true
   185  						continue
   186  					}
   187  				}
   188  
   189  				// There's a STRICT port mode, so we need to reference this policy in the workload
   190  				if foundStrict {
   191  					effectivePortLevelPolicyKey = workloadCfg.Namespace + "/" + model.GetAmbientPolicyConfigName(model.ConfigKey{
   192  						Name:      workloadCfg.Name,
   193  						Kind:      kind.PeerAuthentication,
   194  						Namespace: workloadCfg.Namespace,
   195  					})
   196  				}
   197  			}
   198  		}
   199  	}
   200  
   201  	return effectivePeerAuthenticationKeys(rootNamespace, isEffectiveStrictPolicy, effectivePortLevelPolicyKey)
   202  }
   203  
   204  func effectivePeerAuthenticationKeys(rootNamespace string, isEffectiveStringPolicy bool, effectiveWorkloadPolicyKey string) []string {
   205  	res := sets.New[string]()
   206  
   207  	if isEffectiveStringPolicy {
   208  		res.Insert(fmt.Sprintf("%s/%s", rootNamespace, staticStrictPolicyName))
   209  	}
   210  
   211  	if effectiveWorkloadPolicyKey != "" {
   212  		res.Insert(effectiveWorkloadPolicyKey)
   213  	}
   214  
   215  	return sets.SortedList(res)
   216  }
   217  
   218  // convertPeerAuthentication converts a PeerAuthentication to an L4 authorization policy (i.e. security.Authorization) iff
   219  // 1. the PeerAuthentication has a workload selector
   220  // 2. The PeerAuthentication is NOT in the root namespace
   221  // 3. There is a portLevelMtls policy (technically implied by 1)
   222  // 4. If the top-level mode is PERMISSIVE or DISABLE, there is at least one portLevelMtls policy with mode STRICT
   223  //
   224  // STRICT policies that don't have portLevelMtls will be
   225  // handled when the Workload xDS resource is pushed (a static STRICT-equivalent policy will always be pushed)
   226  func convertPeerAuthentication(rootNamespace string, cfg *securityclient.PeerAuthentication) *security.Authorization {
   227  	pa := &cfg.Spec
   228  
   229  	mode := pa.GetMtls().GetMode()
   230  
   231  	scope := security.Scope_WORKLOAD_SELECTOR
   232  	// violates case #1, #2, or #3
   233  	if cfg.Namespace == rootNamespace || pa.Selector == nil || len(pa.PortLevelMtls) == 0 {
   234  		log.Debugf("skipping PeerAuthentication %s/%s for ambient since it isn't a workload policy with port level mTLS", cfg.Namespace, cfg.Name)
   235  		return nil
   236  	}
   237  
   238  	action := security.Action_DENY
   239  	var rules []*security.Rules
   240  
   241  	if mode == v1beta1.PeerAuthentication_MutualTLS_STRICT {
   242  		rules = append(rules, &security.Rules{
   243  			Matches: []*security.Match{
   244  				{
   245  					NotPrincipals: []*security.StringMatch{
   246  						{
   247  							MatchType: &security.StringMatch_Presence{},
   248  						},
   249  					},
   250  				},
   251  			},
   252  		})
   253  	}
   254  
   255  	// If we have a strict policy and all of the ports are strict, it's effectively a strict policy
   256  	// so we can exit early and have the WorkloadRbac xDS server push its static strict policy.
   257  	// Note that this doesn't actually attach the policy to any workload; it just makes it available
   258  	// to ztunnel in case a workload needs it.
   259  	foundNonStrictPortmTLS := false
   260  	for port, mtls := range pa.PortLevelMtls {
   261  		switch portMtlsMode := mtls.GetMode(); {
   262  		case portMtlsMode == v1beta1.PeerAuthentication_MutualTLS_STRICT:
   263  			rules = append(rules, &security.Rules{
   264  				Matches: []*security.Match{
   265  					{
   266  						NotPrincipals: []*security.StringMatch{
   267  							{
   268  								MatchType: &security.StringMatch_Presence{},
   269  							},
   270  						},
   271  						DestinationPorts: []uint32{port},
   272  					},
   273  				},
   274  			})
   275  		case portMtlsMode == v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE:
   276  			// Check top-level mode
   277  			if mode == v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE || mode == v1beta1.PeerAuthentication_MutualTLS_DISABLE {
   278  				// we don't care; log and continue
   279  				log.Debugf("skipping port %s/%s for PeerAuthentication %s/%s for ambient since the parent mTLS mode is %s",
   280  					port, portMtlsMode, cfg.Namespace, cfg.Name, mode)
   281  				continue
   282  			}
   283  			foundNonStrictPortmTLS = true
   284  
   285  			// If the top level policy is STRICT, we need to add a rule for the port that exempts it from the deny policy
   286  			rules = append(rules, &security.Rules{
   287  				Matches: []*security.Match{
   288  					{
   289  						NotDestinationPorts: []uint32{port}, // if the incoming connection does not match this port, deny (notice there's no principals requirement)
   290  					},
   291  				},
   292  			})
   293  		case portMtlsMode == v1beta1.PeerAuthentication_MutualTLS_DISABLE:
   294  			// Check top-level mode
   295  			if mode == v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE || mode == v1beta1.PeerAuthentication_MutualTLS_DISABLE {
   296  				// we don't care; log and continue
   297  				log.Debugf("skipping port %s/%s for PeerAuthentication %s/%s for ambient since the parent mTLS mode is %s",
   298  					port, portMtlsMode, cfg.Namespace, cfg.Name, mode)
   299  				continue
   300  			}
   301  			foundNonStrictPortmTLS = true
   302  
   303  			// If the top level policy is STRICT, we need to add a rule for the port that exempts it from the deny policy
   304  			rules = append(rules, &security.Rules{
   305  				Matches: []*security.Match{
   306  					{
   307  						NotDestinationPorts: []uint32{port}, // if the incoming connection does not match this port, deny (notice there's no principals requirement)
   308  					},
   309  				},
   310  			})
   311  		default:
   312  			log.Debugf("skipping port %s for PeerAuthentication %s/%s for ambient since it is %s", port, cfg.Namespace, cfg.Name, portMtlsMode)
   313  			continue
   314  		}
   315  	}
   316  
   317  	// If the top level TLS mode is STRICT and all of the port level mTLS modes are STRICT, this is just a strict policy and we'll exit early
   318  	if mode == v1beta1.PeerAuthentication_MutualTLS_STRICT && !foundNonStrictPortmTLS {
   319  		return nil
   320  	}
   321  
   322  	if len(rules) == 0 {
   323  		// we never added any rules; return
   324  		return nil
   325  	}
   326  
   327  	opol := &security.Authorization{
   328  		Name: model.GetAmbientPolicyConfigName(model.ConfigKey{
   329  			Name:      cfg.Name,
   330  			Kind:      kind.PeerAuthentication,
   331  			Namespace: cfg.Namespace,
   332  		}),
   333  		Namespace: cfg.Namespace,
   334  		Scope:     scope,
   335  		Action:    action,
   336  		Groups:    []*security.Group{{Rules: rules}},
   337  	}
   338  
   339  	return opol
   340  }
   341  
   342  func convertAuthorizationPolicy(rootns string, obj *securityclient.AuthorizationPolicy) *security.Authorization {
   343  	pol := &obj.Spec
   344  
   345  	polTargetRef := model.GetTargetRefs(pol)
   346  	if len(polTargetRef) > 0 {
   347  		// TargetRef is not intended for ztunnel
   348  		return nil
   349  	}
   350  
   351  	scope := security.Scope_WORKLOAD_SELECTOR
   352  	if pol.GetSelector() == nil {
   353  		scope = security.Scope_NAMESPACE
   354  		// TODO: TDA
   355  		if rootns == obj.Namespace {
   356  			scope = security.Scope_GLOBAL // TODO: global workload?
   357  		}
   358  	}
   359  	action := security.Action_ALLOW
   360  	switch pol.Action {
   361  	case v1beta1.AuthorizationPolicy_ALLOW:
   362  	case v1beta1.AuthorizationPolicy_DENY:
   363  		action = security.Action_DENY
   364  	default:
   365  		return nil
   366  	}
   367  	opol := &security.Authorization{
   368  		Name:      obj.Name,
   369  		Namespace: obj.Namespace,
   370  		Scope:     scope,
   371  		Action:    action,
   372  		Groups:    nil,
   373  	}
   374  
   375  	for _, rule := range pol.Rules {
   376  		rules := handleRule(action, rule)
   377  		if rules != nil {
   378  			rg := &security.Group{
   379  				Rules: rules,
   380  			}
   381  			opol.Groups = append(opol.Groups, rg)
   382  		}
   383  	}
   384  
   385  	return opol
   386  }
   387  
   388  func anyNonEmpty[T any](arr ...[]T) bool {
   389  	for _, a := range arr {
   390  		if len(a) > 0 {
   391  			return true
   392  		}
   393  	}
   394  	return false
   395  }
   396  
   397  func handleRule(action security.Action, rule *v1beta1.Rule) []*security.Rules {
   398  	toMatches := []*security.Match{}
   399  	for _, to := range rule.To {
   400  		op := to.Operation
   401  		if action == security.Action_ALLOW && anyNonEmpty(op.Hosts, op.NotHosts, op.Methods, op.NotMethods, op.Paths, op.NotPaths) {
   402  			// L7 policies never match for ALLOW
   403  			// For DENY they will always match, so it is more restrictive
   404  			return nil
   405  		}
   406  		match := &security.Match{
   407  			DestinationPorts:    stringToPort(op.Ports),
   408  			NotDestinationPorts: stringToPort(op.NotPorts),
   409  		}
   410  		toMatches = append(toMatches, match)
   411  	}
   412  	fromMatches := []*security.Match{}
   413  	for _, from := range rule.From {
   414  		op := from.Source
   415  		if action == security.Action_ALLOW && anyNonEmpty(op.RemoteIpBlocks, op.NotRemoteIpBlocks, op.RequestPrincipals, op.NotRequestPrincipals) {
   416  			// L7 policies never match for ALLOW
   417  			// For DENY they will always match, so it is more restrictive
   418  			return nil
   419  		}
   420  		match := &security.Match{
   421  			SourceIps:     stringToIP(op.IpBlocks),
   422  			NotSourceIps:  stringToIP(op.NotIpBlocks),
   423  			Namespaces:    stringToMatch(op.Namespaces),
   424  			NotNamespaces: stringToMatch(op.NotNamespaces),
   425  			Principals:    stringToMatch(op.Principals),
   426  			NotPrincipals: stringToMatch(op.NotPrincipals),
   427  		}
   428  		fromMatches = append(fromMatches, match)
   429  	}
   430  
   431  	rules := []*security.Rules{}
   432  	if len(toMatches) > 0 {
   433  		rules = append(rules, &security.Rules{Matches: toMatches})
   434  	}
   435  	if len(fromMatches) > 0 {
   436  		rules = append(rules, &security.Rules{Matches: fromMatches})
   437  	}
   438  	for _, when := range rule.When {
   439  		l4 := l4WhenAttributes.Contains(when.Key)
   440  		if action == security.Action_ALLOW && !l4 {
   441  			// L7 policies never match for ALLOW
   442  			// For DENY they will always match, so it is more restrictive
   443  			return nil
   444  		}
   445  		positiveMatch := &security.Match{
   446  			Namespaces:       whenMatch("source.namespace", when, false, stringToMatch),
   447  			Principals:       whenMatch("source.principal", when, false, stringToMatch),
   448  			SourceIps:        whenMatch("source.ip", when, false, stringToIP),
   449  			DestinationPorts: whenMatch("destination.port", when, false, stringToPort),
   450  			DestinationIps:   whenMatch("destination.ip", when, false, stringToIP),
   451  
   452  			NotNamespaces:       whenMatch("source.namespace", when, true, stringToMatch),
   453  			NotPrincipals:       whenMatch("source.principal", when, true, stringToMatch),
   454  			NotSourceIps:        whenMatch("source.ip", when, true, stringToIP),
   455  			NotDestinationPorts: whenMatch("destination.port", when, true, stringToPort),
   456  			NotDestinationIps:   whenMatch("destination.ip", when, true, stringToIP),
   457  		}
   458  		rules = append(rules, &security.Rules{Matches: []*security.Match{positiveMatch}})
   459  	}
   460  	return rules
   461  }
   462  
   463  var l4WhenAttributes = sets.New(
   464  	"source.ip",
   465  	"source.namespace",
   466  	"source.principal",
   467  	"destination.ip",
   468  	"destination.port",
   469  )
   470  
   471  func whenMatch[T any](s string, when *v1beta1.Condition, invert bool, f func(v []string) []T) []T {
   472  	if when.Key != s {
   473  		return nil
   474  	}
   475  	if invert {
   476  		return f(when.NotValues)
   477  	}
   478  	return f(when.Values)
   479  }
   480  
   481  func stringToMatch(rules []string) []*security.StringMatch {
   482  	res := make([]*security.StringMatch, 0, len(rules))
   483  	for _, v := range rules {
   484  		var sm *security.StringMatch
   485  		switch {
   486  		case v == "*":
   487  			sm = &security.StringMatch{MatchType: &security.StringMatch_Presence{}}
   488  		case strings.HasPrefix(v, "*"):
   489  			sm = &security.StringMatch{MatchType: &security.StringMatch_Suffix{
   490  				Suffix: strings.TrimPrefix(v, "*"),
   491  			}}
   492  		case strings.HasSuffix(v, "*"):
   493  			sm = &security.StringMatch{MatchType: &security.StringMatch_Prefix{
   494  				Prefix: strings.TrimSuffix(v, "*"),
   495  			}}
   496  		default:
   497  			sm = &security.StringMatch{MatchType: &security.StringMatch_Exact{
   498  				Exact: v,
   499  			}}
   500  		}
   501  		res = append(res, sm)
   502  	}
   503  	return res
   504  }
   505  
   506  func stringToPort(rules []string) []uint32 {
   507  	res := make([]uint32, 0, len(rules))
   508  	for _, m := range rules {
   509  		p, err := strconv.ParseUint(m, 10, 32)
   510  		if err != nil || p > 65535 {
   511  			continue
   512  		}
   513  		res = append(res, uint32(p))
   514  	}
   515  	return res
   516  }
   517  
   518  func stringToIP(rules []string) []*security.Address {
   519  	res := make([]*security.Address, 0, len(rules))
   520  	for _, m := range rules {
   521  		if len(m) == 0 {
   522  			continue
   523  		}
   524  
   525  		var (
   526  			ipAddr        netip.Addr
   527  			maxCidrPrefix uint32
   528  		)
   529  
   530  		if strings.Contains(m, "/") {
   531  			ipp, err := netip.ParsePrefix(m)
   532  			if err != nil {
   533  				continue
   534  			}
   535  			ipAddr = ipp.Addr()
   536  			maxCidrPrefix = uint32(ipp.Bits())
   537  		} else {
   538  			ipa, err := netip.ParseAddr(m)
   539  			if err != nil {
   540  				continue
   541  			}
   542  
   543  			ipAddr = ipa
   544  			maxCidrPrefix = uint32(ipAddr.BitLen())
   545  		}
   546  
   547  		res = append(res, &security.Address{
   548  			Address: ipAddr.AsSlice(),
   549  			Length:  maxCidrPrefix,
   550  		})
   551  	}
   552  	return res
   553  }
   554  
   555  func isMtlsModeUnset(mtls *v1beta1.PeerAuthentication_MutualTLS) bool {
   556  	return mtls == nil || mtls.Mode == v1beta1.PeerAuthentication_MutualTLS_UNSET
   557  }
   558  
   559  func isMtlsModeStrict(mtls *v1beta1.PeerAuthentication_MutualTLS) bool {
   560  	return mtls != nil && mtls.Mode == v1beta1.PeerAuthentication_MutualTLS_STRICT
   561  }
   562  
   563  func isMtlsModeDisable(mtls *v1beta1.PeerAuthentication_MutualTLS) bool {
   564  	return mtls != nil && mtls.Mode == v1beta1.PeerAuthentication_MutualTLS_DISABLE
   565  }
   566  
   567  func isMtlsModePermissive(mtls *v1beta1.PeerAuthentication_MutualTLS) bool {
   568  	return mtls != nil && mtls.Mode == v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE
   569  }