istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/authentication.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 model
    16  
    17  import (
    18  	"crypto/md5"
    19  	"fmt"
    20  	"strings"
    21  	"time"
    22  
    23  	"istio.io/api/security/v1beta1"
    24  	"istio.io/istio/pkg/config"
    25  	"istio.io/istio/pkg/config/labels"
    26  	"istio.io/istio/pkg/config/schema/gvk"
    27  	"istio.io/istio/pkg/config/schema/kind"
    28  )
    29  
    30  // MutualTLSMode is the mutual TLS mode specified by authentication policy.
    31  type MutualTLSMode int
    32  
    33  const (
    34  	// MTLSUnknown is used to indicate the variable hasn't been initialized correctly (with the authentication policy).
    35  	MTLSUnknown MutualTLSMode = iota
    36  
    37  	// MTLSDisable if authentication policy disable mTLS.
    38  	MTLSDisable
    39  
    40  	// MTLSPermissive if authentication policy enable mTLS in permissive mode.
    41  	MTLSPermissive
    42  
    43  	// MTLSStrict if authentication policy enable mTLS in strict mode.
    44  	MTLSStrict
    45  )
    46  
    47  // In Ambient, we convert k8s PeerAuthentication resources to the same type as AuthorizationPolicies
    48  // To prevent conflicts in xDS, we add this prefix to the converted PeerAuthentication resources.
    49  const convertedPeerAuthenticationPrefix = "converted_peer_authentication_" // use '_' character since those are illegal in k8s names
    50  
    51  // String converts MutualTLSMode to human readable string for debugging.
    52  func (mode MutualTLSMode) String() string {
    53  	switch mode {
    54  	case MTLSDisable:
    55  		return "DISABLE"
    56  	case MTLSPermissive:
    57  		return "PERMISSIVE"
    58  	case MTLSStrict:
    59  		return "STRICT"
    60  	default:
    61  		return "UNKNOWN"
    62  	}
    63  }
    64  
    65  // ConvertToMutualTLSMode converts from peer authn MTLS mode (`PeerAuthentication_MutualTLS_Mode`)
    66  // to the MTLS mode specified by authn policy.
    67  func ConvertToMutualTLSMode(mode v1beta1.PeerAuthentication_MutualTLS_Mode) MutualTLSMode {
    68  	switch mode {
    69  	case v1beta1.PeerAuthentication_MutualTLS_DISABLE:
    70  		return MTLSDisable
    71  	case v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE:
    72  		return MTLSPermissive
    73  	case v1beta1.PeerAuthentication_MutualTLS_STRICT:
    74  		return MTLSStrict
    75  	default:
    76  		return MTLSUnknown
    77  	}
    78  }
    79  
    80  // AuthenticationPolicies organizes authentication (mTLS + JWT) policies by namespace.
    81  type AuthenticationPolicies struct {
    82  	// Maps from namespace to the v1beta1 authentication policies.
    83  	requestAuthentications map[string][]config.Config
    84  
    85  	peerAuthentications map[string][]config.Config
    86  
    87  	// namespaceMutualTLSMode is the MutualTLSMode corresponding to the namespace-level PeerAuthentication.
    88  	// All namespace-level policies, and only them, are added to this map. If the policy mTLS mode is set
    89  	// to UNSET, it will be resolved to the value set by mesh policy if exist (i.e not UNKNOWN), or MTLSPermissive
    90  	// otherwise.
    91  	namespaceMutualTLSMode map[string]MutualTLSMode
    92  
    93  	// globalMutualTLSMode is the MutualTLSMode corresponding to the mesh-level PeerAuthentication.
    94  	// This value can be MTLSUnknown, if there is no mesh-level policy.
    95  	globalMutualTLSMode MutualTLSMode
    96  
    97  	rootNamespace string
    98  
    99  	// aggregateVersion contains the versions of all peer authentications.
   100  	aggregateVersion string
   101  }
   102  
   103  // initAuthenticationPolicies creates a new AuthenticationPolicies struct and populates with the
   104  // authentication policies in the mesh environment.
   105  func initAuthenticationPolicies(env *Environment) *AuthenticationPolicies {
   106  	policy := &AuthenticationPolicies{
   107  		requestAuthentications: map[string][]config.Config{},
   108  		peerAuthentications:    map[string][]config.Config{},
   109  		globalMutualTLSMode:    MTLSUnknown,
   110  		rootNamespace:          env.Mesh().GetRootNamespace(),
   111  	}
   112  
   113  	policy.addRequestAuthentication(sortConfigByCreationTime(env.List(gvk.RequestAuthentication, NamespaceAll)))
   114  	policy.addPeerAuthentication(sortConfigByCreationTime(env.List(gvk.PeerAuthentication, NamespaceAll)))
   115  
   116  	return policy
   117  }
   118  
   119  func (policy *AuthenticationPolicies) addRequestAuthentication(configs []config.Config) {
   120  	for _, config := range configs {
   121  		policy.requestAuthentications[config.Namespace] = append(policy.requestAuthentications[config.Namespace], config)
   122  	}
   123  }
   124  
   125  func (policy *AuthenticationPolicies) addPeerAuthentication(configs []config.Config) {
   126  	// Sort configs in ascending order by their creation time.
   127  	sortConfigByCreationTime(configs)
   128  
   129  	foundNamespaceMTLS := make(map[string]v1beta1.PeerAuthentication_MutualTLS_Mode)
   130  	// Track which namespace/mesh level policy seen so far to make sure the oldest one is used.
   131  	seenNamespaceOrMeshConfig := make(map[string]time.Time)
   132  	versions := []string{}
   133  
   134  	for _, config := range configs {
   135  		versions = append(versions, config.UID+"."+config.ResourceVersion)
   136  		// Mesh & namespace level policy are those that have empty selector.
   137  		spec := config.Spec.(*v1beta1.PeerAuthentication)
   138  		if spec.Selector == nil || len(spec.Selector.MatchLabels) == 0 {
   139  			if t, ok := seenNamespaceOrMeshConfig[config.Namespace]; ok {
   140  				log.Warnf(
   141  					"Namespace/mesh-level PeerAuthentication is already defined for %q at time %v. Ignore %q which was created at time %v",
   142  					config.Namespace, t, config.Name, config.CreationTimestamp)
   143  				continue
   144  			}
   145  			seenNamespaceOrMeshConfig[config.Namespace] = config.CreationTimestamp
   146  
   147  			mode := v1beta1.PeerAuthentication_MutualTLS_UNSET
   148  			if spec.Mtls != nil {
   149  				mode = spec.Mtls.Mode
   150  			}
   151  			if config.Namespace == policy.rootNamespace {
   152  				// This is mesh-level policy. UNSET is treated as permissive for mesh-policy.
   153  				if mode == v1beta1.PeerAuthentication_MutualTLS_UNSET {
   154  					policy.globalMutualTLSMode = MTLSPermissive
   155  				} else {
   156  					policy.globalMutualTLSMode = ConvertToMutualTLSMode(mode)
   157  				}
   158  			} else {
   159  				// For regular namespace, just add to the intermediate map.
   160  				foundNamespaceMTLS[config.Namespace] = mode
   161  			}
   162  		}
   163  
   164  		// Add the config to the map by namespace for future look up. This is done after namespace/mesh
   165  		// singleton check so there should be at most one namespace/mesh config is added to the map.
   166  		policy.peerAuthentications[config.Namespace] = append(policy.peerAuthentications[config.Namespace], config)
   167  	}
   168  
   169  	// nolint: gosec
   170  	// Not security sensitive code
   171  	policy.aggregateVersion = fmt.Sprintf("%x", md5.Sum([]byte(strings.Join(versions, ";"))))
   172  
   173  	// Process found namespace-level policy.
   174  	policy.namespaceMutualTLSMode = make(map[string]MutualTLSMode, len(foundNamespaceMTLS))
   175  
   176  	inheritedMTLSMode := policy.globalMutualTLSMode
   177  	if inheritedMTLSMode == MTLSUnknown {
   178  		// If the mesh policy is not explicitly presented, use default value MTLSPermissive.
   179  		inheritedMTLSMode = MTLSPermissive
   180  	}
   181  	for ns, mtlsMode := range foundNamespaceMTLS {
   182  		if mtlsMode == v1beta1.PeerAuthentication_MutualTLS_UNSET {
   183  			policy.namespaceMutualTLSMode[ns] = inheritedMTLSMode
   184  		} else {
   185  			policy.namespaceMutualTLSMode[ns] = ConvertToMutualTLSMode(mtlsMode)
   186  		}
   187  	}
   188  }
   189  
   190  // GetNamespaceMutualTLSMode returns the MutualTLSMode as defined by a namespace or mesh level
   191  // PeerAuthentication. The return value could be `MTLSUnknown` if there is no mesh nor namespace
   192  // PeerAuthentication policy for the given namespace.
   193  func (policy *AuthenticationPolicies) GetNamespaceMutualTLSMode(namespace string) MutualTLSMode {
   194  	if mode, ok := policy.namespaceMutualTLSMode[namespace]; ok {
   195  		return mode
   196  	}
   197  	return policy.globalMutualTLSMode
   198  }
   199  
   200  // GetJwtPoliciesForWorkload returns a list of JWT policies matching to labels.
   201  func (policy *AuthenticationPolicies) GetJwtPoliciesForWorkload(policyMatcher WorkloadPolicyMatcher) []*config.Config {
   202  	return getConfigsForWorkload(policy.rootNamespace, policy.requestAuthentications, policyMatcher)
   203  }
   204  
   205  // GetPeerAuthenticationsForWorkload returns a list of peer authentication policies matching to labels.
   206  func (policy *AuthenticationPolicies) GetPeerAuthenticationsForWorkload(policyMatcher WorkloadPolicyMatcher) []*config.Config {
   207  	return getConfigsForWorkload(policy.rootNamespace, policy.peerAuthentications, policyMatcher)
   208  }
   209  
   210  // GetRootNamespace return root namespace that is tracked by the policy object.
   211  func (policy *AuthenticationPolicies) GetRootNamespace() string {
   212  	return policy.rootNamespace
   213  }
   214  
   215  // GetVersion return versions of all peer authentications..
   216  func (policy *AuthenticationPolicies) GetVersion() string {
   217  	return policy.aggregateVersion
   218  }
   219  
   220  func GetAmbientPolicyConfigName(key ConfigKey) string {
   221  	switch key.Kind {
   222  	case kind.PeerAuthentication:
   223  		return convertedPeerAuthenticationPrefix + key.Name
   224  	default:
   225  		return key.Name
   226  	}
   227  }
   228  
   229  func getConfigsForWorkload(rootNamespace string, configsByNamespace map[string][]config.Config, selectionOpts WorkloadPolicyMatcher) []*config.Config {
   230  	workloadLabels := selectionOpts.WorkloadLabels
   231  	namespace := selectionOpts.Namespace
   232  	configs := make([]*config.Config, 0)
   233  	var lookupInNamespaces []string
   234  	if namespace != rootNamespace {
   235  		// Only check the root namespace if the (workload) namespace is not already the root namespace
   236  		// to avoid double inclusion.
   237  		lookupInNamespaces = []string{namespace, rootNamespace}
   238  	} else {
   239  		lookupInNamespaces = []string{namespace}
   240  	}
   241  	for _, ns := range lookupInNamespaces {
   242  		if nsConfig, ok := configsByNamespace[ns]; ok {
   243  			for idx := range nsConfig {
   244  				cfg := &nsConfig[idx]
   245  				if ns != cfg.Namespace {
   246  					// Should never come here. Log warning just in case.
   247  					log.Warnf("Seeing config %s with namespace %s in map entry for %s. Ignored", cfg.Name, cfg.Namespace, ns)
   248  					continue
   249  				}
   250  				switch cfg.GroupVersionKind {
   251  				case gvk.RequestAuthentication:
   252  					ra := cfg.Spec.(*v1beta1.RequestAuthentication)
   253  					should := selectionOpts.ShouldAttachPolicy(cfg.GroupVersionKind, cfg.NamespacedName(), ra)
   254  					if should {
   255  						configs = append(configs, cfg)
   256  					}
   257  				case gvk.PeerAuthentication:
   258  					selector := labels.Instance(cfg.Spec.(*v1beta1.PeerAuthentication).GetSelector().GetMatchLabels())
   259  					if selector.SubsetOf(workloadLabels) {
   260  						configs = append(configs, cfg)
   261  					}
   262  				default:
   263  					log.Warnf("Not support authentication type %q", cfg.GroupVersionKind)
   264  					continue
   265  				}
   266  			}
   267  		}
   268  	}
   269  
   270  	return configs
   271  }