1
1
import util from 'node:util' ;
2
2
import path from 'node:path' ;
3
+ import fs from 'node:fs/promises' ;
3
4
import test from 'ava' ;
4
5
import * as grpc from '@grpc/grpc-js' ;
5
6
import * as protoLoader from '@grpc/proto-loader' ;
@@ -20,6 +21,23 @@ async function bindLocalhost(server: grpc.Server): Promise<number> {
20
21
return await util . promisify ( server . bindAsync . bind ( server ) ) ( '127.0.0.1:0' , grpc . ServerCredentials . createInsecure ( ) ) ;
21
22
}
22
23
24
+ async function bindLocalhostTls ( server : grpc . Server ) : Promise < number > {
25
+ const caCert = await fs . readFile ( path . resolve ( __dirname , `../tls_certs/test-ca.crt` ) ) ;
26
+ const serverChainCert = await fs . readFile ( path . resolve ( __dirname , `../tls_certs/test-server-chain.crt` ) ) ;
27
+ const serverKey = await fs . readFile ( path . resolve ( __dirname , `../tls_certs/test-server.key` ) ) ;
28
+ const credentials = grpc . ServerCredentials . createSsl (
29
+ caCert ,
30
+ [
31
+ {
32
+ cert_chain : serverChainCert ,
33
+ private_key : serverKey ,
34
+ } ,
35
+ ] ,
36
+ true
37
+ ) ;
38
+ return await util . promisify ( server . bindAsync . bind ( server ) ) ( 'localhost:0' , credentials ) ;
39
+ }
40
+
23
41
test ( 'withMetadata / withDeadline set the CallContext for RPC call' , async ( t ) => {
24
42
const server = new grpc . Server ( ) ;
25
43
let gotTestHeaders = false ;
@@ -32,7 +50,7 @@ test('withMetadata / withDeadline set the CallContext for RPC call', async (t) =
32
50
temporal . api . workflowservice . v1 . IRegisterNamespaceRequest ,
33
51
temporal . api . workflowservice . v1 . IRegisterNamespaceResponse
34
52
> ,
35
- callback : grpc . sendUnaryData < temporal . api . workflowservice . v1 . IRegisterNamespaceResponse >
53
+ callback : grpc . sendUnaryData < temporal . api . workflowservice . v1 . IDescribeWorkflowExecutionResponse >
36
54
) {
37
55
const [ testValue ] = call . metadata . get ( 'test' ) ;
38
56
const [ otherValue ] = call . metadata . get ( 'otherKey' ) ;
@@ -111,7 +129,7 @@ test('grpc retry passes request and headers on retry, propagates responses', asy
111
129
temporal . api . workflowservice . v1 . IDescribeWorkflowExecutionRequest ,
112
130
temporal . api . workflowservice . v1 . IDescribeWorkflowExecutionResponse
113
131
> ,
114
- callback : grpc . sendUnaryData < temporal . api . workflowservice . v1 . IRegisterNamespaceResponse >
132
+ callback : grpc . sendUnaryData < temporal . api . workflowservice . v1 . IDescribeWorkflowExecutionResponse >
115
133
) {
116
134
const { namespace } = call . request ;
117
135
if ( typeof namespace === 'string' ) {
@@ -172,3 +190,89 @@ test('Default keepalive settings are set while maintaining user provided channel
172
190
// User setting overrides default
173
191
t . is ( channelArgs [ 'grpc.keepalive_permit_without_calls' ] , 0 ) ;
174
192
} ) ;
193
+
194
+ test ( 'Can configure TLS + call credentials' , async ( t ) => {
195
+ const meta = Array < string [ ] > ( ) ;
196
+
197
+ const server = new grpc . Server ( ) ;
198
+
199
+ server . addService ( workflowServiceProtoDescriptor . temporal . api . workflowservice . v1 . WorkflowService . service , {
200
+ getSystemInfo (
201
+ call : grpc . ServerUnaryCall <
202
+ temporal . api . workflowservice . v1 . IGetSystemInfoRequest ,
203
+ temporal . api . workflowservice . v1 . IGetSystemInfoResponse
204
+ > ,
205
+ callback : grpc . sendUnaryData < temporal . api . workflowservice . v1 . IGetSystemInfoResponse >
206
+ ) {
207
+ const [ aValue ] = call . metadata . get ( 'a' ) ;
208
+ const [ authorizationValue ] = call . metadata . get ( 'authorization' ) ;
209
+ if ( typeof aValue === 'string' && typeof authorizationValue === 'string' ) {
210
+ meta . push ( [ aValue , authorizationValue ] ) ;
211
+ }
212
+
213
+ const response : temporal . api . workflowservice . v1 . IGetSystemInfoResponse = {
214
+ serverVersion : 'test' ,
215
+ capabilities : undefined ,
216
+ } ;
217
+ callback ( null , response ) ;
218
+ } ,
219
+
220
+ describeWorkflowExecution (
221
+ call : grpc . ServerUnaryCall <
222
+ temporal . api . workflowservice . v1 . IDescribeWorkflowExecutionRequest ,
223
+ temporal . api . workflowservice . v1 . IDescribeWorkflowExecutionResponse
224
+ > ,
225
+ callback : grpc . sendUnaryData < temporal . api . workflowservice . v1 . IDescribeWorkflowExecutionResponse >
226
+ ) {
227
+ const [ aValue ] = call . metadata . get ( 'a' ) ;
228
+ const [ authorizationValue ] = call . metadata . get ( 'authorization' ) ;
229
+ if ( typeof aValue === 'string' && typeof authorizationValue === 'string' ) {
230
+ meta . push ( [ aValue , authorizationValue ] ) ;
231
+ }
232
+
233
+ const response : temporal . api . workflowservice . v1 . IDescribeWorkflowExecutionResponse = {
234
+ workflowExecutionInfo : { execution : { workflowId : 'test' } } ,
235
+ } ;
236
+ callback ( null , response ) ;
237
+ } ,
238
+ } ) ;
239
+ const port = await bindLocalhostTls ( server ) ;
240
+ server . start ( ) ;
241
+
242
+ let callNumber = 0 ;
243
+ const oauth2Client : grpc . OAuth2Client = {
244
+ getRequestHeaders : async ( ) => {
245
+ const accessToken = `oauth2-access-token-${ ++ callNumber } ` ;
246
+ return { authorization : `Bearer ${ accessToken } ` } ;
247
+ } ,
248
+ } ;
249
+
250
+ // Default interceptor config with backoff factor of 1 to speed things up
251
+ // const interceptor = makeGrpcRetryInterceptor(defaultGrpcRetryOptions({ factor: 1 }));
252
+ const conn = await Connection . connect ( {
253
+ address : `localhost:${ port } ` ,
254
+ metadata : { a : 'bc' } ,
255
+ tls : {
256
+ serverRootCACertificate : await fs . readFile ( path . resolve ( __dirname , `../tls_certs/test-ca.crt` ) ) ,
257
+ clientCertPair : {
258
+ crt : await fs . readFile ( path . resolve ( __dirname , `../tls_certs/test-client-chain.crt` ) ) ,
259
+ key : await fs . readFile ( path . resolve ( __dirname , `../tls_certs/test-client.key` ) ) ,
260
+ } ,
261
+ serverNameOverride : 'Server' ,
262
+ } ,
263
+ callCredentials : [ grpc . credentials . createFromGoogleCredential ( oauth2Client ) ] ,
264
+ } ) ;
265
+
266
+ // Make three calls
267
+ await conn . workflowService . describeWorkflowExecution ( { namespace : 'a' } ) ;
268
+ await conn . workflowService . describeWorkflowExecution ( { namespace : 'b' } ) ;
269
+ await conn . workflowService . describeWorkflowExecution ( { namespace : 'c' } ) ;
270
+
271
+ // Check that both connection level metadata and call credentials metadata are sent correctly
272
+ t . deepEqual ( meta , [
273
+ [ 'bc' , 'Bearer oauth2-access-token-1' ] ,
274
+ [ 'bc' , 'Bearer oauth2-access-token-2' ] ,
275
+ [ 'bc' , 'Bearer oauth2-access-token-3' ] ,
276
+ [ 'bc' , 'Bearer oauth2-access-token-4' ] ,
277
+ ] ) ;
278
+ } ) ;
0 commit comments