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