-
Notifications
You must be signed in to change notification settings - Fork 4.6k
xds bootstrap: enable using JWT Call Credentials (part 2 for A97) #8536
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 42 commits
6e63daa
3268ea5
b18a1f5
eb391af
d43893a
167b86e
439d28c
b36d4b6
26e0451
da2de8c
51ce34c
f87f1f2
15dd057
54cbbcb
9c5035d
ec915dc
1d95fa2
a797ed9
fd388d1
790a2d9
6713190
3f563eb
a38573b
12fedd5
2c80eab
b344fdb
517c6ad
34689c8
0d5d0a5
e465e7a
63ff226
5673c18
51bb1b5
41d248f
5d1d1f6
9ca7db8
ce93551
2de494e
6e5d5ce
167e6b9
6026155
3b22242
1ab6a7e
aa00501
cf10bb9
0533504
cc1aa48
851f56d
9cb3bf4
63b00bd
3dbb546
651cba1
c09c763
deaa239
6a9dc14
5cbe1a1
a1944c6
92c8a49
0dc3856
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,7 @@ import ( | |
"strings" | ||
|
||
"google.golang.org/grpc" | ||
"google.golang.org/grpc/credentials" | ||
"google.golang.org/grpc/credentials/tls/certprovider" | ||
"google.golang.org/grpc/internal" | ||
"google.golang.org/grpc/internal/envconfig" | ||
|
@@ -83,6 +84,21 @@ func (cc ChannelCreds) String() string { | |
return cc.Type + "-" + string(b) | ||
} | ||
|
||
// CallCreds contains the call credentials configuration for individual RPCs. | ||
// It implements gRFC A97 call credentials structure. | ||
type CallCreds struct { | ||
// Type contains a unique name identifying the call credentials type. | ||
|
||
// Currently only "jwt_token_file" is supported. | ||
easwars marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
Type string `json:"type,omitempty"` | ||
// Config contains the JSON configuration relevant for the call credentials. | ||
easwars marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
Config json.RawMessage `json:"config,omitempty"` | ||
} | ||
|
||
// Equal reports whether cc and other are considered equal. | ||
func (cc CallCreds) Equal(other CallCreds) bool { | ||
return cc.Type == other.Type && bytes.Equal(cc.Config, other.Config) | ||
} | ||
|
||
// ServerConfigs represents a collection of server configurations. | ||
type ServerConfigs []*ServerConfig | ||
|
||
|
@@ -165,14 +181,16 @@ func (a *Authority) Equal(other *Authority) bool { | |
type ServerConfig struct { | ||
serverURI string | ||
channelCreds []ChannelCreds | ||
callCreds []CallCreds | ||
serverFeatures []string | ||
|
||
// As part of unmarshalling the JSON config into this struct, we ensure that | ||
// the credentials config is valid by building an instance of the specified | ||
// credentials and store it here for easy access. | ||
selectedCreds ChannelCreds | ||
credsDialOption grpc.DialOption | ||
extraDialOptions []grpc.DialOption | ||
selectedCreds ChannelCreds | ||
easwars marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
selectedCallCreds []credentials.PerRPCCredentials | ||
credsDialOption grpc.DialOption | ||
extraDialOptions []grpc.DialOption | ||
|
||
cleanups []func() | ||
} | ||
|
@@ -194,6 +212,18 @@ func (sc *ServerConfig) ServerFeatures() []string { | |
return sc.serverFeatures | ||
} | ||
|
||
// CallCreds returns the call credentials configuration for this server. | ||
func (sc *ServerConfig) CallCreds() []CallCreds { | ||
|
||
return sc.callCreds | ||
} | ||
|
||
// SelectedCallCreds returns the built call credentials that are ready to use. | ||
// These are the credentials that were successfully built from the call_creds | ||
// configuration. | ||
func (sc *ServerConfig) SelectedCallCreds() []credentials.PerRPCCredentials { | ||
return sc.selectedCallCreds | ||
} | ||
|
||
// ServerFeaturesIgnoreResourceDeletion returns true if this server supports a | ||
// feature where the xDS client can ignore resource deletions from this server, | ||
// as described in gRFC A53. | ||
|
@@ -227,6 +257,24 @@ func (sc *ServerConfig) DialOptions() []grpc.DialOption { | |
return dopts | ||
} | ||
|
||
// DialOptionsWithCallCredsForTransport returns dial options with call | ||
// credentials but only if they are compatible with the specified transport | ||
// credentials type. For example, JWT call credentials require secure transport | ||
// and are skipped for insecure. | ||
func (sc *ServerConfig) DialOptionsWithCallCredsForTransport(transportCredsType string, transportCreds credentials.TransportCredentials) []grpc.DialOption { | ||
dopts := sc.DialOptions() | ||
isInsecureTransport := transportCredsType == "insecure" || | ||
(transportCreds != nil && transportCreds.Info().SecurityProtocol == "insecure") | ||
|
||
for _, callCred := range sc.selectedCallCreds { | ||
if isInsecureTransport && callCred.RequireTransportSecurity() { | ||
continue | ||
} | ||
dopts = append(dopts, grpc.WithPerRPCCredentials(callCred)) | ||
} | ||
return dopts | ||
} | ||
easwars marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
// Cleanups returns a collection of functions to be called when the xDS client | ||
// for this server is closed. Allows cleaning up resources created specifically | ||
// for this server. | ||
|
@@ -245,6 +293,8 @@ func (sc *ServerConfig) Equal(other *ServerConfig) bool { | |
return false | ||
case !slices.EqualFunc(sc.channelCreds, other.channelCreds, func(a, b ChannelCreds) bool { return a.Equal(b) }): | ||
return false | ||
case !slices.EqualFunc(sc.callCreds, other.callCreds, func(a, b CallCreds) bool { return a.Equal(b) }): | ||
return false | ||
case !slices.Equal(sc.serverFeatures, other.serverFeatures): | ||
return false | ||
case !sc.selectedCreds.Equal(other.selectedCreds): | ||
|
@@ -266,6 +316,7 @@ func (sc *ServerConfig) String() string { | |
type serverConfigJSON struct { | ||
ServerURI string `json:"server_uri,omitempty"` | ||
ChannelCreds []ChannelCreds `json:"channel_creds,omitempty"` | ||
CallCreds []CallCreds `json:"call_creds,omitempty"` | ||
ServerFeatures []string `json:"server_features,omitempty"` | ||
} | ||
|
||
|
@@ -274,6 +325,7 @@ func (sc *ServerConfig) MarshalJSON() ([]byte, error) { | |
server := &serverConfigJSON{ | ||
ServerURI: sc.serverURI, | ||
ChannelCreds: sc.channelCreds, | ||
CallCreds: sc.callCreds, | ||
ServerFeatures: sc.serverFeatures, | ||
} | ||
return json.Marshal(server) | ||
|
@@ -294,6 +346,7 @@ func (sc *ServerConfig) UnmarshalJSON(data []byte) error { | |
|
||
sc.serverURI = server.ServerURI | ||
sc.channelCreds = server.ChannelCreds | ||
sc.callCreds = server.CallCreds | ||
sc.serverFeatures = server.ServerFeatures | ||
|
||
for _, cc := range server.ChannelCreds { | ||
|
@@ -314,6 +367,26 @@ func (sc *ServerConfig) UnmarshalJSON(data []byte) error { | |
sc.cleanups = append(sc.cleanups, cancel) | ||
break | ||
} | ||
|
||
// Process call credentials - unlike channel creds, we use ALL supported | ||
// types. Also, call credentials are optional as per gRFC A97. | ||
for _, callCred := range server.CallCreds { | ||
c := bootstrap.GetCredentials(callCred.Type) | ||
if c == nil { | ||
// Skip unsupported call credential types (don't fail bootstrap). | ||
continue | ||
} | ||
bundle, cancel, err := c.Build(callCred.Config) | ||
if err != nil { | ||
// Call credential validation failed - this should fail bootstrap. | ||
return fmt.Errorf("failed to build call credentials from bootstrap for %q: %v", callCred.Type, err) | ||
} | ||
if callCredentials := bundle.PerRPCCredentials(); callCredentials != nil { | ||
sc.selectedCallCreds = append(sc.selectedCallCreds, callCredentials) | ||
} | ||
sc.cleanups = append(sc.cleanups, cancel) | ||
} | ||
|
||
if sc.serverURI == "" { | ||
return fmt.Errorf("xds: `server_uri` field in server config cannot be empty: %s", string(data)) | ||
} | ||
|
@@ -333,6 +406,9 @@ type ServerConfigTestingOptions struct { | |
// ChannelCreds contains a list of channel credentials to use when talking | ||
// to this server. If unspecified, `insecure` credentials will be used. | ||
ChannelCreds []ChannelCreds | ||
// CallCreds contains a list of call credentials to use for individual RPCs | ||
// to this server. Optional. | ||
CallCreds []CallCreds | ||
// ServerFeatures represents the list of features supported by this server. | ||
ServerFeatures []string | ||
} | ||
|
@@ -349,6 +425,7 @@ func ServerConfigForTesting(opts ServerConfigTestingOptions) (*ServerConfig, err | |
scInternal := &serverConfigJSON{ | ||
ServerURI: opts.URI, | ||
ChannelCreds: cc, | ||
CallCreds: opts.CallCreds, | ||
ServerFeatures: opts.ServerFeatures, | ||
} | ||
scJSON, err := json.Marshal(scInternal) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: This environment variable guards the newly added bootstrap field
call_creds
. I agree that the only call credentials that we currently support via this mechanism is the JWT call creds, but that could change any day (before we get rid of this env var). So, I would prefer we more explicitly call out what this env var guards.