@@ -167,6 +167,10 @@ underlying stream. If any RPCs are in progress on the channel when it is closed,
167
167
they will be cancelled. The channel is also closed if the context used to create
168
168
the stream is cancelled or times out.
169
169
170
+ To use client interceptors with these channels, wrap them using
171
+ [ ` grpchan.InterceptClientConn ` ] ( https://pkg.go.dev/github.com/fullstorydev/grpchan#InterceptClientConn )
172
+ before creating stubs.
173
+
170
174
### Server
171
175
172
176
To handle RPCs that are issued over tunnels, the server must register service
@@ -190,6 +194,25 @@ barpb.RegisterBarServer(handler, serviceImpl)
190
194
barpb.RegisterBarServer (svr, serviceImpl)
191
195
```
192
196
197
+ To use server interceptors with these handlers, wrap the ` TunnelServiceHandler `
198
+ using [ ` grpchan.WithInterceptor ` ] ( https://pkg.go.dev/github.com/fullstorydev/grpchan#WithInterceptor )
199
+ before registering the other handlers.
200
+
201
+ ### Authn/Authz
202
+
203
+ With forward tunnels, authentication can be done on the initial ` OpenTunnel ` RPC
204
+ that opens the tunnel. The identity of the client can then be stored in a
205
+ context value, from a server interceptor. These context values are also
206
+ available to server interceptors and handlers that process tunneled requests.
207
+ So an authorization interceptor could extract the client identity from the
208
+ request context.
209
+
210
+ If using mutual TLS, you can use ` peer.FromContext ` (part of the gRPC runtime)
211
+ to examine the client's identity, which would have been authenticated via client
212
+ certificate. Like other context values, this value is available to all server
213
+ interceptors and handlers of tunneled requests and will be the same peer that
214
+ opened the tunnel.
215
+
193
216
## Reverse Tunnels
194
217
195
218
A reverse tunnel is one in which the tunnel client is actually the network
@@ -253,6 +276,10 @@ The `Serve` function returns once the tunnel is closed, either via the
253
276
tunnel client closing the channel or some other interruption of the
254
277
stream (including the context being cancelled or timing out).
255
278
279
+ To use server interceptors with these handlers, wrap the ` ReverseTunnelServer `
280
+ using [ ` grpchan.WithInterceptor ` ] ( https://pkg.go.dev/github.com/fullstorydev/grpchan#WithInterceptor )
281
+ before registering the other handlers.
282
+
256
283
### Server
257
284
258
285
The network server for reverse services is where things get really interesting.
@@ -300,3 +327,94 @@ most usages:
300
327
301
328
All of these channels can be used just like a ` *grpc.ClientConn ` , for creating
302
329
RPC stubs and then issuing RPCs to the corresponding network client.
330
+
331
+ To use client interceptors with these channels, wrap them using
332
+ [ ` grpchan.InterceptClientConn ` ] ( https://pkg.go.dev/github.com/fullstorydev/grpchan#InterceptClientConn )
333
+ before creating stubs.
334
+
335
+ ### Authn/Authz
336
+
337
+ With reverse tunnels, authentication is a little different than with forward
338
+ tunnels. The credentials associated with the initial ` OpenReverseTunnel ` RPC
339
+ are those for the tunnel _ server_ . Unless you are using mutual TLS (where
340
+ both parties authenticate via certificate), you will need to supply additional
341
+ authentication material with tunneled requests.
342
+
343
+ One way to send authentication material is to have the client (which is actually
344
+ the network server) use client interceptors to include per-call credentials with
345
+ every request. This approach closely resembles how non-tunneled RPCs are
346
+ handled: both sides use interceptors to send and verify credentials with every
347
+ operation.
348
+
349
+ A more efficient way involves only authenticating once, since all calls over the
350
+ tunnel will have the same authenticated client. This can be done by having a
351
+ server interceptor that sends authentication materials in _ response_ headers.
352
+ These will be received by the client almost immediately after the tunnel is
353
+ opened. This identity can then be stored in the context using a mutable value:
354
+
355
+ ``` go
356
+ // Client interceptor for the OpenReverseTunnel RPC:
357
+ func reverseCredentialsInterceptor (
358
+ ctx context.Context ,
359
+ desc *grpc.StreamDesc ,
360
+ cc *grpc.ClientConn ,
361
+ method string ,
362
+ streamer grpc.Streamer ,
363
+ opts ...grpc .CallOption ,
364
+ ) (grpc.ClientStream , error ) {
365
+ if method != " /grpctunnel.v1.TunnelService/OpenReverseTunnel" {
366
+ return streamer (ctx, desc, cc, method, opts...)
367
+ }
368
+ // Store mutable value in context.
369
+ var authInfo any
370
+ ctx = context.WithValue (ctx, reverseCredentialsKey{}, &authInfo)
371
+ // Invoke RPC; open the tunnel.
372
+ stream , err := streamer (ctx, desc, cc, method, opts...)
373
+ if err != nil {
374
+ return nil , err
375
+ }
376
+ // Get credentials from response headers.
377
+ md , err := stream.Header ()
378
+ if err != nil {
379
+ return nil , err
380
+ }
381
+ // If authentication fails, authInfo could include details about
382
+ // the failure so that tunneled RPCs can fail with appropriate
383
+ // error details.
384
+ //
385
+ // An alternative is to just close the tunnel immediately, right
386
+ // here. But then there is no way to send information about the
387
+ // authn error to the peer.
388
+ //
389
+ // Note that modifying authInfo here is okay. But it is not safe
390
+ // to modify after returning from this interceptor since that
391
+ // could lead to data races with tunneled RPCs reading it.
392
+ authInfo = authenticate (md)
393
+ return stream, nil
394
+ }
395
+
396
+ // Server interceptor for tunneled RPCs.
397
+ // (Unary interceptor shown; streaming interceptor would be similar.)
398
+ func tunneledAuthzInterceptor (
399
+ ctx context.Context ,
400
+ method string ,
401
+ req, reply interface {},
402
+ cc *grpc.ClientConn ,
403
+ invoker grpc.UnaryInvoker ,
404
+ opts ...grpc .CallOption ,
405
+ ) error {
406
+ // Get authInfo from context. This was stored in the context by
407
+ // the interceptor above. More detailed error messages could be
408
+ // used or error details added if authInfo contains details about
409
+ // authn failures.
410
+ authInfo , ok := ctx.Value (reverseCredentialsKey{}).(*any)
411
+ if !ok || authInfo == nil || *authInfo == nil {
412
+ return status.Error (codes.Unauthenticated , " unauthenticated" )
413
+ }
414
+ if !isAllowed (method, *authInfo) {
415
+ return status.Error (codes.PermissionDenied , " not authorized" )
416
+ }
417
+ // RPC is allowed.
418
+ return invoker (ctx, method, req, reply, cc, opts...)
419
+ }
420
+ ```
0 commit comments