1
18
19
20
21 package bootstrap
22
23 import (
24 "bytes"
25 "encoding/json"
26 "fmt"
27 "net/url"
28 "os"
29 "strings"
30
31 "google.golang.org/grpc"
32 "google.golang.org/grpc/credentials/tls/certprovider"
33 "google.golang.org/grpc/internal"
34 "google.golang.org/grpc/internal/envconfig"
35 "google.golang.org/grpc/internal/pretty"
36 "google.golang.org/grpc/xds/bootstrap"
37 "google.golang.org/protobuf/encoding/protojson"
38
39 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
40 )
41
42 const (
43
44
45
46
47
48
49
50 serverFeaturesV3 = "xds_v3"
51 serverFeaturesIgnoreResourceDeletion = "ignore_resource_deletion"
52
53 gRPCUserAgentName = "gRPC Go"
54 clientFeatureNoOverprovisioning = "envoy.lb.does_not_support_overprovisioning"
55 clientFeatureResourceWrapper = "xds.config.resource-in-sotw"
56 )
57
58
59 var bootstrapFileReadFunc = os.ReadFile
60
61
62
63 type ChannelCreds struct {
64
65
66 Type string
67
68 Config json.RawMessage
69 }
70
71
72 func (cc ChannelCreds) Equal(other ChannelCreds) bool {
73 return cc.Type == other.Type && bytes.Equal(cc.Config, other.Config)
74 }
75
76
77
78 func (cc ChannelCreds) String() string {
79 if cc.Config == nil {
80 return cc.Type
81 }
82
83
84
85
86 b, _ := json.Marshal(cc.Config)
87 return cc.Type + "-" + string(b)
88 }
89
90
91
92
93
94
95
96
97
98 type ServerConfig struct {
99
100
101
102
103 ServerURI string
104
105
106 Creds ChannelCreds
107
108
109 ServerFeatures []string
110
111
112
113
114
115 credsDialOption grpc.DialOption
116
117
118
119
120
121
122 IgnoreResourceDeletion bool
123
124
125
126 Cleanups []func()
127 }
128
129
130 func (sc *ServerConfig) CredsDialOption() grpc.DialOption {
131 return sc.credsDialOption
132 }
133
134
135
136
137
138
139
140
141
142
143 func (sc *ServerConfig) String() string {
144 features := strings.Join(sc.ServerFeatures, "-")
145 return strings.Join([]string{sc.ServerURI, sc.Creds.String(), features}, "-")
146 }
147
148
149 func (sc ServerConfig) MarshalJSON() ([]byte, error) {
150 server := xdsServer{
151 ServerURI: sc.ServerURI,
152 ChannelCreds: []channelCreds{{Type: sc.Creds.Type, Config: sc.Creds.Config}},
153 ServerFeatures: sc.ServerFeatures,
154 }
155 server.ServerFeatures = []string{serverFeaturesV3}
156 if sc.IgnoreResourceDeletion {
157 server.ServerFeatures = append(server.ServerFeatures, serverFeaturesIgnoreResourceDeletion)
158 }
159 return json.Marshal(server)
160 }
161
162
163 func (sc *ServerConfig) UnmarshalJSON(data []byte) error {
164 var server xdsServer
165 if err := json.Unmarshal(data, &server); err != nil {
166 return fmt.Errorf("xds: json.Unmarshal(data) for field ServerConfig failed during bootstrap: %v", err)
167 }
168
169 sc.ServerURI = server.ServerURI
170 sc.ServerFeatures = server.ServerFeatures
171 for _, f := range server.ServerFeatures {
172 if f == serverFeaturesIgnoreResourceDeletion {
173 sc.IgnoreResourceDeletion = true
174 }
175 }
176 for _, cc := range server.ChannelCreds {
177
178 c := bootstrap.GetCredentials(cc.Type)
179 if c == nil {
180 continue
181 }
182 bundle, cancel, err := c.Build(cc.Config)
183 if err != nil {
184 return fmt.Errorf("failed to build credentials bundle from bootstrap for %q: %v", cc.Type, err)
185 }
186 sc.Creds = ChannelCreds(cc)
187 sc.credsDialOption = grpc.WithCredentialsBundle(bundle)
188 sc.Cleanups = append(sc.Cleanups, cancel)
189 break
190 }
191 return nil
192 }
193
194
195
196
197 func ServerConfigFromJSON(data []byte) (*ServerConfig, error) {
198 sc := new(ServerConfig)
199 if err := sc.UnmarshalJSON(data); err != nil {
200 return nil, err
201 }
202 return sc, nil
203 }
204
205
206 func (sc *ServerConfig) Equal(other *ServerConfig) bool {
207 switch {
208 case sc == nil && other == nil:
209 return true
210 case (sc != nil) != (other != nil):
211 return false
212 case sc.ServerURI != other.ServerURI:
213 return false
214 case !sc.Creds.Equal(other.Creds):
215 return false
216 case !equalStringSlice(sc.ServerFeatures, other.ServerFeatures):
217 return false
218 }
219 return true
220 }
221
222 func equalStringSlice(a, b []string) bool {
223 if len(a) != len(b) {
224 return false
225 }
226 for i := range a {
227 if a[i] != b[i] {
228 return false
229 }
230 }
231 return true
232 }
233
234
235 func unmarshalJSONServerConfigSlice(data []byte) ([]*ServerConfig, error) {
236 var servers []*ServerConfig
237 if err := json.Unmarshal(data, &servers); err != nil {
238 return nil, fmt.Errorf("failed to unmarshal JSON to []*ServerConfig: %v", err)
239 }
240 if len(servers) < 1 {
241 return nil, fmt.Errorf("no management server found in JSON")
242 }
243 return servers, nil
244 }
245
246
247
248 type Authority struct {
249
250
251
252
253
254
255
256
257
258
259
260
261
262 ClientListenerResourceNameTemplate string
263
264
265 XDSServer *ServerConfig
266 }
267
268
269 func (a *Authority) UnmarshalJSON(data []byte) error {
270 var jsonData map[string]json.RawMessage
271 if err := json.Unmarshal(data, &jsonData); err != nil {
272 return fmt.Errorf("xds: failed to parse authority: %v", err)
273 }
274
275 for k, v := range jsonData {
276 switch k {
277 case "xds_servers":
278 servers, err := unmarshalJSONServerConfigSlice(v)
279 if err != nil {
280 return fmt.Errorf("xds: json.Unmarshal(data) for field %q failed during bootstrap: %v", k, err)
281 }
282 a.XDSServer = servers[0]
283 case "client_listener_resource_name_template":
284 if err := json.Unmarshal(v, &a.ClientListenerResourceNameTemplate); err != nil {
285 return fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err)
286 }
287 }
288 }
289 return nil
290 }
291
292
293
294
295 type Config struct {
296
297
298
299
300 XDSServer *ServerConfig
301
302
303 CertProviderConfigs map[string]*certprovider.BuildableConfig
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318 ServerListenerResourceNameTemplate string
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333 ClientDefaultListenerResourceNameTemplate string
334
335
336
337
338
339
340
341
342
343
344
345
346
347 Authorities map[string]*Authority
348
349
350 NodeProto *v3corepb.Node
351 }
352
353 type channelCreds struct {
354 Type string `json:"type"`
355 Config json.RawMessage `json:"config,omitempty"`
356 }
357
358 type xdsServer struct {
359 ServerURI string `json:"server_uri"`
360 ChannelCreds []channelCreds `json:"channel_creds"`
361 ServerFeatures []string `json:"server_features"`
362 }
363
364 func bootstrapConfigFromEnvVariable() ([]byte, error) {
365 fName := envconfig.XDSBootstrapFileName
366 fContent := envconfig.XDSBootstrapFileContent
367
368
369 if fName != "" {
370
371
372
373
374
375
376 logger.Debugf("Using bootstrap file with name %q", fName)
377 return bootstrapFileReadFunc(fName)
378 }
379
380 if fContent != "" {
381 return []byte(fContent), nil
382 }
383
384 return nil, fmt.Errorf("none of the bootstrap environment variables (%q or %q) defined",
385 envconfig.XDSBootstrapFileNameEnv, envconfig.XDSBootstrapFileContentEnv)
386 }
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401 func NewConfig() (*Config, error) {
402
403
404 data, err := bootstrapConfigFromEnvVariable()
405 if err != nil {
406 return nil, fmt.Errorf("xds: Failed to read bootstrap config: %v", err)
407 }
408 return newConfigFromContents(data)
409 }
410
411
412
413 func NewConfigFromContents(data []byte) (*Config, error) {
414 return newConfigFromContents(data)
415 }
416
417 func newConfigFromContents(data []byte) (*Config, error) {
418 config := &Config{}
419
420 var jsonData map[string]json.RawMessage
421 if err := json.Unmarshal(data, &jsonData); err != nil {
422 return nil, fmt.Errorf("xds: failed to parse bootstrap config: %v", err)
423 }
424
425 var node *v3corepb.Node
426 opts := protojson.UnmarshalOptions{DiscardUnknown: true}
427 for k, v := range jsonData {
428 switch k {
429 case "node":
430 node = &v3corepb.Node{}
431 if err := opts.Unmarshal(v, node); err != nil {
432 return nil, fmt.Errorf("xds: protojson.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err)
433 }
434 case "xds_servers":
435 servers, err := unmarshalJSONServerConfigSlice(v)
436 if err != nil {
437 return nil, fmt.Errorf("xds: json.Unmarshal(data) for field %q failed during bootstrap: %v", k, err)
438 }
439 config.XDSServer = servers[0]
440 case "certificate_providers":
441 var providerInstances map[string]json.RawMessage
442 if err := json.Unmarshal(v, &providerInstances); err != nil {
443 return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err)
444 }
445 configs := make(map[string]*certprovider.BuildableConfig)
446 getBuilder := internal.GetCertificateProviderBuilder.(func(string) certprovider.Builder)
447 for instance, data := range providerInstances {
448 var nameAndConfig struct {
449 PluginName string `json:"plugin_name"`
450 Config json.RawMessage `json:"config"`
451 }
452 if err := json.Unmarshal(data, &nameAndConfig); err != nil {
453 return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), instance, err)
454 }
455
456 name := nameAndConfig.PluginName
457 parser := getBuilder(nameAndConfig.PluginName)
458 if parser == nil {
459
460 continue
461 }
462 bc, err := parser.ParseConfig(nameAndConfig.Config)
463 if err != nil {
464 return nil, fmt.Errorf("xds: config parsing for plugin %q failed: %v", name, err)
465 }
466 configs[instance] = bc
467 }
468 config.CertProviderConfigs = configs
469 case "server_listener_resource_name_template":
470 if err := json.Unmarshal(v, &config.ServerListenerResourceNameTemplate); err != nil {
471 return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err)
472 }
473 case "client_default_listener_resource_name_template":
474 if err := json.Unmarshal(v, &config.ClientDefaultListenerResourceNameTemplate); err != nil {
475 return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err)
476 }
477 case "authorities":
478 if err := json.Unmarshal(v, &config.Authorities); err != nil {
479 return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err)
480 }
481 default:
482 logger.Warningf("Bootstrap content has unknown field: %s", k)
483 }
484
485
486
487 }
488
489 if config.ClientDefaultListenerResourceNameTemplate == "" {
490
491 config.ClientDefaultListenerResourceNameTemplate = "%s"
492 }
493 if config.XDSServer == nil {
494 return nil, fmt.Errorf("xds: required field %q not found in bootstrap %s", "xds_servers", jsonData["xds_servers"])
495 }
496 if config.XDSServer.ServerURI == "" {
497 return nil, fmt.Errorf("xds: required field %q not found in bootstrap %s", "xds_servers.server_uri", jsonData["xds_servers"])
498 }
499 if config.XDSServer.CredsDialOption() == nil {
500 return nil, fmt.Errorf("xds: required field %q doesn't contain valid value in bootstrap %s", "xds_servers.channel_creds", jsonData["xds_servers"])
501 }
502
503
504
505 for name, authority := range config.Authorities {
506 prefix := fmt.Sprintf("xdstp://%s", url.PathEscape(name))
507 if authority.ClientListenerResourceNameTemplate == "" {
508 authority.ClientListenerResourceNameTemplate = prefix + "/envoy.config.listener.v3.Listener/%s"
509 continue
510 }
511 if !strings.HasPrefix(authority.ClientListenerResourceNameTemplate, prefix) {
512 return nil, fmt.Errorf("xds: field ClientListenerResourceNameTemplate %q of authority %q doesn't start with prefix %q", authority.ClientListenerResourceNameTemplate, name, prefix)
513 }
514 }
515
516
517
518 if node == nil {
519 node = &v3corepb.Node{}
520 }
521 node.UserAgentName = gRPCUserAgentName
522 node.UserAgentVersionType = &v3corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version}
523 node.ClientFeatures = append(node.ClientFeatures, clientFeatureNoOverprovisioning, clientFeatureResourceWrapper)
524 config.NodeProto = node
525
526 if logger.V(2) {
527 logger.Infof("Bootstrap config for creating xds-client: %v", pretty.ToJSON(config))
528 }
529 return config, nil
530 }
531
View as plain text