vitess.io/vitess@v0.16.2/go/test/endtoend/encryption/encryptedtransport/encrypted_transport_test.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 /* This test makes sure encrypted transport over gRPC works. 18 19 The security chains are setup the following way: 20 21 * root CA 22 * vttablet server CA 23 * vttablet server instance cert/key 24 * vttablet client CA 25 * vttablet client 1 cert/key 26 * vtgate server CA 27 * vtgate server instance cert/key (common name is 'localhost') 28 * vtgate client CA 29 * vtgate client 1 cert/key 30 * vtgate client 2 cert/key 31 32 The following table shows all the checks we perform: 33 process: will check its peer is signed by: for link: 34 35 vttablet vttablet client CA vtgate -> vttablet 36 vtgate vttablet server CA vtgate -> vttablet 37 38 vtgate vtgate client CA client -> vtgate 39 client vtgate server CA client -> vtgate 40 41 Additionally, we have the following constraints: 42 - the client certificate common name is used as immediate 43 caller ID by vtgate, and forwarded to vttablet. This allows us to use 44 table ACLs on the vttablet side. 45 - the vtgate server certificate common name is set to 'localhost' so it matches 46 the hostname dialed by the vtgate clients. This is not a requirement for the 47 go client, that can set its expected server name. However, the python gRPC 48 client doesn't have the ability to set the server name, so they must match. 49 - the python client needs to have the full chain for the server validation 50 (that is 'vtgate server CA' + 'root CA'). A go client doesn't. So we read both 51 below when using the python client, but we only pass the intermediate cert 52 to the go clients (for vtgate -> vttablet link). */ 53 54 package encryptedtransport 55 56 import ( 57 "context" 58 "flag" 59 "fmt" 60 "io" 61 "os" 62 "os/exec" 63 "path" 64 "testing" 65 66 "github.com/pkg/errors" 67 68 "vitess.io/vitess/go/test/endtoend/encryption" 69 70 "vitess.io/vitess/go/vt/proto/vtrpc" 71 "vitess.io/vitess/go/vt/vterrors" 72 73 "github.com/stretchr/testify/assert" 74 "github.com/stretchr/testify/require" 75 76 "vitess.io/vitess/go/test/endtoend/cluster" 77 "vitess.io/vitess/go/vt/grpcclient" 78 "vitess.io/vitess/go/vt/log" 79 querypb "vitess.io/vitess/go/vt/proto/query" 80 vtgatepb "vitess.io/vitess/go/vt/proto/vtgate" 81 vtgateservicepb "vitess.io/vitess/go/vt/proto/vtgateservice" 82 ) 83 84 var ( 85 clusterInstance *cluster.LocalProcessCluster 86 createVtInsertTest = `create table vt_insert_test ( 87 id bigint auto_increment, 88 msg varchar(64), 89 keyspace_id bigint(20) unsigned NOT NULL, 90 primary key (id) 91 ) Engine = InnoDB` 92 keyspace = "test_keyspace" 93 hostname = "localhost" 94 shardName = "0" 95 cell = "zone1" 96 certDirectory string 97 grpcCert = "" 98 grpcKey = "" 99 grpcCa = "" 100 grpcName = "" 101 ) 102 103 func TestSecureTransport(t *testing.T) { 104 defer cluster.PanicHandler(t) 105 flag.Parse() 106 107 // initialize cluster 108 _, err := clusterSetUp(t) 109 require.Nil(t, err, "setup failed") 110 111 primaryTablet := *clusterInstance.Keyspaces[0].Shards[0].Vttablets[0] 112 replicaTablet := *clusterInstance.Keyspaces[0].Shards[0].Vttablets[1] 113 114 // creating table_acl_config.json file 115 tableACLConfigJSON := path.Join(certDirectory, "table_acl_config.json") 116 f, err := os.Create(tableACLConfigJSON) 117 require.NoError(t, err) 118 119 _, err = f.WriteString(`{ 120 "table_groups": [ 121 { 122 "table_names_or_prefixes": ["vt_insert_test"], 123 "readers": ["vtgate client 1"], 124 "writers": ["vtgate client 1"], 125 "admins": ["vtgate client 1"] 126 } 127 ] 128 }`) 129 require.NoError(t, err) 130 err = f.Close() 131 require.NoError(t, err) 132 133 // start the tablets 134 for _, tablet := range []cluster.Vttablet{primaryTablet, replicaTablet} { 135 tablet.VttabletProcess.ExtraArgs = append(tablet.VttabletProcess.ExtraArgs, "--table-acl-config", tableACLConfigJSON, "--queryserver-config-strict-table-acl") 136 tablet.VttabletProcess.ExtraArgs = append(tablet.VttabletProcess.ExtraArgs, serverExtraArguments("vttablet-server-instance", "vttablet-client")...) 137 err = tablet.VttabletProcess.Setup() 138 require.NoError(t, err) 139 } 140 141 // setup replication 142 var vtctlClientArgs []string 143 144 vtctlClientTmArgs := append(vtctlClientArgs, tmclientExtraArgs("vttablet-client-1")...) 145 146 // Reparenting 147 vtctlClientArgs = append(vtctlClientTmArgs, "InitShardPrimary", "--", "--force", "test_keyspace/0", primaryTablet.Alias) 148 err = clusterInstance.VtctlProcess.ExecuteCommand(vtctlClientArgs...) 149 require.NoError(t, err) 150 151 err = clusterInstance.StartVTOrc("test_keyspace") 152 require.NoError(t, err) 153 154 // Apply schema 155 var vtctlApplySchemaArgs = append(vtctlClientTmArgs, "ApplySchema", "--", "--sql", createVtInsertTest, "test_keyspace") 156 err = clusterInstance.VtctlProcess.ExecuteCommand(vtctlApplySchemaArgs...) 157 require.NoError(t, err) 158 159 for _, tablet := range []cluster.Vttablet{primaryTablet, replicaTablet} { 160 var vtctlTabletArgs []string 161 vtctlTabletArgs = append(vtctlTabletArgs, tmclientExtraArgs("vttablet-client-1")...) 162 vtctlTabletArgs = append(vtctlTabletArgs, "RunHealthCheck", tablet.Alias) 163 _, err = clusterInstance.VtctlProcess.ExecuteCommandWithOutput(vtctlTabletArgs...) 164 require.NoError(t, err) 165 } 166 167 // start vtgate 168 clusterInstance.VtGateExtraArgs = append(clusterInstance.VtGateExtraArgs, tabletConnExtraArgs("vttablet-client-1")...) 169 clusterInstance.VtGateExtraArgs = append(clusterInstance.VtGateExtraArgs, serverExtraArguments("vtgate-server-instance", "vtgate-client")...) 170 err = clusterInstance.StartVtgate() 171 require.NoError(t, err) 172 173 grpcAddress := fmt.Sprintf("%s:%d", "localhost", clusterInstance.VtgateProcess.GrpcPort) 174 175 // 'vtgate client 1' is authorized to access vt_insert_test 176 setCreds(t, "vtgate-client-1", "vtgate-server") 177 ctx := context.Background() 178 request := getRequest("select * from vt_insert_test") 179 vc, err := getVitessClient(grpcAddress) 180 require.NoError(t, err) 181 182 qr, err := vc.Execute(ctx, request) 183 require.NoError(t, err) 184 err = vterrors.FromVTRPC(qr.Error) 185 require.NoError(t, err) 186 187 // 'vtgate client 2' is not authorized to access vt_insert_test 188 setCreds(t, "vtgate-client-2", "vtgate-server") 189 request = getRequest("select * from vt_insert_test") 190 vc, err = getVitessClient(grpcAddress) 191 require.NoError(t, err) 192 qr, err = vc.Execute(ctx, request) 193 require.NoError(t, err) 194 err = vterrors.FromVTRPC(qr.Error) 195 require.Error(t, err) 196 assert.Contains(t, err.Error(), "Select command denied to user") 197 assert.Contains(t, err.Error(), "for table 'vt_insert_test' (ACL check error)") 198 199 useEffectiveCallerID(ctx, t) 200 useEffectiveGroups(ctx, t) 201 202 clusterInstance.Teardown() 203 } 204 205 func useEffectiveCallerID(ctx context.Context, t *testing.T) { 206 // now restart vtgate in the mode where we don't use SSL 207 // for client connections, but we copy effective caller id 208 // into immediate caller id. 209 clusterInstance.VtGateExtraArgs = []string{"--grpc_use_effective_callerid"} 210 clusterInstance.VtGateExtraArgs = append(clusterInstance.VtGateExtraArgs, tabletConnExtraArgs("vttablet-client-1")...) 211 err := clusterInstance.RestartVtgate() 212 require.NoError(t, err) 213 214 grpcAddress := fmt.Sprintf("%s:%d", "localhost", clusterInstance.VtgateProcess.GrpcPort) 215 216 setSSLInfoEmpty() 217 218 // get vitess client 219 vc, err := getVitessClient(grpcAddress) 220 require.NoError(t, err) 221 222 // test with empty effective caller Id 223 request := getRequest("select * from vt_insert_test") 224 qr, err := vc.Execute(ctx, request) 225 require.NoError(t, err) 226 err = vterrors.FromVTRPC(qr.Error) 227 require.Error(t, err) 228 assert.Contains(t, err.Error(), "Select command denied to user") 229 assert.Contains(t, err.Error(), "for table 'vt_insert_test' (ACL check error)") 230 231 // 'vtgate client 1' is authorized to access vt_insert_test 232 callerID := &vtrpc.CallerID{ 233 Principal: "vtgate client 1", 234 } 235 request = getRequestWithCallerID(callerID, "select * from vt_insert_test") 236 qr, err = vc.Execute(ctx, request) 237 require.NoError(t, err) 238 err = vterrors.FromVTRPC(qr.Error) 239 require.NoError(t, err) 240 241 // 'vtgate client 2' is not authorized to access vt_insert_test 242 callerID = &vtrpc.CallerID{ 243 Principal: "vtgate client 2", 244 } 245 request = getRequestWithCallerID(callerID, "select * from vt_insert_test") 246 qr, err = vc.Execute(ctx, request) 247 require.NoError(t, err) 248 err = vterrors.FromVTRPC(qr.Error) 249 require.Error(t, err) 250 assert.Contains(t, err.Error(), "Select command denied to user") 251 assert.Contains(t, err.Error(), "for table 'vt_insert_test' (ACL check error)") 252 } 253 254 func useEffectiveGroups(ctx context.Context, t *testing.T) { 255 // now restart vtgate in the mode where we don't use SSL 256 // for client connections, but we copy effective caller's groups 257 // into immediate caller id. 258 clusterInstance.VtGateExtraArgs = []string{"--grpc_use_effective_callerid", "--grpc-use-effective-groups"} 259 clusterInstance.VtGateExtraArgs = append(clusterInstance.VtGateExtraArgs, tabletConnExtraArgs("vttablet-client-1")...) 260 err := clusterInstance.RestartVtgate() 261 require.NoError(t, err) 262 263 grpcAddress := fmt.Sprintf("%s:%d", "localhost", clusterInstance.VtgateProcess.GrpcPort) 264 265 setSSLInfoEmpty() 266 267 // get vitess client 268 vc, err := getVitessClient(grpcAddress) 269 require.NoError(t, err) 270 271 // test with empty effective caller Id 272 request := getRequest("select * from vt_insert_test") 273 qr, err := vc.Execute(ctx, request) 274 require.NoError(t, err) 275 err = vterrors.FromVTRPC(qr.Error) 276 require.Error(t, err) 277 assert.Contains(t, err.Error(), "Select command denied to user") 278 assert.Contains(t, err.Error(), "for table 'vt_insert_test' (ACL check error)") 279 280 // 'vtgate client 1' is authorized to access vt_insert_test 281 callerID := &vtrpc.CallerID{ 282 Principal: "my-caller", 283 Groups: []string{"vtgate client 1"}, 284 } 285 request = getRequestWithCallerID(callerID, "select * from vt_insert_test") 286 qr, err = vc.Execute(ctx, request) 287 require.NoError(t, err) 288 err = vterrors.FromVTRPC(qr.Error) 289 require.NoError(t, err) 290 291 // 'vtgate client 2' is not authorized to access vt_insert_test 292 callerID = &vtrpc.CallerID{ 293 Principal: "my-caller", 294 Groups: []string{"vtgate client 2"}, 295 } 296 request = getRequestWithCallerID(callerID, "select * from vt_insert_test") 297 qr, err = vc.Execute(ctx, request) 298 require.NoError(t, err) 299 err = vterrors.FromVTRPC(qr.Error) 300 require.Error(t, err) 301 assert.Contains(t, err.Error(), "Select command denied to user") 302 assert.Contains(t, err.Error(), "for table 'vt_insert_test' (ACL check error)") 303 } 304 305 func clusterSetUp(t *testing.T) (int, error) { 306 var mysqlProcesses []*exec.Cmd 307 clusterInstance = cluster.NewCluster(cell, hostname) 308 309 // Start topo server 310 if err := clusterInstance.StartTopo(); err != nil { 311 return 1, errors.Wrap(err, "unable to start topo") 312 } 313 314 // create all certs 315 log.Info("Creating certificates") 316 certDirectory = path.Join(clusterInstance.TmpDirectory, "certs") 317 _ = encryption.CreateDirectory(certDirectory, 0700) 318 319 err := encryption.ExecuteVttlstestCommand("--root", certDirectory, "CreateCA") 320 require.NoError(t, err) 321 322 err = createIntermediateCA("ca", "01", "vttablet-server", "vttablet server CA") 323 require.NoError(t, err) 324 325 err = createIntermediateCA("ca", "02", "vttablet-client", "vttablet client CA") 326 require.NoError(t, err) 327 328 err = createIntermediateCA("ca", "03", "vtgate-server", "vtgate server CA") 329 require.NoError(t, err) 330 331 err = createIntermediateCA("ca", "04", "vtgate-client", "vtgate client CA") 332 require.NoError(t, err) 333 334 err = createSignedCert("vttablet-server", "01", "vttablet-server-instance", "vttablet server instance") 335 require.NoError(t, err) 336 337 err = createSignedCert("vttablet-client", "01", "vttablet-client-1", "vttablet client 1") 338 require.NoError(t, err) 339 340 err = createSignedCert("vtgate-server", "01", "vtgate-server-instance", "localhost") 341 require.NoError(t, err) 342 343 err = createSignedCert("vtgate-client", "01", "vtgate-client-1", "vtgate client 1") 344 require.NoError(t, err) 345 346 err = createSignedCert("vtgate-client", "02", "vtgate-client-2", "vtgate client 2") 347 require.NoError(t, err) 348 349 for _, keyspaceStr := range []string{keyspace} { 350 KeyspacePtr := &cluster.Keyspace{Name: keyspaceStr} 351 keyspace := *KeyspacePtr 352 if err := clusterInstance.VtctlProcess.CreateKeyspace(keyspace.Name); err != nil { 353 return 1, err 354 } 355 shard := &cluster.Shard{ 356 Name: shardName, 357 } 358 for i := 0; i < 2; i++ { 359 // instantiate vttablet object with reserved ports 360 tablet := clusterInstance.NewVttabletInstance("replica", 0, cell) 361 362 // Start Mysqlctl process 363 tablet.MysqlctlProcess = *cluster.MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, clusterInstance.TmpDirectory) 364 proc, err := tablet.MysqlctlProcess.StartProcess() 365 if err != nil { 366 return 1, err 367 } 368 mysqlProcesses = append(mysqlProcesses, proc) 369 // start vttablet process 370 tablet.VttabletProcess = cluster.VttabletProcessInstance( 371 tablet.HTTPPort, 372 tablet.GrpcPort, 373 tablet.TabletUID, 374 clusterInstance.Cell, 375 shardName, 376 keyspace.Name, 377 clusterInstance.VtctldProcess.Port, 378 tablet.Type, 379 clusterInstance.TopoProcess.Port, 380 clusterInstance.Hostname, 381 clusterInstance.TmpDirectory, 382 clusterInstance.VtTabletExtraArgs, 383 clusterInstance.DefaultCharset) 384 tablet.Alias = tablet.VttabletProcess.TabletPath 385 shard.Vttablets = append(shard.Vttablets, tablet) 386 } 387 keyspace.Shards = append(keyspace.Shards, *shard) 388 clusterInstance.Keyspaces = append(clusterInstance.Keyspaces, keyspace) 389 } 390 for _, proc := range mysqlProcesses { 391 err := proc.Wait() 392 if err != nil { 393 return 1, errors.Wrap(err, "unable to wait on mysql process") 394 } 395 } 396 return 0, nil 397 } 398 399 func createIntermediateCA(ca string, serial string, name string, commonName string) error { 400 log.Infof("Creating intermediate signed cert and key %s", commonName) 401 tmpProcess := exec.Command( 402 "vttlstest", 403 "CreateIntermediateCA", 404 "--root", certDirectory, 405 "--parent", ca, 406 "--serial", serial, 407 "--common-name", commonName, 408 name) 409 return tmpProcess.Run() 410 } 411 412 func createSignedCert(ca string, serial string, name string, commonName string) error { 413 log.Infof("Creating signed cert and key %s", commonName) 414 tmpProcess := exec.Command( 415 "vttlstest", 416 "CreateSignedCert", 417 "--root", certDirectory, 418 "--parent", ca, 419 "--serial", serial, 420 "--common-name", commonName, 421 name) 422 return tmpProcess.Run() 423 } 424 425 func serverExtraArguments(name string, ca string) []string { 426 args := []string{"--grpc_cert", certDirectory + "/" + name + "-cert.pem", 427 "--grpc_key", certDirectory + "/" + name + "-key.pem", 428 "--grpc_ca", certDirectory + "/" + ca + "-cert.pem"} 429 return args 430 } 431 432 func tmclientExtraArgs(name string) []string { 433 ca := "vttablet-server" 434 var args = []string{"--tablet_manager_grpc_cert", certDirectory + "/" + name + "-cert.pem", 435 "--tablet_manager_grpc_key", certDirectory + "/" + name + "-key.pem", 436 "--tablet_manager_grpc_ca", certDirectory + "/" + ca + "-cert.pem", 437 "--tablet_manager_grpc_server_name", "vttablet server instance"} 438 return args 439 } 440 441 func tabletConnExtraArgs(name string) []string { 442 ca := "vttablet-server" 443 args := []string{"--tablet_grpc_cert", certDirectory + "/" + name + "-cert.pem", 444 "--tablet_grpc_key", certDirectory + "/" + name + "-key.pem", 445 "--tablet_grpc_ca", certDirectory + "/" + ca + "-cert.pem", 446 "--tablet_grpc_server_name", "vttablet server instance"} 447 return args 448 } 449 450 func getVitessClient(addr string) (vtgateservicepb.VitessClient, error) { 451 opt, err := grpcclient.SecureDialOption(grpcCert, grpcKey, grpcCa, "", grpcName) 452 if err != nil { 453 return nil, err 454 } 455 cc, err := grpcclient.Dial(addr, grpcclient.FailFast(false), opt) 456 if err != nil { 457 return nil, err 458 } 459 c := vtgateservicepb.NewVitessClient(cc) 460 return c, nil 461 } 462 463 func setCreds(t *testing.T, name string, ca string) { 464 f1, err := os.Open(path.Join(certDirectory, "ca-cert.pem")) 465 require.NoError(t, err) 466 b1, err := io.ReadAll(f1) 467 require.NoError(t, err) 468 469 f2, err := os.Open(path.Join(certDirectory, ca+"-cert.pem")) 470 require.NoError(t, err) 471 b2, err := io.ReadAll(f2) 472 require.NoError(t, err) 473 474 caContent := append(b1, b2...) 475 fileName := "ca-" + name + ".pem" 476 caVtgateClient := path.Join(certDirectory, fileName) 477 f, err := os.Create(caVtgateClient) 478 require.NoError(t, err) 479 _, err = f.Write(caContent) 480 require.NoError(t, err) 481 482 grpcCa = caVtgateClient 483 grpcKey = path.Join(certDirectory, name+"-key.pem") 484 grpcCert = path.Join(certDirectory, name+"-cert.pem") 485 486 err = f.Close() 487 require.NoError(t, err) 488 err = f2.Close() 489 require.NoError(t, err) 490 err = f1.Close() 491 require.NoError(t, err) 492 } 493 494 func setSSLInfoEmpty() { 495 grpcCa = "" 496 grpcCert = "" 497 grpcKey = "" 498 grpcName = "" 499 } 500 501 func getSession() *vtgatepb.Session { 502 return &vtgatepb.Session{ 503 TargetString: "test_keyspace:0@primary", 504 } 505 } 506 507 func getRequestWithCallerID(callerID *vtrpc.CallerID, sql string) *vtgatepb.ExecuteRequest { 508 session := getSession() 509 return &vtgatepb.ExecuteRequest{ 510 CallerId: callerID, 511 Session: session, 512 Query: &querypb.BoundQuery{ 513 Sql: sql, 514 }, 515 } 516 } 517 518 func getRequest(sql string) *vtgatepb.ExecuteRequest { 519 session := getSession() 520 return &vtgatepb.ExecuteRequest{ 521 Session: session, 522 Query: &querypb.BoundQuery{ 523 Sql: sql, 524 }, 525 } 526 }