github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/xds/internal/xdsclient/bootstrap/bootstrap.go (about)

     1  /*
     2   *
     3   * Copyright 2019 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  
    19  // Package bootstrap provides the functionality to initialize certain aspects
    20  // of an xDS client by reading a bootstrap file.
    21  package bootstrap
    22  
    23  import (
    24  	"bytes"
    25  	"encoding/json"
    26  	"fmt"
    27  	"io/ioutil"
    28  	"strings"
    29  
    30  	"github.com/golang/protobuf/jsonpb"
    31  	"github.com/golang/protobuf/proto"
    32  	grpc "github.com/hxx258456/ccgo/grpc"
    33  	"github.com/hxx258456/ccgo/grpc/credentials/google"
    34  	"github.com/hxx258456/ccgo/grpc/credentials/insecure"
    35  	"github.com/hxx258456/ccgo/grpc/credentials/tls/certprovider"
    36  	"github.com/hxx258456/ccgo/grpc/internal"
    37  	"github.com/hxx258456/ccgo/grpc/internal/envconfig"
    38  	"github.com/hxx258456/ccgo/grpc/internal/pretty"
    39  	"github.com/hxx258456/ccgo/grpc/xds/internal/xdsclient/xdsresource/version"
    40  
    41  	v2corepb "github.com/hxx258456/ccgo/go-control-plane/envoy/api/v2/core"
    42  	v3corepb "github.com/hxx258456/ccgo/go-control-plane/envoy/config/core/v3"
    43  )
    44  
    45  const (
    46  	// The "server_features" field in the bootstrap file contains a list of
    47  	// features supported by the server. A value of "xds_v3" indicates that the
    48  	// server supports the v3 version of the xDS transport protocol.
    49  	serverFeaturesV3 = "xds_v3"
    50  
    51  	// Type name for Google default credentials.
    52  	credsGoogleDefault              = "google_default"
    53  	credsInsecure                   = "insecure"
    54  	gRPCUserAgentName               = "gRPC Go"
    55  	clientFeatureNoOverprovisioning = "envoy.lb.does_not_support_overprovisioning"
    56  )
    57  
    58  var gRPCVersion = fmt.Sprintf("%s %s", gRPCUserAgentName, grpc.Version)
    59  
    60  // For overriding in unit tests.
    61  var bootstrapFileReadFunc = ioutil.ReadFile
    62  
    63  // ServerConfig contains the configuration to connect to a server, including
    64  // URI, creds, and transport API version (e.g. v2 or v3).
    65  type ServerConfig struct {
    66  	// ServerURI is the management server to connect to.
    67  	//
    68  	// The bootstrap file contains an ordered list of xDS servers to contact for
    69  	// this authority. The first one is picked.
    70  	ServerURI string
    71  	// Creds contains the credentials to be used while talking to the xDS
    72  	// server, as a grpc.DialOption.
    73  	Creds grpc.DialOption
    74  	// CredsType is the type of the creds. It will be used to dedup servers.
    75  	CredsType string
    76  	// TransportAPI indicates the API version of xDS transport protocol to use.
    77  	// This describes the xDS gRPC endpoint and version of
    78  	// DiscoveryRequest/Response used on the wire.
    79  	TransportAPI version.TransportAPI
    80  	// NodeProto contains the Node proto to be used in xDS requests. The actual
    81  	// type depends on the transport protocol version used.
    82  	//
    83  	// Note that it's specified in the bootstrap globally for all the servers,
    84  	// but we keep it in each server config so that its type (e.g. *v2pb.Node or
    85  	// *v3pb.Node) is consistent with the transport API version.
    86  	NodeProto proto.Message
    87  }
    88  
    89  // String returns the string representation of the ServerConfig.
    90  //
    91  // This string representation will be used as map keys in federation
    92  // (`map[ServerConfig]authority`), so that the xDS ClientConn and stream will be
    93  // shared by authorities with different names but the same server config.
    94  //
    95  // It covers (almost) all the fields so the string can represent the config
    96  // content. It doesn't cover NodeProto because NodeProto isn't used by
    97  // federation.
    98  func (sc *ServerConfig) String() string {
    99  	var ver string
   100  	switch sc.TransportAPI {
   101  	case version.TransportV3:
   102  		ver = "xDSv3"
   103  	case version.TransportV2:
   104  		ver = "xDSv2"
   105  	}
   106  	return strings.Join([]string{sc.ServerURI, sc.CredsType, ver}, "-")
   107  }
   108  
   109  // UnmarshalJSON takes the json data (a list of servers) and unmarshals the
   110  // first one in the list.
   111  func (sc *ServerConfig) UnmarshalJSON(data []byte) error {
   112  	var servers []*xdsServer
   113  	if err := json.Unmarshal(data, &servers); err != nil {
   114  		return fmt.Errorf("xds: json.Unmarshal(data) for field xds_servers failed during bootstrap: %v", err)
   115  	}
   116  	if len(servers) < 1 {
   117  		return fmt.Errorf("xds: bootstrap file parsing failed during bootstrap: file doesn't contain any management server to connect to")
   118  	}
   119  	xs := servers[0]
   120  	sc.ServerURI = xs.ServerURI
   121  	for _, cc := range xs.ChannelCreds {
   122  		// We stop at the first credential type that we support.
   123  		sc.CredsType = cc.Type
   124  		if cc.Type == credsGoogleDefault {
   125  			sc.Creds = grpc.WithCredentialsBundle(google.NewDefaultCredentials())
   126  			break
   127  		} else if cc.Type == credsInsecure {
   128  			sc.Creds = grpc.WithTransportCredentials(insecure.NewCredentials())
   129  			break
   130  		}
   131  	}
   132  	for _, f := range xs.ServerFeatures {
   133  		if f == serverFeaturesV3 {
   134  			sc.TransportAPI = version.TransportV3
   135  		}
   136  	}
   137  	return nil
   138  }
   139  
   140  // Authority contains configuration for an Authority for an xDS control plane
   141  // server. See the Authorities field in the Config struct for how it's used.
   142  type Authority struct {
   143  	// ClientListenerResourceNameTemplate is template for the name of the
   144  	// Listener resource to subscribe to for a gRPC client channel.  Used only
   145  	// when the channel is created using an "xds:" URI with this authority name.
   146  	//
   147  	// The token "%s", if present in this string, will be replaced
   148  	// with %-encoded service authority (i.e., the path part of the target
   149  	// URI used to create the gRPC channel).
   150  	//
   151  	// Must start with "xdstp://<authority_name>/".  If it does not,
   152  	// that is considered a bootstrap file parsing error.
   153  	//
   154  	// If not present in the bootstrap file, defaults to
   155  	// "xdstp://<authority_name>/envoy.config.listener.v3.Listener/%s".
   156  	ClientListenerResourceNameTemplate string
   157  	// XDSServer contains the management server and config to connect to for
   158  	// this authority.
   159  	XDSServer *ServerConfig
   160  }
   161  
   162  // UnmarshalJSON implement json unmarshaller.
   163  func (a *Authority) UnmarshalJSON(data []byte) error {
   164  	var jsonData map[string]json.RawMessage
   165  	if err := json.Unmarshal(data, &jsonData); err != nil {
   166  		return fmt.Errorf("xds: failed to parse authority: %v", err)
   167  	}
   168  
   169  	for k, v := range jsonData {
   170  		switch k {
   171  		case "xds_servers":
   172  			if err := json.Unmarshal(v, &a.XDSServer); err != nil {
   173  				return fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err)
   174  			}
   175  		case "client_listener_resource_name_template":
   176  			if err := json.Unmarshal(v, &a.ClientListenerResourceNameTemplate); err != nil {
   177  				return fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err)
   178  			}
   179  		}
   180  	}
   181  	return nil
   182  }
   183  
   184  // Config provides the xDS client with several key bits of information that it
   185  // requires in its interaction with the management server. The Config is
   186  // initialized from the bootstrap file.
   187  type Config struct {
   188  	// XDSServer is the management server to connect to.
   189  	//
   190  	// The bootstrap file contains a list of servers (with name+creds), but we
   191  	// pick the first one.
   192  	XDSServer *ServerConfig
   193  	// CertProviderConfigs contains a mapping from certificate provider plugin
   194  	// instance names to parsed buildable configs.
   195  	CertProviderConfigs map[string]*certprovider.BuildableConfig
   196  	// ServerListenerResourceNameTemplate is a template for the name of the
   197  	// Listener resource to subscribe to for a gRPC server.
   198  	//
   199  	// If starts with "xdstp:", will be interpreted as a new-style name,
   200  	// in which case the authority of the URI will be used to select the
   201  	// relevant configuration in the "authorities" map.
   202  	//
   203  	// The token "%s", if present in this string, will be replaced with the IP
   204  	// and port on which the server is listening.  (e.g., "0.0.0.0:8080",
   205  	// "[::]:8080"). For example, a value of "example/resource/%s" could become
   206  	// "example/resource/0.0.0.0:8080". If the template starts with "xdstp:",
   207  	// the replaced string will be %-encoded.
   208  	//
   209  	// There is no default; if unset, xDS-based server creation fails.
   210  	ServerListenerResourceNameTemplate string
   211  	// A template for the name of the Listener resource to subscribe to
   212  	// for a gRPC client channel.  Used only when the channel is created
   213  	// with an "xds:" URI with no authority.
   214  	//
   215  	// If starts with "xdstp:", will be interpreted as a new-style name,
   216  	// in which case the authority of the URI will be used to select the
   217  	// relevant configuration in the "authorities" map.
   218  	//
   219  	// The token "%s", if present in this string, will be replaced with
   220  	// the service authority (i.e., the path part of the target URI
   221  	// used to create the gRPC channel).  If the template starts with
   222  	// "xdstp:", the replaced string will be %-encoded.
   223  	//
   224  	// Defaults to "%s".
   225  	ClientDefaultListenerResourceNameTemplate string
   226  
   227  	// Authorities is a map of authority name to corresponding configuration.
   228  	//
   229  	// This is used in the following cases:
   230  	// - A gRPC client channel is created using an "xds:" URI that includes
   231  	//   an authority.
   232  	// - A gRPC client channel is created using an "xds:" URI with no
   233  	//   authority, but the "client_default_listener_resource_name_template"
   234  	//   field above turns it into an "xdstp:" URI.
   235  	// - A gRPC server is created and the
   236  	//   "server_listener_resource_name_template" field is an "xdstp:" URI.
   237  	//
   238  	// In any of those cases, it is an error if the specified authority is
   239  	// not present in this map.
   240  	Authorities map[string]*Authority
   241  }
   242  
   243  type channelCreds struct {
   244  	Type   string          `json:"type"`
   245  	Config json.RawMessage `json:"config"`
   246  }
   247  
   248  type xdsServer struct {
   249  	ServerURI      string         `json:"server_uri"`
   250  	ChannelCreds   []channelCreds `json:"channel_creds"`
   251  	ServerFeatures []string       `json:"server_features"`
   252  }
   253  
   254  func bootstrapConfigFromEnvVariable() ([]byte, error) {
   255  	fName := envconfig.XDSBootstrapFileName
   256  	fContent := envconfig.XDSBootstrapFileContent
   257  
   258  	// Bootstrap file name has higher priority than bootstrap content.
   259  	if fName != "" {
   260  		// If file name is set
   261  		// - If file not found (or other errors), fail
   262  		// - Otherwise, use the content.
   263  		//
   264  		// Note that even if the content is invalid, we don't failover to the
   265  		// file content env variable.
   266  		logger.Debugf("xds: using bootstrap file with name %q", fName)
   267  		return bootstrapFileReadFunc(fName)
   268  	}
   269  
   270  	if fContent != "" {
   271  		return []byte(fContent), nil
   272  	}
   273  
   274  	return nil, fmt.Errorf("none of the bootstrap environment variables (%q or %q) defined",
   275  		envconfig.XDSBootstrapFileNameEnv, envconfig.XDSBootstrapFileContentEnv)
   276  }
   277  
   278  // NewConfig returns a new instance of Config initialized by reading the
   279  // bootstrap file found at ${GRPC_XDS_BOOTSTRAP}.
   280  //
   281  // Currently, we support exactly one type of credential, which is
   282  // "google_default", where we use the host's default certs for transport
   283  // credentials and a Google oauth token for call credentials.
   284  //
   285  // This function tries to process as much of the bootstrap file as possible (in
   286  // the presence of the errors) and may return a Config object with certain
   287  // fields left unspecified, in which case the caller should use some sane
   288  // defaults.
   289  func NewConfig() (*Config, error) {
   290  	// Examples of the bootstrap json can be found in the generator tests
   291  	// https://github.com/GoogleCloudPlatform/traffic-director-grpc-bootstrap/blob/master/main_test.go.
   292  	data, err := bootstrapConfigFromEnvVariable()
   293  	if err != nil {
   294  		return nil, fmt.Errorf("xds: Failed to read bootstrap config: %v", err)
   295  	}
   296  	logger.Debugf("Bootstrap content: %s", data)
   297  	return NewConfigFromContents(data)
   298  }
   299  
   300  // NewConfigFromContents returns a new Config using the specified bootstrap
   301  // file contents instead of reading the environment variable.  This is only
   302  // suitable for testing purposes.
   303  func NewConfigFromContents(data []byte) (*Config, error) {
   304  	config := &Config{}
   305  
   306  	var jsonData map[string]json.RawMessage
   307  	if err := json.Unmarshal(data, &jsonData); err != nil {
   308  		return nil, fmt.Errorf("xds: Failed to parse bootstrap config: %v", err)
   309  	}
   310  
   311  	var node *v3corepb.Node
   312  	m := jsonpb.Unmarshaler{AllowUnknownFields: true}
   313  	for k, v := range jsonData {
   314  		switch k {
   315  		case "node":
   316  			// We unconditionally convert the JSON into a v3.Node proto. The v3
   317  			// proto does not contain the deprecated field "build_version" from
   318  			// the v2 proto. We do not expect the bootstrap file to contain the
   319  			// "build_version" field. In any case, the unmarshal will succeed
   320  			// because we have set the `AllowUnknownFields` option on the
   321  			// unmarshaler.
   322  			node = &v3corepb.Node{}
   323  			if err := m.Unmarshal(bytes.NewReader(v), node); err != nil {
   324  				return nil, fmt.Errorf("xds: jsonpb.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err)
   325  			}
   326  		case "xds_servers":
   327  			if err := json.Unmarshal(v, &config.XDSServer); err != nil {
   328  				return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err)
   329  			}
   330  		case "certificate_providers":
   331  			var providerInstances map[string]json.RawMessage
   332  			if err := json.Unmarshal(v, &providerInstances); err != nil {
   333  				return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err)
   334  			}
   335  			configs := make(map[string]*certprovider.BuildableConfig)
   336  			getBuilder := internal.GetCertificateProviderBuilder.(func(string) certprovider.Builder)
   337  			for instance, data := range providerInstances {
   338  				var nameAndConfig struct {
   339  					PluginName string          `json:"plugin_name"`
   340  					Config     json.RawMessage `json:"config"`
   341  				}
   342  				if err := json.Unmarshal(data, &nameAndConfig); err != nil {
   343  					return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), instance, err)
   344  				}
   345  
   346  				name := nameAndConfig.PluginName
   347  				parser := getBuilder(nameAndConfig.PluginName)
   348  				if parser == nil {
   349  					// We ignore plugins that we do not know about.
   350  					continue
   351  				}
   352  				bc, err := parser.ParseConfig(nameAndConfig.Config)
   353  				if err != nil {
   354  					return nil, fmt.Errorf("xds: Config parsing for plugin %q failed: %v", name, err)
   355  				}
   356  				configs[instance] = bc
   357  			}
   358  			config.CertProviderConfigs = configs
   359  		case "server_listener_resource_name_template":
   360  			if err := json.Unmarshal(v, &config.ServerListenerResourceNameTemplate); err != nil {
   361  				return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err)
   362  			}
   363  		case "client_default_listener_resource_name_template":
   364  			if !envconfig.XDSFederation {
   365  				logger.Warningf("xds: bootstrap field %v is not support when Federation is disabled", k)
   366  				continue
   367  			}
   368  			if err := json.Unmarshal(v, &config.ClientDefaultListenerResourceNameTemplate); err != nil {
   369  				return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err)
   370  			}
   371  		case "authorities":
   372  			if !envconfig.XDSFederation {
   373  				logger.Warningf("xds: bootstrap field %v is not support when Federation is disabled", k)
   374  				continue
   375  			}
   376  			if err := json.Unmarshal(v, &config.Authorities); err != nil {
   377  				return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err)
   378  			}
   379  		default:
   380  			logger.Warningf("Bootstrap content has unknown field: %s", k)
   381  		}
   382  		// Do not fail the xDS bootstrap when an unknown field is seen. This can
   383  		// happen when an older version client reads a newer version bootstrap
   384  		// file with new fields.
   385  	}
   386  
   387  	if config.ClientDefaultListenerResourceNameTemplate == "" {
   388  		// Default value of the default client listener name template is "%s".
   389  		config.ClientDefaultListenerResourceNameTemplate = "%s"
   390  	}
   391  	if config.XDSServer == nil {
   392  		return nil, fmt.Errorf("xds: Required field %q not found in bootstrap %s", "xds_servers", jsonData["xds_servers"])
   393  	}
   394  	if config.XDSServer.ServerURI == "" {
   395  		return nil, fmt.Errorf("xds: Required field %q not found in bootstrap %s", "xds_servers.server_uri", jsonData["xds_servers"])
   396  	}
   397  	if config.XDSServer.Creds == nil {
   398  		return nil, fmt.Errorf("xds: Required field %q doesn't contain valid value in bootstrap %s", "xds_servers.channel_creds", jsonData["xds_servers"])
   399  	}
   400  	// Post-process the authorities' client listener resource template field:
   401  	// - if set, it must start with "xdstp://<authority_name>/"
   402  	// - if not set, it defaults to "xdstp://<authority_name>/envoy.config.listener.v3.Listener/%s"
   403  	for name, authority := range config.Authorities {
   404  		prefix := fmt.Sprintf("xdstp://%s", name)
   405  		if authority.ClientListenerResourceNameTemplate == "" {
   406  			authority.ClientListenerResourceNameTemplate = prefix + "/envoy.config.listener.v3.Listener/%s"
   407  			continue
   408  		}
   409  		if !strings.HasPrefix(authority.ClientListenerResourceNameTemplate, prefix) {
   410  			return nil, fmt.Errorf("xds: field ClientListenerResourceNameTemplate %q of authority %q doesn't start with prefix %q", authority.ClientListenerResourceNameTemplate, name, prefix)
   411  		}
   412  	}
   413  
   414  	if err := config.updateNodeProto(node); err != nil {
   415  		return nil, err
   416  	}
   417  	logger.Infof("Bootstrap config for creating xds-client: %v", pretty.ToJSON(config))
   418  	return config, nil
   419  }
   420  
   421  // updateNodeProto updates the node proto read from the bootstrap file.
   422  //
   423  // The input node is a v3.Node protobuf message corresponding to the JSON
   424  // contents found in the bootstrap file. This method performs some post
   425  // processing on it:
   426  // 1. If the node is nil, we create an empty one here. That way, callers of this
   427  // function can always expect that the NodeProto field is non-nil.
   428  // 2. Some additional fields which are not expected to be set in the bootstrap
   429  // file are populated here.
   430  // 3. For each server config (both top level and in each authority), we set its
   431  // node field to the v3.Node, or a v2.Node with the same content, depending on
   432  // the server's transprot API version.
   433  func (c *Config) updateNodeProto(node *v3corepb.Node) error {
   434  	v3 := node
   435  	if v3 == nil {
   436  		v3 = &v3corepb.Node{}
   437  	}
   438  	v3.UserAgentName = gRPCUserAgentName
   439  	v3.UserAgentVersionType = &v3corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version}
   440  	v3.ClientFeatures = append(v3.ClientFeatures, clientFeatureNoOverprovisioning)
   441  
   442  	v2 := &v2corepb.Node{}
   443  	v3bytes, err := proto.Marshal(v3)
   444  	if err != nil {
   445  		return fmt.Errorf("xds: proto.Marshal(%v): %v", v3, err)
   446  	}
   447  	if err := proto.Unmarshal(v3bytes, v2); err != nil {
   448  		return fmt.Errorf("xds: proto.Unmarshal(%v): %v", v3bytes, err)
   449  	}
   450  	// BuildVersion is deprecated, and is replaced by user_agent_name and
   451  	// user_agent_version. But the management servers are still using the old
   452  	// field, so we will keep both set.
   453  	v2.BuildVersion = gRPCVersion
   454  	v2.UserAgentVersionType = &v2corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version}
   455  
   456  	switch c.XDSServer.TransportAPI {
   457  	case version.TransportV2:
   458  		c.XDSServer.NodeProto = v2
   459  	case version.TransportV3:
   460  		c.XDSServer.NodeProto = v3
   461  	}
   462  
   463  	for _, a := range c.Authorities {
   464  		if a.XDSServer == nil {
   465  			continue
   466  		}
   467  		switch a.XDSServer.TransportAPI {
   468  		case version.TransportV2:
   469  			a.XDSServer.NodeProto = v2
   470  		case version.TransportV3:
   471  			a.XDSServer.NodeProto = v3
   472  		}
   473  	}
   474  
   475  	return nil
   476  }