google.golang.org/grpc@v1.74.2/internal/xds/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  	"maps"
    28  	"net/url"
    29  	"os"
    30  	"slices"
    31  	"strings"
    32  
    33  	"google.golang.org/grpc"
    34  	"google.golang.org/grpc/credentials/tls/certprovider"
    35  	"google.golang.org/grpc/internal"
    36  	"google.golang.org/grpc/internal/envconfig"
    37  	"google.golang.org/grpc/xds/bootstrap"
    38  	"google.golang.org/protobuf/proto"
    39  	"google.golang.org/protobuf/types/known/structpb"
    40  
    41  	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    42  )
    43  
    44  const (
    45  	serverFeaturesIgnoreResourceDeletion = "ignore_resource_deletion"
    46  	gRPCUserAgentName                    = "gRPC Go"
    47  	clientFeatureNoOverprovisioning      = "envoy.lb.does_not_support_overprovisioning"
    48  	clientFeatureResourceWrapper         = "xds.config.resource-in-sotw"
    49  )
    50  
    51  // For overriding in unit tests.
    52  var bootstrapFileReadFunc = os.ReadFile
    53  
    54  // ChannelCreds contains the credentials to be used while communicating with an
    55  // xDS server. It is also used to dedup servers with the same server URI.
    56  //
    57  // This type does not implement custom JSON marshal/unmarshal logic because it
    58  // is straightforward to accomplish the same with json struct tags.
    59  type ChannelCreds struct {
    60  	// Type contains a unique name identifying the credentials type. The only
    61  	// supported types currently are "google_default" and "insecure".
    62  	Type string `json:"type,omitempty"`
    63  	// Config contains the JSON configuration associated with the credentials.
    64  	Config json.RawMessage `json:"config,omitempty"`
    65  }
    66  
    67  // Equal reports whether cc and other are considered equal.
    68  func (cc ChannelCreds) Equal(other ChannelCreds) bool {
    69  	return cc.Type == other.Type && bytes.Equal(cc.Config, other.Config)
    70  }
    71  
    72  // String returns a string representation of the credentials. It contains the
    73  // type and the config (if non-nil) separated by a "-".
    74  func (cc ChannelCreds) String() string {
    75  	if cc.Config == nil {
    76  		return cc.Type
    77  	}
    78  
    79  	// We do not expect the Marshal call to fail since we wrote to cc.Config
    80  	// after a successful unmarshalling from JSON configuration. Therefore,
    81  	// it is safe to ignore the error here.
    82  	b, _ := json.Marshal(cc.Config)
    83  	return cc.Type + "-" + string(b)
    84  }
    85  
    86  // ServerConfigs represents a collection of server configurations.
    87  type ServerConfigs []*ServerConfig
    88  
    89  // Equal returns true if scs equals other.
    90  func (scs *ServerConfigs) Equal(other *ServerConfigs) bool {
    91  	if len(*scs) != len(*other) {
    92  		return false
    93  	}
    94  	for i := range *scs {
    95  		if !(*scs)[i].Equal((*other)[i]) {
    96  			return false
    97  		}
    98  	}
    99  	return true
   100  }
   101  
   102  // UnmarshalJSON takes the json data (a list of server configurations) and
   103  // unmarshals it to the struct.
   104  func (scs *ServerConfigs) UnmarshalJSON(data []byte) error {
   105  	servers := []*ServerConfig{}
   106  	if err := json.Unmarshal(data, &servers); err != nil {
   107  		return fmt.Errorf("xds: failed to JSON unmarshal server configurations during bootstrap: %v, config:\n%s", err, string(data))
   108  	}
   109  	// Only use the first server config if fallback support is disabled.
   110  	if !envconfig.XDSFallbackSupport {
   111  		if len(servers) > 1 {
   112  			servers = servers[:1]
   113  		}
   114  	}
   115  	*scs = servers
   116  	return nil
   117  }
   118  
   119  // String returns a string representation of the ServerConfigs, by concatenating
   120  // the string representations of the underlying server configs.
   121  func (scs *ServerConfigs) String() string {
   122  	ret := ""
   123  	for i, sc := range *scs {
   124  		if i > 0 {
   125  			ret += ", "
   126  		}
   127  		ret += sc.String()
   128  	}
   129  	return ret
   130  }
   131  
   132  // Authority contains configuration for an xDS control plane authority.
   133  //
   134  // This type does not implement custom JSON marshal/unmarshal logic because it
   135  // is straightforward to accomplish the same with json struct tags.
   136  type Authority struct {
   137  	// ClientListenerResourceNameTemplate is template for the name of the
   138  	// Listener resource to subscribe to for a gRPC client channel.  Used only
   139  	// when the channel is created using an "xds:" URI with this authority name.
   140  	//
   141  	// The token "%s", if present in this string, will be replaced
   142  	// with %-encoded service authority (i.e., the path part of the target
   143  	// URI used to create the gRPC channel).
   144  	//
   145  	// Must start with "xdstp://<authority_name>/".  If it does not,
   146  	// that is considered a bootstrap file parsing error.
   147  	//
   148  	// If not present in the bootstrap file, defaults to
   149  	// "xdstp://<authority_name>/envoy.config.listener.v3.Listener/%s".
   150  	ClientListenerResourceNameTemplate string `json:"client_listener_resource_name_template,omitempty"`
   151  	// XDSServers contains the list of server configurations for this authority.
   152  	XDSServers ServerConfigs `json:"xds_servers,omitempty"`
   153  }
   154  
   155  // Equal returns true if a equals other.
   156  func (a *Authority) Equal(other *Authority) bool {
   157  	switch {
   158  	case a == nil && other == nil:
   159  		return true
   160  	case (a != nil) != (other != nil):
   161  		return false
   162  	case a.ClientListenerResourceNameTemplate != other.ClientListenerResourceNameTemplate:
   163  		return false
   164  	case !a.XDSServers.Equal(&other.XDSServers):
   165  		return false
   166  	}
   167  	return true
   168  }
   169  
   170  // ServerConfig contains the configuration to connect to a server.
   171  type ServerConfig struct {
   172  	serverURI      string
   173  	channelCreds   []ChannelCreds
   174  	serverFeatures []string
   175  
   176  	// As part of unmarshalling the JSON config into this struct, we ensure that
   177  	// the credentials config is valid by building an instance of the specified
   178  	// credentials and store it here for easy access.
   179  	selectedCreds    ChannelCreds
   180  	credsDialOption  grpc.DialOption
   181  	extraDialOptions []grpc.DialOption
   182  
   183  	cleanups []func()
   184  }
   185  
   186  // ServerURI returns the URI of the management server to connect to.
   187  func (sc *ServerConfig) ServerURI() string {
   188  	return sc.serverURI
   189  }
   190  
   191  // ChannelCreds returns the credentials configuration to use when communicating
   192  // with this server. Also used to dedup servers with the same server URI.
   193  func (sc *ServerConfig) ChannelCreds() []ChannelCreds {
   194  	return sc.channelCreds
   195  }
   196  
   197  // ServerFeatures returns the list of features supported by this server. Also
   198  // used to dedup servers with the same server URI and channel creds.
   199  func (sc *ServerConfig) ServerFeatures() []string {
   200  	return sc.serverFeatures
   201  }
   202  
   203  // ServerFeaturesIgnoreResourceDeletion returns true if this server supports a
   204  // feature where the xDS client can ignore resource deletions from this server,
   205  // as described in gRFC A53.
   206  //
   207  // This feature controls the behavior of the xDS client when the server deletes
   208  // a previously sent Listener or Cluster resource. If set, the xDS client will
   209  // not invoke the watchers' ResourceError() method when a resource is
   210  // deleted, nor will it remove the existing resource value from its cache.
   211  func (sc *ServerConfig) ServerFeaturesIgnoreResourceDeletion() bool {
   212  	for _, sf := range sc.serverFeatures {
   213  		if sf == serverFeaturesIgnoreResourceDeletion {
   214  			return true
   215  		}
   216  	}
   217  	return false
   218  }
   219  
   220  // SelectedCreds returns the selected credentials configuration for
   221  // communicating with this server.
   222  func (sc *ServerConfig) SelectedCreds() ChannelCreds {
   223  	return sc.selectedCreds
   224  }
   225  
   226  // DialOptions returns a slice of all the configured dial options for this
   227  // server except grpc.WithCredentialsBundle().
   228  func (sc *ServerConfig) DialOptions() []grpc.DialOption {
   229  	var dopts []grpc.DialOption
   230  	if sc.extraDialOptions != nil {
   231  		dopts = append(dopts, sc.extraDialOptions...)
   232  	}
   233  	return dopts
   234  }
   235  
   236  // Cleanups returns a collection of functions to be called when the xDS client
   237  // for this server is closed. Allows cleaning up resources created specifically
   238  // for this server.
   239  func (sc *ServerConfig) Cleanups() []func() {
   240  	return sc.cleanups
   241  }
   242  
   243  // Equal reports whether sc and other are considered equal.
   244  func (sc *ServerConfig) Equal(other *ServerConfig) bool {
   245  	switch {
   246  	case sc == nil && other == nil:
   247  		return true
   248  	case (sc != nil) != (other != nil):
   249  		return false
   250  	case sc.serverURI != other.serverURI:
   251  		return false
   252  	case !slices.EqualFunc(sc.channelCreds, other.channelCreds, func(a, b ChannelCreds) bool { return a.Equal(b) }):
   253  		return false
   254  	case !slices.Equal(sc.serverFeatures, other.serverFeatures):
   255  		return false
   256  	case !sc.selectedCreds.Equal(other.selectedCreds):
   257  		return false
   258  	}
   259  	return true
   260  }
   261  
   262  // String returns the string representation of the ServerConfig.
   263  func (sc *ServerConfig) String() string {
   264  	if len(sc.serverFeatures) == 0 {
   265  		return fmt.Sprintf("%s-%s", sc.serverURI, sc.selectedCreds.String())
   266  	}
   267  	features := strings.Join(sc.serverFeatures, "-")
   268  	return strings.Join([]string{sc.serverURI, sc.selectedCreds.String(), features}, "-")
   269  }
   270  
   271  // The following fields correspond 1:1 with the JSON schema for ServerConfig.
   272  type serverConfigJSON struct {
   273  	ServerURI      string         `json:"server_uri,omitempty"`
   274  	ChannelCreds   []ChannelCreds `json:"channel_creds,omitempty"`
   275  	ServerFeatures []string       `json:"server_features,omitempty"`
   276  }
   277  
   278  // MarshalJSON returns marshaled JSON bytes corresponding to this server config.
   279  func (sc *ServerConfig) MarshalJSON() ([]byte, error) {
   280  	server := &serverConfigJSON{
   281  		ServerURI:      sc.serverURI,
   282  		ChannelCreds:   sc.channelCreds,
   283  		ServerFeatures: sc.serverFeatures,
   284  	}
   285  	return json.Marshal(server)
   286  }
   287  
   288  // extraDialOptions captures custom dial options specified via
   289  // credentials.Bundle.
   290  type extraDialOptions interface {
   291  	DialOptions() []grpc.DialOption
   292  }
   293  
   294  // UnmarshalJSON takes the json data (a server) and unmarshals it to the struct.
   295  func (sc *ServerConfig) UnmarshalJSON(data []byte) error {
   296  	server := serverConfigJSON{}
   297  	if err := json.Unmarshal(data, &server); err != nil {
   298  		return fmt.Errorf("xds: failed to JSON unmarshal server configuration during bootstrap: %v, config:\n%s", err, string(data))
   299  	}
   300  
   301  	sc.serverURI = server.ServerURI
   302  	sc.channelCreds = server.ChannelCreds
   303  	sc.serverFeatures = server.ServerFeatures
   304  
   305  	for _, cc := range server.ChannelCreds {
   306  		// We stop at the first credential type that we support.
   307  		c := bootstrap.GetCredentials(cc.Type)
   308  		if c == nil {
   309  			continue
   310  		}
   311  		bundle, cancel, err := c.Build(cc.Config)
   312  		if err != nil {
   313  			return fmt.Errorf("failed to build credentials bundle from bootstrap for %q: %v", cc.Type, err)
   314  		}
   315  		sc.selectedCreds = cc
   316  		sc.credsDialOption = grpc.WithCredentialsBundle(bundle)
   317  		if d, ok := bundle.(extraDialOptions); ok {
   318  			sc.extraDialOptions = d.DialOptions()
   319  		}
   320  		sc.cleanups = append(sc.cleanups, cancel)
   321  		break
   322  	}
   323  	if sc.serverURI == "" {
   324  		return fmt.Errorf("xds: `server_uri` field in server config cannot be empty: %s", string(data))
   325  	}
   326  	if sc.credsDialOption == nil {
   327  		return fmt.Errorf("xds: `channel_creds` field in server config cannot be empty: %s", string(data))
   328  	}
   329  	return nil
   330  }
   331  
   332  // ServerConfigTestingOptions specifies options for creating a new ServerConfig
   333  // for testing purposes.
   334  //
   335  // # Testing-Only
   336  type ServerConfigTestingOptions struct {
   337  	// URI is the name of the server corresponding to this server config.
   338  	URI string
   339  	// ChannelCreds contains a list of channel credentials to use when talking
   340  	// to this server. If unspecified, `insecure` credentials will be used.
   341  	ChannelCreds []ChannelCreds
   342  	// ServerFeatures represents the list of features supported by this server.
   343  	ServerFeatures []string
   344  }
   345  
   346  // ServerConfigForTesting creates a new ServerConfig from the passed in options,
   347  // for testing purposes.
   348  //
   349  // # Testing-Only
   350  func ServerConfigForTesting(opts ServerConfigTestingOptions) (*ServerConfig, error) {
   351  	cc := opts.ChannelCreds
   352  	if cc == nil {
   353  		cc = []ChannelCreds{{Type: "insecure"}}
   354  	}
   355  	scInternal := &serverConfigJSON{
   356  		ServerURI:      opts.URI,
   357  		ChannelCreds:   cc,
   358  		ServerFeatures: opts.ServerFeatures,
   359  	}
   360  	scJSON, err := json.Marshal(scInternal)
   361  	if err != nil {
   362  		return nil, err
   363  	}
   364  
   365  	sc := new(ServerConfig)
   366  	if err := sc.UnmarshalJSON(scJSON); err != nil {
   367  		return nil, err
   368  	}
   369  	return sc, nil
   370  }
   371  
   372  // Config is the internal representation of the bootstrap configuration provided
   373  // to the xDS client.
   374  type Config struct {
   375  	xDSServers                                ServerConfigs
   376  	cpcs                                      map[string]certproviderNameAndConfig
   377  	serverListenerResourceNameTemplate        string
   378  	clientDefaultListenerResourceNameTemplate string
   379  	authorities                               map[string]*Authority
   380  	node                                      node
   381  
   382  	// A map from certprovider instance names to parsed buildable configs.
   383  	certProviderConfigs map[string]*certprovider.BuildableConfig
   384  }
   385  
   386  // XDSServers returns the top-level list of management servers to connect to,
   387  // ordered by priority.
   388  func (c *Config) XDSServers() ServerConfigs {
   389  	return c.xDSServers
   390  }
   391  
   392  // CertProviderConfigs returns a map from certificate provider plugin instance
   393  // name to their configuration. Callers must not modify the returned map.
   394  func (c *Config) CertProviderConfigs() map[string]*certprovider.BuildableConfig {
   395  	return c.certProviderConfigs
   396  }
   397  
   398  // ServerListenerResourceNameTemplate returns template for the name of the
   399  // Listener resource to subscribe to for a gRPC server.
   400  //
   401  // If starts with "xdstp:", will be interpreted as a new-style name,
   402  // in which case the authority of the URI will be used to select the
   403  // relevant configuration in the "authorities" map.
   404  //
   405  // The token "%s", if present in this string, will be replaced with the IP
   406  // and port on which the server is listening.  (e.g., "0.0.0.0:8080",
   407  // "[::]:8080"). For example, a value of "example/resource/%s" could become
   408  // "example/resource/0.0.0.0:8080". If the template starts with "xdstp:",
   409  // the replaced string will be %-encoded.
   410  //
   411  // There is no default; if unset, xDS-based server creation fails.
   412  func (c *Config) ServerListenerResourceNameTemplate() string {
   413  	return c.serverListenerResourceNameTemplate
   414  }
   415  
   416  // ClientDefaultListenerResourceNameTemplate returns a template for the name of
   417  // the Listener resource to subscribe to for a gRPC client channel.  Used only
   418  // when the channel is created with an "xds:" URI with no authority.
   419  //
   420  // If starts with "xdstp:", will be interpreted as a new-style name,
   421  // in which case the authority of the URI will be used to select the
   422  // relevant configuration in the "authorities" map.
   423  //
   424  // The token "%s", if present in this string, will be replaced with
   425  // the service authority (i.e., the path part of the target URI
   426  // used to create the gRPC channel).  If the template starts with
   427  // "xdstp:", the replaced string will be %-encoded.
   428  //
   429  // Defaults to "%s".
   430  func (c *Config) ClientDefaultListenerResourceNameTemplate() string {
   431  	return c.clientDefaultListenerResourceNameTemplate
   432  }
   433  
   434  // Authorities returns a map of authority name to corresponding configuration.
   435  // Callers must not modify the returned map.
   436  //
   437  // This is used in the following cases:
   438  //   - A gRPC client channel is created using an "xds:" URI that includes
   439  //     an authority.
   440  //   - A gRPC client channel is created using an "xds:" URI with no
   441  //     authority, but the "client_default_listener_resource_name_template"
   442  //     field above turns it into an "xdstp:" URI.
   443  //   - A gRPC server is created and the
   444  //     "server_listener_resource_name_template" field is an "xdstp:" URI.
   445  //
   446  // In any of those cases, it is an error if the specified authority is
   447  // not present in this map.
   448  func (c *Config) Authorities() map[string]*Authority {
   449  	return c.authorities
   450  }
   451  
   452  // Node returns xDS a v3 Node proto corresponding to the node field in the
   453  // bootstrap configuration, which identifies a specific gRPC instance.
   454  func (c *Config) Node() *v3corepb.Node {
   455  	return c.node.toProto()
   456  }
   457  
   458  // Equal returns true if c equals other.
   459  func (c *Config) Equal(other *Config) bool {
   460  	switch {
   461  	case c == nil && other == nil:
   462  		return true
   463  	case (c != nil) != (other != nil):
   464  		return false
   465  	case !c.xDSServers.Equal(&other.xDSServers):
   466  		return false
   467  	case !maps.EqualFunc(c.certProviderConfigs, other.certProviderConfigs, func(a, b *certprovider.BuildableConfig) bool { return a.String() == b.String() }):
   468  		return false
   469  	case c.serverListenerResourceNameTemplate != other.serverListenerResourceNameTemplate:
   470  		return false
   471  	case c.clientDefaultListenerResourceNameTemplate != other.clientDefaultListenerResourceNameTemplate:
   472  		return false
   473  	case !maps.EqualFunc(c.authorities, other.authorities, func(a, b *Authority) bool { return a.Equal(b) }):
   474  		return false
   475  	case !c.node.Equal(other.node):
   476  		return false
   477  	}
   478  	return true
   479  }
   480  
   481  // String returns a string representation of the Config.
   482  func (c *Config) String() string {
   483  	s, _ := c.MarshalJSON()
   484  	return string(s)
   485  }
   486  
   487  // The following fields correspond 1:1 with the JSON schema for Config.
   488  type configJSON struct {
   489  	XDSServers                                ServerConfigs                        `json:"xds_servers,omitempty"`
   490  	CertificateProviders                      map[string]certproviderNameAndConfig `json:"certificate_providers,omitempty"`
   491  	ServerListenerResourceNameTemplate        string                               `json:"server_listener_resource_name_template,omitempty"`
   492  	ClientDefaultListenerResourceNameTemplate string                               `json:"client_default_listener_resource_name_template,omitempty"`
   493  	Authorities                               map[string]*Authority                `json:"authorities,omitempty"`
   494  	Node                                      node                                 `json:"node,omitempty"`
   495  }
   496  
   497  // MarshalJSON returns marshaled JSON bytes corresponding to this config.
   498  func (c *Config) MarshalJSON() ([]byte, error) {
   499  	config := &configJSON{
   500  		XDSServers:                                c.xDSServers,
   501  		CertificateProviders:                      c.cpcs,
   502  		ServerListenerResourceNameTemplate:        c.serverListenerResourceNameTemplate,
   503  		ClientDefaultListenerResourceNameTemplate: c.clientDefaultListenerResourceNameTemplate,
   504  		Authorities:                               c.authorities,
   505  		Node:                                      c.node,
   506  	}
   507  	return json.MarshalIndent(config, " ", " ")
   508  }
   509  
   510  // UnmarshalJSON takes the json data (the complete bootstrap configuration) and
   511  // unmarshals it to the struct.
   512  func (c *Config) UnmarshalJSON(data []byte) error {
   513  	// Initialize the node field with client controlled values. This ensures
   514  	// even if the bootstrap configuration did not contain the node field, we
   515  	// will have a node field with client controlled fields alone.
   516  	config := configJSON{Node: newNode()}
   517  	if err := json.Unmarshal(data, &config); err != nil {
   518  		return fmt.Errorf("xds: json.Unmarshal(%s) failed during bootstrap: %v", string(data), err)
   519  	}
   520  
   521  	c.xDSServers = config.XDSServers
   522  	c.cpcs = config.CertificateProviders
   523  	c.serverListenerResourceNameTemplate = config.ServerListenerResourceNameTemplate
   524  	c.clientDefaultListenerResourceNameTemplate = config.ClientDefaultListenerResourceNameTemplate
   525  	c.authorities = config.Authorities
   526  	c.node = config.Node
   527  
   528  	// Build the certificate providers configuration to ensure that it is valid.
   529  	cpcCfgs := make(map[string]*certprovider.BuildableConfig)
   530  	getBuilder := internal.GetCertificateProviderBuilder.(func(string) certprovider.Builder)
   531  	for instance, nameAndConfig := range c.cpcs {
   532  		name := nameAndConfig.PluginName
   533  		parser := getBuilder(nameAndConfig.PluginName)
   534  		if parser == nil {
   535  			// We ignore plugins that we do not know about.
   536  			continue
   537  		}
   538  		bc, err := parser.ParseConfig(nameAndConfig.Config)
   539  		if err != nil {
   540  			return fmt.Errorf("xds: config parsing for certificate provider plugin %q failed during bootstrap: %v", name, err)
   541  		}
   542  		cpcCfgs[instance] = bc
   543  	}
   544  	c.certProviderConfigs = cpcCfgs
   545  
   546  	// Default value of the default client listener name template is "%s".
   547  	if c.clientDefaultListenerResourceNameTemplate == "" {
   548  		c.clientDefaultListenerResourceNameTemplate = "%s"
   549  	}
   550  	if len(c.xDSServers) == 0 {
   551  		return fmt.Errorf("xds: required field `xds_servers` not found in bootstrap configuration: %s", string(data))
   552  	}
   553  
   554  	// Post-process the authorities' client listener resource template field:
   555  	// - if set, it must start with "xdstp://<authority_name>/"
   556  	// - if not set, it defaults to "xdstp://<authority_name>/envoy.config.listener.v3.Listener/%s"
   557  	for name, authority := range c.authorities {
   558  		prefix := fmt.Sprintf("xdstp://%s", url.PathEscape(name))
   559  		if authority.ClientListenerResourceNameTemplate == "" {
   560  			authority.ClientListenerResourceNameTemplate = prefix + "/envoy.config.listener.v3.Listener/%s"
   561  			continue
   562  		}
   563  		if !strings.HasPrefix(authority.ClientListenerResourceNameTemplate, prefix) {
   564  			return fmt.Errorf("xds: field clientListenerResourceNameTemplate %q of authority %q doesn't start with prefix %q", authority.ClientListenerResourceNameTemplate, name, prefix)
   565  		}
   566  	}
   567  	return nil
   568  }
   569  
   570  // GetConfiguration returns the bootstrap configuration initialized by reading
   571  // the bootstrap file found at ${GRPC_XDS_BOOTSTRAP} or bootstrap contents
   572  // specified at ${GRPC_XDS_BOOTSTRAP_CONFIG}. If both env vars are set, the
   573  // former is preferred.
   574  //
   575  // This function tries to process as much of the bootstrap file as possible (in
   576  // the presence of the errors) and may return a Config object with certain
   577  // fields left unspecified, in which case the caller should use some sane
   578  // defaults.
   579  //
   580  // This function returns an error if it's unable to parse the contents of the
   581  // bootstrap config. It returns (nil, nil) if none of the env vars are set.
   582  func GetConfiguration() (*Config, error) {
   583  	fName := envconfig.XDSBootstrapFileName
   584  	fContent := envconfig.XDSBootstrapFileContent
   585  
   586  	if fName != "" {
   587  		if logger.V(2) {
   588  			logger.Infof("Using bootstrap file with name %q from GRPC_XDS_BOOTSTRAP environment variable", fName)
   589  		}
   590  		cfg, err := bootstrapFileReadFunc(fName)
   591  		if err != nil {
   592  			return nil, fmt.Errorf("xds: failed to read bootstrap config from file %q: %v", fName, err)
   593  		}
   594  		return NewConfigFromContents(cfg)
   595  	}
   596  
   597  	if fContent != "" {
   598  		if logger.V(2) {
   599  			logger.Infof("Using bootstrap contents from GRPC_XDS_BOOTSTRAP_CONFIG environment variable")
   600  		}
   601  		return NewConfigFromContents([]byte(fContent))
   602  	}
   603  
   604  	return nil, nil
   605  }
   606  
   607  // NewConfigFromContents creates a new bootstrap configuration from the provided
   608  // contents.
   609  func NewConfigFromContents(data []byte) (*Config, error) {
   610  	// Normalize the input configuration.
   611  	buf := bytes.Buffer{}
   612  	err := json.Indent(&buf, data, "", "")
   613  	if err != nil {
   614  		return nil, fmt.Errorf("xds: error normalizing JSON bootstrap configuration: %v", err)
   615  	}
   616  	data = bytes.TrimSpace(buf.Bytes())
   617  
   618  	config := &Config{}
   619  	if err := config.UnmarshalJSON(data); err != nil {
   620  		return nil, err
   621  	}
   622  	return config, nil
   623  }
   624  
   625  // ConfigOptionsForTesting specifies options for creating a new bootstrap
   626  // configuration for testing purposes.
   627  //
   628  // # Testing-Only
   629  type ConfigOptionsForTesting struct {
   630  	// Servers is the top-level xDS server configuration. It contains a list of
   631  	// server configurations.
   632  	Servers json.RawMessage
   633  	// CertificateProviders is the certificate providers configuration.
   634  	CertificateProviders map[string]json.RawMessage
   635  	// ServerListenerResourceNameTemplate is the listener resource name template
   636  	// to be used on the gRPC server.
   637  	ServerListenerResourceNameTemplate string
   638  	// ClientDefaultListenerResourceNameTemplate is the default listener
   639  	// resource name template to be used on the gRPC client.
   640  	ClientDefaultListenerResourceNameTemplate string
   641  	// Authorities is a list of non-default authorities.
   642  	Authorities map[string]json.RawMessage
   643  	// Node identifies the gRPC client/server node in the
   644  	// proxyless service mesh.
   645  	Node json.RawMessage
   646  }
   647  
   648  // NewContentsForTesting creates a new bootstrap configuration from the passed in
   649  // options, for testing purposes.
   650  //
   651  // # Testing-Only
   652  func NewContentsForTesting(opts ConfigOptionsForTesting) ([]byte, error) {
   653  	var servers ServerConfigs
   654  	if err := json.Unmarshal(opts.Servers, &servers); err != nil {
   655  		return nil, err
   656  	}
   657  	certProviders := make(map[string]certproviderNameAndConfig)
   658  	for k, v := range opts.CertificateProviders {
   659  		cp := certproviderNameAndConfig{}
   660  		if err := json.Unmarshal(v, &cp); err != nil {
   661  			return nil, fmt.Errorf("failed to unmarshal certificate provider configuration for %s: %s", k, string(v))
   662  		}
   663  		certProviders[k] = cp
   664  	}
   665  	authorities := make(map[string]*Authority)
   666  	for k, v := range opts.Authorities {
   667  		a := &Authority{}
   668  		if err := json.Unmarshal(v, a); err != nil {
   669  			return nil, fmt.Errorf("failed to unmarshal authority configuration for %s: %s", k, string(v))
   670  		}
   671  		authorities[k] = a
   672  	}
   673  	node := newNode()
   674  	if err := json.Unmarshal(opts.Node, &node); err != nil {
   675  		return nil, fmt.Errorf("failed to unmarshal node configuration %s: %v", string(opts.Node), err)
   676  	}
   677  	cfgJSON := configJSON{
   678  		XDSServers:                                servers,
   679  		CertificateProviders:                      certProviders,
   680  		ServerListenerResourceNameTemplate:        opts.ServerListenerResourceNameTemplate,
   681  		ClientDefaultListenerResourceNameTemplate: opts.ClientDefaultListenerResourceNameTemplate,
   682  		Authorities:                               authorities,
   683  		Node:                                      node,
   684  	}
   685  	contents, err := json.MarshalIndent(cfgJSON, " ", " ")
   686  	if err != nil {
   687  		return nil, fmt.Errorf("failed to marshal bootstrap configuration for provided options %+v: %v", opts, err)
   688  	}
   689  	return contents, nil
   690  }
   691  
   692  // certproviderNameAndConfig is the internal representation of
   693  // the`certificate_providers` field in the bootstrap configuration.
   694  type certproviderNameAndConfig struct {
   695  	PluginName string          `json:"plugin_name"`
   696  	Config     json.RawMessage `json:"config"`
   697  }
   698  
   699  // locality is the internal representation of the locality field within node.
   700  type locality struct {
   701  	Region  string `json:"region,omitempty"`
   702  	Zone    string `json:"zone,omitempty"`
   703  	SubZone string `json:"sub_zone,omitempty"`
   704  }
   705  
   706  func (l locality) Equal(other locality) bool {
   707  	return l.Region == other.Region && l.Zone == other.Zone && l.SubZone == other.SubZone
   708  }
   709  
   710  func (l locality) isEmpty() bool {
   711  	return l.Equal(locality{})
   712  }
   713  
   714  type userAgentVersion struct {
   715  	UserAgentVersion string `json:"user_agent_version,omitempty"`
   716  }
   717  
   718  // node is the internal representation of the node field in the bootstrap
   719  // configuration.
   720  type node struct {
   721  	ID       string           `json:"id,omitempty"`
   722  	Cluster  string           `json:"cluster,omitempty"`
   723  	Locality locality         `json:"locality,omitempty"`
   724  	Metadata *structpb.Struct `json:"metadata,omitempty"`
   725  
   726  	// The following fields are controlled by the client implementation and
   727  	// should not unmarshaled from JSON.
   728  	userAgentName        string
   729  	userAgentVersionType userAgentVersion
   730  	clientFeatures       []string
   731  }
   732  
   733  // newNode is a convenience function to create a new node instance with fields
   734  // controlled by the client implementation set to the desired values.
   735  func newNode() node {
   736  	return node{
   737  		userAgentName:        gRPCUserAgentName,
   738  		userAgentVersionType: userAgentVersion{UserAgentVersion: grpc.Version},
   739  		clientFeatures:       []string{clientFeatureNoOverprovisioning, clientFeatureResourceWrapper},
   740  	}
   741  }
   742  
   743  func (n node) Equal(other node) bool {
   744  	switch {
   745  	case n.ID != other.ID:
   746  		return false
   747  	case n.Cluster != other.Cluster:
   748  		return false
   749  	case !n.Locality.Equal(other.Locality):
   750  		return false
   751  	case n.userAgentName != other.userAgentName:
   752  		return false
   753  	case n.userAgentVersionType != other.userAgentVersionType:
   754  		return false
   755  	}
   756  
   757  	// Consider failures in JSON marshaling as being unable to perform the
   758  	// comparison, and hence return false.
   759  	nMetadata, err := n.Metadata.MarshalJSON()
   760  	if err != nil {
   761  		return false
   762  	}
   763  	otherMetadata, err := other.Metadata.MarshalJSON()
   764  	if err != nil {
   765  		return false
   766  	}
   767  	if !bytes.Equal(nMetadata, otherMetadata) {
   768  		return false
   769  	}
   770  
   771  	return slices.Equal(n.clientFeatures, other.clientFeatures)
   772  }
   773  
   774  func (n node) toProto() *v3corepb.Node {
   775  	return &v3corepb.Node{
   776  		Id:      n.ID,
   777  		Cluster: n.Cluster,
   778  		Locality: func() *v3corepb.Locality {
   779  			if n.Locality.isEmpty() {
   780  				return nil
   781  			}
   782  			return &v3corepb.Locality{
   783  				Region:  n.Locality.Region,
   784  				Zone:    n.Locality.Zone,
   785  				SubZone: n.Locality.SubZone,
   786  			}
   787  		}(),
   788  		Metadata:             proto.Clone(n.Metadata).(*structpb.Struct),
   789  		UserAgentName:        n.userAgentName,
   790  		UserAgentVersionType: &v3corepb.Node_UserAgentVersion{UserAgentVersion: n.userAgentVersionType.UserAgentVersion},
   791  		ClientFeatures:       slices.Clone(n.clientFeatures),
   792  	}
   793  }