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 }