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 }