Skip to content

Commit 9660a5c

Browse files
tbgrafiss
authored andcommitted
Test connecting tenants via password over TLS through proxy
This commit adds infrastructure for testing secure connections through the multi-tenancy proxy to a standalone SQL server. This works "out of the box" for essentially those tests for which we previously had client certificates working. The tests that fail here are GoPG, Hibernate, Sequelize, Django and they all fail for the basic reason that they don't support secure query strings, at least not in the way they are supplied right now. To *really* find out whether they have issues with multi-tenancy (or even client certs), we need to customize the way they receive the connection parameters. This is not something I plan to investigate. Closes cockroachdb/cockroach#52413.
1 parent 5aad6f4 commit 9660a5c

File tree

1 file changed

+130
-49
lines changed

1 file changed

+130
-49
lines changed

testing/main_test.go

Lines changed: 130 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,19 @@ type tenantServer interface {
4747
}
4848

4949
// newServer creates a new cockroachDB server.
50-
func newServer(t *testing.T, insecure bool) testserver.TestServer {
50+
func newServer(t *testing.T, auth authMode) testserver.TestServer {
5151
t.Helper()
5252
var ts testserver.TestServer
5353
var err error
54-
if insecure {
55-
ts, err = testserver.NewTestServer()
56-
} else {
54+
switch auth {
55+
case authClientCert:
5756
ts, err = testserver.NewTestServer(testserver.SecureOpt())
57+
case authPassword:
58+
ts, err = testserver.NewTestServer(testserver.SecureOpt(), testserver.RootPasswordOpt("hunter2"))
59+
case authInsecure:
60+
ts, err = testserver.NewTestServer()
61+
default:
62+
err = fmt.Errorf("unknown authMode %d", auth)
5863
}
5964
if err != nil {
6065
t.Fatal(err)
@@ -64,9 +69,9 @@ func newServer(t *testing.T, insecure bool) testserver.TestServer {
6469

6570
// newTenant creates a new SQL Tenant pointed at the given TestServer. See
6671
// TestServer.NewTenantServer for more information.
67-
func newTenant(t *testing.T, ts testserver.TestServer) testserver.TestServer {
72+
func newTenant(t *testing.T, ts testserver.TestServer, proxy bool) testserver.TestServer {
6873
t.Helper()
69-
tenant, err := ts.(tenantServer).NewTenantServer(false /* proxy */)
74+
tenant, err := ts.(tenantServer).NewTenantServer(proxy)
7075
if err != nil {
7176
t.Fatal(err)
7277
}
@@ -193,17 +198,39 @@ var minRequiredVersionsByORMName = map[string]struct {
193198
},
194199
}
195200

201+
type authMode byte
202+
203+
const (
204+
// Use client certs. When testing tenants, does not use the proxy (as the proxy does not support client certs).
205+
authClientCert authMode = iota
206+
// Use password auth. When testing tenants, tests through a proxy.
207+
authPassword
208+
// Use --insecure. When testing tenants, does not use the proxy (as the proxy does not support insecure connections).
209+
authInsecure
210+
211+
authModeSentinel // sentinel to iterate over all modes
212+
)
213+
214+
func (mode authMode) String() string {
215+
switch mode {
216+
case authClientCert:
217+
return "client-cert"
218+
case authPassword:
219+
return "password"
220+
case authInsecure:
221+
return "insecure"
222+
default:
223+
return "unknown"
224+
}
225+
}
226+
196227
type testInfo struct {
197228
language, orm string
198229
tableNames testTableNames // defaults to defaultTestTableNames
199230
columnNames testColumnNames // defaults to defaultTestColumnNames
200-
// insecure is set if ORM does not handle secure servers (client certs).
201-
// In that case, we start an insecure server (and don't test in tenant
202-
// mode).
203-
insecure bool
204231
}
205232

206-
func testORM(t *testing.T, info testInfo) {
233+
func testORM(t *testing.T, info testInfo, auth authMode) {
207234
if info.tableNames == (testTableNames{}) {
208235
info.tableNames = defaultTestTableNames
209236
}
@@ -222,7 +249,7 @@ func testORM(t *testing.T, info testInfo) {
222249
}
223250
var testCases []testCase
224251
{
225-
ts := newServer(t, info.insecure)
252+
ts := newServer(t, auth)
226253
db, dbURL, stopDB := startServerWithApplication(t, ts, app)
227254
defer stopDB()
228255

@@ -268,11 +295,19 @@ FROM
268295
t.Fatalf("unable to read cluster version: %s", err)
269296
}
270297
if tenantsSupported {
271-
tenant := newTenant(t, ts)
298+
// Connect to the tenant through the SQL proxy, which is only supported
299+
// when using secure+password auth. (The proxy does not support client
300+
// certs or insecure connections).
301+
proxySupported := auth == authPassword
302+
name := "RegularTenant"
303+
if proxySupported {
304+
name += "ThroughProxy"
305+
}
306+
tenant := newTenant(t, ts, proxySupported)
272307
db, dbURL, stopDB := startServerWithApplication(t, tenant, app)
273308
defer stopDB()
274309
testCases = append(testCases, testCase{
275-
name: "RegularTenant",
310+
name: name,
276311
db: db,
277312
dbURL: dbURL,
278313
})
@@ -368,62 +403,108 @@ FROM
368403
}
369404
}
370405

406+
func testORMForAuthModesExcept(t *testing.T, info testInfo, skips map[authMode]string /* mode -> reason */) {
407+
for auth := authMode(0); auth < authModeSentinel; auth++ {
408+
t.Run(fmt.Sprint(auth), func(t *testing.T) {
409+
if msg := skips[auth]; msg != "" {
410+
t.Skip(msg)
411+
}
412+
testORM(t, info, auth)
413+
})
414+
}
415+
}
416+
417+
func nothingSkipped() map[authMode]string { return nil }
418+
371419
func TestGORM(t *testing.T) {
372-
testORM(t, testInfo{language: "go", orm: "gorm"})
420+
testORMForAuthModesExcept(t, testInfo{language: "go", orm: "gorm"}, nothingSkipped())
373421
}
374422

375423
func TestGOPG(t *testing.T) {
376-
testORM(t, testInfo{
377-
language: "go",
378-
orm: "gopg",
379-
// GoPG does not support client certs:
380-
// https://github.com/go-pg/pg/blob/v10/options.go
381-
// If we set up a secure deployment and went through the proxy, it would work (or should anyway), but only
382-
// via the 'database' parameter; GoPG also does not support the 'options' parameter.
383-
insecure: true,
384-
})
424+
testORMForAuthModesExcept(t,
425+
testInfo{language: "go", orm: "gopg"},
426+
map[authMode]string{
427+
// https://github.com/go-pg/pg/blob/v10/options.go
428+
// If we set up a secure deployment and went through the proxy, it would work (or should anyway), but only
429+
// via the 'database' parameter; GoPG also does not support the 'options' parameter.
430+
//
431+
// pg: options other than 'sslmode', 'application_name' and 'connect_timeout' are not supported
432+
authClientCert: "GoPG does not support custom root cert",
433+
authPassword: "GoPG does not support custom root cert",
434+
})
385435
}
386436

387437
func TestHibernate(t *testing.T) {
388-
testORM(t, testInfo{
389-
language: "java",
390-
orm: "hibernate",
391-
// Possibly does not unescape the path correctly:
392-
// Caused by: java.io.FileNotFoundException:
393-
// %2Ftmp%2Fcockroach-testserver913095208%2Fcerts%2Fca.crt (No such file or directory)
394-
insecure: true,
395-
})
438+
testORMForAuthModesExcept(t,
439+
testInfo{language: "java", orm: "hibernate"},
440+
map[authMode]string{
441+
// Driver does not unescape the path correctly:
442+
// Caused by: java.io.FileNotFoundException:
443+
// %2Ftmp%2Fcockroach-testserver913095208%2Fcerts%2Fca.crt (No such file or directory)
444+
//
445+
// Furthermore, if we preprocess the query string via
446+
//
447+
// tc.dbURL.RawQuery, err = url.QueryUnescape(tc.dbURL.RawQuery)
448+
//
449+
// then we run into
450+
// https://github.com/dbeaver/dbeaver/issues/1835
451+
// because hibernate expects the key in DER format, but it is PEM.
452+
authClientCert: "needs DER format and unescaped query string",
453+
// Doesn't seem to understand connection strings.
454+
// Caused by: java.net.UnknownHostException: root:hunter2@localhost
455+
authPassword: "needs custom setup for password support",
456+
},
457+
)
396458
}
397459

398460
func TestSequelize(t *testing.T) {
399-
testORM(t, testInfo{
400-
language: "node",
401-
orm: "sequelize",
402-
// Requires bespoke code to actually use SSL, see:
403-
// https://github.com/sequelize/sequelize/issues/10015
404-
insecure: true,
405-
})
461+
testORMForAuthModesExcept(t,
462+
testInfo{language: "node", orm: "sequelize"},
463+
map[authMode]string{
464+
// Requires bespoke code to actually use SSL, see:
465+
// https://github.com/sequelize/sequelize/issues/10015
466+
authClientCert: "needs custom SSL setup",
467+
authPassword: "needs custom SSL setup",
468+
},
469+
)
406470
}
407471

408472
func TestSQLAlchemy(t *testing.T) {
409-
testORM(t, testInfo{
410-
language: "python",
411-
orm: "sqlalchemy",
412-
})
473+
testORMForAuthModesExcept(t, testInfo{language: "python", orm: "sqlalchemy"}, nothingSkipped())
413474
}
414475

415476
func TestDjango(t *testing.T) {
416-
testORM(t, testInfo{
477+
testORMForAuthModesExcept(t, testInfo{
417478
language: "python",
418479
orm: "django",
419480
tableNames: djangoTestTableNames,
420481
columnNames: djangoTestColumnNames,
421-
// No support for client certs (at least not via the query string).
422-
// psycopg2.OperationalError: fe_sendauth: no password supplied
423-
insecure: true,
424-
})
482+
},
483+
map[authMode]string{
484+
// No support for client certs (at least not via the query string).
485+
// psycopg2.OperationalError: fe_sendauth: no password supplied
486+
authClientCert: "client certs via query string unsupported",
487+
// Ditto,
488+
// psycopg2.OperationalError: fe_sendauth: no password supplied
489+
authPassword: "password via query string unsupported",
490+
},
491+
)
425492
}
426493

494+
// TODO(rafiss): why can't I run the ActiveRecords tests manually
495+
// with this invocation?
496+
//
497+
// ./docker.sh make deps
498+
// ./docker.sh go test -v -run ActiveRecord ./testing
499+
//
500+
// It always fails opaquely like this (after some normal-looking output):
501+
//
502+
// => Run `rails server -h` for more startup options
503+
// Exiting
504+
// make: *** [Makefile:23: start] Error 1
505+
//
506+
427507
func TestActiveRecord(t *testing.T) {
428-
testORM(t, testInfo{language: "ruby", orm: "activerecord"})
508+
testORMForAuthModesExcept(t, testInfo{language: "ruby", orm: "activerecord"}, nothingSkipped())
429509
}
510+

0 commit comments

Comments
 (0)