istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/networking/core/cluster_tls.go (about) 1 // Copyright Istio Authors. All Rights Reserved. 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 core 16 17 import ( 18 "fmt" 19 20 cluster "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" 21 core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 22 internalupstream "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/internal_upstream/v3" 23 tlsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" 24 http "github.com/envoyproxy/go-control-plane/envoy/extensions/upstreams/http/v3" 25 metadata "github.com/envoyproxy/go-control-plane/envoy/type/metadata/v3" 26 "google.golang.org/protobuf/types/known/structpb" 27 28 "istio.io/api/mesh/v1alpha1" 29 networking "istio.io/api/networking/v1alpha3" 30 "istio.io/istio/pilot/pkg/features" 31 "istio.io/istio/pilot/pkg/model" 32 "istio.io/istio/pilot/pkg/networking/util" 33 sec_model "istio.io/istio/pilot/pkg/security/model" 34 "istio.io/istio/pilot/pkg/serviceregistry/provider" 35 "istio.io/istio/pilot/pkg/util/protoconv" 36 xdsfilters "istio.io/istio/pilot/pkg/xds/filters" 37 "istio.io/istio/pkg/log" 38 "istio.io/istio/pkg/security" 39 "istio.io/istio/pkg/wellknown" 40 ) 41 42 var istioMtlsTransportSocketMatch = &structpb.Struct{ 43 Fields: map[string]*structpb.Value{ 44 model.TLSModeLabelShortname: {Kind: &structpb.Value_StringValue{StringValue: model.IstioMutualTLSModeLabel}}, 45 }, 46 } 47 48 var internalUpstreamSocket = &core.TransportSocket{ 49 Name: "envoy.transport_sockets.internal_upstream", 50 ConfigType: &core.TransportSocket_TypedConfig{TypedConfig: protoconv.MessageToAny(&internalupstream.InternalUpstreamTransport{ 51 PassthroughMetadata: []*internalupstream.InternalUpstreamTransport_MetadataValueSource{ 52 { 53 Kind: &metadata.MetadataKind{Kind: &metadata.MetadataKind_Host_{}}, 54 Name: util.OriginalDstMetadataKey, 55 }, 56 { 57 Kind: &metadata.MetadataKind{Kind: &metadata.MetadataKind_Cluster_{ 58 Cluster: &metadata.MetadataKind_Cluster{}, 59 }}, 60 Name: "istio", 61 }, 62 { 63 Kind: &metadata.MetadataKind{Kind: &metadata.MetadataKind_Host_{ 64 Host: &metadata.MetadataKind_Host{}, 65 }}, 66 Name: "istio", 67 }, 68 }, 69 TransportSocket: xdsfilters.RawBufferTransportSocket, 70 })}, 71 } 72 73 var hboneTransportSocket = &cluster.Cluster_TransportSocketMatch{ 74 Name: "hbone", 75 Match: &structpb.Struct{ 76 Fields: map[string]*structpb.Value{ 77 model.TunnelLabelShortName: {Kind: &structpb.Value_StringValue{StringValue: model.TunnelHTTP}}, 78 }, 79 }, 80 TransportSocket: internalUpstreamSocket, 81 } 82 83 var hboneOrPlaintextSocket = []*cluster.Cluster_TransportSocketMatch{ 84 hboneTransportSocket, 85 defaultTransportSocketMatch(), 86 } 87 88 // applyUpstreamTLSSettings applies upstream tls context to the cluster 89 func (cb *ClusterBuilder) applyUpstreamTLSSettings( 90 opts *buildClusterOpts, 91 tls *networking.ClientTLSSettings, 92 mtlsCtxType mtlsContextType, 93 ) { 94 c := opts.mutable 95 tlsContext, err := cb.buildUpstreamClusterTLSContext(opts, tls) 96 if err != nil { 97 log.Errorf("failed to build Upstream TLSContext: %s", err.Error()) 98 return 99 } 100 101 if tlsContext != nil { 102 c.cluster.TransportSocket = &core.TransportSocket{ 103 Name: wellknown.TransportSocketTLS, 104 ConfigType: &core.TransportSocket_TypedConfig{TypedConfig: protoconv.MessageToAny(tlsContext)}, 105 } 106 } 107 istioAutodetectedMtls := tls != nil && tls.Mode == networking.ClientTLSSettings_ISTIO_MUTUAL && 108 mtlsCtxType == autoDetected 109 if cb.sendHbone { 110 cb.applyHBONETransportSocketMatches(c.cluster, tls, istioAutodetectedMtls) 111 } else if c.cluster.GetType() != cluster.Cluster_ORIGINAL_DST { 112 // For headless service, discovery type will be `Cluster_ORIGINAL_DST` 113 // Apply auto mtls to clusters excluding these kind of headless services. 114 if istioAutodetectedMtls { 115 // convert to transport socket matcher if the mode was auto detected 116 transportSocket := c.cluster.TransportSocket 117 c.cluster.TransportSocket = nil 118 c.cluster.TransportSocketMatches = []*cluster.Cluster_TransportSocketMatch{ 119 { 120 Name: "tlsMode-" + model.IstioMutualTLSModeLabel, 121 Match: istioMtlsTransportSocketMatch, 122 TransportSocket: transportSocket, 123 }, 124 defaultTransportSocketMatch(), 125 } 126 } 127 } 128 } 129 130 func (cb *ClusterBuilder) buildUpstreamClusterTLSContext(opts *buildClusterOpts, tls *networking.ClientTLSSettings) (*tlsv3.UpstreamTlsContext, error) { 131 if tls == nil { 132 return nil, nil 133 } 134 // Hack to avoid egress sds cluster config generation for sidecar when 135 // CredentialName is set in DestinationRule without a workloadSelector. 136 // We do not want to support CredentialName setting in non workloadSelector based DestinationRules, because 137 // that would result in the CredentialName being supplied to all the sidecars which the DestinationRule is scoped to, 138 // resulting in delayed startup of sidecars who do not have access to the credentials. 139 if tls.CredentialName != "" && cb.sidecarProxy() && !opts.isDrWithSelector { 140 if tls.Mode == networking.ClientTLSSettings_SIMPLE || tls.Mode == networking.ClientTLSSettings_MUTUAL { 141 return nil, nil 142 } 143 } 144 145 c := opts.mutable 146 var tlsContext *tlsv3.UpstreamTlsContext 147 148 switch tls.Mode { 149 case networking.ClientTLSSettings_DISABLE: 150 tlsContext = nil 151 case networking.ClientTLSSettings_ISTIO_MUTUAL: 152 tlsContext = &tlsv3.UpstreamTlsContext{ 153 CommonTlsContext: defaultUpstreamCommonTLSContext(), 154 Sni: tls.Sni, 155 } 156 157 tlsContext.CommonTlsContext.TlsCertificateSdsSecretConfigs = append(tlsContext.CommonTlsContext.TlsCertificateSdsSecretConfigs, 158 sec_model.ConstructSdsSecretConfig(sec_model.SDSDefaultResourceName)) 159 160 tlsContext.CommonTlsContext.ValidationContextType = &tlsv3.CommonTlsContext_CombinedValidationContext{ 161 CombinedValidationContext: &tlsv3.CommonTlsContext_CombinedCertificateValidationContext{ 162 DefaultValidationContext: &tlsv3.CertificateValidationContext{MatchSubjectAltNames: util.StringToExactMatch(tls.SubjectAltNames)}, 163 ValidationContextSdsSecretConfig: sec_model.ConstructSdsSecretConfig(sec_model.SDSRootResourceName), 164 }, 165 } 166 // Set default SNI of cluster name for istio_mutual if sni is not set. 167 if len(tlsContext.Sni) == 0 { 168 tlsContext.Sni = c.cluster.Name 169 } 170 // `istio-peer-exchange` alpn is only used when using mtls communication between peers. 171 // We add `istio-peer-exchange` to the list of alpn strings. 172 // The code has repeated snippets because We want to use predefined alpn strings for efficiency. 173 if cb.isHttp2Cluster(c) { 174 // This is HTTP/2 in-mesh cluster, advertise it with ALPN. 175 if features.MetadataExchange && !features.DisableMxALPN { 176 tlsContext.CommonTlsContext.AlpnProtocols = util.ALPNInMeshH2WithMxc 177 } else { 178 tlsContext.CommonTlsContext.AlpnProtocols = util.ALPNInMeshH2 179 } 180 } else { 181 // This is in-mesh cluster, advertise it with ALPN. 182 if features.MetadataExchange && !features.DisableMxALPN { 183 tlsContext.CommonTlsContext.AlpnProtocols = util.ALPNInMeshWithMxc 184 } else { 185 tlsContext.CommonTlsContext.AlpnProtocols = util.ALPNInMesh 186 } 187 } 188 case networking.ClientTLSSettings_SIMPLE: 189 tlsContext = &tlsv3.UpstreamTlsContext{ 190 CommonTlsContext: defaultUpstreamCommonTLSContext(), 191 Sni: tls.Sni, 192 } 193 194 cb.setAutoSniAndAutoSanValidation(c, tls) 195 196 // Use subject alt names specified in service entry if TLS settings does not have subject alt names. 197 if opts.serviceRegistry == provider.External && len(tls.SubjectAltNames) == 0 { 198 tls = tls.DeepCopy() 199 tls.SubjectAltNames = opts.serviceAccounts 200 } 201 if tls.CredentialName != "" { 202 // If credential name is specified at Destination Rule config and originating node is egress gateway, create 203 // SDS config for egress gateway to fetch key/cert at gateway agent. 204 sec_model.ApplyCustomSDSToClientCommonTLSContext(tlsContext.CommonTlsContext, tls, cb.credentialSocketExist) 205 } else { 206 // If CredentialName is not set fallback to files specified in DR. 207 res := security.SdsCertificateConfig{ 208 CaCertificatePath: tls.CaCertificates, 209 } 210 // If tls.CaCertificate or CaCertificate in Metadata isn't configured, or tls.InsecureSkipVerify is true, 211 // don't set up SdsSecretConfig 212 if !res.IsRootCertificate() || tls.GetInsecureSkipVerify().GetValue() { 213 tlsContext.CommonTlsContext.ValidationContextType = &tlsv3.CommonTlsContext_ValidationContext{} 214 } else { 215 defaultValidationContext := &tlsv3.CertificateValidationContext{MatchSubjectAltNames: util.StringToExactMatch(tls.SubjectAltNames)} 216 if tls.GetCaCrl() != "" { 217 defaultValidationContext.Crl = &core.DataSource{ 218 Specifier: &core.DataSource_Filename{ 219 Filename: tls.GetCaCrl(), 220 }, 221 } 222 } 223 tlsContext.CommonTlsContext.ValidationContextType = &tlsv3.CommonTlsContext_CombinedValidationContext{ 224 CombinedValidationContext: &tlsv3.CommonTlsContext_CombinedCertificateValidationContext{ 225 DefaultValidationContext: defaultValidationContext, 226 ValidationContextSdsSecretConfig: sec_model.ConstructSdsSecretConfig(res.GetRootResourceName()), 227 }, 228 } 229 230 } 231 } 232 233 applyTLSDefaults(tlsContext, opts.mesh.GetTlsDefaults()) 234 235 if cb.isHttp2Cluster(c) { 236 // This is HTTP/2 cluster, advertise it with ALPN. 237 tlsContext.CommonTlsContext.AlpnProtocols = util.ALPNH2Only 238 } 239 240 case networking.ClientTLSSettings_MUTUAL: 241 tlsContext = &tlsv3.UpstreamTlsContext{ 242 CommonTlsContext: defaultUpstreamCommonTLSContext(), 243 Sni: tls.Sni, 244 } 245 246 cb.setAutoSniAndAutoSanValidation(c, tls) 247 248 // Use subject alt names specified in service entry if TLS settings does not have subject alt names. 249 if opts.serviceRegistry == provider.External && len(tls.SubjectAltNames) == 0 { 250 tls = tls.DeepCopy() 251 tls.SubjectAltNames = opts.serviceAccounts 252 } 253 if tls.CredentialName != "" { 254 // If credential name is specified at Destination Rule config and originating node is egress gateway, create 255 // SDS config for egress gateway to fetch key/cert at gateway agent. 256 sec_model.ApplyCustomSDSToClientCommonTLSContext(tlsContext.CommonTlsContext, tls, cb.credentialSocketExist) 257 } else { 258 // If CredentialName is not set fallback to file based approach 259 if tls.ClientCertificate == "" || tls.PrivateKey == "" { 260 err := fmt.Errorf("failed to apply tls setting for %s: client certificate and private key must not be empty", 261 c.cluster.Name) 262 return nil, err 263 } 264 // These are certs being mounted from within the pod and specified in Destination Rules. 265 // Rather than reading directly in Envoy, which does not support rotation, we will 266 // serve them over SDS by reading the files. 267 res := security.SdsCertificateConfig{ 268 CertificatePath: tls.ClientCertificate, 269 PrivateKeyPath: tls.PrivateKey, 270 CaCertificatePath: tls.CaCertificates, 271 } 272 tlsContext.CommonTlsContext.TlsCertificateSdsSecretConfigs = append(tlsContext.CommonTlsContext.TlsCertificateSdsSecretConfigs, 273 sec_model.ConstructSdsSecretConfig(res.GetResourceName())) 274 275 // If tls.CaCertificate or CaCertificate in Metadata isn't configured, or tls.InsecureSkipVerify is true, 276 // don't set up SdsSecretConfig 277 if !res.IsRootCertificate() || tls.GetInsecureSkipVerify().GetValue() { 278 tlsContext.CommonTlsContext.ValidationContextType = &tlsv3.CommonTlsContext_ValidationContext{} 279 } else { 280 defaultValidationContext := &tlsv3.CertificateValidationContext{MatchSubjectAltNames: util.StringToExactMatch(tls.SubjectAltNames)} 281 if tls.GetCaCrl() != "" { 282 defaultValidationContext.Crl = &core.DataSource{ 283 Specifier: &core.DataSource_Filename{ 284 Filename: tls.GetCaCrl(), 285 }, 286 } 287 } 288 tlsContext.CommonTlsContext.ValidationContextType = &tlsv3.CommonTlsContext_CombinedValidationContext{ 289 CombinedValidationContext: &tlsv3.CommonTlsContext_CombinedCertificateValidationContext{ 290 DefaultValidationContext: defaultValidationContext, 291 ValidationContextSdsSecretConfig: sec_model.ConstructSdsSecretConfig(res.GetRootResourceName()), 292 }, 293 } 294 } 295 } 296 297 applyTLSDefaults(tlsContext, opts.mesh.GetTlsDefaults()) 298 299 if cb.isHttp2Cluster(c) { 300 // This is HTTP/2 cluster, advertise it with ALPN. 301 tlsContext.CommonTlsContext.AlpnProtocols = util.ALPNH2Only 302 } 303 } 304 305 // Compliance for Envoy TLS upstreams. 306 if tlsContext != nil { 307 sec_model.EnforceCompliance(tlsContext.CommonTlsContext) 308 } 309 return tlsContext, nil 310 } 311 312 // applyTLSDefaults applies tls default settings from mesh config to UpstreamTlsContext. 313 func applyTLSDefaults(tlsContext *tlsv3.UpstreamTlsContext, tlsDefaults *v1alpha1.MeshConfig_TLSConfig) { 314 if tlsDefaults == nil { 315 return 316 } 317 if len(tlsDefaults.EcdhCurves) > 0 { 318 tlsContext.CommonTlsContext.TlsParams.EcdhCurves = tlsDefaults.EcdhCurves 319 } 320 if len(tlsDefaults.CipherSuites) > 0 { 321 tlsContext.CommonTlsContext.TlsParams.CipherSuites = tlsDefaults.CipherSuites 322 } 323 } 324 325 // Set auto_sni if EnableAutoSni feature flag is enabled and if sni field is not explicitly set in DR. 326 // Set auto_san_validation if VerifyCertAtClient feature flag is enabled and if there is no explicit SubjectAltNames specified in DR. 327 func (cb *ClusterBuilder) setAutoSniAndAutoSanValidation(mc *clusterWrapper, tls *networking.ClientTLSSettings) { 328 if mc == nil || !features.EnableAutoSni { 329 return 330 } 331 332 setAutoSni := false 333 setAutoSanValidation := false 334 if len(tls.Sni) == 0 { 335 setAutoSni = true 336 } 337 if features.VerifyCertAtClient && setAutoSni && len(tls.SubjectAltNames) == 0 && !tls.GetInsecureSkipVerify().GetValue() { 338 setAutoSanValidation = true 339 } 340 341 if setAutoSni || setAutoSanValidation { 342 if mc.httpProtocolOptions == nil { 343 mc.httpProtocolOptions = &http.HttpProtocolOptions{} 344 } 345 if mc.httpProtocolOptions.UpstreamHttpProtocolOptions == nil { 346 mc.httpProtocolOptions.UpstreamHttpProtocolOptions = &core.UpstreamHttpProtocolOptions{} 347 } 348 if setAutoSni { 349 mc.httpProtocolOptions.UpstreamHttpProtocolOptions.AutoSni = true 350 } 351 if setAutoSanValidation { 352 mc.httpProtocolOptions.UpstreamHttpProtocolOptions.AutoSanValidation = true 353 } 354 } 355 } 356 357 func (cb *ClusterBuilder) applyHBONETransportSocketMatches(c *cluster.Cluster, tls *networking.ClientTLSSettings, 358 istioAutoDetectedMtls bool, 359 ) { 360 if tls == nil { 361 c.TransportSocketMatches = hboneOrPlaintextSocket 362 return 363 } 364 // For headless service, discovery type will be `Cluster_ORIGINAL_DST` 365 // Apply auto mtls to clusters excluding these kind of headless services. 366 if c.GetType() != cluster.Cluster_ORIGINAL_DST { 367 // convert to transport socket matcher if the mode was auto detected 368 if istioAutoDetectedMtls { 369 transportSocket := c.TransportSocket 370 c.TransportSocket = nil 371 c.TransportSocketMatches = []*cluster.Cluster_TransportSocketMatch{ 372 hboneTransportSocket, 373 { 374 Name: "tlsMode-" + model.IstioMutualTLSModeLabel, 375 Match: istioMtlsTransportSocketMatch, 376 TransportSocket: transportSocket, 377 }, 378 defaultTransportSocketMatch(), 379 } 380 } else { 381 if c.TransportSocket == nil { 382 c.TransportSocketMatches = hboneOrPlaintextSocket 383 } else { 384 ts := c.TransportSocket 385 c.TransportSocket = nil 386 387 c.TransportSocketMatches = []*cluster.Cluster_TransportSocketMatch{ 388 hboneTransportSocket, 389 { 390 Name: "tlsMode-" + model.IstioMutualTLSModeLabel, 391 TransportSocket: ts, 392 }, 393 } 394 } 395 } 396 } 397 } 398 399 func defaultUpstreamCommonTLSContext() *tlsv3.CommonTlsContext { 400 return &tlsv3.CommonTlsContext{ 401 TlsParams: &tlsv3.TlsParameters{ 402 // if not specified, envoy use TLSv1_2 as default for client. 403 TlsMaximumProtocolVersion: tlsv3.TlsParameters_TLSv1_3, 404 TlsMinimumProtocolVersion: tlsv3.TlsParameters_TLSv1_2, 405 }, 406 } 407 } 408 409 // defaultTransportSocketMatch applies to endpoints that have no security.istio.io/tlsMode label 410 // or those whose label value does not match "istio" 411 func defaultTransportSocketMatch() *cluster.Cluster_TransportSocketMatch { 412 return &cluster.Cluster_TransportSocketMatch{ 413 Name: "tlsMode-disabled", 414 Match: &structpb.Struct{}, 415 TransportSocket: xdsfilters.RawBufferTransportSocket, 416 } 417 } 418 419 // buildUpstreamTLSSettings fills key cert fields for all TLSSettings when the mode is `ISTIO_MUTUAL`. 420 // If the (input) TLS setting is nil (i.e not set), *and* the service mTLS mode is STRICT, it also 421 // creates and populates the config as if they are set as ISTIO_MUTUAL. 422 func (cb *ClusterBuilder) buildUpstreamTLSSettings( 423 tls *networking.ClientTLSSettings, 424 serviceAccounts []string, 425 sni string, 426 autoMTLSEnabled bool, 427 meshExternal bool, 428 serviceMTLSMode model.MutualTLSMode, 429 ) (*networking.ClientTLSSettings, mtlsContextType) { 430 if tls != nil { 431 if tls.Mode == networking.ClientTLSSettings_DISABLE || tls.Mode == networking.ClientTLSSettings_SIMPLE { 432 return tls, userSupplied 433 } 434 // For backward compatibility, use metadata certs if provided. 435 if cb.hasMetadataCerts() { 436 // When building Mutual TLS settings, we should always use user supplied SubjectAltNames and SNI 437 // in destination rule. The Service Accounts and auto computed SNI should only be used for 438 // ISTIO_MUTUAL. 439 return cb.buildMutualTLS(tls.SubjectAltNames, tls.Sni), userSupplied 440 } 441 if tls.Mode != networking.ClientTLSSettings_ISTIO_MUTUAL { 442 return tls, userSupplied 443 } 444 // Update TLS settings for ISTIO_MUTUAL. Use client provided SNI if set. Otherwise, 445 // overwrite with the auto generated SNI. User specified SNIs in the istio mtls settings 446 // are useful when routing via gateways. Use Service Accounts if Subject Alt names 447 // are not specified in TLS settings. 448 sniToUse := tls.Sni 449 if len(sniToUse) == 0 { 450 sniToUse = sni 451 } 452 subjectAltNamesToUse := tls.SubjectAltNames 453 if subjectAltNamesToUse == nil { 454 subjectAltNamesToUse = serviceAccounts 455 } 456 return cb.buildIstioMutualTLS(subjectAltNamesToUse, sniToUse), userSupplied 457 } 458 459 if meshExternal || !autoMTLSEnabled || serviceMTLSMode == model.MTLSUnknown || serviceMTLSMode == model.MTLSDisable { 460 return nil, userSupplied 461 } 462 463 // For backward compatibility, use metadata certs if provided. 464 if cb.hasMetadataCerts() { 465 return cb.buildMutualTLS(serviceAccounts, sni), autoDetected 466 } 467 468 // Build settings for auto MTLS. 469 return cb.buildIstioMutualTLS(serviceAccounts, sni), autoDetected 470 } 471 472 func (cb *ClusterBuilder) hasMetadataCerts() bool { 473 return cb.metadataCerts != nil 474 } 475 476 // buildMutualTLS returns a `TLSSettings` for MUTUAL mode with proxy metadata certificates. 477 func (cb *ClusterBuilder) buildMutualTLS(serviceAccounts []string, sni string) *networking.ClientTLSSettings { 478 return &networking.ClientTLSSettings{ 479 Mode: networking.ClientTLSSettings_MUTUAL, 480 CaCertificates: cb.metadataCerts.tlsClientRootCert, 481 ClientCertificate: cb.metadataCerts.tlsClientCertChain, 482 PrivateKey: cb.metadataCerts.tlsClientKey, 483 SubjectAltNames: serviceAccounts, 484 Sni: sni, 485 } 486 } 487 488 // buildIstioMutualTLS returns a `TLSSettings` for ISTIO_MUTUAL mode. 489 func (cb *ClusterBuilder) buildIstioMutualTLS(san []string, sni string) *networking.ClientTLSSettings { 490 return &networking.ClientTLSSettings{ 491 Mode: networking.ClientTLSSettings_ISTIO_MUTUAL, 492 SubjectAltNames: san, 493 Sni: sni, 494 } 495 }