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