@@ -30,7 +30,11 @@ import { XdsConfig } from './xds-dependency-manager';
30
30
import { LocalityEndpoint , PriorityChildRaw } from './load-balancer-priority' ;
31
31
import { Locality__Output } from './generated/envoy/config/core/v3/Locality' ;
32
32
import { AGGREGATE_CLUSTER_BACKWARDS_COMPAT , EXPERIMENTAL_OUTLIER_DETECTION } from './environment' ;
33
- import { XDS_CONFIG_KEY } from './resolver-xds' ;
33
+ import { XDS_CLIENT_KEY , XDS_CONFIG_KEY } from './resolver-xds' ;
34
+ import { ContainsValueMatcher , Matcher , PrefixValueMatcher , RejectValueMatcher , SafeRegexValueMatcher , SuffixValueMatcher , ValueMatcher } from './matcher' ;
35
+ import { StringMatcher__Output } from './generated/envoy/type/matcher/v3/StringMatcher' ;
36
+ import { isIPv6 } from 'net' ;
37
+ import { formatIPv6 , parseIPv6 } from './cidr' ;
34
38
35
39
const TRACER_NAME = 'cds_balancer' ;
36
40
@@ -67,6 +71,125 @@ class CdsLoadBalancingConfig implements TypedLoadBalancingConfig {
67
71
}
68
72
}
69
73
74
+ type SupportedSanType = 'DNS' | 'URI' | 'email' | 'IP Address' ;
75
+
76
+ function isSupportedSanType ( type : string ) : type is SupportedSanType {
77
+ return [ 'DNS' , 'URI' , 'email' , 'IP Address' ] . includes ( type ) ;
78
+ }
79
+
80
+ class DnsExactValueMatcher implements ValueMatcher {
81
+ constructor ( private targetValue : string , private ignoreCase : boolean ) {
82
+ if ( ignoreCase ) {
83
+ this . targetValue = this . targetValue . toLowerCase ( ) ;
84
+ }
85
+ }
86
+ apply ( entry : string ) : boolean {
87
+ let [ type , value ] = entry . split ( ':' ) ;
88
+ if ( ! isSupportedSanType ( type ) ) {
89
+ return false ;
90
+ }
91
+ if ( ! value ) {
92
+ return false ;
93
+ }
94
+ if ( this . ignoreCase ) {
95
+ value = value . toLowerCase ( ) ;
96
+ }
97
+ if ( type === 'DNS' && value . startsWith ( '*.' ) && this . targetValue . includes ( '.' , 1 ) ) {
98
+ return value . substring ( 2 ) === this . targetValue . substring ( this . targetValue . indexOf ( '.' ) + 1 ) ;
99
+ } else {
100
+ return value === this . targetValue ;
101
+ }
102
+ }
103
+
104
+ toString ( ) {
105
+ return 'DnsExact(' + this . targetValue + ', ignore_case=' + this . ignoreCase + ')' ;
106
+ }
107
+ }
108
+
109
+ function canonicalizeSanEntryValue ( type : SupportedSanType , value : string ) : string {
110
+ if ( type === 'IP Address' && isIPv6 ( value ) ) {
111
+ return formatIPv6 ( parseIPv6 ( value ) ) ;
112
+ }
113
+ return value ;
114
+ }
115
+
116
+ class SanEntryMatcher implements ValueMatcher {
117
+ private childMatcher : ValueMatcher ;
118
+ constructor ( matcherConfig : StringMatcher__Output ) {
119
+ const ignoreCase = matcherConfig . ignore_case ;
120
+ switch ( matcherConfig . match_pattern ) {
121
+ case 'exact' :
122
+ throw new Error ( 'Unexpected exact matcher in SAN entry matcher' ) ;
123
+ case 'prefix' :
124
+ this . childMatcher = new PrefixValueMatcher ( matcherConfig . prefix ! , ignoreCase ) ;
125
+ break ;
126
+ case 'suffix' :
127
+ this . childMatcher = new SuffixValueMatcher ( matcherConfig . suffix ! , ignoreCase ) ;
128
+ break ;
129
+ case 'safe_regex' :
130
+ this . childMatcher = new SafeRegexValueMatcher ( matcherConfig . safe_regex ! . regex ) ;
131
+ break ;
132
+ case 'contains' :
133
+ this . childMatcher = new ContainsValueMatcher ( matcherConfig . contains ! , ignoreCase ) ;
134
+ break ;
135
+ default :
136
+ this . childMatcher = new RejectValueMatcher ( ) ;
137
+ }
138
+ }
139
+ apply ( entry : string ) : boolean {
140
+ let [ type , value ] = entry . split ( ':' ) ;
141
+ if ( ! isSupportedSanType ( type ) ) {
142
+ return false ;
143
+ }
144
+ value = canonicalizeSanEntryValue ( type , value ) ;
145
+ if ( ! entry ) {
146
+ return false ;
147
+ }
148
+ return this . childMatcher . apply ( value ) ;
149
+ }
150
+ toString ( ) : string {
151
+ return this . childMatcher . toString ( ) ;
152
+ }
153
+
154
+ }
155
+
156
+ export class SanMatcher implements ValueMatcher {
157
+ private childMatchers : ValueMatcher [ ] ;
158
+ constructor ( matcherConfigs : StringMatcher__Output [ ] ) {
159
+ this . childMatchers = matcherConfigs . map ( config => {
160
+ if ( config . match_pattern === 'exact' ) {
161
+ return new DnsExactValueMatcher ( config . exact ! , config . ignore_case ) ;
162
+ } else {
163
+ return new SanEntryMatcher ( config ) ;
164
+ }
165
+ } ) ;
166
+ }
167
+ apply ( value : string ) : boolean {
168
+ if ( this . childMatchers . length === 0 ) {
169
+ return true ;
170
+ }
171
+ for ( const entry of value . split ( ', ' ) ) {
172
+ for ( const matcher of this . childMatchers ) {
173
+ const checkResult = matcher . apply ( entry ) ;
174
+ if ( checkResult ) {
175
+ return true ;
176
+ }
177
+ }
178
+ }
179
+ return false ;
180
+ }
181
+ toString ( ) : string {
182
+ return 'SanMatcher(' + this . childMatchers . map ( matcher => matcher . toString ( ) ) . sort ( ) . join ( ', ' ) + ')' ;
183
+ }
184
+
185
+ equals ( other : SanMatcher ) : boolean {
186
+ return this . toString ( ) === other . toString ( ) ;
187
+ }
188
+ }
189
+
190
+ export const CA_CERT_PROVIDER_KEY = 'grpc.internal.ca_cert_provider' ;
191
+ export const IDENTITY_CERT_PROVIDER_KEY = 'grpc.internal.identity_cert_provider' ;
192
+ export const SAN_MATCHER_KEY = 'grpc.internal.san_matcher' ;
70
193
71
194
const RECURSION_DEPTH_LIMIT = 15 ;
72
195
@@ -102,6 +225,8 @@ export class CdsLoadBalancer implements LoadBalancer {
102
225
private priorityNames : string [ ] = [ ] ;
103
226
private nextPriorityChildNumber = 0 ;
104
227
228
+ private latestSanMatcher : SanMatcher | null = null ;
229
+
105
230
constructor ( private readonly channelControlHelper : ChannelControlHelper ) {
106
231
this . childBalancer = new ChildLoadBalancerHandler ( channelControlHelper ) ;
107
232
}
@@ -140,7 +265,7 @@ export class CdsLoadBalancer implements LoadBalancer {
140
265
leafClusters = getLeafClusters ( xdsConfig , clusterName ) ;
141
266
} catch ( e ) {
142
267
trace ( 'xDS config parsing failed with error ' + ( e as Error ) . message ) ;
143
- this . channelControlHelper . updateState ( connectivityState . TRANSIENT_FAILURE , new UnavailablePicker ( { code : status . UNAVAILABLE , details : `xDS config parsing failed with error ${ ( e as Error ) . message } ` , metadata : new Metadata ( ) } ) ) ;
268
+ this . channelControlHelper . updateState ( connectivityState . TRANSIENT_FAILURE , new UnavailablePicker ( { code : status . UNAVAILABLE , details : `xDS config parsing failed with error ${ ( e as Error ) . message } ` } ) ) ;
144
269
return ;
145
270
}
146
271
const priorityChildren : { [ name : string ] : PriorityChildRaw } = { } ;
@@ -165,7 +290,7 @@ export class CdsLoadBalancer implements LoadBalancer {
165
290
typedChildConfig = parseLoadBalancingConfig ( childConfig ) ;
166
291
} catch ( e ) {
167
292
trace ( 'LB policy config parsing failed with error ' + ( e as Error ) . message ) ;
168
- this . channelControlHelper . updateState ( connectivityState . TRANSIENT_FAILURE , new UnavailablePicker ( { code : status . UNAVAILABLE , details : `LB policy config parsing failed with error ${ ( e as Error ) . message } ` , metadata : new Metadata ( ) } ) ) ;
293
+ this . channelControlHelper . updateState ( connectivityState . TRANSIENT_FAILURE , new UnavailablePicker ( { code : status . UNAVAILABLE , details : `LB policy config parsing failed with error ${ ( e as Error ) . message } ` } ) ) ;
169
294
return ;
170
295
}
171
296
this . childBalancer . updateAddressList ( endpointList , typedChildConfig , { ...options , [ ROOT_CLUSTER_KEY ] : clusterName } ) ;
@@ -272,17 +397,39 @@ export class CdsLoadBalancer implements LoadBalancer {
272
397
} else {
273
398
childConfig = xdsClusterImplConfig ;
274
399
}
275
- trace ( JSON . stringify ( childConfig , undefined , 2 ) ) ;
276
400
let typedChildConfig : TypedLoadBalancingConfig ;
277
401
try {
278
402
typedChildConfig = parseLoadBalancingConfig ( childConfig ) ;
279
403
} catch ( e ) {
280
404
trace ( 'LB policy config parsing failed with error ' + ( e as Error ) . message ) ;
281
- this . channelControlHelper . updateState ( connectivityState . TRANSIENT_FAILURE , new UnavailablePicker ( { code : status . UNAVAILABLE , details : `LB policy config parsing failed with error ${ ( e as Error ) . message } ` , metadata : new Metadata ( ) } ) ) ;
405
+ this . channelControlHelper . updateState ( connectivityState . TRANSIENT_FAILURE , new UnavailablePicker ( { code : status . UNAVAILABLE , details : `LB policy config parsing failed with error ${ ( e as Error ) . message } ` } ) ) ;
282
406
return ;
283
407
}
284
- trace ( JSON . stringify ( typedChildConfig . toJsonObject ( ) , undefined , 2 ) ) ;
285
- this . childBalancer . updateAddressList ( childEndpointList , typedChildConfig , options ) ;
408
+ const childOptions : ChannelOptions = { ...options } ;
409
+ if ( clusterConfig . cluster . securityUpdate ) {
410
+ const securityUpdate = clusterConfig . cluster . securityUpdate ;
411
+ const xdsClient = options [ XDS_CLIENT_KEY ] as XdsClient ;
412
+ const caCertProvider = xdsClient . getCertificateProvider ( securityUpdate . caCertificateProviderInstance ) ;
413
+ if ( ! caCertProvider ) {
414
+ this . channelControlHelper . updateState ( connectivityState . TRANSIENT_FAILURE , new UnavailablePicker ( { code : status . UNAVAILABLE , details : `Cluster ${ clusterName } configured with CA certificate provider ${ securityUpdate . caCertificateProviderInstance } not in bootstrap` } ) ) ;
415
+ return ;
416
+ }
417
+ if ( securityUpdate . identityCertificateProviderInstance ) {
418
+ const identityCertProvider = xdsClient . getCertificateProvider ( securityUpdate . identityCertificateProviderInstance ) ;
419
+ if ( ! identityCertProvider ) {
420
+ this . channelControlHelper . updateState ( connectivityState . TRANSIENT_FAILURE , new UnavailablePicker ( { code : status . UNAVAILABLE , details : `Cluster ${ clusterName } configured with identity certificate provider ${ securityUpdate . identityCertificateProviderInstance } not in bootstrap` } ) ) ;
421
+ return ;
422
+ }
423
+ childOptions [ IDENTITY_CERT_PROVIDER_KEY ] = identityCertProvider ;
424
+ }
425
+ childOptions [ CA_CERT_PROVIDER_KEY ] = caCertProvider ;
426
+ const sanMatcher = new SanMatcher ( securityUpdate . subjectAltNameMatchers ) ;
427
+ if ( this . latestSanMatcher === null || ! this . latestSanMatcher . equals ( sanMatcher ) ) {
428
+ this . latestSanMatcher = sanMatcher ;
429
+ }
430
+ childOptions [ SAN_MATCHER_KEY ] = this . latestSanMatcher ;
431
+ }
432
+ this . childBalancer . updateAddressList ( childEndpointList , typedChildConfig , childOptions ) ;
286
433
}
287
434
}
288
435
exitIdle ( ) : void {
0 commit comments