Skip to content

Commit cadae08

Browse files
Cherry-pick #8536 to v1.77.x (#8691)
Original PR: #8536 RELEASE NOTES: - xds: add support for loading a JWT from file and use it as Call Credentials (A97). To enable this feature, set the environment variable `GRPC_EXPERIMENTAL_XDS_BOOTSTRAP_CALL_CREDS` to `true` (case insensitive). Co-authored-by: Dimitar Pavlov <[email protected]>
1 parent 4288cfc commit cadae08

File tree

12 files changed

+988
-107
lines changed

12 files changed

+988
-107
lines changed

internal/envconfig/xds.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,9 @@ var (
7474
// For more details, see:
7575
// https://github.com/grpc/proposal/blob/master/A86-xds-http-connect.md
7676
XDSHTTPConnectEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_HTTP_CONNECT", false)
77+
78+
// XDSBootstrapCallCredsEnabled controls if call credentials can be used in
79+
// xDS bootstrap configuration via the `call_creds` field. For more details,
80+
// see: https://github.com/grpc/proposal/blob/master/A97-xds-jwt-call-creds.md
81+
XDSBootstrapCallCredsEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_BOOTSTRAP_CALL_CREDS", false)
7782
)

internal/xds/bootstrap/bootstrap.go

Lines changed: 97 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
87123
type 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.
165201
type 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.
257302
func (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.
266311
type 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.
273319
func (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

Comments
 (0)