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