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. 21package bootstrap 22 23import ( 24 "bytes" 25 "encoding/json" 26 "fmt" 27 "io/ioutil" 28 29 v2corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" 30 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 31 "github.com/golang/protobuf/jsonpb" 32 "github.com/golang/protobuf/proto" 33 "google.golang.org/grpc" 34 "google.golang.org/grpc/credentials/google" 35 "google.golang.org/grpc/credentials/insecure" 36 "google.golang.org/grpc/credentials/tls/certprovider" 37 "google.golang.org/grpc/internal" 38 "google.golang.org/grpc/internal/pretty" 39 "google.golang.org/grpc/internal/xds/env" 40 "google.golang.org/grpc/xds/internal/version" 41) 42 43const ( 44 // The "server_features" field in the bootstrap file contains a list of 45 // features supported by the server. A value of "xds_v3" indicates that the 46 // server supports the v3 version of the xDS transport protocol. 47 serverFeaturesV3 = "xds_v3" 48 49 // Type name for Google default credentials. 50 credsGoogleDefault = "google_default" 51 credsInsecure = "insecure" 52 gRPCUserAgentName = "gRPC Go" 53 clientFeatureNoOverprovisioning = "envoy.lb.does_not_support_overprovisioning" 54) 55 56var gRPCVersion = fmt.Sprintf("%s %s", gRPCUserAgentName, grpc.Version) 57 58// For overriding in unit tests. 59var bootstrapFileReadFunc = ioutil.ReadFile 60 61// Config provides the xDS client with several key bits of information that it 62// requires in its interaction with the management server. The Config is 63// initialized from the bootstrap file. 64type Config struct { 65 // BalancerName is the name of the management server to connect to. 66 // 67 // The bootstrap file contains a list of servers (with name+creds), but we 68 // pick the first one. 69 BalancerName string 70 // Creds contains the credentials to be used while talking to the xDS 71 // server, as a grpc.DialOption. 72 Creds grpc.DialOption 73 // TransportAPI indicates the API version of xDS transport protocol to use. 74 // This describes the xDS gRPC endpoint and version of 75 // DiscoveryRequest/Response used on the wire. 76 TransportAPI version.TransportAPI 77 // NodeProto contains the Node proto to be used in xDS requests. The actual 78 // type depends on the transport protocol version used. 79 NodeProto proto.Message 80 // CertProviderConfigs contains a mapping from certificate provider plugin 81 // instance names to parsed buildable configs. 82 CertProviderConfigs map[string]*certprovider.BuildableConfig 83 // ServerListenerResourceNameTemplate is a template for the name of the 84 // Listener resource to subscribe to for a gRPC server. If the token `%s` is 85 // present in the string, it will be replaced with the server's listening 86 // "IP:port" (e.g., "0.0.0.0:8080", "[::]:8080"). For example, a value of 87 // "example/resource/%s" could become "example/resource/0.0.0.0:8080". 88 ServerListenerResourceNameTemplate string 89} 90 91type channelCreds struct { 92 Type string `json:"type"` 93 Config json.RawMessage `json:"config"` 94} 95 96type xdsServer struct { 97 ServerURI string `json:"server_uri"` 98 ChannelCreds []channelCreds `json:"channel_creds"` 99 ServerFeatures []string `json:"server_features"` 100} 101 102func bootstrapConfigFromEnvVariable() ([]byte, error) { 103 fName := env.BootstrapFileName 104 fContent := env.BootstrapFileContent 105 106 // Bootstrap file name has higher priority than bootstrap content. 107 if fName != "" { 108 // If file name is set 109 // - If file not found (or other errors), fail 110 // - Otherwise, use the content. 111 // 112 // Note that even if the content is invalid, we don't failover to the 113 // file content env variable. 114 logger.Debugf("xds: using bootstrap file with name %q", fName) 115 return bootstrapFileReadFunc(fName) 116 } 117 118 if fContent != "" { 119 return []byte(fContent), nil 120 } 121 122 return nil, fmt.Errorf("none of the bootstrap environment variables (%q or %q) defined", env.BootstrapFileNameEnv, env.BootstrapFileContentEnv) 123} 124 125// NewConfig returns a new instance of Config initialized by reading the 126// bootstrap file found at ${GRPC_XDS_BOOTSTRAP}. 127// 128// The format of the bootstrap file will be as follows: 129// { 130// "xds_servers": [ 131// { 132// "server_uri": <string containing URI of management server>, 133// "channel_creds": [ 134// { 135// "type": <string containing channel cred type>, 136// "config": <JSON object containing config for the type> 137// } 138// ], 139// "server_features": [ ... ], 140// } 141// ], 142// "node": <JSON form of Node proto>, 143// "certificate_providers" : { 144// "default": { 145// "plugin_name": "default-plugin-name", 146// "config": { default plugin config in JSON } 147// }, 148// "foo": { 149// "plugin_name": "foo", 150// "config": { foo plugin config in JSON } 151// } 152// }, 153// "server_listener_resource_name_template": "grpc/server?xds.resource.listening_address=%s" 154// } 155// 156// Currently, we support exactly one type of credential, which is 157// "google_default", where we use the host's default certs for transport 158// credentials and a Google oauth token for call credentials. 159// 160// This function tries to process as much of the bootstrap file as possible (in 161// the presence of the errors) and may return a Config object with certain 162// fields left unspecified, in which case the caller should use some sane 163// defaults. 164func NewConfig() (*Config, error) { 165 data, err := bootstrapConfigFromEnvVariable() 166 if err != nil { 167 return nil, fmt.Errorf("xds: Failed to read bootstrap config: %v", err) 168 } 169 logger.Debugf("Bootstrap content: %s", data) 170 return NewConfigFromContents(data) 171} 172 173// NewConfigFromContents returns a new Config using the specified bootstrap 174// file contents instead of reading the environment variable. This is only 175// suitable for testing purposes. 176func NewConfigFromContents(data []byte) (*Config, error) { 177 config := &Config{} 178 179 var jsonData map[string]json.RawMessage 180 if err := json.Unmarshal(data, &jsonData); err != nil { 181 return nil, fmt.Errorf("xds: Failed to parse bootstrap config: %v", err) 182 } 183 184 serverSupportsV3 := false 185 m := jsonpb.Unmarshaler{AllowUnknownFields: true} 186 for k, v := range jsonData { 187 switch k { 188 case "node": 189 // We unconditionally convert the JSON into a v3.Node proto. The v3 190 // proto does not contain the deprecated field "build_version" from 191 // the v2 proto. We do not expect the bootstrap file to contain the 192 // "build_version" field. In any case, the unmarshal will succeed 193 // because we have set the `AllowUnknownFields` option on the 194 // unmarshaler. 195 n := &v3corepb.Node{} 196 if err := m.Unmarshal(bytes.NewReader(v), n); err != nil { 197 return nil, fmt.Errorf("xds: jsonpb.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err) 198 } 199 config.NodeProto = n 200 case "xds_servers": 201 var servers []*xdsServer 202 if err := json.Unmarshal(v, &servers); err != nil { 203 return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err) 204 } 205 if len(servers) < 1 { 206 return nil, fmt.Errorf("xds: bootstrap file parsing failed during bootstrap: file doesn't contain any management server to connect to") 207 } 208 xs := servers[0] 209 config.BalancerName = xs.ServerURI 210 for _, cc := range xs.ChannelCreds { 211 // We stop at the first credential type that we support. 212 if cc.Type == credsGoogleDefault { 213 config.Creds = grpc.WithCredentialsBundle(google.NewDefaultCredentials()) 214 break 215 } else if cc.Type == credsInsecure { 216 config.Creds = grpc.WithTransportCredentials(insecure.NewCredentials()) 217 break 218 } 219 } 220 for _, f := range xs.ServerFeatures { 221 switch f { 222 case serverFeaturesV3: 223 serverSupportsV3 = true 224 } 225 } 226 case "certificate_providers": 227 var providerInstances map[string]json.RawMessage 228 if err := json.Unmarshal(v, &providerInstances); err != nil { 229 return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err) 230 } 231 configs := make(map[string]*certprovider.BuildableConfig) 232 getBuilder := internal.GetCertificateProviderBuilder.(func(string) certprovider.Builder) 233 for instance, data := range providerInstances { 234 var nameAndConfig struct { 235 PluginName string `json:"plugin_name"` 236 Config json.RawMessage `json:"config"` 237 } 238 if err := json.Unmarshal(data, &nameAndConfig); err != nil { 239 return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), instance, err) 240 } 241 242 name := nameAndConfig.PluginName 243 parser := getBuilder(nameAndConfig.PluginName) 244 if parser == nil { 245 // We ignore plugins that we do not know about. 246 continue 247 } 248 bc, err := parser.ParseConfig(nameAndConfig.Config) 249 if err != nil { 250 return nil, fmt.Errorf("xds: Config parsing for plugin %q failed: %v", name, err) 251 } 252 configs[instance] = bc 253 } 254 config.CertProviderConfigs = configs 255 case "server_listener_resource_name_template": 256 if err := json.Unmarshal(v, &config.ServerListenerResourceNameTemplate); err != nil { 257 return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err) 258 } 259 } 260 // Do not fail the xDS bootstrap when an unknown field is seen. This can 261 // happen when an older version client reads a newer version bootstrap 262 // file with new fields. 263 } 264 265 if config.BalancerName == "" { 266 return nil, fmt.Errorf("xds: Required field %q not found in bootstrap %s", "xds_servers.server_uri", jsonData["xds_servers"]) 267 } 268 if config.Creds == nil { 269 return nil, fmt.Errorf("xds: Required field %q doesn't contain valid value in bootstrap %s", "xds_servers.channel_creds", jsonData["xds_servers"]) 270 } 271 272 // We end up using v3 transport protocol version only if the server supports 273 // v3, indicated by the presence of "xds_v3" in server_features. The default 274 // value of the enum type "version.TransportAPI" is v2. 275 if serverSupportsV3 { 276 config.TransportAPI = version.TransportV3 277 } 278 279 if err := config.updateNodeProto(); err != nil { 280 return nil, err 281 } 282 logger.Infof("Bootstrap config for creating xds-client: %v", pretty.ToJSON(config)) 283 return config, nil 284} 285 286// updateNodeProto updates the node proto read from the bootstrap file. 287// 288// Node proto in Config contains a v3.Node protobuf message corresponding to the 289// JSON contents found in the bootstrap file. This method performs some post 290// processing on it: 291// 1. If we don't find a nodeProto in the bootstrap file, we create an empty one 292// here. That way, callers of this function can always expect that the NodeProto 293// field is non-nil. 294// 2. If the transport protocol version to be used is not v3, we convert the 295// current v3.Node proto in a v2.Node proto. 296// 3. Some additional fields which are not expected to be set in the bootstrap 297// file are populated here. 298func (c *Config) updateNodeProto() error { 299 if c.TransportAPI == version.TransportV3 { 300 v3, _ := c.NodeProto.(*v3corepb.Node) 301 if v3 == nil { 302 v3 = &v3corepb.Node{} 303 } 304 v3.UserAgentName = gRPCUserAgentName 305 v3.UserAgentVersionType = &v3corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version} 306 v3.ClientFeatures = append(v3.ClientFeatures, clientFeatureNoOverprovisioning) 307 c.NodeProto = v3 308 return nil 309 } 310 311 v2 := &v2corepb.Node{} 312 if c.NodeProto != nil { 313 v3, err := proto.Marshal(c.NodeProto) 314 if err != nil { 315 return fmt.Errorf("xds: proto.Marshal(%v): %v", c.NodeProto, err) 316 } 317 if err := proto.Unmarshal(v3, v2); err != nil { 318 return fmt.Errorf("xds: proto.Unmarshal(%v): %v", v3, err) 319 } 320 } 321 c.NodeProto = v2 322 323 // BuildVersion is deprecated, and is replaced by user_agent_name and 324 // user_agent_version. But the management servers are still using the old 325 // field, so we will keep both set. 326 v2.BuildVersion = gRPCVersion 327 v2.UserAgentName = gRPCUserAgentName 328 v2.UserAgentVersionType = &v2corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version} 329 v2.ClientFeatures = append(v2.ClientFeatures, clientFeatureNoOverprovisioning) 330 return nil 331} 332