Skip to content

Commit a764d3f

Browse files
xds/bootstrap: add trusted_xds_server server feature (#8692)
This PR implements the Bootstrap config changes for [gRFC A81](https://github.com/grpc/proposal/blob/master/A81-xds-authority-rewriting.md). Authority rewriting is a security-sensitive feature that should only be enabled when the xDS server is explicitly trusted to provide such configuration. gRFC A81 specifies that this trust is indicated by adding `trusted_xds_server` to the server_features list for a given server in the bootstrap file. RELEASE NOTES: None
1 parent 08d0792 commit a764d3f

File tree

6 files changed

+168
-35
lines changed

6 files changed

+168
-35
lines changed

internal/xds/bootstrap/bootstrap.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import (
4444

4545
const (
4646
serverFeaturesIgnoreResourceDeletion = "ignore_resource_deletion"
47+
serverFeaturesTrustedXDSServer = "trusted_xds_server"
4748
gRPCUserAgentName = "gRPC Go"
4849
clientFeatureNoOverprovisioning = "envoy.lb.does_not_support_overprovisioning"
4950
clientFeatureResourceWrapper = "xds.config.resource-in-sotw"
@@ -256,6 +257,18 @@ func (sc *ServerConfig) ServerFeaturesIgnoreResourceDeletion() bool {
256257
return false
257258
}
258259

260+
// ServerFeaturesTrustedXDSServer returns true if this server is trusted,
261+
// and gRPC should accept security-config-affecting fields from the server
262+
// as described in gRFC A81.
263+
func (sc *ServerConfig) ServerFeaturesTrustedXDSServer() bool {
264+
for _, sf := range sc.serverFeatures {
265+
if sf == serverFeaturesTrustedXDSServer {
266+
return true
267+
}
268+
}
269+
return false
270+
}
271+
259272
// SelectedChannelCreds returns the selected credentials configuration for
260273
// communicating with this server.
261274
func (sc *ServerConfig) SelectedChannelCreds() ChannelCreds {

internal/xds/bootstrap/bootstrap_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,22 @@ var (
267267
"server_features" : ["xds_v3"]
268268
}]
269269
}`,
270+
"serverSupportsTrustedXDSServer": `
271+
{
272+
"node": {
273+
"id": "ENVOY_NODE_ID",
274+
"metadata": {
275+
"TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
276+
}
277+
},
278+
"xds_servers" : [{
279+
"server_uri": "trafficdirector.googleapis.com:443",
280+
"channel_creds": [
281+
{ "type": "google_default" }
282+
],
283+
"server_features" : ["trusted_xds_server", "xds_v3"]
284+
}]
285+
}`,
270286
}
271287
metadata = &structpb.Struct{
272288
Fields: map[string]*structpb.Value{
@@ -338,6 +354,16 @@ var (
338354
node: v3Node,
339355
clientDefaultListenerResourceNameTemplate: "%s",
340356
}
357+
configWithGoogleDefaultCredsAndTrustedXDSServer = &Config{
358+
xDSServers: []*ServerConfig{{
359+
serverURI: "trafficdirector.googleapis.com:443",
360+
channelCreds: []ChannelCreds{{Type: "google_default"}},
361+
serverFeatures: []string{"trusted_xds_server", "xds_v3"},
362+
selectedChannelCreds: ChannelCreds{Type: "google_default"},
363+
}},
364+
node: v3Node,
365+
clientDefaultListenerResourceNameTemplate: "%s",
366+
}
341367
configWithGoogleDefaultCredsAndNoServerFeatures = &Config{
342368
xDSServers: []*ServerConfig{{
343369
serverURI: "trafficdirector.googleapis.com:443",
@@ -539,6 +565,7 @@ func (s) TestGetConfiguration_Success(t *testing.T) {
539565
{"goodBootstrap", configWithGoogleDefaultCredsAndV3},
540566
{"multipleXDSServers", configWithMultipleServers},
541567
{"serverSupportsIgnoreResourceDeletion", configWithGoogleDefaultCredsAndIgnoreResourceDeletion},
568+
{"serverSupportsTrustedXDSServer", configWithGoogleDefaultCredsAndTrustedXDSServer},
542569
{"istioStyleInsecureWithoutCallCreds", configWithIstioStyleNoCallCreds},
543570
}
544571

internal/xds/clients/xdsclient/authority.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ func (a *authority) handleADSResourceUpdate(serverConfig *ServerConfig, rType Re
470470
// "resource-not-found" error.
471471
continue
472472
}
473-
if serverConfig.IgnoreResourceDeletion {
473+
if serverConfig.SupportsServerFeature(ServerFeatureIgnoreResourceDeletion) {
474474
// Per A53, resource deletions are ignored if the
475475
// `ignore_resource_deletion` server feature is enabled through the
476476
// xDS client configuration. If the resource deletion is to be

internal/xds/clients/xdsclient/xdsconfig.go

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,20 @@ import (
2424
"google.golang.org/grpc/internal/xds/clients"
2525
)
2626

27+
// ServerFeature indicates the features that will be supported by an xDS server.
28+
type ServerFeature uint64
29+
30+
const (
31+
// ServerFeatureIgnoreResourceDeletion indicates that the server supports a
32+
// feature where the xDS client can ignore resource deletions from this server,
33+
// as described in gRFC A53.
34+
ServerFeatureIgnoreResourceDeletion ServerFeature = 1 << iota
35+
// ServerFeatureTrustedXDSServer returns true if this server is trusted,
36+
// and gRPC should accept security-config-affecting fields from the server
37+
// as described in gRFC A81.
38+
ServerFeatureTrustedXDSServer
39+
)
40+
2741
// Config is used to configure an xDS client. After one has been passed to the
2842
// xDS client's New function, no part of it may be modified. A Config may be
2943
// reused; the xdsclient package will also not modify it.
@@ -74,17 +88,7 @@ type Config struct {
7488
// ServerConfig contains configuration for an xDS management server.
7589
type ServerConfig struct {
7690
ServerIdentifier clients.ServerIdentifier
77-
78-
// IgnoreResourceDeletion is a server feature which if set to true,
79-
// indicates that resource deletion errors from xDS management servers can
80-
// be ignored and cached resource data can be used.
81-
//
82-
// This will be removed in the future once we implement gRFC A88
83-
// and two new fields FailOnDataErrors and
84-
// ResourceTimerIsTransientError will be introduced.
85-
IgnoreResourceDeletion bool
86-
87-
// TODO: Link to gRFC A88
91+
ServerFeature ServerFeature // ServerFeature stores a bitmap of supported features.
8892
}
8993

9094
// Authority contains configuration for an xDS control plane authority.
@@ -98,5 +102,11 @@ type Authority struct {
98102
}
99103

100104
func isServerConfigEqual(a, b *ServerConfig) bool {
101-
return a.ServerIdentifier == b.ServerIdentifier && a.IgnoreResourceDeletion == b.IgnoreResourceDeletion
105+
return a.ServerIdentifier == b.ServerIdentifier && a.ServerFeature == b.ServerFeature
106+
}
107+
108+
// SupportsServerFeature returns true if the server configuration indicates that
109+
// the server supports the given feature.
110+
func (s *ServerConfig) SupportsServerFeature(feature ServerFeature) bool {
111+
return s.ServerFeature&feature != 0
102112
}

internal/xds/xdsclient/clientimpl.go

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,32 @@ func (c *clientImpl) decrRef() int32 {
162162
return atomic.AddInt32(&c.refCount, -1)
163163
}
164164

165+
func buildServerConfigs(bootstrapSC []*bootstrap.ServerConfig, grpcTransportConfigs map[string]grpctransport.Config, gServerCfgMap map[xdsclient.ServerConfig]*bootstrap.ServerConfig) ([]xdsclient.ServerConfig, error) {
166+
var gServerCfg []xdsclient.ServerConfig
167+
for _, sc := range bootstrapSC {
168+
if err := populateGRPCTransportConfigsFromServerConfig(sc, grpcTransportConfigs); err != nil {
169+
return nil, err
170+
}
171+
var serverFeatures xdsclient.ServerFeature
172+
if sc.ServerFeaturesIgnoreResourceDeletion() {
173+
serverFeatures = serverFeatures | xdsclient.ServerFeatureIgnoreResourceDeletion
174+
}
175+
if sc.ServerFeaturesTrustedXDSServer() {
176+
serverFeatures = serverFeatures | xdsclient.ServerFeatureTrustedXDSServer
177+
}
178+
gsc := xdsclient.ServerConfig{
179+
ServerIdentifier: clients.ServerIdentifier{
180+
ServerURI: sc.ServerURI(),
181+
Extensions: grpctransport.ServerIdentifierExtension{ConfigName: sc.SelectedChannelCreds().Type},
182+
},
183+
ServerFeature: serverFeatures,
184+
}
185+
gServerCfg = append(gServerCfg, gsc)
186+
gServerCfgMap[gsc] = sc
187+
}
188+
return gServerCfg, nil
189+
}
190+
165191
// buildXDSClientConfig builds the xdsclient.Config from the bootstrap.Config.
166192
func buildXDSClientConfig(config *bootstrap.Config, metricsRecorder estats.MetricsRecorder, target string, watchExpiryTimeout time.Duration) (xdsclient.Config, error) {
167193
grpcTransportConfigs := make(map[string]grpctransport.Config)
@@ -175,30 +201,16 @@ func buildXDSClientConfig(config *bootstrap.Config, metricsRecorder estats.Metri
175201
if len(cfg.XDSServers) >= 1 {
176202
serverCfg = cfg.XDSServers
177203
}
178-
var gServerCfg []xdsclient.ServerConfig
179-
for _, sc := range serverCfg {
180-
if err := populateGRPCTransportConfigsFromServerConfig(sc, grpcTransportConfigs); err != nil {
181-
return xdsclient.Config{}, err
182-
}
183-
gsc := xdsclient.ServerConfig{
184-
ServerIdentifier: clients.ServerIdentifier{ServerURI: sc.ServerURI(), Extensions: grpctransport.ServerIdentifierExtension{ConfigName: sc.SelectedChannelCreds().Type}},
185-
IgnoreResourceDeletion: sc.ServerFeaturesIgnoreResourceDeletion()}
186-
gServerCfg = append(gServerCfg, gsc)
187-
gServerCfgMap[gsc] = sc
204+
gsc, err := buildServerConfigs(serverCfg, grpcTransportConfigs, gServerCfgMap)
205+
if err != nil {
206+
return xdsclient.Config{}, err
188207
}
189-
gAuthorities[name] = xdsclient.Authority{XDSServers: gServerCfg}
208+
gAuthorities[name] = xdsclient.Authority{XDSServers: gsc}
190209
}
191210

192-
gServerCfgs := make([]xdsclient.ServerConfig, 0, len(config.XDSServers()))
193-
for _, sc := range config.XDSServers() {
194-
if err := populateGRPCTransportConfigsFromServerConfig(sc, grpcTransportConfigs); err != nil {
195-
return xdsclient.Config{}, err
196-
}
197-
gsc := xdsclient.ServerConfig{
198-
ServerIdentifier: clients.ServerIdentifier{ServerURI: sc.ServerURI(), Extensions: grpctransport.ServerIdentifierExtension{ConfigName: sc.SelectedChannelCreds().Type}},
199-
IgnoreResourceDeletion: sc.ServerFeaturesIgnoreResourceDeletion()}
200-
gServerCfgs = append(gServerCfgs, gsc)
201-
gServerCfgMap[gsc] = sc
211+
gServerCfgs, err := buildServerConfigs(config.XDSServers(), grpcTransportConfigs, gServerCfgMap)
212+
if err != nil {
213+
return xdsclient.Config{}, err
202214
}
203215

204216
node := config.Node()

internal/xds/xdsclient/clientimpl_test.go

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,78 @@ func (s) TestBuildXDSClientConfig_Success(t *testing.T) {
146146
}`, testXDSServerURL, testNodeID)),
147147
wantXDSClientConfig: func(c *bootstrap.Config) xdsclient.Config {
148148
node, serverCfg := c.Node(), c.XDSServers()[0]
149-
expectedServer := xdsclient.ServerConfig{ServerIdentifier: clients.ServerIdentifier{ServerURI: serverCfg.ServerURI(), Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "insecure"}}, IgnoreResourceDeletion: true}
149+
serverFeature := xdsclient.ServerFeatureIgnoreResourceDeletion
150+
expectedServer := xdsclient.ServerConfig{
151+
ServerIdentifier: clients.ServerIdentifier{ServerURI: serverCfg.ServerURI(), Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "insecure"}},
152+
ServerFeature: serverFeature}
153+
gServerCfgMap := map[xdsclient.ServerConfig]*bootstrap.ServerConfig{expectedServer: serverCfg}
154+
return xdsclient.Config{
155+
Servers: []xdsclient.ServerConfig{expectedServer},
156+
Node: clients.Node{ID: node.GetId(), Cluster: node.GetCluster(), Metadata: node.Metadata, UserAgentName: node.UserAgentName, UserAgentVersion: node.GetUserAgentVersion()},
157+
Authorities: map[string]xdsclient.Authority{},
158+
ResourceTypes: map[string]xdsclient.ResourceType{
159+
version.V3ListenerURL: {TypeURL: version.V3ListenerURL, TypeName: xdsresource.ListenerResourceTypeName, AllResourcesRequiredInSotW: true, Decoder: xdsresource.NewListenerResourceTypeDecoder(c)},
160+
version.V3RouteConfigURL: {TypeURL: version.V3RouteConfigURL, TypeName: xdsresource.RouteConfigTypeName, AllResourcesRequiredInSotW: false, Decoder: xdsresource.NewRouteConfigResourceTypeDecoder(c)},
161+
version.V3ClusterURL: {TypeURL: version.V3ClusterURL, TypeName: xdsresource.ClusterResourceTypeName, AllResourcesRequiredInSotW: true, Decoder: xdsresource.NewClusterResourceTypeDecoder(c, gServerCfgMap)},
162+
version.V3EndpointsURL: {TypeURL: version.V3EndpointsURL, TypeName: xdsresource.EndpointsResourceTypeName, AllResourcesRequiredInSotW: false, Decoder: xdsresource.NewEndpointsResourceTypeDecoder(c)},
163+
},
164+
MetricsReporter: &metricsReporter{recorder: stats.NewTestMetricsRecorder(), target: testTargetName},
165+
TransportBuilder: grpctransport.NewBuilder(map[string]grpctransport.Config{
166+
"insecure": {
167+
Credentials: insecure.NewBundle(),
168+
GRPCNewClient: func(target string, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
169+
opts = append(opts, serverCfg.DialOptions()...)
170+
return grpc.NewClient(target, opts...)
171+
}},
172+
}),
173+
}
174+
},
175+
},
176+
{
177+
name: "server features with trusted_xds_server",
178+
bootstrapContents: []byte(fmt.Sprintf(`{
179+
"xds_servers": [{"server_uri": "%s", "channel_creds": [{"type": "insecure"}], "server_features": ["trusted_xds_server"]}],
180+
"node": {"id": "%s"}
181+
}`, testXDSServerURL, testNodeID)),
182+
wantXDSClientConfig: func(c *bootstrap.Config) xdsclient.Config {
183+
node, serverCfg := c.Node(), c.XDSServers()[0]
184+
serverFeature := xdsclient.ServerFeatureTrustedXDSServer
185+
expectedServer := xdsclient.ServerConfig{ServerIdentifier: clients.ServerIdentifier{ServerURI: serverCfg.ServerURI(), Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "insecure"}},
186+
ServerFeature: serverFeature}
187+
gServerCfgMap := map[xdsclient.ServerConfig]*bootstrap.ServerConfig{expectedServer: serverCfg}
188+
return xdsclient.Config{
189+
Servers: []xdsclient.ServerConfig{expectedServer},
190+
Node: clients.Node{ID: node.GetId(), Cluster: node.GetCluster(), Metadata: node.Metadata, UserAgentName: node.UserAgentName, UserAgentVersion: node.GetUserAgentVersion()},
191+
Authorities: map[string]xdsclient.Authority{},
192+
ResourceTypes: map[string]xdsclient.ResourceType{
193+
version.V3ListenerURL: {TypeURL: version.V3ListenerURL, TypeName: xdsresource.ListenerResourceTypeName, AllResourcesRequiredInSotW: true, Decoder: xdsresource.NewListenerResourceTypeDecoder(c)},
194+
version.V3RouteConfigURL: {TypeURL: version.V3RouteConfigURL, TypeName: xdsresource.RouteConfigTypeName, AllResourcesRequiredInSotW: false, Decoder: xdsresource.NewRouteConfigResourceTypeDecoder(c)},
195+
version.V3ClusterURL: {TypeURL: version.V3ClusterURL, TypeName: xdsresource.ClusterResourceTypeName, AllResourcesRequiredInSotW: true, Decoder: xdsresource.NewClusterResourceTypeDecoder(c, gServerCfgMap)},
196+
version.V3EndpointsURL: {TypeURL: version.V3EndpointsURL, TypeName: xdsresource.EndpointsResourceTypeName, AllResourcesRequiredInSotW: false, Decoder: xdsresource.NewEndpointsResourceTypeDecoder(c)},
197+
},
198+
MetricsReporter: &metricsReporter{recorder: stats.NewTestMetricsRecorder(), target: testTargetName},
199+
TransportBuilder: grpctransport.NewBuilder(map[string]grpctransport.Config{
200+
"insecure": {
201+
Credentials: insecure.NewBundle(),
202+
GRPCNewClient: func(target string, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
203+
opts = append(opts, serverCfg.DialOptions()...)
204+
return grpc.NewClient(target, opts...)
205+
}},
206+
}),
207+
}
208+
},
209+
},
210+
{
211+
name: "server features with ignore_resource_deletion and trusted_xds_server",
212+
bootstrapContents: []byte(fmt.Sprintf(`{
213+
"xds_servers": [{"server_uri": "%s", "channel_creds": [{"type": "insecure"}], "server_features": ["ignore_resource_deletion", "trusted_xds_server"]}],
214+
"node": {"id": "%s"}
215+
}`, testXDSServerURL, testNodeID)),
216+
wantXDSClientConfig: func(c *bootstrap.Config) xdsclient.Config {
217+
node, serverCfg := c.Node(), c.XDSServers()[0]
218+
serverFeature := xdsclient.ServerFeatureTrustedXDSServer | xdsclient.ServerFeatureIgnoreResourceDeletion
219+
expectedServer := xdsclient.ServerConfig{ServerIdentifier: clients.ServerIdentifier{ServerURI: serverCfg.ServerURI(), Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "insecure"}},
220+
ServerFeature: serverFeature}
150221
gServerCfgMap := map[xdsclient.ServerConfig]*bootstrap.ServerConfig{expectedServer: serverCfg}
151222
return xdsclient.Config{
152223
Servers: []xdsclient.ServerConfig{expectedServer},

0 commit comments

Comments
 (0)