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  }