Skip to content

Commit 7b49af4

Browse files
committed
Adding the grpc_use_effective_callerid flag.
Along with end-to-end test and documentation.
1 parent 2e8c910 commit 7b49af4

3 files changed

Lines changed: 86 additions & 8 deletions

File tree

doc/VitessTransportSecurityModel.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ queries. There are two different Caller IDs:
4545

4646
## gRPC Transport
4747

48+
### gRPC Encrypted Transport
49+
4850
When using gRPC transport, Vitess can use the usual TLS security features
4951
(familiarity with SSL / TLS is necessary here):
5052

@@ -70,12 +72,33 @@ Note this is not enabled by default, as usually the different Vitess servers
7072
will run on a private network (in a Cloud environment, usually all local traffic
7173
is already secured over a VPN, for instance).
7274

75+
### Certificates and Caller ID
76+
7377
Additionally, if a client uses a certificate to connect to Vitess (vtgate), the
7478
common name of that certificate is passed to vttablet as the Immediate Caller
7579
ID. It can then be used by table ACLs, to grant read, write or admin access to
7680
individual tables. This should be used if different clients should have
7781
different access to Vitess tables.
7882

83+
### Caller ID Override
84+
85+
In a private network, where SSL security is not required, it might still be
86+
desirable to use table ACLs as a safety mechanism to prevent a user from
87+
accessing sensitive data. The gRPC connector provides the
88+
grpc\_use\_effective\_callerid flag for this purpose: if specified when running
89+
vtgate, the Effective Caller ID's principal is copied into the Immediate Caller
90+
ID, and then used throughout the Vitess stack.
91+
92+
**Important**: this is not secure. Any user code can provide any value for
93+
the Effective Caller ID's principal, and therefore access any data. This is
94+
intended as a safety feature to make sure some applications do not misbehave.
95+
Therefore, this flag is not enabled by default.
96+
97+
### Example
98+
7999
For a concrete example, see
80100
[test/encrypted\_transport.py](https://github.com/youtube/vitess/blob/master/test/encrypted_transport.py)
81-
in the source tree.
101+
in the source tree. It first sets up all the certificates, and some table ACLs,
102+
then uses the python client to connect with SSL. It also exercises the
103+
grpc\_use\_effective\_callerid flag, by connecting without SSL.
104+

go/vt/vtgate/grpcvtgateservice/server.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
package grpcvtgateservice
77

88
import (
9+
"flag"
10+
911
"google.golang.org/grpc"
1012
"google.golang.org/grpc/credentials"
1113
"google.golang.org/grpc/peer"
@@ -29,33 +31,37 @@ const (
2931
unsecureClient = "unsecure grpc client"
3032
)
3133

34+
var (
35+
useEffective = flag.Bool("grpc_use_effective_callerid", false, "If set, and SSL is not used, will set the immediate caller id from the effective caller id's principal.")
36+
)
37+
3238
// VTGate is the public structure that is exported via gRPC
3339
type VTGate struct {
3440
server vtgateservice.VTGateService
3541
}
3642

3743
// immediateCallerID tries to extract the common name of the certificate
3844
// that was used to connect to vtgate. If it fails for any reason,
39-
// it will return unsecureClient. That immediate caller id is then inserted
45+
// it will return "". That immediate caller id is then inserted
4046
// into a Context, and will be used when talking to vttablet.
4147
// vttablet in turn can use table ACLs to validate access is authorized.
4248
func immediateCallerID(ctx context.Context) string {
4349
p, ok := peer.FromContext(ctx)
4450
if !ok {
45-
return unsecureClient
51+
return ""
4652
}
4753
if p.AuthInfo == nil {
48-
return unsecureClient
54+
return ""
4955
}
5056
tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo)
5157
if !ok {
52-
return unsecureClient
58+
return ""
5359
}
5460
if len(tlsInfo.State.VerifiedChains) < 1 {
55-
return unsecureClient
61+
return ""
5662
}
5763
if len(tlsInfo.State.VerifiedChains[0]) < 1 {
58-
return unsecureClient
64+
return ""
5965
}
6066
cert := tlsInfo.State.VerifiedChains[0][0]
6167
return cert.Subject.CommonName
@@ -64,9 +70,16 @@ func immediateCallerID(ctx context.Context) string {
6470
// withCallerIDContext creates a context that extracts what we need
6571
// from the incoming call and can be forwarded for use when talking to vttablet.
6672
func withCallerIDContext(ctx context.Context, effectiveCallerID *vtrpcpb.CallerID) context.Context {
73+
immediate := immediateCallerID(ctx)
74+
if immediate == "" && *useEffective && effectiveCallerID != nil {
75+
immediate = effectiveCallerID.Principal
76+
}
77+
if immediate == "" {
78+
immediate = unsecureClient
79+
}
6780
return callerid.NewContext(callinfo.GRPCCallInfo(ctx),
6881
effectiveCallerID,
69-
callerid.NewImmediateCallerID(immediateCallerID(ctx)))
82+
callerid.NewImmediateCallerID(immediate))
7083
}
7184

7285
// Execute is the RPC version of vtgateservice.VTGateService method

test/encrypted_transport.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,48 @@ def test_secure(self):
336336
self.assertIn('cannot run PASS_SELECT on table', s)
337337
conn.close()
338338

339+
# now restart vtgate in the mode where we don't use SSL
340+
# for client connections, but we copy effective caller id
341+
# into immediate caller id.
342+
utils.vtgate.kill()
343+
utils.VtGate().start(extra_args=tabletconn_extra_args('vttablet-client-1')+
344+
['-grpc_use_effective_callerid'])
345+
346+
protocol, addr = utils.vtgate.rpc_endpoint(python=True)
347+
conn = vtgate_client.connect(protocol, addr, 30.0)
348+
cursor = conn.cursor(tablet_type='master', keyspace='test_keyspace',
349+
shards=['0'])
350+
351+
# not passing any immediate caller id should fail as using
352+
# the unsecure user "unsecure grpc client"
353+
cursor.set_effective_caller_id(None)
354+
try:
355+
cursor.execute('select * from vt_insert_test', {})
356+
self.fail('Execute went through')
357+
except dbexceptions.DatabaseError, e:
358+
s = str(e)
359+
self.assertIn('table acl error', s)
360+
self.assertIn('cannot run PASS_SELECT on table', s)
361+
self.assertIn('unsecure grpc client', s)
362+
363+
# 'vtgate client 1' is authorized to access vt_insert_test
364+
cursor.set_effective_caller_id(vtgate_client.CallerID(
365+
principal='vtgate client 1'))
366+
cursor.execute('select * from vt_insert_test', {})
367+
368+
# 'vtgate client 2' is not authorized to access vt_insert_test
369+
cursor.set_effective_caller_id(vtgate_client.CallerID(
370+
principal='vtgate client 2'))
371+
try:
372+
cursor.execute('select * from vt_insert_test', {})
373+
self.fail('Execute went through')
374+
except dbexceptions.DatabaseError, e:
375+
s = str(e)
376+
self.assertIn('table acl error', s)
377+
self.assertIn('cannot run PASS_SELECT on table', s)
378+
379+
conn.close()
380+
339381

340382
if __name__ == '__main__':
341383
utils.main()

0 commit comments

Comments
 (0)