@@ -31,6 +31,7 @@ import (
3131 "strings"
3232
3333 "google.golang.org/grpc"
34+ "google.golang.org/grpc/credentials"
3435 "google.golang.org/grpc/credentials/tls/certprovider"
3536 "google.golang.org/grpc/internal"
3637 "google.golang.org/grpc/internal/envconfig"
@@ -83,6 +84,41 @@ func (cc ChannelCreds) String() string {
8384 return cc .Type + "-" + string (b )
8485}
8586
87+ // CallCredsConfig contains the call credentials configuration to be used on
88+ // RPCs to the management server.
89+ type CallCredsConfig struct {
90+ // Type contains a name identifying the call credentials type.
91+ Type string `json:"type,omitempty"`
92+ // Config contains the JSON configuration for this call credentials.
93+ // Optional as per gRFC A97.
94+ Config json.RawMessage `json:"config,omitempty"`
95+ }
96+
97+ // Equal reports whether cc and other are considered equal.
98+ func (cc CallCredsConfig ) Equal (other CallCredsConfig ) bool {
99+ return cc .Type == other .Type && bytes .Equal (cc .Config , other .Config )
100+ }
101+
102+ func (cc CallCredsConfig ) String () string {
103+ if len (cc .Config ) == 0 {
104+ return cc .Type
105+ }
106+ // We do not expect the Marshal call to fail since we wrote to cc.Config.
107+ b , _ := json .Marshal (cc .Config )
108+ return cc .Type + "-" + string (b )
109+ }
110+
111+ // CallCredsConfigs represents a collection of call credentials configurations.
112+ type CallCredsConfigs []CallCredsConfig
113+
114+ func (ccs CallCredsConfigs ) String () string {
115+ var creds []string
116+ for _ , cc := range ccs {
117+ creds = append (creds , cc .String ())
118+ }
119+ return strings .Join (creds , "," )
120+ }
121+
86122// ServerConfigs represents a collection of server configurations.
87123type ServerConfigs []* ServerConfig
88124
@@ -163,16 +199,20 @@ func (a *Authority) Equal(other *Authority) bool {
163199
164200// ServerConfig contains the configuration to connect to a server.
165201type ServerConfig struct {
166- serverURI string
167- channelCreds []ChannelCreds
168- serverFeatures []string
202+ serverURI string
203+ // TODO: rename ChannelCreds to ChannelCredsConfigs for consistency with
204+ // CallCredsConfigs.
205+ channelCreds []ChannelCreds
206+ callCredsConfigs []CallCredsConfig
207+ serverFeatures []string
169208
170209 // As part of unmarshalling the JSON config into this struct, we ensure that
171210 // the credentials config is valid by building an instance of the specified
172211 // credentials and store it here for easy access.
173- selectedCreds ChannelCreds
174- credsDialOption grpc.DialOption
175- extraDialOptions []grpc.DialOption
212+ selectedChannelCreds ChannelCreds
213+ selectedCallCreds []credentials.PerRPCCredentials
214+ credsDialOption grpc.DialOption
215+ extraDialOptions []grpc.DialOption
176216
177217 cleanups []func ()
178218}
@@ -194,6 +234,11 @@ func (sc *ServerConfig) ServerFeatures() []string {
194234 return sc .serverFeatures
195235}
196236
237+ // CallCredsConfigs returns the call credentials configuration for this server.
238+ func (sc * ServerConfig ) CallCredsConfigs () CallCredsConfigs {
239+ return sc .callCredsConfigs
240+ }
241+
197242// ServerFeaturesIgnoreResourceDeletion returns true if this server supports a
198243// feature where the xDS client can ignore resource deletions from this server,
199244// as described in gRFC A53.
@@ -211,10 +256,10 @@ func (sc *ServerConfig) ServerFeaturesIgnoreResourceDeletion() bool {
211256 return false
212257}
213258
214- // SelectedCreds returns the selected credentials configuration for
259+ // SelectedChannelCreds returns the selected credentials configuration for
215260// communicating with this server.
216- func (sc * ServerConfig ) SelectedCreds () ChannelCreds {
217- return sc .selectedCreds
261+ func (sc * ServerConfig ) SelectedChannelCreds () ChannelCreds {
262+ return sc .selectedChannelCreds
218263}
219264
220265// DialOptions returns a slice of all the configured dial options for this
@@ -245,9 +290,9 @@ func (sc *ServerConfig) Equal(other *ServerConfig) bool {
245290 return false
246291 case ! slices .EqualFunc (sc .channelCreds , other .channelCreds , func (a , b ChannelCreds ) bool { return a .Equal (b ) }):
247292 return false
248- case ! slices .Equal (sc .serverFeatures , other .serverFeatures ):
293+ case ! slices .EqualFunc (sc .callCredsConfigs , other .callCredsConfigs , func ( a , b CallCredsConfig ) bool { return a . Equal ( b ) } ):
249294 return false
250- case ! sc . selectedCreds . Equal (other .selectedCreds ):
295+ case ! slices . Equal (sc . serverFeatures , other .serverFeatures ):
251296 return false
252297 }
253298 return true
@@ -256,25 +301,27 @@ func (sc *ServerConfig) Equal(other *ServerConfig) bool {
256301// String returns the string representation of the ServerConfig.
257302func (sc * ServerConfig ) String () string {
258303 if len (sc .serverFeatures ) == 0 {
259- return fmt . Sprintf ( "%s-%s" , sc .serverURI , sc .selectedCreds .String ())
304+ return strings . Join ([] string { sc . serverURI , sc .selectedChannelCreds . String () , sc .CallCredsConfigs () .String ()}, "-" )
260305 }
261306 features := strings .Join (sc .serverFeatures , "-" )
262- return strings .Join ([]string {sc .serverURI , sc .selectedCreds .String (), features }, "-" )
307+ return strings .Join ([]string {sc .serverURI , sc .selectedChannelCreds .String (), features , sc . CallCredsConfigs (). String () }, "-" )
263308}
264309
265310// The following fields correspond 1:1 with the JSON schema for ServerConfig.
266311type serverConfigJSON struct {
267- ServerURI string `json:"server_uri,omitempty"`
268- ChannelCreds []ChannelCreds `json:"channel_creds,omitempty"`
269- ServerFeatures []string `json:"server_features,omitempty"`
312+ ServerURI string `json:"server_uri,omitempty"`
313+ ChannelCreds []ChannelCreds `json:"channel_creds,omitempty"`
314+ CallCredsConfigs []CallCredsConfig `json:"call_creds,omitempty"`
315+ ServerFeatures []string `json:"server_features,omitempty"`
270316}
271317
272318// MarshalJSON returns marshaled JSON bytes corresponding to this server config.
273319func (sc * ServerConfig ) MarshalJSON () ([]byte , error ) {
274320 server := & serverConfigJSON {
275- ServerURI : sc .serverURI ,
276- ChannelCreds : sc .channelCreds ,
277- ServerFeatures : sc .serverFeatures ,
321+ ServerURI : sc .serverURI ,
322+ ChannelCreds : sc .channelCreds ,
323+ CallCredsConfigs : sc .callCredsConfigs ,
324+ ServerFeatures : sc .serverFeatures ,
278325 }
279326 return json .Marshal (server )
280327}
@@ -294,26 +341,48 @@ func (sc *ServerConfig) UnmarshalJSON(data []byte) error {
294341
295342 sc .serverURI = server .ServerURI
296343 sc .channelCreds = server .ChannelCreds
344+ sc .callCredsConfigs = server .CallCredsConfigs
297345 sc .serverFeatures = server .ServerFeatures
298346
299347 for _ , cc := range server .ChannelCreds {
300348 // We stop at the first credential type that we support.
301- c := bootstrap .GetCredentials (cc .Type )
349+ c := bootstrap .GetChannelCredentials (cc .Type )
302350 if c == nil {
303351 continue
304352 }
305353 bundle , cancel , err := c .Build (cc .Config )
306354 if err != nil {
307355 return fmt .Errorf ("failed to build credentials bundle from bootstrap for %q: %v" , cc .Type , err )
308356 }
309- sc .selectedCreds = cc
357+ sc .selectedChannelCreds = cc
310358 sc .credsDialOption = grpc .WithCredentialsBundle (bundle )
311359 if d , ok := bundle .(extraDialOptions ); ok {
312360 sc .extraDialOptions = d .DialOptions ()
313361 }
314362 sc .cleanups = append (sc .cleanups , cancel )
315363 break
316364 }
365+
366+ if envconfig .XDSBootstrapCallCredsEnabled {
367+ // Process call credentials - unlike channel creds, we use ALL supported
368+ // types. Also, call credentials are optional as per gRFC A97.
369+ for _ , cfg := range server .CallCredsConfigs {
370+ c := bootstrap .GetCallCredentials (cfg .Type )
371+ if c == nil {
372+ // Skip unsupported call credential types (don't fail bootstrap).
373+ continue
374+ }
375+ callCreds , cancel , err := c .Build (cfg .Config )
376+ if err != nil {
377+ // Call credential validation failed - this should fail bootstrap.
378+ return fmt .Errorf ("failed to build call credentials from bootstrap for %q: %v" , cfg .Type , err )
379+ }
380+ sc .selectedCallCreds = append (sc .selectedCallCreds , callCreds )
381+ sc .extraDialOptions = append (sc .extraDialOptions , grpc .WithPerRPCCredentials (callCreds ))
382+ sc .cleanups = append (sc .cleanups , cancel )
383+ }
384+ }
385+
317386 if sc .serverURI == "" {
318387 return fmt .Errorf ("xds: `server_uri` field in server config cannot be empty: %s" , string (data ))
319388 }
@@ -333,6 +402,9 @@ type ServerConfigTestingOptions struct {
333402 // ChannelCreds contains a list of channel credentials to use when talking
334403 // to this server. If unspecified, `insecure` credentials will be used.
335404 ChannelCreds []ChannelCreds
405+ // CallCredsConfigs contains a list of call credentials to use for individual RPCs
406+ // to this server. Optional.
407+ CallCredsConfigs []CallCredsConfig
336408 // ServerFeatures represents the list of features supported by this server.
337409 ServerFeatures []string
338410}
@@ -347,9 +419,10 @@ func ServerConfigForTesting(opts ServerConfigTestingOptions) (*ServerConfig, err
347419 cc = []ChannelCreds {{Type : "insecure" }}
348420 }
349421 scInternal := & serverConfigJSON {
350- ServerURI : opts .URI ,
351- ChannelCreds : cc ,
352- ServerFeatures : opts .ServerFeatures ,
422+ ServerURI : opts .URI ,
423+ ChannelCreds : cc ,
424+ CallCredsConfigs : opts .CallCredsConfigs ,
425+ ServerFeatures : opts .ServerFeatures ,
353426 }
354427 scJSON , err := json .Marshal (scInternal )
355428 if err != nil {
0 commit comments