grpc-xds/greeter-go/pkg/xdsclient/bootstrap/bootstrap.go (96 lines of code) (raw):

// Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Modified from the original // [source]: https://github.com/grpc/grpc-go/blob/v1.57.0/xds/internal/xdsclient/bootstrap/bootstrap.go // by only parsing a subset of the gRPC xDS bootstrap configuration. // Copyright 2019 gRPC authors // Licensed under the Apache License, Version 2.0 // Package bootstrap provides the functionality to initialize certain aspects // of an xDS client by reading a bootstrap file. package bootstrap import ( "encoding/json" "fmt" "os" v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" "google.golang.org/grpc/credentials/tls/certprovider" "google.golang.org/protobuf/encoding/protojson" ) const ( // XDSBootstrapFileNameEnv is the env variable to set bootstrap file name. // Do not use this and read from env directly. Its value is read and kept in // variable XDSBootstrapFileName. // // When both bootstrap FileName and FileContent are set, FileName is used. XDSBootstrapFileNameEnv = "GRPC_XDS_BOOTSTRAP" // XDSBootstrapFileContentEnv is the env variable to set bootstrap file // content. Do not use this and read from env directly. Its value is read // and kept in variable XDSBootstrapFileContent. // // When both bootstrap FileName and FileContent are set, FileName is used. XDSBootstrapFileContentEnv = "GRPC_XDS_BOOTSTRAP_CONFIG" ) var ( // XDSBootstrapFileName holds the name of the file which contains xDS // bootstrap configuration. Users can specify the location of the bootstrap // file by setting the environment variable "GRPC_XDS_BOOTSTRAP". // // When both bootstrap FileName and FileContent are set, FileName is used. XDSBootstrapFileName = os.Getenv(XDSBootstrapFileNameEnv) // XDSBootstrapFileContent holds the content of the xDS bootstrap // configuration. Users can specify the bootstrap config by setting the // environment variable "GRPC_XDS_BOOTSTRAP_CONFIG". // // When both bootstrap FileName and FileContent are set, FileName is used. XDSBootstrapFileContent = os.Getenv(XDSBootstrapFileContentEnv) // GetCertificateProviderBuilder returns the registered builder for the // given name. This is set by package certprovider for use from xDS // bootstrap code while parsing certificate provider configs in the // bootstrap file. GetCertificateProviderBuilder any // func(string) certprovider.Builder errNoBootstrapEnvVar = fmt.Errorf("none of the bootstrap environment variables (%q or %q) defined", XDSBootstrapFileNameEnv, XDSBootstrapFileContentEnv) ) // Config provides the xDS client with several key bits of information that it // requires in its interaction with the management server. The Config is // initialized from the bootstrap file. type Config struct { // CertProviderConfigs contains a mapping from certificate provider plugin // instance names to parsed buildable configs. CertProviderConfigs map[string]*certprovider.BuildableConfig // NodeProto contains the Node proto to be used in xDS requests. This will be // of type *v3corepb.Node. NodeProto *v3corepb.Node } // NewConfigPartial returns a new instance of Config initialized by reading the // bootstrap file found at ${GRPC_XDS_BOOTSTRAP} or bootstrap contents specified // at ${GRPC_XDS_BOOTSTRAP_CONFIG}. If both env vars are set, the former is // preferred. // // Compared to the original `NewConfig()` function in the package // `google.golang.org/grpc/xds/internal/xdsclient/bootstrap`, // ([Source]: https://github.com/grpc/grpc-go/blob/v1.57.0/xds/internal/xdsclient/bootstrap/bootstrap.go#L414) // this partial implementation only reads the `node` and `certificate_provider` // sections. // // We support a credential registration mechanism and only credentials // registered through that mechanism will be accepted here. See package // `xds/bootstrap` for details. func NewConfigPartial() (*Config, error) { // Examples of the bootstrap json can be found in the generator tests // https://github.com/GoogleCloudPlatform/traffic-director-grpc-bootstrap/blob/master/main_test.go. data, err := bootstrapConfigFromEnvVariable() if err != nil { return nil, fmt.Errorf("xds: Failed to read bootstrap config: %w", err) } return newConfigFromContents(data) } func bootstrapConfigFromEnvVariable() ([]byte, error) { fName := XDSBootstrapFileName fContent := XDSBootstrapFileContent // Bootstrap file name has higher priority than bootstrap content. if fName != "" { // If file name is set // - If file not found (or other errors), fail // - Otherwise, use the content. // // Note that even if the content is invalid, we don't fail over to the // file content env variable. return os.ReadFile(fName) } if fContent != "" { return []byte(fContent), nil } return nil, errNoBootstrapEnvVar } func newConfigFromContents(data []byte) (*Config, error) { config := &Config{} var jsonData map[string]json.RawMessage if err := json.Unmarshal(data, &jsonData); err != nil { return nil, fmt.Errorf("xds: failed to parse bootstrap config: %w", err) } var node *v3corepb.Node m := protojson.UnmarshalOptions{ AllowPartial: true, DiscardUnknown: true, } for k, v := range jsonData { switch k { case "node": node = &v3corepb.Node{} if err := m.Unmarshal(v, node); err != nil { return nil, fmt.Errorf("xds: jsonpb.Unmarshal(%v) for field %q failed during bootstrap: %w", string(v), k, err) } case "certificate_providers": var providerInstances map[string]json.RawMessage if err := json.Unmarshal(v, &providerInstances); err != nil { return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %w", string(v), k, err) } configs, err := parseCertificateProviders(providerInstances) if err != nil { return nil, err } config.CertProviderConfigs = configs } } if node == nil { node = &v3corepb.Node{} } config.NodeProto = node return config, nil } func parseCertificateProviders(providerInstances map[string]json.RawMessage) (map[string]*certprovider.BuildableConfig, error) { configs := make(map[string]*certprovider.BuildableConfig) for instance, data := range providerInstances { var nameAndConfig struct { PluginName string `json:"plugin_name"` Config json.RawMessage `json:"config"` } if err := json.Unmarshal(data, &nameAndConfig); err != nil { return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %w", string(data), instance, err) } bc := certprovider.NewBuildableConfig( nameAndConfig.PluginName, []byte{}, func(options certprovider.BuildOptions) certprovider.Provider { return nil }) configs[instance] = bc } return configs, nil }