dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/client/resource/unmarshal_cds.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /* 19 * 20 * Copyright 2021 gRPC authors. 21 * 22 */ 23 24 package resource 25 26 import ( 27 "errors" 28 "fmt" 29 "net" 30 "strconv" 31 ) 32 33 import ( 34 dubbogoLogger "github.com/dubbogo/gost/log/logger" 35 36 v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" 37 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 38 v3aggregateclusterpb "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/aggregate/v3" 39 v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" 40 41 "github.com/golang/protobuf/proto" 42 43 "google.golang.org/protobuf/types/known/anypb" 44 ) 45 46 import ( 47 "dubbo.apache.org/dubbo-go/v3/xds/client/resource/version" 48 "dubbo.apache.org/dubbo-go/v3/xds/utils/envconfig" 49 "dubbo.apache.org/dubbo-go/v3/xds/utils/matcher" 50 "dubbo.apache.org/dubbo-go/v3/xds/utils/pretty" 51 ) 52 53 // TransportSocket proto message has a `name` field which is expected to be set 54 // to this value by the management server. 55 const transportSocketName = "envoy.transport_sockets.tls" 56 57 // UnmarshalCluster processes resources received in an CDS response, validates 58 // them, and transforms them into a native struct which contains only fields we 59 // are interested in. 60 func UnmarshalCluster(opts *UnmarshalOptions) (map[string]ClusterUpdateErrTuple, UpdateMetadata, error) { 61 update := make(map[string]ClusterUpdateErrTuple) 62 md, err := processAllResources(opts, update) 63 return update, md, err 64 } 65 66 func unmarshalClusterResource(r *anypb.Any, f UpdateValidatorFunc, logger dubbogoLogger.Logger) (string, ClusterUpdate, error) { 67 if !IsClusterResource(r.GetTypeUrl()) { 68 return "", ClusterUpdate{}, fmt.Errorf("unexpected resource type: %q ", r.GetTypeUrl()) 69 } 70 71 cluster := &v3clusterpb.Cluster{} 72 if err := proto.Unmarshal(r.GetValue(), cluster); err != nil { 73 return "", ClusterUpdate{}, fmt.Errorf("failed to unmarshal resource: %v", err) 74 } 75 dubbogoLogger.Debugf("Resource with name: %v, type: %T, contains: %v", cluster.GetName(), cluster, pretty.ToJSON(cluster)) 76 cu, err := validateClusterAndConstructClusterUpdate(cluster) 77 if err != nil { 78 return cluster.GetName(), ClusterUpdate{}, err 79 } 80 cu.Raw = r 81 if f != nil { 82 if err := f(cu); err != nil { 83 return "", ClusterUpdate{}, err 84 } 85 } 86 87 return cluster.GetName(), cu, nil 88 } 89 90 const ( 91 defaultRingHashMinSize = 1024 92 defaultRingHashMaxSize = 8 * 1024 * 1024 // 8M 93 ringHashSizeUpperBound = 8 * 1024 * 1024 // 8M 94 ) 95 96 func validateClusterAndConstructClusterUpdate(cluster *v3clusterpb.Cluster) (ClusterUpdate, error) { 97 var lbPolicy *ClusterLBPolicyRingHash 98 // todo @(laurence) this direct set 99 cluster.LbPolicy = v3clusterpb.Cluster_ROUND_ROBIN 100 switch cluster.GetLbPolicy() { 101 case v3clusterpb.Cluster_ROUND_ROBIN: 102 lbPolicy = nil // The default is round_robin, and there's no config to set. 103 case v3clusterpb.Cluster_RING_HASH: 104 if !envconfig.XDSRingHash { 105 return ClusterUpdate{}, fmt.Errorf("unexpected lbPolicy %v in response: %+v", cluster.GetLbPolicy(), cluster) 106 } 107 rhc := cluster.GetRingHashLbConfig() 108 if rhc.GetHashFunction() != v3clusterpb.Cluster_RingHashLbConfig_XX_HASH { 109 return ClusterUpdate{}, fmt.Errorf("unsupported ring_hash hash function %v in response: %+v", rhc.GetHashFunction(), cluster) 110 } 111 // Minimum defaults to 1024 entries, and limited to 8M entries Maximum 112 // defaults to 8M entries, and limited to 8M entries 113 var minSize, maxSize uint64 = defaultRingHashMinSize, defaultRingHashMaxSize 114 if min := rhc.GetMinimumRingSize(); min != nil { 115 if min.GetValue() > ringHashSizeUpperBound { 116 return ClusterUpdate{}, fmt.Errorf("unexpected ring_hash mininum ring size %v in response: %+v", min.GetValue(), cluster) 117 } 118 minSize = min.GetValue() 119 } 120 if max := rhc.GetMaximumRingSize(); max != nil { 121 if max.GetValue() > ringHashSizeUpperBound { 122 return ClusterUpdate{}, fmt.Errorf("unexpected ring_hash maxinum ring size %v in response: %+v", max.GetValue(), cluster) 123 } 124 maxSize = max.GetValue() 125 } 126 if minSize > maxSize { 127 return ClusterUpdate{}, fmt.Errorf("ring_hash config min size %v is greater than max %v", minSize, maxSize) 128 } 129 lbPolicy = &ClusterLBPolicyRingHash{MinimumRingSize: minSize, MaximumRingSize: maxSize} 130 default: 131 return ClusterUpdate{}, fmt.Errorf("unexpected lbPolicy %v in response: %+v", cluster.GetLbPolicy(), cluster) 132 } 133 134 // Process security configuration received from the control plane iff the 135 // corresponding environment variable is set. 136 var sc *SecurityConfig 137 if envconfig.XDSClientSideSecurity { 138 var err error 139 if sc, err = securityConfigFromCluster(cluster); err != nil { 140 return ClusterUpdate{}, err 141 } 142 } 143 144 ret := ClusterUpdate{ 145 ClusterName: cluster.GetName(), 146 EnableLRS: cluster.GetLrsServer().GetSelf() != nil, 147 SecurityCfg: sc, 148 MaxRequests: circuitBreakersFromCluster(cluster), 149 LBPolicy: lbPolicy, 150 } 151 152 // Validate and set cluster type from the response. 153 // todo @laurence this set cluster 154 if x, ok := cluster.GetClusterDiscoveryType().(*v3clusterpb.Cluster_Type); ok { 155 x.Type = v3clusterpb.Cluster_EDS 156 } 157 switch { 158 case cluster.GetType() == v3clusterpb.Cluster_EDS: 159 if cluster.GetEdsClusterConfig().GetEdsConfig().GetAds() == nil { 160 return ClusterUpdate{}, fmt.Errorf("unexpected edsConfig in response: %+v", cluster) 161 } 162 ret.ClusterType = ClusterTypeEDS 163 ret.EDSServiceName = cluster.GetEdsClusterConfig().GetServiceName() 164 return ret, nil 165 case cluster.GetType() == v3clusterpb.Cluster_LOGICAL_DNS: 166 if !envconfig.XDSAggregateAndDNS { 167 return ClusterUpdate{}, fmt.Errorf("unsupported cluster type (%v, %v) in response: %+v", cluster.GetType(), cluster.GetClusterType(), cluster) 168 } 169 ret.ClusterType = ClusterTypeLogicalDNS 170 dnsHN, err := dnsHostNameFromCluster(cluster) 171 if err != nil { 172 return ClusterUpdate{}, err 173 } 174 ret.DNSHostName = dnsHN 175 return ret, nil 176 case cluster.GetClusterType() != nil && cluster.GetClusterType().Name == "envoy.clusters.aggregate": 177 if !envconfig.XDSAggregateAndDNS { 178 return ClusterUpdate{}, fmt.Errorf("unsupported cluster type (%v, %v) in response: %+v", cluster.GetType(), cluster.GetClusterType(), cluster) 179 } 180 clusters := &v3aggregateclusterpb.ClusterConfig{} 181 if err := proto.Unmarshal(cluster.GetClusterType().GetTypedConfig().GetValue(), clusters); err != nil { 182 return ClusterUpdate{}, fmt.Errorf("failed to unmarshal resource: %v", err) 183 } 184 ret.ClusterType = ClusterTypeAggregate 185 ret.PrioritizedClusterNames = clusters.Clusters 186 return ret, nil 187 default: 188 return ClusterUpdate{}, fmt.Errorf("unsupported cluster type (%v, %v) in response: %+v", cluster.GetType(), cluster.GetClusterType(), cluster) 189 } 190 } 191 192 // dnsHostNameFromCluster extracts the DNS host name from the cluster's load 193 // assignment. 194 // 195 // There should be exactly one locality, with one endpoint, whose address 196 // contains the address and port. 197 func dnsHostNameFromCluster(cluster *v3clusterpb.Cluster) (string, error) { 198 loadAssignment := cluster.GetLoadAssignment() 199 if loadAssignment == nil { 200 return "", fmt.Errorf("load_assignment not present for LOGICAL_DNS cluster") 201 } 202 if len(loadAssignment.GetEndpoints()) != 1 { 203 return "", fmt.Errorf("load_assignment for LOGICAL_DNS cluster must have exactly one locality, got: %+v", loadAssignment) 204 } 205 endpoints := loadAssignment.GetEndpoints()[0].GetLbEndpoints() 206 if len(endpoints) != 1 { 207 return "", fmt.Errorf("locality for LOGICAL_DNS cluster must have exactly one endpoint, got: %+v", endpoints) 208 } 209 endpoint := endpoints[0].GetEndpoint() 210 if endpoint == nil { 211 return "", fmt.Errorf("endpoint for LOGICAL_DNS cluster not set") 212 } 213 socketAddr := endpoint.GetAddress().GetSocketAddress() 214 if socketAddr == nil { 215 return "", fmt.Errorf("socket address for endpoint for LOGICAL_DNS cluster not set") 216 } 217 if socketAddr.GetResolverName() != "" { 218 return "", fmt.Errorf("socket address for endpoint for LOGICAL_DNS cluster not set has unexpected custom resolver name: %v", socketAddr.GetResolverName()) 219 } 220 host := socketAddr.GetAddress() 221 if host == "" { 222 return "", fmt.Errorf("host for endpoint for LOGICAL_DNS cluster not set") 223 } 224 port := socketAddr.GetPortValue() 225 if port == 0 { 226 return "", fmt.Errorf("port for endpoint for LOGICAL_DNS cluster not set") 227 } 228 return net.JoinHostPort(host, strconv.Itoa(int(port))), nil 229 } 230 231 // securityConfigFromCluster extracts the relevant security configuration from 232 // the received Cluster resource. 233 func securityConfigFromCluster(cluster *v3clusterpb.Cluster) (*SecurityConfig, error) { 234 if tsm := cluster.GetTransportSocketMatches(); len(tsm) != 0 { 235 return nil, fmt.Errorf("unsupport transport_socket_matches field is non-empty: %+v", tsm) 236 } 237 // The Cluster resource contains a `transport_socket` field, which contains 238 // a oneof `typed_config` field of type `protobuf.Any`. The any proto 239 // contains a marshaled representation of an `UpstreamTlsContext` message. 240 ts := cluster.GetTransportSocket() 241 if ts == nil { 242 return nil, nil 243 } 244 if name := ts.GetName(); name != transportSocketName { 245 return nil, fmt.Errorf("transport_socket field has unexpected name: %s", name) 246 } 247 any := ts.GetTypedConfig() 248 if any == nil || any.TypeUrl != version.V3UpstreamTLSContextURL { 249 return nil, fmt.Errorf("transport_socket field has unexpected typeURL: %s", any.TypeUrl) 250 } 251 upstreamCtx := &v3tlspb.UpstreamTlsContext{} 252 if err := proto.Unmarshal(any.GetValue(), upstreamCtx); err != nil { 253 return nil, fmt.Errorf("failed to unmarshal UpstreamTlsContext in CDS response: %v", err) 254 } 255 // The following fields from `UpstreamTlsContext` are ignored: 256 // - sni 257 // - allow_renegotiation 258 // - max_session_keys 259 if upstreamCtx.GetCommonTlsContext() == nil { 260 return nil, errors.New("UpstreamTlsContext in CDS response does not contain a CommonTlsContext") 261 } 262 263 return securityConfigFromCommonTLSContext(upstreamCtx.GetCommonTlsContext(), false) 264 } 265 266 // common is expected to be not nil. 267 // The `alpn_protocols` field is ignored. 268 func securityConfigFromCommonTLSContext(common *v3tlspb.CommonTlsContext, server bool) (*SecurityConfig, error) { 269 if common.GetTlsParams() != nil { 270 return nil, fmt.Errorf("unsupported tls_params field in CommonTlsContext message: %+v", common) 271 } 272 if common.GetCustomHandshaker() != nil { 273 return nil, fmt.Errorf("unsupported custom_handshaker field in CommonTlsContext message: %+v", common) 274 } 275 276 // For now, if we can't get a valid security config from the new fields, we 277 // fallback to the old deprecated fields. 278 // TODO: Drop support for deprecated fields. NACK if err != nil here. 279 sc, _ := securityConfigFromCommonTLSContextUsingNewFields(common, server) 280 if sc == nil || sc.Equal(&SecurityConfig{}) { 281 var err error 282 sc, err = securityConfigFromCommonTLSContextWithDeprecatedFields(common, server) 283 if err != nil { 284 return nil, err 285 } 286 } 287 if sc != nil { 288 // sc == nil is a valid case where the control plane has not sent us any 289 // security configuration. xDS creds will use fallback creds. 290 if server { 291 if sc.IdentityInstanceName == "" { 292 return nil, errors.New("security configuration on the server-side does not contain identity certificate provider instance name") 293 } 294 } else { 295 if sc.RootInstanceName == "" { 296 return nil, errors.New("security configuration on the client-side does not contain root certificate provider instance name") 297 } 298 } 299 } 300 return sc, nil 301 } 302 303 func securityConfigFromCommonTLSContextWithDeprecatedFields(common *v3tlspb.CommonTlsContext, server bool) (*SecurityConfig, error) { 304 // The `CommonTlsContext` contains a 305 // `tls_certificate_certificate_provider_instance` field of type 306 // `CertificateProviderInstance`, which contains the provider instance name 307 // and the certificate name to fetch identity certs. 308 sc := &SecurityConfig{} 309 if identity := common.GetTlsCertificateCertificateProviderInstance(); identity != nil { 310 sc.IdentityInstanceName = identity.GetInstanceName() 311 sc.IdentityCertName = identity.GetCertificateName() 312 } 313 314 // The `CommonTlsContext` contains a `validation_context_type` field which 315 // is a oneof. We can get the values that we are interested in from two of 316 // those possible values: 317 // - combined validation context: 318 // - contains a default validation context which holds the list of 319 // matchers for accepted SANs. 320 // - contains certificate provider instance configuration 321 // - certificate provider instance configuration 322 // - in this case, we do not get a list of accepted SANs. 323 switch t := common.GetValidationContextType().(type) { 324 case *v3tlspb.CommonTlsContext_CombinedValidationContext: 325 combined := common.GetCombinedValidationContext() 326 var matchers []matcher.StringMatcher 327 if def := combined.GetDefaultValidationContext(); def != nil { 328 for _, m := range def.GetMatchSubjectAltNames() { 329 matcher, err := matcher.StringMatcherFromProto(m) 330 if err != nil { 331 return nil, err 332 } 333 matchers = append(matchers, matcher) 334 } 335 } 336 if server && len(matchers) != 0 { 337 return nil, fmt.Errorf("match_subject_alt_names field in validation context is not supported on the server: %v", common) 338 } 339 sc.SubjectAltNameMatchers = matchers 340 if pi := combined.GetValidationContextCertificateProviderInstance(); pi != nil { 341 sc.RootInstanceName = pi.GetInstanceName() 342 sc.RootCertName = pi.GetCertificateName() 343 } 344 case *v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance: 345 pi := common.GetValidationContextCertificateProviderInstance() 346 sc.RootInstanceName = pi.GetInstanceName() 347 sc.RootCertName = pi.GetCertificateName() 348 case nil: 349 // It is valid for the validation context to be nil on the server side. 350 default: 351 return nil, fmt.Errorf("validation context contains unexpected type: %T", t) 352 } 353 return sc, nil 354 } 355 356 // gRFC A29 https://github.com/grpc/proposal/blob/master/A29-xds-tls-security.md 357 // specifies the new way to fetch security configuration and says the following: 358 // 359 // Although there are various ways to obtain certificates as per this proto 360 // (which are supported by Envoy), gRPC supports only one of them and that is 361 // the `CertificateProviderPluginInstance` proto. 362 // 363 // This helper function attempts to fetch security configuration from the 364 // `CertificateProviderPluginInstance` message, given a CommonTlsContext. 365 func securityConfigFromCommonTLSContextUsingNewFields(common *v3tlspb.CommonTlsContext, server bool) (*SecurityConfig, error) { 366 // The `tls_certificate_provider_instance` field of type 367 // `CertificateProviderPluginInstance` is used to fetch the identity 368 // certificate provider. 369 sc := &SecurityConfig{} 370 identity := common.GetTlsCertificateProviderInstance() 371 if identity == nil && len(common.GetTlsCertificates()) != 0 { 372 return nil, fmt.Errorf("expected field tls_certificate_provider_instance is not set, while unsupported field tls_certificates is set in CommonTlsContext message: %+v", common) 373 } 374 if identity == nil && common.GetTlsCertificateSdsSecretConfigs() != nil { 375 return nil, fmt.Errorf("expected field tls_certificate_provider_instance is not set, while unsupported field tls_certificate_sds_secret_configs is set in CommonTlsContext message: %+v", common) 376 } 377 sc.IdentityInstanceName = identity.GetInstanceName() 378 sc.IdentityCertName = identity.GetCertificateName() 379 380 // The `CommonTlsContext` contains a oneof field `validation_context_type`, 381 // which contains the `CertificateValidationContext` message in one of the 382 // following ways: 383 // - `validation_context` field 384 // - this is directly of type `CertificateValidationContext` 385 // - `combined_validation_context` field 386 // - this is of type `CombinedCertificateValidationContext` and contains 387 // a `default validation context` field of type 388 // `CertificateValidationContext` 389 // 390 // The `CertificateValidationContext` message has the following fields that 391 // we are interested in: 392 // - `ca_certificate_provider_instance` 393 // - this is of type `CertificateProviderPluginInstance` 394 // - `match_subject_alt_names` 395 // - this is a list of string matchers 396 // 397 // The `CertificateProviderPluginInstance` message contains two fields 398 // - instance_name 399 // - this is the certificate provider instance name to be looked up in 400 // the bootstrap configuration 401 // - certificate_name 402 // - this is an opaque name passed to the certificate provider 403 var validationCtx *v3tlspb.CertificateValidationContext 404 switch typ := common.GetValidationContextType().(type) { 405 case *v3tlspb.CommonTlsContext_ValidationContext: 406 validationCtx = common.GetValidationContext() 407 case *v3tlspb.CommonTlsContext_CombinedValidationContext: 408 validationCtx = common.GetCombinedValidationContext().GetDefaultValidationContext() 409 case nil: 410 // It is valid for the validation context to be nil on the server side. 411 return sc, nil 412 default: 413 return nil, fmt.Errorf("validation context contains unexpected type: %T", typ) 414 } 415 // If we get here, it means that the `CertificateValidationContext` message 416 // was found through one of the supported ways. It is an error if the 417 // validation context is specified, but it does not contain the 418 // ca_certificate_provider_instance field which contains information about 419 // the certificate provider to be used for the root certificates. 420 if validationCtx.GetCaCertificateProviderInstance() == nil { 421 return nil, fmt.Errorf("expected field ca_certificate_provider_instance is missing in CommonTlsContext message: %+v", common) 422 } 423 // The following fields are ignored: 424 // - trusted_ca 425 // - watched_directory 426 // - allow_expired_certificate 427 // - trust_chain_verification 428 switch { 429 case len(validationCtx.GetVerifyCertificateSpki()) != 0: 430 return nil, fmt.Errorf("unsupported verify_certificate_spki field in CommonTlsContext message: %+v", common) 431 case len(validationCtx.GetVerifyCertificateHash()) != 0: 432 return nil, fmt.Errorf("unsupported verify_certificate_hash field in CommonTlsContext message: %+v", common) 433 case validationCtx.GetRequireSignedCertificateTimestamp().GetValue(): 434 return nil, fmt.Errorf("unsupported require_sugned_ceritificate_timestamp field in CommonTlsContext message: %+v", common) 435 case validationCtx.GetCrl() != nil: 436 return nil, fmt.Errorf("unsupported crl field in CommonTlsContext message: %+v", common) 437 case validationCtx.GetCustomValidatorConfig() != nil: 438 return nil, fmt.Errorf("unsupported custom_validator_config field in CommonTlsContext message: %+v", common) 439 } 440 441 if rootProvider := validationCtx.GetCaCertificateProviderInstance(); rootProvider != nil { 442 sc.RootInstanceName = rootProvider.GetInstanceName() 443 sc.RootCertName = rootProvider.GetCertificateName() 444 } 445 var matchers []matcher.StringMatcher 446 for _, m := range validationCtx.GetMatchSubjectAltNames() { 447 matcher, err := matcher.StringMatcherFromProto(m) 448 if err != nil { 449 return nil, err 450 } 451 matchers = append(matchers, matcher) 452 } 453 if server && len(matchers) != 0 { 454 return nil, fmt.Errorf("match_subject_alt_names field in validation context is not supported on the server: %v", common) 455 } 456 sc.SubjectAltNameMatchers = matchers 457 return sc, nil 458 } 459 460 // circuitBreakersFromCluster extracts the circuit breakers configuration from 461 // the received cluster resource. Returns nil if no CircuitBreakers or no 462 // Thresholds in CircuitBreakers. 463 func circuitBreakersFromCluster(cluster *v3clusterpb.Cluster) *uint32 { 464 for _, threshold := range cluster.GetCircuitBreakers().GetThresholds() { 465 if threshold.GetPriority() != v3corepb.RoutingPriority_DEFAULT { 466 continue 467 } 468 maxRequestsPb := threshold.GetMaxRequests() 469 if maxRequestsPb == nil { 470 return nil 471 } 472 maxRequests := maxRequestsPb.GetValue() 473 return &maxRequests 474 } 475 return nil 476 }