google.golang.org/grpc@v1.62.1/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 "net/url" 28 "os" 29 "strings" 30 31 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 32 "github.com/golang/protobuf/jsonpb" 33 "google.golang.org/grpc" 34 "google.golang.org/grpc/credentials" 35 "google.golang.org/grpc/credentials/google" 36 "google.golang.org/grpc/credentials/insecure" 37 "google.golang.org/grpc/credentials/tls/certprovider" 38 "google.golang.org/grpc/internal" 39 "google.golang.org/grpc/internal/envconfig" 40 "google.golang.org/grpc/internal/pretty" 41 "google.golang.org/grpc/xds/bootstrap" 42 "google.golang.org/grpc/xds/internal/xdsclient/tlscreds" 43 ) 44 45 const ( 46 // The "server_features" field in the bootstrap file contains a list of 47 // features supported by the server: 48 // - A value of "xds_v3" indicates that the server supports the v3 version of 49 // the xDS transport protocol. 50 // - A value of "ignore_resource_deletion" indicates that the client should 51 // ignore deletion of Listener and Cluster resources in updates from the 52 // server. 53 serverFeaturesV3 = "xds_v3" 54 serverFeaturesIgnoreResourceDeletion = "ignore_resource_deletion" 55 56 gRPCUserAgentName = "gRPC Go" 57 clientFeatureNoOverprovisioning = "envoy.lb.does_not_support_overprovisioning" 58 clientFeatureResourceWrapper = "xds.config.resource-in-sotw" 59 ) 60 61 func init() { 62 bootstrap.RegisterCredentials(&insecureCredsBuilder{}) 63 bootstrap.RegisterCredentials(&googleDefaultCredsBuilder{}) 64 bootstrap.RegisterCredentials(&tlsCredsBuilder{}) 65 } 66 67 // For overriding in unit tests. 68 var bootstrapFileReadFunc = os.ReadFile 69 70 // insecureCredsBuilder implements the `Credentials` interface defined in 71 // package `xds/bootstrap` and encapsulates an insecure credential. 72 type insecureCredsBuilder struct{} 73 74 func (i *insecureCredsBuilder) Build(json.RawMessage) (credentials.Bundle, func(), error) { 75 return insecure.NewBundle(), func() {}, nil 76 } 77 78 func (i *insecureCredsBuilder) Name() string { 79 return "insecure" 80 } 81 82 // tlsCredsBuilder implements the `Credentials` interface defined in 83 // package `xds/bootstrap` and encapsulates a TLS credential. 84 type tlsCredsBuilder struct{} 85 86 func (t *tlsCredsBuilder) Build(config json.RawMessage) (credentials.Bundle, func(), error) { 87 return tlscreds.NewBundle(config) 88 } 89 90 func (t *tlsCredsBuilder) Name() string { 91 return "tls" 92 } 93 94 // googleDefaultCredsBuilder implements the `Credentials` interface defined in 95 // package `xds/boostrap` and encapsulates a Google Default credential. 96 type googleDefaultCredsBuilder struct{} 97 98 func (d *googleDefaultCredsBuilder) Build(json.RawMessage) (credentials.Bundle, func(), error) { 99 return google.NewDefaultCredentials(), func() {}, nil 100 } 101 102 func (d *googleDefaultCredsBuilder) Name() string { 103 return "google_default" 104 } 105 106 // ChannelCreds contains the credentials to be used while communicating with an 107 // xDS server. It is also used to dedup servers with the same server URI. 108 type ChannelCreds struct { 109 // Type contains a unique name identifying the credentials type. The only 110 // supported types currently are "google_default" and "insecure". 111 Type string 112 // Config contains the JSON configuration associated with the credentials. 113 Config json.RawMessage 114 } 115 116 // Equal reports whether cc and other are considered equal. 117 func (cc ChannelCreds) Equal(other ChannelCreds) bool { 118 return cc.Type == other.Type && bytes.Equal(cc.Config, other.Config) 119 } 120 121 // String returns a string representation of the credentials. It contains the 122 // type and the config (if non-nil) separated by a "-". 123 func (cc ChannelCreds) String() string { 124 if cc.Config == nil { 125 return cc.Type 126 } 127 128 // We do not expect the Marshal call to fail since we wrote to cc.Config 129 // after a successful unmarshaling from JSON configuration. Therefore, 130 // it is safe to ignore the error here. 131 b, _ := json.Marshal(cc.Config) 132 return cc.Type + "-" + string(b) 133 } 134 135 // ServerConfig contains the configuration to connect to a server, including 136 // URI, creds, and transport API version (e.g. v2 or v3). 137 // 138 // It contains unexported fields that are initialized when unmarshaled from JSON 139 // using either the UnmarshalJSON() method or the ServerConfigFromJSON() 140 // function. Hence users are strongly encouraged not to use a literal struct 141 // initialization to create an instance of this type, but instead unmarshal from 142 // JSON using one of the two available options. 143 type ServerConfig struct { 144 // ServerURI is the management server to connect to. 145 // 146 // The bootstrap file contains an ordered list of xDS servers to contact for 147 // this authority. The first one is picked. 148 ServerURI string 149 // Creds contains the credentials to be used while communicationg with this 150 // xDS server. It is also used to dedup servers with the same server URI. 151 Creds ChannelCreds 152 // ServerFeatures contains a list of features supported by this xDS server. 153 // It is also used to dedup servers with the same server URI and creds. 154 ServerFeatures []string 155 156 // As part of unmarshaling the JSON config into this struct, we ensure that 157 // the credentials config is valid by building an instance of the specified 158 // credentials and store it here as a grpc.DialOption for easy access when 159 // dialing this xDS server. 160 credsDialOption grpc.DialOption 161 162 // IgnoreResourceDeletion controls the behavior of the xDS client when the 163 // server deletes a previously sent Listener or Cluster resource. If set, the 164 // xDS client will not invoke the watchers' OnResourceDoesNotExist() method 165 // when a resource is deleted, nor will it remove the existing resource value 166 // from its cache. 167 IgnoreResourceDeletion bool 168 169 // Cleanups are called when the xDS client for this server is closed. Allows 170 // cleaning up resources created specifically for this ServerConfig. 171 Cleanups []func() 172 } 173 174 // CredsDialOption returns the configured credentials as a grpc dial option. 175 func (sc *ServerConfig) CredsDialOption() grpc.DialOption { 176 return sc.credsDialOption 177 } 178 179 // String returns the string representation of the ServerConfig. 180 // 181 // This string representation will be used as map keys in federation 182 // (`map[ServerConfig]authority`), so that the xDS ClientConn and stream will be 183 // shared by authorities with different names but the same server config. 184 // 185 // It covers (almost) all the fields so the string can represent the config 186 // content. It doesn't cover NodeProto because NodeProto isn't used by 187 // federation. 188 func (sc *ServerConfig) String() string { 189 features := strings.Join(sc.ServerFeatures, "-") 190 return strings.Join([]string{sc.ServerURI, sc.Creds.String(), features}, "-") 191 } 192 193 // MarshalJSON marshals the ServerConfig to json. 194 func (sc ServerConfig) MarshalJSON() ([]byte, error) { 195 server := xdsServer{ 196 ServerURI: sc.ServerURI, 197 ChannelCreds: []channelCreds{{Type: sc.Creds.Type, Config: sc.Creds.Config}}, 198 ServerFeatures: sc.ServerFeatures, 199 } 200 server.ServerFeatures = []string{serverFeaturesV3} 201 if sc.IgnoreResourceDeletion { 202 server.ServerFeatures = append(server.ServerFeatures, serverFeaturesIgnoreResourceDeletion) 203 } 204 return json.Marshal(server) 205 } 206 207 // UnmarshalJSON takes the json data (a server) and unmarshals it to the struct. 208 func (sc *ServerConfig) UnmarshalJSON(data []byte) error { 209 var server xdsServer 210 if err := json.Unmarshal(data, &server); err != nil { 211 return fmt.Errorf("xds: json.Unmarshal(data) for field ServerConfig failed during bootstrap: %v", err) 212 } 213 214 sc.ServerURI = server.ServerURI 215 sc.ServerFeatures = server.ServerFeatures 216 for _, f := range server.ServerFeatures { 217 if f == serverFeaturesIgnoreResourceDeletion { 218 sc.IgnoreResourceDeletion = true 219 } 220 } 221 for _, cc := range server.ChannelCreds { 222 // We stop at the first credential type that we support. 223 c := bootstrap.GetCredentials(cc.Type) 224 if c == nil { 225 continue 226 } 227 bundle, cancel, err := c.Build(cc.Config) 228 if err != nil { 229 return fmt.Errorf("failed to build credentials bundle from bootstrap for %q: %v", cc.Type, err) 230 } 231 sc.Creds = ChannelCreds(cc) 232 sc.credsDialOption = grpc.WithCredentialsBundle(bundle) 233 sc.Cleanups = append(sc.Cleanups, cancel) 234 break 235 } 236 return nil 237 } 238 239 // ServerConfigFromJSON creates a new ServerConfig from the given JSON 240 // configuration. This is the preferred way of creating a ServerConfig when 241 // hand-crafting the JSON configuration. 242 func ServerConfigFromJSON(data []byte) (*ServerConfig, error) { 243 sc := new(ServerConfig) 244 if err := sc.UnmarshalJSON(data); err != nil { 245 return nil, err 246 } 247 return sc, nil 248 } 249 250 // Equal reports whether sc and other are considered equal. 251 func (sc *ServerConfig) Equal(other *ServerConfig) bool { 252 switch { 253 case sc == nil && other == nil: 254 return true 255 case (sc != nil) != (other != nil): 256 return false 257 case sc.ServerURI != other.ServerURI: 258 return false 259 case !sc.Creds.Equal(other.Creds): 260 return false 261 case !equalStringSlice(sc.ServerFeatures, other.ServerFeatures): 262 return false 263 } 264 return true 265 } 266 267 func equalStringSlice(a, b []string) bool { 268 if len(a) != len(b) { 269 return false 270 } 271 for i := range a { 272 if a[i] != b[i] { 273 return false 274 } 275 } 276 return true 277 } 278 279 // unmarshalJSONServerConfigSlice unmarshals JSON to a slice. 280 func unmarshalJSONServerConfigSlice(data []byte) ([]*ServerConfig, error) { 281 var servers []*ServerConfig 282 if err := json.Unmarshal(data, &servers); err != nil { 283 return nil, fmt.Errorf("failed to unmarshal JSON to []*ServerConfig: %v", err) 284 } 285 if len(servers) < 1 { 286 return nil, fmt.Errorf("no management server found in JSON") 287 } 288 return servers, nil 289 } 290 291 // Authority contains configuration for an Authority for an xDS control plane 292 // server. See the Authorities field in the Config struct for how it's used. 293 type Authority struct { 294 // ClientListenerResourceNameTemplate is template for the name of the 295 // Listener resource to subscribe to for a gRPC client channel. Used only 296 // when the channel is created using an "xds:" URI with this authority name. 297 // 298 // The token "%s", if present in this string, will be replaced 299 // with %-encoded service authority (i.e., the path part of the target 300 // URI used to create the gRPC channel). 301 // 302 // Must start with "xdstp://<authority_name>/". If it does not, 303 // that is considered a bootstrap file parsing error. 304 // 305 // If not present in the bootstrap file, defaults to 306 // "xdstp://<authority_name>/envoy.config.listener.v3.Listener/%s". 307 ClientListenerResourceNameTemplate string 308 // XDSServer contains the management server and config to connect to for 309 // this authority. 310 XDSServer *ServerConfig 311 } 312 313 // UnmarshalJSON implement json unmarshaller. 314 func (a *Authority) UnmarshalJSON(data []byte) error { 315 var jsonData map[string]json.RawMessage 316 if err := json.Unmarshal(data, &jsonData); err != nil { 317 return fmt.Errorf("xds: failed to parse authority: %v", err) 318 } 319 320 for k, v := range jsonData { 321 switch k { 322 case "xds_servers": 323 servers, err := unmarshalJSONServerConfigSlice(v) 324 if err != nil { 325 return fmt.Errorf("xds: json.Unmarshal(data) for field %q failed during bootstrap: %v", k, err) 326 } 327 a.XDSServer = servers[0] 328 case "client_listener_resource_name_template": 329 if err := json.Unmarshal(v, &a.ClientListenerResourceNameTemplate); err != nil { 330 return fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err) 331 } 332 } 333 } 334 return nil 335 } 336 337 // Config provides the xDS client with several key bits of information that it 338 // requires in its interaction with the management server. The Config is 339 // initialized from the bootstrap file. 340 type Config struct { 341 // XDSServer is the management server to connect to. 342 // 343 // The bootstrap file contains a list of servers (with name+creds), but we 344 // pick the first one. 345 XDSServer *ServerConfig 346 // CertProviderConfigs contains a mapping from certificate provider plugin 347 // instance names to parsed buildable configs. 348 CertProviderConfigs map[string]*certprovider.BuildableConfig 349 // ServerListenerResourceNameTemplate is a template for the name of the 350 // Listener resource to subscribe to for a gRPC server. 351 // 352 // If starts with "xdstp:", will be interpreted as a new-style name, 353 // in which case the authority of the URI will be used to select the 354 // relevant configuration in the "authorities" map. 355 // 356 // The token "%s", if present in this string, will be replaced with the IP 357 // and port on which the server is listening. (e.g., "0.0.0.0:8080", 358 // "[::]:8080"). For example, a value of "example/resource/%s" could become 359 // "example/resource/0.0.0.0:8080". If the template starts with "xdstp:", 360 // the replaced string will be %-encoded. 361 // 362 // There is no default; if unset, xDS-based server creation fails. 363 ServerListenerResourceNameTemplate string 364 // A template for the name of the Listener resource to subscribe to 365 // for a gRPC client channel. Used only when the channel is created 366 // with an "xds:" URI with no authority. 367 // 368 // If starts with "xdstp:", will be interpreted as a new-style name, 369 // in which case the authority of the URI will be used to select the 370 // relevant configuration in the "authorities" map. 371 // 372 // The token "%s", if present in this string, will be replaced with 373 // the service authority (i.e., the path part of the target URI 374 // used to create the gRPC channel). If the template starts with 375 // "xdstp:", the replaced string will be %-encoded. 376 // 377 // Defaults to "%s". 378 ClientDefaultListenerResourceNameTemplate string 379 // Authorities is a map of authority name to corresponding configuration. 380 // 381 // This is used in the following cases: 382 // - A gRPC client channel is created using an "xds:" URI that includes 383 // an authority. 384 // - A gRPC client channel is created using an "xds:" URI with no 385 // authority, but the "client_default_listener_resource_name_template" 386 // field above turns it into an "xdstp:" URI. 387 // - A gRPC server is created and the 388 // "server_listener_resource_name_template" field is an "xdstp:" URI. 389 // 390 // In any of those cases, it is an error if the specified authority is 391 // not present in this map. 392 Authorities map[string]*Authority 393 // NodeProto contains the Node proto to be used in xDS requests. This will be 394 // of type *v3corepb.Node. 395 NodeProto *v3corepb.Node 396 } 397 398 type channelCreds struct { 399 Type string `json:"type"` 400 Config json.RawMessage `json:"config,omitempty"` 401 } 402 403 type xdsServer struct { 404 ServerURI string `json:"server_uri"` 405 ChannelCreds []channelCreds `json:"channel_creds"` 406 ServerFeatures []string `json:"server_features"` 407 } 408 409 func bootstrapConfigFromEnvVariable() ([]byte, error) { 410 fName := envconfig.XDSBootstrapFileName 411 fContent := envconfig.XDSBootstrapFileContent 412 413 // Bootstrap file name has higher priority than bootstrap content. 414 if fName != "" { 415 // If file name is set 416 // - If file not found (or other errors), fail 417 // - Otherwise, use the content. 418 // 419 // Note that even if the content is invalid, we don't failover to the 420 // file content env variable. 421 logger.Debugf("Using bootstrap file with name %q", fName) 422 return bootstrapFileReadFunc(fName) 423 } 424 425 if fContent != "" { 426 return []byte(fContent), nil 427 } 428 429 return nil, fmt.Errorf("none of the bootstrap environment variables (%q or %q) defined", 430 envconfig.XDSBootstrapFileNameEnv, envconfig.XDSBootstrapFileContentEnv) 431 } 432 433 // NewConfig returns a new instance of Config initialized by reading the 434 // bootstrap file found at ${GRPC_XDS_BOOTSTRAP} or bootstrap contents specified 435 // at ${GRPC_XDS_BOOTSTRAP_CONFIG}. If both env vars are set, the former is 436 // preferred. 437 // 438 // We support a credential registration mechanism and only credentials 439 // registered through that mechanism will be accepted here. See package 440 // `xds/bootstrap` for details. 441 // 442 // This function tries to process as much of the bootstrap file as possible (in 443 // the presence of the errors) and may return a Config object with certain 444 // fields left unspecified, in which case the caller should use some sane 445 // defaults. 446 func NewConfig() (*Config, error) { 447 // Examples of the bootstrap json can be found in the generator tests 448 // https://github.com/GoogleCloudPlatform/traffic-director-grpc-bootstrap/blob/master/main_test.go. 449 data, err := bootstrapConfigFromEnvVariable() 450 if err != nil { 451 return nil, fmt.Errorf("xds: Failed to read bootstrap config: %v", err) 452 } 453 return newConfigFromContents(data) 454 } 455 456 // NewConfigFromContentsForTesting returns a new Config using the specified 457 // bootstrap file contents instead of reading the environment variable. 458 // 459 // This is only suitable for testing purposes. 460 func NewConfigFromContentsForTesting(data []byte) (*Config, error) { 461 return newConfigFromContents(data) 462 } 463 464 func newConfigFromContents(data []byte) (*Config, error) { 465 config := &Config{} 466 467 var jsonData map[string]json.RawMessage 468 if err := json.Unmarshal(data, &jsonData); err != nil { 469 return nil, fmt.Errorf("xds: failed to parse bootstrap config: %v", err) 470 } 471 472 var node *v3corepb.Node 473 m := jsonpb.Unmarshaler{AllowUnknownFields: true} 474 for k, v := range jsonData { 475 switch k { 476 case "node": 477 node = &v3corepb.Node{} 478 if err := m.Unmarshal(bytes.NewReader(v), node); err != nil { 479 return nil, fmt.Errorf("xds: jsonpb.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err) 480 } 481 case "xds_servers": 482 servers, err := unmarshalJSONServerConfigSlice(v) 483 if err != nil { 484 return nil, fmt.Errorf("xds: json.Unmarshal(data) for field %q failed during bootstrap: %v", k, err) 485 } 486 config.XDSServer = servers[0] 487 case "certificate_providers": 488 var providerInstances map[string]json.RawMessage 489 if err := json.Unmarshal(v, &providerInstances); err != nil { 490 return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err) 491 } 492 configs := make(map[string]*certprovider.BuildableConfig) 493 getBuilder := internal.GetCertificateProviderBuilder.(func(string) certprovider.Builder) 494 for instance, data := range providerInstances { 495 var nameAndConfig struct { 496 PluginName string `json:"plugin_name"` 497 Config json.RawMessage `json:"config"` 498 } 499 if err := json.Unmarshal(data, &nameAndConfig); err != nil { 500 return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), instance, err) 501 } 502 503 name := nameAndConfig.PluginName 504 parser := getBuilder(nameAndConfig.PluginName) 505 if parser == nil { 506 // We ignore plugins that we do not know about. 507 continue 508 } 509 bc, err := parser.ParseConfig(nameAndConfig.Config) 510 if err != nil { 511 return nil, fmt.Errorf("xds: config parsing for plugin %q failed: %v", name, err) 512 } 513 configs[instance] = bc 514 } 515 config.CertProviderConfigs = configs 516 case "server_listener_resource_name_template": 517 if err := json.Unmarshal(v, &config.ServerListenerResourceNameTemplate); err != nil { 518 return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err) 519 } 520 case "client_default_listener_resource_name_template": 521 if err := json.Unmarshal(v, &config.ClientDefaultListenerResourceNameTemplate); err != nil { 522 return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err) 523 } 524 case "authorities": 525 if err := json.Unmarshal(v, &config.Authorities); err != nil { 526 return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err) 527 } 528 default: 529 logger.Warningf("Bootstrap content has unknown field: %s", k) 530 } 531 // Do not fail the xDS bootstrap when an unknown field is seen. This can 532 // happen when an older version client reads a newer version bootstrap 533 // file with new fields. 534 } 535 536 if config.ClientDefaultListenerResourceNameTemplate == "" { 537 // Default value of the default client listener name template is "%s". 538 config.ClientDefaultListenerResourceNameTemplate = "%s" 539 } 540 if config.XDSServer == nil { 541 return nil, fmt.Errorf("xds: required field %q not found in bootstrap %s", "xds_servers", jsonData["xds_servers"]) 542 } 543 if config.XDSServer.ServerURI == "" { 544 return nil, fmt.Errorf("xds: required field %q not found in bootstrap %s", "xds_servers.server_uri", jsonData["xds_servers"]) 545 } 546 if config.XDSServer.CredsDialOption() == nil { 547 return nil, fmt.Errorf("xds: required field %q doesn't contain valid value in bootstrap %s", "xds_servers.channel_creds", jsonData["xds_servers"]) 548 } 549 // Post-process the authorities' client listener resource template field: 550 // - if set, it must start with "xdstp://<authority_name>/" 551 // - if not set, it defaults to "xdstp://<authority_name>/envoy.config.listener.v3.Listener/%s" 552 for name, authority := range config.Authorities { 553 prefix := fmt.Sprintf("xdstp://%s", url.PathEscape(name)) 554 if authority.ClientListenerResourceNameTemplate == "" { 555 authority.ClientListenerResourceNameTemplate = prefix + "/envoy.config.listener.v3.Listener/%s" 556 continue 557 } 558 if !strings.HasPrefix(authority.ClientListenerResourceNameTemplate, prefix) { 559 return nil, fmt.Errorf("xds: field ClientListenerResourceNameTemplate %q of authority %q doesn't start with prefix %q", authority.ClientListenerResourceNameTemplate, name, prefix) 560 } 561 } 562 563 // Performing post-production on the node information. Some additional fields 564 // which are not expected to be set in the bootstrap file are populated here. 565 if node == nil { 566 node = &v3corepb.Node{} 567 } 568 node.UserAgentName = gRPCUserAgentName 569 node.UserAgentVersionType = &v3corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version} 570 node.ClientFeatures = append(node.ClientFeatures, clientFeatureNoOverprovisioning, clientFeatureResourceWrapper) 571 config.NodeProto = node 572 573 logger.Debugf("Bootstrap config for creating xds-client: %v", pretty.ToJSON(config)) 574 return config, nil 575 }