...

Source file src/google.golang.org/grpc/internal/xds/bootstrap/bootstrap_test.go

Documentation: google.golang.org/grpc/internal/xds/bootstrap

     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
    20  
    21  import (
    22  	"encoding/json"
    23  	"errors"
    24  	"fmt"
    25  	"os"
    26  	"testing"
    27  
    28  	"github.com/google/go-cmp/cmp"
    29  	"github.com/google/go-cmp/cmp/cmpopts"
    30  	"google.golang.org/grpc"
    31  	"google.golang.org/grpc/credentials/tls/certprovider"
    32  	"google.golang.org/grpc/internal"
    33  	"google.golang.org/grpc/internal/envconfig"
    34  	"google.golang.org/grpc/xds/bootstrap"
    35  	"google.golang.org/protobuf/proto"
    36  	"google.golang.org/protobuf/types/known/structpb"
    37  
    38  	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    39  )
    40  
    41  var (
    42  	v3BootstrapFileMap = map[string]string{
    43  		"serverFeaturesIncludesXDSV3": `
    44  		{
    45  			"node": {
    46  				"id": "ENVOY_NODE_ID",
    47  				"metadata": {
    48  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
    49  			    }
    50  			},
    51  			"xds_servers" : [{
    52  				"server_uri": "trafficdirector.googleapis.com:443",
    53  				"channel_creds": [
    54  					{ "type": "google_default" }
    55  				],
    56  				"server_features" : ["xds_v3"]
    57  			}]
    58  		}`,
    59  		"serverFeaturesExcludesXDSV3": `
    60  		{
    61  			"node": {
    62  				"id": "ENVOY_NODE_ID",
    63  				"metadata": {
    64  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
    65  			    }
    66  			},
    67  			"xds_servers" : [{
    68  				"server_uri": "trafficdirector.googleapis.com:443",
    69  				"channel_creds": [
    70  					{ "type": "google_default" }
    71  				]
    72  			}]
    73  		}`,
    74  		"emptyNodeProto": `
    75  		{
    76  			"xds_servers" : [{
    77  				"server_uri": "trafficdirector.googleapis.com:443",
    78  				"channel_creds": [
    79  					{ "type": "insecure" }
    80  				]
    81  			}]
    82  		}`,
    83  		"unknownTopLevelFieldInFile": `
    84  		{
    85  			"node": {
    86  				"id": "ENVOY_NODE_ID",
    87  				"metadata": {
    88  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
    89  			    }
    90  			},
    91  			"xds_servers" : [{
    92  				"server_uri": "trafficdirector.googleapis.com:443",
    93  				"channel_creds": [
    94  					{ "type": "insecure" }
    95  				]
    96  			}],
    97  			"unknownField": "foobar"
    98  		}`,
    99  		"unknownFieldInNodeProto": `
   100  		{
   101  			"node": {
   102  				"id": "ENVOY_NODE_ID",
   103  				"unknownField": "foobar",
   104  				"metadata": {
   105  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   106  			    }
   107  			},
   108  			"xds_servers" : [{
   109  				"server_uri": "trafficdirector.googleapis.com:443",
   110  				"channel_creds": [
   111  					{ "type": "insecure" }
   112  				]
   113  			}]
   114  		}`,
   115  		"unknownFieldInXdsServer": `
   116  		{
   117  			"node": {
   118  				"id": "ENVOY_NODE_ID",
   119  				"metadata": {
   120  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   121  			    }
   122  			},
   123  			"xds_servers" : [{
   124  				"server_uri": "trafficdirector.googleapis.com:443",
   125  				"channel_creds": [
   126  					{ "type": "insecure" }
   127  				],
   128  				"unknownField": "foobar"
   129  			}]
   130  		}`,
   131  		"multipleChannelCreds": `
   132  		{
   133  			"node": {
   134  				"id": "ENVOY_NODE_ID",
   135  				"metadata": {
   136  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   137  			    }
   138  			},
   139  			"xds_servers" : [{
   140  				"server_uri": "trafficdirector.googleapis.com:443",
   141  				"channel_creds": [
   142  					{ "type": "not-google-default" },
   143  					{ "type": "google_default" }
   144  				],
   145  				"server_features": ["xds_v3"]
   146  			}]
   147  		}`,
   148  		"goodBootstrap": `
   149  		{
   150  			"node": {
   151  				"id": "ENVOY_NODE_ID",
   152  				"metadata": {
   153  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   154  			    }
   155  			},
   156  			"xds_servers" : [{
   157  				"server_uri": "trafficdirector.googleapis.com:443",
   158  				"channel_creds": [
   159  					{ "type": "google_default" }
   160  				],
   161  				"server_features": ["xds_v3"]
   162  			}]
   163  		}`,
   164  		"multipleXDSServers": `
   165  		{
   166  			"node": {
   167  				"id": "ENVOY_NODE_ID",
   168  				"metadata": {
   169  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   170  			    }
   171  			},
   172  			"xds_servers" : [
   173  				{
   174  					"server_uri": "trafficdirector.googleapis.com:443",
   175  					"channel_creds": [{ "type": "google_default" }],
   176  					"server_features": ["xds_v3"]
   177  				},
   178  				{
   179  					"server_uri": "backup.never.use.com:1234",
   180  					"channel_creds": [{ "type": "not-google-default" }]
   181  				}
   182  			]
   183  		}`,
   184  		"serverSupportsIgnoreResourceDeletion": `
   185  		{
   186  			"node": {
   187  				"id": "ENVOY_NODE_ID",
   188  				"metadata": {
   189  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   190  			    }
   191  			},
   192  			"xds_servers" : [{
   193  				"server_uri": "trafficdirector.googleapis.com:443",
   194  				"channel_creds": [
   195  					{ "type": "google_default" }
   196  				],
   197  				"server_features" : ["ignore_resource_deletion", "xds_v3"]
   198  			}]
   199  		}`,
   200  	}
   201  	metadata = &structpb.Struct{
   202  		Fields: map[string]*structpb.Value{
   203  			"TRAFFICDIRECTOR_GRPC_HOSTNAME": {
   204  				Kind: &structpb.Value_StringValue{StringValue: "trafficdirector"},
   205  			},
   206  		},
   207  	}
   208  	v3NodeProto = &v3corepb.Node{
   209  		Id:                   "ENVOY_NODE_ID",
   210  		Metadata:             metadata,
   211  		UserAgentName:        gRPCUserAgentName,
   212  		UserAgentVersionType: &v3corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version},
   213  		ClientFeatures:       []string{clientFeatureNoOverprovisioning, clientFeatureResourceWrapper},
   214  	}
   215  	nilCredsConfigNoServerFeatures = &Config{
   216  		XDSServer: &ServerConfig{
   217  			ServerURI: "trafficdirector.googleapis.com:443",
   218  			Creds:     ChannelCreds{Type: "insecure"},
   219  		},
   220  		NodeProto: v3NodeProto,
   221  		ClientDefaultListenerResourceNameTemplate: "%s",
   222  	}
   223  	nonNilCredsConfigV3 = &Config{
   224  		XDSServer: &ServerConfig{
   225  			ServerURI:      "trafficdirector.googleapis.com:443",
   226  			Creds:          ChannelCreds{Type: "google_default"},
   227  			ServerFeatures: []string{"xds_v3"},
   228  		},
   229  		NodeProto: v3NodeProto,
   230  		ClientDefaultListenerResourceNameTemplate: "%s",
   231  	}
   232  	nonNilCredsConfigWithDeletionIgnored = &Config{
   233  		XDSServer: &ServerConfig{
   234  			ServerURI:              "trafficdirector.googleapis.com:443",
   235  			Creds:                  ChannelCreds{Type: "google_default"},
   236  			IgnoreResourceDeletion: true,
   237  			ServerFeatures:         []string{"ignore_resource_deletion", "xds_v3"},
   238  		},
   239  		NodeProto: v3NodeProto,
   240  		ClientDefaultListenerResourceNameTemplate: "%s",
   241  	}
   242  	nonNilCredsConfigNoServerFeatures = &Config{
   243  		XDSServer: &ServerConfig{
   244  			ServerURI: "trafficdirector.googleapis.com:443",
   245  			Creds:     ChannelCreds{Type: "google_default"},
   246  		},
   247  		NodeProto: v3NodeProto,
   248  		ClientDefaultListenerResourceNameTemplate: "%s",
   249  	}
   250  )
   251  
   252  func (c *Config) compare(want *Config) error {
   253  	if diff := cmp.Diff(want, c,
   254  		cmpopts.EquateEmpty(),
   255  		cmp.Comparer(proto.Equal),
   256  		cmp.Comparer(func(a, b grpc.DialOption) bool { return (a != nil) == (b != nil) }),
   257  		cmp.Transformer("certproviderconfigstring", func(a *certprovider.BuildableConfig) string { return a.String() }),
   258  	); diff != "" {
   259  		return fmt.Errorf("unexpected diff in config (-want, +got):\n%s", diff)
   260  	}
   261  	return nil
   262  }
   263  
   264  func fileReadFromFileMap(bootstrapFileMap map[string]string, name string) ([]byte, error) {
   265  	if b, ok := bootstrapFileMap[name]; ok {
   266  		return []byte(b), nil
   267  	}
   268  	return nil, os.ErrNotExist
   269  }
   270  
   271  func setupBootstrapOverride(bootstrapFileMap map[string]string) func() {
   272  	oldFileReadFunc := bootstrapFileReadFunc
   273  	bootstrapFileReadFunc = func(filename string) ([]byte, error) {
   274  		return fileReadFromFileMap(bootstrapFileMap, filename)
   275  	}
   276  	return func() { bootstrapFileReadFunc = oldFileReadFunc }
   277  }
   278  
   279  // TODO: enable leak check for this package when
   280  // https://github.com/googleapis/google-cloud-go/issues/2417 is fixed.
   281  
   282  // This function overrides the bootstrap file NAME env variable, to test the
   283  // code that reads file with the given fileName.
   284  func testNewConfigWithFileNameEnv(t *testing.T, fileName string, wantError bool, wantConfig *Config) {
   285  	origBootstrapFileName := envconfig.XDSBootstrapFileName
   286  	envconfig.XDSBootstrapFileName = fileName
   287  	defer func() { envconfig.XDSBootstrapFileName = origBootstrapFileName }()
   288  
   289  	c, err := NewConfig()
   290  	if (err != nil) != wantError {
   291  		t.Fatalf("NewConfig() returned error %v, wantError: %v", err, wantError)
   292  	}
   293  	if wantError {
   294  		return
   295  	}
   296  	if err := c.compare(wantConfig); err != nil {
   297  		t.Fatal(err)
   298  	}
   299  }
   300  
   301  // This function overrides the bootstrap file CONTENT env variable, to test the
   302  // code that uses the content from env directly.
   303  func testNewConfigWithFileContentEnv(t *testing.T, fileName string, wantError bool, wantConfig *Config) {
   304  	t.Helper()
   305  	b, err := bootstrapFileReadFunc(fileName)
   306  	if err != nil {
   307  		t.Skip(err)
   308  	}
   309  	origBootstrapContent := envconfig.XDSBootstrapFileContent
   310  	envconfig.XDSBootstrapFileContent = string(b)
   311  	defer func() { envconfig.XDSBootstrapFileContent = origBootstrapContent }()
   312  
   313  	c, err := NewConfig()
   314  	if (err != nil) != wantError {
   315  		t.Fatalf("NewConfig() returned error %v, wantError: %v", err, wantError)
   316  	}
   317  	if wantError {
   318  		return
   319  	}
   320  	if err := c.compare(wantConfig); err != nil {
   321  		t.Fatal(err)
   322  	}
   323  }
   324  
   325  // TestNewConfigV3ProtoFailure exercises the functionality in NewConfig with
   326  // different bootstrap file contents which are expected to fail.
   327  func TestNewConfigV3ProtoFailure(t *testing.T) {
   328  	bootstrapFileMap := map[string]string{
   329  		"empty":          "",
   330  		"badJSON":        `["test": 123]`,
   331  		"noBalancerName": `{"node": {"id": "ENVOY_NODE_ID"}}`,
   332  		"emptyXdsServer": `
   333  		{
   334  			"node": {
   335  				"id": "ENVOY_NODE_ID",
   336  				"metadata": {
   337  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   338  			    }
   339  			}
   340  		}`,
   341  		"emptyChannelCreds": `
   342  		{
   343  			"node": {
   344  				"id": "ENVOY_NODE_ID",
   345  				"metadata": {
   346  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   347  			    }
   348  			},
   349  			"xds_servers" : [{
   350  				"server_uri": "trafficdirector.googleapis.com:443"
   351  			}]
   352  		}`,
   353  		"nonGoogleDefaultCreds": `
   354  		{
   355  			"node": {
   356  				"id": "ENVOY_NODE_ID",
   357  				"metadata": {
   358  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   359  			    }
   360  			},
   361  			"xds_servers" : [{
   362  				"server_uri": "trafficdirector.googleapis.com:443",
   363  				"channel_creds": [
   364  					{ "type": "not-google-default" }
   365  				]
   366  			}]
   367  		}`,
   368  	}
   369  	cancel := setupBootstrapOverride(bootstrapFileMap)
   370  	defer cancel()
   371  
   372  	tests := []struct {
   373  		name      string
   374  		wantError bool
   375  	}{
   376  		{"nonExistentBootstrapFile", true},
   377  		{"empty", true},
   378  		{"badJSON", true},
   379  		{"noBalancerName", true},
   380  		{"emptyXdsServer", true},
   381  	}
   382  
   383  	for _, test := range tests {
   384  		t.Run(test.name, func(t *testing.T) {
   385  			testNewConfigWithFileNameEnv(t, test.name, true, nil)
   386  			testNewConfigWithFileContentEnv(t, test.name, true, nil)
   387  		})
   388  	}
   389  }
   390  
   391  // TestNewConfigV3ProtoSuccess exercises the functionality in NewConfig with
   392  // different bootstrap file contents. It overrides the fileReadFunc by returning
   393  // bootstrap file contents defined in this test, instead of reading from a file.
   394  func TestNewConfigV3ProtoSuccess(t *testing.T) {
   395  	cancel := setupBootstrapOverride(v3BootstrapFileMap)
   396  	defer cancel()
   397  
   398  	tests := []struct {
   399  		name       string
   400  		wantConfig *Config
   401  	}{
   402  		{
   403  			"emptyNodeProto", &Config{
   404  				XDSServer: &ServerConfig{
   405  					ServerURI: "trafficdirector.googleapis.com:443",
   406  					Creds:     ChannelCreds{Type: "insecure"},
   407  				},
   408  				NodeProto: &v3corepb.Node{
   409  					UserAgentName:        gRPCUserAgentName,
   410  					UserAgentVersionType: &v3corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version},
   411  					ClientFeatures:       []string{clientFeatureNoOverprovisioning, clientFeatureResourceWrapper},
   412  				},
   413  				ClientDefaultListenerResourceNameTemplate: "%s",
   414  			},
   415  		},
   416  		{"unknownTopLevelFieldInFile", nilCredsConfigNoServerFeatures},
   417  		{"unknownFieldInNodeProto", nilCredsConfigNoServerFeatures},
   418  		{"unknownFieldInXdsServer", nilCredsConfigNoServerFeatures},
   419  		{"multipleChannelCreds", nonNilCredsConfigV3},
   420  		{"goodBootstrap", nonNilCredsConfigV3},
   421  		{"multipleXDSServers", nonNilCredsConfigV3},
   422  		{"serverSupportsIgnoreResourceDeletion", nonNilCredsConfigWithDeletionIgnored},
   423  	}
   424  
   425  	for _, test := range tests {
   426  		t.Run(test.name, func(t *testing.T) {
   427  			testNewConfigWithFileNameEnv(t, test.name, false, test.wantConfig)
   428  			testNewConfigWithFileContentEnv(t, test.name, false, test.wantConfig)
   429  		})
   430  	}
   431  }
   432  
   433  // TestNewConfigBootstrapEnvPriority tests that the two env variables are read
   434  // in correct priority.
   435  //
   436  // "GRPC_XDS_BOOTSTRAP" which specifies the file name containing the bootstrap
   437  // configuration takes precedence over "GRPC_XDS_BOOTSTRAP_CONFIG", which
   438  // directly specifies the bootstrap configuration in itself.
   439  func TestNewConfigBootstrapEnvPriority(t *testing.T) {
   440  	oldFileReadFunc := bootstrapFileReadFunc
   441  	bootstrapFileReadFunc = func(filename string) ([]byte, error) {
   442  		return fileReadFromFileMap(v3BootstrapFileMap, filename)
   443  	}
   444  	defer func() { bootstrapFileReadFunc = oldFileReadFunc }()
   445  
   446  	goodFileName1 := "serverFeaturesIncludesXDSV3"
   447  	goodConfig1 := nonNilCredsConfigV3
   448  
   449  	goodFileName2 := "serverFeaturesExcludesXDSV3"
   450  	goodFileContent2 := v3BootstrapFileMap[goodFileName2]
   451  	goodConfig2 := nonNilCredsConfigNoServerFeatures
   452  
   453  	origBootstrapFileName := envconfig.XDSBootstrapFileName
   454  	envconfig.XDSBootstrapFileName = ""
   455  	defer func() { envconfig.XDSBootstrapFileName = origBootstrapFileName }()
   456  
   457  	origBootstrapContent := envconfig.XDSBootstrapFileContent
   458  	envconfig.XDSBootstrapFileContent = ""
   459  	defer func() { envconfig.XDSBootstrapFileContent = origBootstrapContent }()
   460  
   461  	// When both env variables are empty, NewConfig should fail.
   462  	if _, err := NewConfig(); err == nil {
   463  		t.Errorf("NewConfig() returned nil error, expected to fail")
   464  	}
   465  
   466  	// When one of them is set, it should be used.
   467  	envconfig.XDSBootstrapFileName = goodFileName1
   468  	envconfig.XDSBootstrapFileContent = ""
   469  	c, err := NewConfig()
   470  	if err != nil {
   471  		t.Errorf("NewConfig() failed: %v", err)
   472  	}
   473  	if err := c.compare(goodConfig1); err != nil {
   474  		t.Error(err)
   475  	}
   476  
   477  	envconfig.XDSBootstrapFileName = ""
   478  	envconfig.XDSBootstrapFileContent = goodFileContent2
   479  	c, err = NewConfig()
   480  	if err != nil {
   481  		t.Errorf("NewConfig() failed: %v", err)
   482  	}
   483  	if err := c.compare(goodConfig2); err != nil {
   484  		t.Error(err)
   485  	}
   486  
   487  	// Set both, file name should be read.
   488  	envconfig.XDSBootstrapFileName = goodFileName1
   489  	envconfig.XDSBootstrapFileContent = goodFileContent2
   490  	c, err = NewConfig()
   491  	if err != nil {
   492  		t.Errorf("NewConfig() failed: %v", err)
   493  	}
   494  	if err := c.compare(goodConfig1); err != nil {
   495  		t.Error(err)
   496  	}
   497  }
   498  
   499  func init() {
   500  	certprovider.Register(&fakeCertProviderBuilder{})
   501  }
   502  
   503  const fakeCertProviderName = "fake-certificate-provider"
   504  
   505  // fakeCertProviderBuilder builds new instances of fakeCertProvider and
   506  // interprets the config provided to it as JSON with a single key and value.
   507  type fakeCertProviderBuilder struct{}
   508  
   509  // ParseConfig expects input in JSON format containing a map from string to
   510  // string, with a single entry and mapKey being "configKey".
   511  func (b *fakeCertProviderBuilder) ParseConfig(cfg any) (*certprovider.BuildableConfig, error) {
   512  	config, ok := cfg.(json.RawMessage)
   513  	if !ok {
   514  		return nil, fmt.Errorf("fakeCertProviderBuilder received config of type %T, want []byte", config)
   515  	}
   516  	var cfgData map[string]string
   517  	if err := json.Unmarshal(config, &cfgData); err != nil {
   518  		return nil, fmt.Errorf("fakeCertProviderBuilder config parsing failed: %v", err)
   519  	}
   520  	if len(cfgData) != 1 || cfgData["configKey"] == "" {
   521  		return nil, errors.New("fakeCertProviderBuilder received invalid config")
   522  	}
   523  	fc := &fakeStableConfig{config: cfgData}
   524  	return certprovider.NewBuildableConfig(fakeCertProviderName, fc.canonical(), func(certprovider.BuildOptions) certprovider.Provider {
   525  		return &fakeCertProvider{}
   526  	}), nil
   527  }
   528  
   529  func (b *fakeCertProviderBuilder) Name() string {
   530  	return fakeCertProviderName
   531  }
   532  
   533  type fakeStableConfig struct {
   534  	config map[string]string
   535  }
   536  
   537  func (c *fakeStableConfig) canonical() []byte {
   538  	var cfg string
   539  	for k, v := range c.config {
   540  		cfg = fmt.Sprintf("%s:%s", k, v)
   541  	}
   542  	return []byte(cfg)
   543  }
   544  
   545  // fakeCertProvider is an empty implementation of the Provider interface.
   546  type fakeCertProvider struct {
   547  	certprovider.Provider
   548  }
   549  
   550  func TestNewConfigWithCertificateProviders(t *testing.T) {
   551  	bootstrapFileMap := map[string]string{
   552  		"badJSONCertProviderConfig": `
   553  		{
   554  			"node": {
   555  				"id": "ENVOY_NODE_ID",
   556  				"metadata": {
   557  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   558  			    }
   559  			},
   560  			"xds_servers" : [{
   561  				"server_uri": "trafficdirector.googleapis.com:443",
   562  				"channel_creds": [
   563  					{ "type": "google_default" }
   564  				],
   565  				"server_features" : ["foo", "bar", "xds_v3"],
   566  			}],
   567  			"certificate_providers": "bad JSON"
   568  		}`,
   569  		"allUnknownCertProviders": `
   570  		{
   571  			"node": {
   572  				"id": "ENVOY_NODE_ID",
   573  				"metadata": {
   574  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   575  			    }
   576  			},
   577  			"xds_servers" : [{
   578  				"server_uri": "trafficdirector.googleapis.com:443",
   579  				"channel_creds": [
   580  					{ "type": "google_default" }
   581  				],
   582  				"server_features" : ["xds_v3"]
   583  			}],
   584  			"certificate_providers": {
   585  				"unknownProviderInstance1": {
   586  					"plugin_name": "foo",
   587  					"config": {"foo": "bar"}
   588  				},
   589  				"unknownProviderInstance2": {
   590  					"plugin_name": "bar",
   591  					"config": {"foo": "bar"}
   592  				}
   593  			}
   594  		}`,
   595  		"badCertProviderConfig": `
   596  		{
   597  			"node": {
   598  				"id": "ENVOY_NODE_ID",
   599  				"metadata": {
   600  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   601  			    }
   602  			},
   603  			"xds_servers" : [{
   604  				"server_uri": "trafficdirector.googleapis.com:443",
   605  				"channel_creds": [
   606  					{ "type": "google_default" }
   607  				],
   608  				"server_features" : ["xds_v3"],
   609  			}],
   610  			"certificate_providers": {
   611  				"unknownProviderInstance": {
   612  					"plugin_name": "foo",
   613  					"config": {"foo": "bar"}
   614  				},
   615  				"fakeProviderInstanceBad": {
   616  					"plugin_name": "fake-certificate-provider",
   617  					"config": {"configKey": 666}
   618  				}
   619  			}
   620  		}`,
   621  		"goodCertProviderConfig": `
   622  		{
   623  			"node": {
   624  				"id": "ENVOY_NODE_ID",
   625  				"metadata": {
   626  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   627  			    }
   628  			},
   629  			"xds_servers" : [{
   630  				"server_uri": "trafficdirector.googleapis.com:443",
   631  				"channel_creds": [
   632  					{ "type": "insecure" }
   633  				],
   634  				"server_features" : ["xds_v3"]
   635  			}],
   636  			"certificate_providers": {
   637  				"unknownProviderInstance": {
   638  					"plugin_name": "foo",
   639  					"config": {"foo": "bar"}
   640  				},
   641  				"fakeProviderInstance": {
   642  					"plugin_name": "fake-certificate-provider",
   643  					"config": {"configKey": "configValue"}
   644  				}
   645  			}
   646  		}`,
   647  	}
   648  
   649  	getBuilder := internal.GetCertificateProviderBuilder.(func(string) certprovider.Builder)
   650  	parser := getBuilder(fakeCertProviderName)
   651  	if parser == nil {
   652  		t.Fatalf("missing certprovider plugin %q", fakeCertProviderName)
   653  	}
   654  	wantCfg, err := parser.ParseConfig(json.RawMessage(`{"configKey": "configValue"}`))
   655  	if err != nil {
   656  		t.Fatalf("config parsing for plugin %q failed: %v", fakeCertProviderName, err)
   657  	}
   658  
   659  	cancel := setupBootstrapOverride(bootstrapFileMap)
   660  	defer cancel()
   661  
   662  	// Cannot use xdstestutils.ServerConfigForAddress here, as it would lead to
   663  	// a cyclic dependency.
   664  	jsonCfg := `{
   665  		"server_uri": "trafficdirector.googleapis.com:443",
   666  		"channel_creds": [{"type": "insecure"}],
   667  		"server_features": ["xds_v3"]
   668  	}`
   669  	serverCfg, err := ServerConfigFromJSON([]byte(jsonCfg))
   670  	if err != nil {
   671  		t.Fatalf("Failed to create server config from JSON %s: %v", jsonCfg, err)
   672  	}
   673  	goodConfig := &Config{
   674  		XDSServer: serverCfg,
   675  		NodeProto: v3NodeProto,
   676  		CertProviderConfigs: map[string]*certprovider.BuildableConfig{
   677  			"fakeProviderInstance": wantCfg,
   678  		},
   679  		ClientDefaultListenerResourceNameTemplate: "%s",
   680  	}
   681  	tests := []struct {
   682  		name       string
   683  		wantConfig *Config
   684  		wantErr    bool
   685  	}{
   686  		{
   687  			name:    "badJSONCertProviderConfig",
   688  			wantErr: true,
   689  		},
   690  		{
   691  
   692  			name:    "badCertProviderConfig",
   693  			wantErr: true,
   694  		},
   695  		{
   696  
   697  			name:       "allUnknownCertProviders",
   698  			wantConfig: nonNilCredsConfigV3,
   699  		},
   700  		{
   701  			name:       "goodCertProviderConfig",
   702  			wantConfig: goodConfig,
   703  		},
   704  	}
   705  
   706  	for _, test := range tests {
   707  		t.Run(test.name, func(t *testing.T) {
   708  			testNewConfigWithFileNameEnv(t, test.name, test.wantErr, test.wantConfig)
   709  			testNewConfigWithFileContentEnv(t, test.name, test.wantErr, test.wantConfig)
   710  		})
   711  	}
   712  }
   713  
   714  func TestNewConfigWithServerListenerResourceNameTemplate(t *testing.T) {
   715  	cancel := setupBootstrapOverride(map[string]string{
   716  		"badServerListenerResourceNameTemplate:": `
   717  		{
   718  			"node": {
   719  				"id": "ENVOY_NODE_ID",
   720  				"metadata": {
   721  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   722  			    }
   723  			},
   724  			"xds_servers" : [{
   725  				"server_uri": "trafficdirector.googleapis.com:443",
   726  				"channel_creds": [
   727  					{ "type": "google_default" }
   728  				]
   729  			}],
   730  			"server_listener_resource_name_template": 123456789
   731  		}`,
   732  		"goodServerListenerResourceNameTemplate": `
   733  		{
   734  			"node": {
   735  				"id": "ENVOY_NODE_ID",
   736  				"metadata": {
   737  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   738  			    }
   739  			},
   740  			"xds_servers" : [{
   741  				"server_uri": "trafficdirector.googleapis.com:443",
   742  				"channel_creds": [
   743  					{ "type": "google_default" }
   744  				]
   745  			}],
   746  			"server_listener_resource_name_template": "grpc/server?xds.resource.listening_address=%s"
   747  		}`,
   748  	})
   749  	defer cancel()
   750  
   751  	tests := []struct {
   752  		name       string
   753  		wantConfig *Config
   754  		wantErr    bool
   755  	}{
   756  		{
   757  			name:    "badServerListenerResourceNameTemplate",
   758  			wantErr: true,
   759  		},
   760  		{
   761  			name: "goodServerListenerResourceNameTemplate",
   762  			wantConfig: &Config{
   763  				XDSServer: &ServerConfig{
   764  					ServerURI: "trafficdirector.googleapis.com:443",
   765  					Creds:     ChannelCreds{Type: "google_default"},
   766  				},
   767  				NodeProto:                                 v3NodeProto,
   768  				ServerListenerResourceNameTemplate:        "grpc/server?xds.resource.listening_address=%s",
   769  				ClientDefaultListenerResourceNameTemplate: "%s",
   770  			},
   771  		},
   772  	}
   773  
   774  	for _, test := range tests {
   775  		t.Run(test.name, func(t *testing.T) {
   776  			testNewConfigWithFileNameEnv(t, test.name, test.wantErr, test.wantConfig)
   777  			testNewConfigWithFileContentEnv(t, test.name, test.wantErr, test.wantConfig)
   778  		})
   779  	}
   780  }
   781  
   782  func TestNewConfigWithFederation(t *testing.T) {
   783  	cancel := setupBootstrapOverride(map[string]string{
   784  		"badClientListenerResourceNameTemplate": `
   785  		{
   786  			"node": { "id": "ENVOY_NODE_ID" },
   787  			"xds_servers" : [{
   788  				"server_uri": "trafficdirector.googleapis.com:443"
   789  			}],
   790  			"client_default_listener_resource_name_template": 123456789
   791  		}`,
   792  		"badClientListenerResourceNameTemplatePerAuthority": `
   793  		{
   794  			"node": { "id": "ENVOY_NODE_ID" },
   795  			"xds_servers" : [{
   796  				"server_uri": "trafficdirector.googleapis.com:443",
   797  				"channel_creds": [ { "type": "google_default" } ]
   798  			}],
   799  			"authorities": {
   800  				"xds.td.com": {
   801  					"client_listener_resource_name_template": "some/template/%s",
   802  					"xds_servers": [{
   803  						"server_uri": "td.com",
   804  						"channel_creds": [ { "type": "google_default" } ],
   805  						"server_features" : ["foo", "bar", "xds_v3"]
   806  					}]
   807  				}
   808  			}
   809  		}`,
   810  		"good": `
   811  		{
   812  			"node": {
   813  				"id": "ENVOY_NODE_ID",
   814  				"metadata": {
   815  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   816  			    }
   817  			},
   818  			"xds_servers" : [{
   819  				"server_uri": "trafficdirector.googleapis.com:443",
   820  				"channel_creds": [ { "type": "google_default" } ]
   821  			}],
   822  			"server_listener_resource_name_template": "xdstp://xds.example.com/envoy.config.listener.v3.Listener/grpc/server?listening_address=%s",
   823  			"client_default_listener_resource_name_template": "xdstp://xds.example.com/envoy.config.listener.v3.Listener/%s",
   824  			"authorities": {
   825  				"xds.td.com": {
   826  					"client_listener_resource_name_template": "xdstp://xds.td.com/envoy.config.listener.v3.Listener/%s",
   827  					"xds_servers": [{
   828  						"server_uri": "td.com",
   829  						"channel_creds": [ { "type": "google_default" } ],
   830  						"server_features" : ["xds_v3"]
   831  					}]
   832  				}
   833  			}
   834  		}`,
   835  		// If client_default_listener_resource_name_template is not set, it
   836  		// defaults to "%s".
   837  		"goodWithDefaultDefaultClientListenerTemplate": `
   838  		{
   839  			"node": {
   840  				"id": "ENVOY_NODE_ID",
   841  				"metadata": {
   842  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   843  			    }
   844  			},
   845  			"xds_servers" : [{
   846  				"server_uri": "trafficdirector.googleapis.com:443",
   847  				"channel_creds": [ { "type": "google_default" } ]
   848  			}]
   849  		}`,
   850  		// If client_listener_resource_name_template in authority is not set, it
   851  		// defaults to
   852  		// "xdstp://<authority_name>/envoy.config.listener.v3.Listener/%s".
   853  		"goodWithDefaultClientListenerTemplatePerAuthority": `
   854  		{
   855  			"node": {
   856  				"id": "ENVOY_NODE_ID",
   857  				"metadata": {
   858  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   859  			    }
   860  			},
   861  			"xds_servers" : [{
   862  				"server_uri": "trafficdirector.googleapis.com:443",
   863  				"channel_creds": [ { "type": "google_default" } ]
   864  			}],
   865  			"client_default_listener_resource_name_template": "xdstp://xds.example.com/envoy.config.listener.v3.Listener/%s",
   866  			"authorities": {
   867  				"xds.td.com": { },
   868  				"#.com": { }
   869  			}
   870  		}`,
   871  		// It's OK for an authority to not have servers. The top-level server
   872  		// will be used.
   873  		"goodWithNoServerPerAuthority": `
   874  		{
   875  			"node": {
   876  				"id": "ENVOY_NODE_ID",
   877  				"metadata": {
   878  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   879  			    }
   880  			},
   881  			"xds_servers" : [{
   882  				"server_uri": "trafficdirector.googleapis.com:443",
   883  				"channel_creds": [ { "type": "google_default" } ]
   884  			}],
   885  			"client_default_listener_resource_name_template": "xdstp://xds.example.com/envoy.config.listener.v3.Listener/%s",
   886  			"authorities": {
   887  				"xds.td.com": {
   888  					"client_listener_resource_name_template": "xdstp://xds.td.com/envoy.config.listener.v3.Listener/%s"
   889  				}
   890  			}
   891  		}`,
   892  	})
   893  	defer cancel()
   894  
   895  	tests := []struct {
   896  		name       string
   897  		wantConfig *Config
   898  		wantErr    bool
   899  	}{
   900  		{
   901  			name:    "badClientListenerResourceNameTemplate",
   902  			wantErr: true,
   903  		},
   904  		{
   905  			name:    "badClientListenerResourceNameTemplatePerAuthority",
   906  			wantErr: true,
   907  		},
   908  		{
   909  			name: "good",
   910  			wantConfig: &Config{
   911  				XDSServer: &ServerConfig{
   912  					ServerURI: "trafficdirector.googleapis.com:443",
   913  					Creds:     ChannelCreds{Type: "google_default"},
   914  				},
   915  				NodeProto:                                 v3NodeProto,
   916  				ServerListenerResourceNameTemplate:        "xdstp://xds.example.com/envoy.config.listener.v3.Listener/grpc/server?listening_address=%s",
   917  				ClientDefaultListenerResourceNameTemplate: "xdstp://xds.example.com/envoy.config.listener.v3.Listener/%s",
   918  				Authorities: map[string]*Authority{
   919  					"xds.td.com": {
   920  						ClientListenerResourceNameTemplate: "xdstp://xds.td.com/envoy.config.listener.v3.Listener/%s",
   921  						XDSServer: &ServerConfig{
   922  							ServerURI:      "td.com",
   923  							Creds:          ChannelCreds{Type: "google_default"},
   924  							ServerFeatures: []string{"xds_v3"},
   925  						},
   926  					},
   927  				},
   928  			},
   929  		},
   930  		{
   931  			name: "goodWithDefaultDefaultClientListenerTemplate",
   932  			wantConfig: &Config{
   933  				XDSServer: &ServerConfig{
   934  					ServerURI: "trafficdirector.googleapis.com:443",
   935  					Creds:     ChannelCreds{Type: "google_default"},
   936  				},
   937  				NodeProto: v3NodeProto,
   938  				ClientDefaultListenerResourceNameTemplate: "%s",
   939  			},
   940  		},
   941  		{
   942  			name: "goodWithDefaultClientListenerTemplatePerAuthority",
   943  			wantConfig: &Config{
   944  				XDSServer: &ServerConfig{
   945  					ServerURI: "trafficdirector.googleapis.com:443",
   946  					Creds:     ChannelCreds{Type: "google_default"},
   947  				},
   948  				NodeProto: v3NodeProto,
   949  				ClientDefaultListenerResourceNameTemplate: "xdstp://xds.example.com/envoy.config.listener.v3.Listener/%s",
   950  				Authorities: map[string]*Authority{
   951  					"xds.td.com": {
   952  						ClientListenerResourceNameTemplate: "xdstp://xds.td.com/envoy.config.listener.v3.Listener/%s",
   953  					},
   954  					"#.com": {
   955  						ClientListenerResourceNameTemplate: "xdstp://%23.com/envoy.config.listener.v3.Listener/%s",
   956  					},
   957  				},
   958  			},
   959  		},
   960  		{
   961  			name: "goodWithNoServerPerAuthority",
   962  			wantConfig: &Config{
   963  				XDSServer: &ServerConfig{
   964  					ServerURI: "trafficdirector.googleapis.com:443",
   965  					Creds:     ChannelCreds{Type: "google_default"},
   966  				},
   967  				NodeProto: v3NodeProto,
   968  				ClientDefaultListenerResourceNameTemplate: "xdstp://xds.example.com/envoy.config.listener.v3.Listener/%s",
   969  				Authorities: map[string]*Authority{
   970  					"xds.td.com": {
   971  						ClientListenerResourceNameTemplate: "xdstp://xds.td.com/envoy.config.listener.v3.Listener/%s",
   972  					},
   973  				},
   974  			},
   975  		},
   976  	}
   977  
   978  	for _, test := range tests {
   979  		t.Run(test.name, func(t *testing.T) {
   980  			testNewConfigWithFileNameEnv(t, test.name, test.wantErr, test.wantConfig)
   981  			testNewConfigWithFileContentEnv(t, test.name, test.wantErr, test.wantConfig)
   982  		})
   983  	}
   984  }
   985  
   986  func TestServerConfigMarshalAndUnmarshal(t *testing.T) {
   987  	jsonCfg := `{
   988  		"server_uri": "test-server",
   989  		"channel_creds": [{"type": "insecure"}],
   990  		"server_features": ["xds_v3"]
   991  	}`
   992  	origConfig, err := ServerConfigFromJSON([]byte(jsonCfg))
   993  	if err != nil {
   994  		t.Fatalf("Failed to create server config from JSON %s: %v", jsonCfg, err)
   995  	}
   996  	bs, err := json.Marshal(origConfig)
   997  	if err != nil {
   998  		t.Fatalf("failed to marshal: %v", err)
   999  	}
  1000  
  1001  	unmarshaledConfig := new(ServerConfig)
  1002  	if err := json.Unmarshal(bs, unmarshaledConfig); err != nil {
  1003  		t.Fatalf("failed to unmarshal: %v", err)
  1004  	}
  1005  	if diff := cmp.Diff(origConfig, unmarshaledConfig); diff != "" {
  1006  		t.Fatalf("Unexpected diff in server config (-want, +got):\n%s", diff)
  1007  	}
  1008  }
  1009  
  1010  func TestDefaultBundles(t *testing.T) {
  1011  	tests := []string{"google_default", "insecure", "tls"}
  1012  
  1013  	for _, typename := range tests {
  1014  		t.Run(typename, func(t *testing.T) {
  1015  			if c := bootstrap.GetCredentials(typename); c == nil {
  1016  				t.Errorf(`bootstrap.GetCredentials(%s) credential is nil, want non-nil`, typename)
  1017  			}
  1018  		})
  1019  	}
  1020  }
  1021  

View as plain text