Skip to content

Commit

Permalink
Fix tsh db connect mongo dbuser logic (gravitational#9196)
Browse files Browse the repository at this point in the history
  • Loading branch information
smallinsky authored Dec 22, 2021
1 parent 7d93a42 commit 31d0990
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 0 deletions.
42 changes: 42 additions & 0 deletions tool/tsh/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package main

import (
"fmt"
"io/ioutil"
"net"
"os"
"os/exec"
Expand Down Expand Up @@ -405,6 +406,17 @@ func needRelogin(cf *CLIConf, tc *client.TeleportClient, database *tlsca.RouteTo
if !found {
return true, nil
}

// For database protocols where database username is encoded in client certificate like Mongo
// check if the command line dbUser matches the encoded username in database certificate.
userChanged, err := dbInfoHasChanged(cf, profile.DatabaseCertPathForCluster(tc.SiteName, database.ServiceName))
if err != nil {
return false, trace.Wrap(err)
}
if userChanged {
return true, nil
}

// Call API and check is a user needs to use MFA to connect to the database.
mfaRequired, err := isMFADatabaseAccessRequired(cf, tc, database)
if err != nil {
Expand All @@ -413,6 +425,36 @@ func needRelogin(cf *CLIConf, tc *client.TeleportClient, database *tlsca.RouteTo
return mfaRequired, nil
}

// dbInfoHasChanged checks if cf.DatabaseUser or cf.DatabaseName info has changed in the user database certificate.
func dbInfoHasChanged(cf *CLIConf, certPath string) (bool, error) {
if cf.DatabaseUser == "" && cf.DatabaseName == "" {
return false, nil
}

buff, err := ioutil.ReadFile(certPath)
if err != nil {
return false, trace.Wrap(err)
}
cert, err := tlsca.ParseCertificatePEM(buff)
if err != nil {
return false, trace.Wrap(err)
}
identity, err := tlsca.FromSubject(cert.Subject, cert.NotAfter)
if err != nil {
return false, trace.Wrap(err)
}

if cf.DatabaseUser != "" && cf.DatabaseUser != identity.RouteToDatabase.Username {
log.Debugf("Will reissue database certificate for user %s (was %s)", cf.DatabaseUser, identity.RouteToDatabase.Username)
return true, nil
}
if cf.DatabaseName != "" && cf.DatabaseName != identity.RouteToDatabase.Database {
log.Debugf("Will reissue database certificate for database name %s (was %s)", cf.DatabaseName, identity.RouteToDatabase.Database)
return true, nil
}
return false, nil
}

// isMFADatabaseAccessRequired calls the IsMFARequired endpoint in order to get from user roles if access to the database
// requires MFA.
func isMFADatabaseAccessRequired(cf *CLIConf, tc *client.TeleportClient, database *tlsca.RouteToDatabase) (bool, error) {
Expand Down
116 changes: 116 additions & 0 deletions tool/tsh/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,22 @@ package main

import (
"context"
"crypto/rand"
"crypto/rsa"
"encoding/pem"
"io/ioutil"
"os"
"path/filepath"
"testing"
"time"

"github.com/gravitational/teleport/api/constants"
apidefaults "github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib"
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/fixtures"
"github.com/gravitational/teleport/lib/service"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
Expand Down Expand Up @@ -125,6 +131,116 @@ func TestFormatConfigCommand(t *testing.T) {
})
}

func TestDBInfoHasChanged(t *testing.T) {
tests := []struct {
name string
databaseUserName string
databaseName string
db tlsca.RouteToDatabase
wantUserHasChanged bool
}{
{
name: "empty cli database user flag",
databaseUserName: "",
db: tlsca.RouteToDatabase{
Username: "alice",
Protocol: defaults.ProtocolMongoDB,
},
wantUserHasChanged: false,
},
{
name: "different user",
databaseUserName: "alice",
db: tlsca.RouteToDatabase{
Username: "bob",
Protocol: defaults.ProtocolMongoDB,
},
wantUserHasChanged: true,
},
{
name: "different user mysql protocol",
databaseUserName: "alice",
db: tlsca.RouteToDatabase{
Username: "bob",
Protocol: defaults.ProtocolMySQL,
},
wantUserHasChanged: true,
},
{
name: "same user",
databaseUserName: "bob",
db: tlsca.RouteToDatabase{
Username: "bob",
Protocol: defaults.ProtocolMongoDB,
},
wantUserHasChanged: false,
},
{
name: "empty cli database user and database name flags",
databaseUserName: "",
databaseName: "",
db: tlsca.RouteToDatabase{
Username: "alice",
Protocol: defaults.ProtocolMongoDB,
},
wantUserHasChanged: false,
},
{
name: "different database name",
databaseUserName: "",
databaseName: "db1",
db: tlsca.RouteToDatabase{
Username: "alice",
Database: "db2",
Protocol: defaults.ProtocolMongoDB,
},
wantUserHasChanged: true,
},
{
name: "same database name",
databaseUserName: "",
databaseName: "db1",
db: tlsca.RouteToDatabase{
Username: "alice",
Database: "db1",
Protocol: defaults.ProtocolMongoDB,
},
wantUserHasChanged: false,
},
}

ca, err := tlsca.FromKeys([]byte(fixtures.TLSCACertPEM), []byte(fixtures.TLSCAKeyPEM))
require.NoError(t, err)
privateKey, err := rsa.GenerateKey(rand.Reader, constants.RSAKeySize)
require.NoError(t, err)

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
identity := tlsca.Identity{
Username: "user",
RouteToDatabase: tc.db,
Groups: []string{"none"},
}
subj, err := identity.Subject()
require.NoError(t, err)
certBytes, err := ca.GenerateCertificate(tlsca.CertificateRequest{
PublicKey: privateKey.Public(),
Subject: subj,
NotAfter: time.Now().Add(time.Hour),
})
require.NoError(t, err)

certPath := filepath.Join(t.TempDir(), "mongo_db_cert.pem")
require.NoError(t, ioutil.WriteFile(certPath, certBytes, 0600))

cliConf := &CLIConf{DatabaseUser: tc.databaseUserName, DatabaseName: tc.databaseName}
got, err := dbInfoHasChanged(cliConf, certPath)
require.NoError(t, err)
require.Equal(t, tc.wantUserHasChanged, got)
})
}
}

func makeTestDatabaseServer(t *testing.T, auth *service.TeleportProcess, proxy *service.TeleportProcess, dbs ...service.Database) (db *service.TeleportProcess) {
// Proxy uses self-signed certificates in tests.
lib.SetInsecureDevMode(true)
Expand Down

0 comments on commit 31d0990

Please sign in to comment.