mirror of
https://github.com/gravitational/teleport
synced 2024-10-21 17:53:28 +00:00
Add CockroachDB support (#8505)
This commit is contained in:
parent
01ced111f4
commit
36998cf566
|
@ -989,6 +989,19 @@ func (c *Config) MySQLProxyHostPort() (string, int) {
|
|||
return webProxyHost, defaults.MySQLListenPort
|
||||
}
|
||||
|
||||
// DatabaseProxyHostPort returns proxy connection endpoint for the database.
|
||||
func (c *Config) DatabaseProxyHostPort(db tlsca.RouteToDatabase) (string, int) {
|
||||
switch db.Protocol {
|
||||
case defaults.ProtocolPostgres, defaults.ProtocolCockroachDB:
|
||||
return c.PostgresProxyHostPort()
|
||||
case defaults.ProtocolMySQL:
|
||||
return c.MySQLProxyHostPort()
|
||||
case defaults.ProtocolMongoDB:
|
||||
return c.WebProxyHostPort()
|
||||
}
|
||||
return c.WebProxyHostPort()
|
||||
}
|
||||
|
||||
// ProxyHost returns the hostname of the proxy server (without any port numbers)
|
||||
func ProxyHost(proxyHost string) string {
|
||||
host, _, err := net.SplitHostPort(proxyHost)
|
||||
|
|
51
lib/client/db/postgres/connstring.go
Normal file
51
lib/client/db/postgres/connstring.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
Copyright 2021 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gravitational/teleport/lib/client/db/profile"
|
||||
)
|
||||
|
||||
// GetConnString returns formatted Postgres connection string for the profile.
|
||||
func GetConnString(c profile.ConnectProfile) string {
|
||||
connStr := "postgres://"
|
||||
if c.User != "" {
|
||||
connStr += c.User + "@"
|
||||
}
|
||||
connStr += net.JoinHostPort(c.Host, strconv.Itoa(c.Port))
|
||||
if c.Database != "" {
|
||||
connStr += "/" + c.Database
|
||||
}
|
||||
params := []string{
|
||||
fmt.Sprintf("sslrootcert=%v", c.CACertPath),
|
||||
fmt.Sprintf("sslcert=%v", c.CertPath),
|
||||
fmt.Sprintf("sslkey=%v", c.KeyPath),
|
||||
}
|
||||
if c.Insecure {
|
||||
params = append(params,
|
||||
fmt.Sprintf("sslmode=%v", SSLModeVerifyCA))
|
||||
} else {
|
||||
params = append(params,
|
||||
fmt.Sprintf("sslmode=%v", SSLModeVerifyFull))
|
||||
}
|
||||
return fmt.Sprintf("%v?%v", connStr, strings.Join(params, "&"))
|
||||
}
|
85
lib/client/db/postgres/connstring_test.go
Normal file
85
lib/client/db/postgres/connstring_test.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
Copyright 2021 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gravitational/teleport/lib/client/db/profile"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestConnString verifies creating Postgres connection string from profile.
|
||||
func TestConnString(t *testing.T) {
|
||||
const (
|
||||
host = "localhost"
|
||||
port = 5432
|
||||
caPath = "/tmp/ca"
|
||||
certPath = "/tmp/cert"
|
||||
keyPath = "/tmp/key"
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
user string
|
||||
database string
|
||||
insecure bool
|
||||
out string
|
||||
}{
|
||||
{
|
||||
name: "default settings",
|
||||
out: "postgres://localhost:5432?sslrootcert=/tmp/ca&sslcert=/tmp/cert&sslkey=/tmp/key&sslmode=verify-full",
|
||||
},
|
||||
{
|
||||
name: "insecure",
|
||||
insecure: true,
|
||||
out: "postgres://localhost:5432?sslrootcert=/tmp/ca&sslcert=/tmp/cert&sslkey=/tmp/key&sslmode=verify-ca",
|
||||
},
|
||||
{
|
||||
name: "user set",
|
||||
user: "alice",
|
||||
out: "postgres://alice@localhost:5432?sslrootcert=/tmp/ca&sslcert=/tmp/cert&sslkey=/tmp/key&sslmode=verify-full",
|
||||
},
|
||||
{
|
||||
name: "database set",
|
||||
database: "test",
|
||||
out: "postgres://localhost:5432/test?sslrootcert=/tmp/ca&sslcert=/tmp/cert&sslkey=/tmp/key&sslmode=verify-full",
|
||||
},
|
||||
{
|
||||
name: "user and database set",
|
||||
user: "alice",
|
||||
database: "test",
|
||||
out: "postgres://alice@localhost:5432/test?sslrootcert=/tmp/ca&sslcert=/tmp/cert&sslkey=/tmp/key&sslmode=verify-full",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
require.Equal(t, test.out, GetConnString(profile.ConnectProfile{
|
||||
Host: host,
|
||||
Port: port,
|
||||
User: test.user,
|
||||
Database: test.database,
|
||||
Insecure: test.insecure,
|
||||
CACertPath: caPath,
|
||||
CertPath: certPath,
|
||||
KeyPath: keyPath,
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
|
@ -68,7 +68,17 @@ func add(tc *client.TeleportClient, db tlsca.RouteToDatabase, clientProfile clie
|
|||
default:
|
||||
return nil, trace.BadParameter("unknown database protocol: %q", db)
|
||||
}
|
||||
connectProfile := profile.ConnectProfile{
|
||||
connectProfile := New(tc, db, clientProfile, host, port)
|
||||
err := profileFile.Upsert(connectProfile)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return &connectProfile, nil
|
||||
}
|
||||
|
||||
// New makes a new database connection profile.
|
||||
func New(tc *client.TeleportClient, db tlsca.RouteToDatabase, clientProfile client.ProfileStatus, host string, port int) profile.ConnectProfile {
|
||||
return profile.ConnectProfile{
|
||||
Name: profileName(tc.SiteName, db.ServiceName),
|
||||
Host: host,
|
||||
Port: port,
|
||||
|
@ -79,11 +89,6 @@ func add(tc *client.TeleportClient, db tlsca.RouteToDatabase, clientProfile clie
|
|||
CertPath: clientProfile.DatabaseCertPath(db.ServiceName),
|
||||
KeyPath: clientProfile.KeyPath(),
|
||||
}
|
||||
err := profileFile.Upsert(connectProfile)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return &connectProfile, nil
|
||||
}
|
||||
|
||||
// Env returns environment variables for the specified database profile.
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gravitational/teleport/api/identityfile"
|
||||
|
@ -61,12 +62,16 @@ const (
|
|||
// configuring a MongoDB database for mutual TLS authentication.
|
||||
FormatMongo Format = "mongodb"
|
||||
|
||||
// FormatCockroach produces CA and key pair in the format suitable for
|
||||
// configuring a CockroachDB database for mutual TLS.
|
||||
FormatCockroach Format = "cockroachdb"
|
||||
|
||||
// DefaultFormat is what Teleport uses by default
|
||||
DefaultFormat = FormatFile
|
||||
)
|
||||
|
||||
// KnownFormats is a list of all above formats.
|
||||
var KnownFormats = []Format{FormatFile, FormatOpenSSH, FormatTLS, FormatKubernetes, FormatDatabase, FormatMongo}
|
||||
var KnownFormats = []Format{FormatFile, FormatOpenSSH, FormatTLS, FormatKubernetes, FormatDatabase, FormatMongo, FormatCockroach}
|
||||
|
||||
// WriteConfig holds the necessary information to write an identity file.
|
||||
type WriteConfig struct {
|
||||
|
@ -147,10 +152,18 @@ func Write(cfg WriteConfig) (filesWritten []string, err error) {
|
|||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
case FormatTLS, FormatDatabase:
|
||||
case FormatTLS, FormatDatabase, FormatCockroach:
|
||||
keyPath := cfg.OutputPath + ".key"
|
||||
certPath := cfg.OutputPath + ".crt"
|
||||
casPath := cfg.OutputPath + ".cas"
|
||||
|
||||
// CockroachDB expects files to be named ca.crt, node.crt and node.key.
|
||||
if cfg.Format == FormatCockroach {
|
||||
keyPath = filepath.Join(cfg.OutputPath, "node.key")
|
||||
certPath = filepath.Join(cfg.OutputPath, "node.crt")
|
||||
casPath = filepath.Join(cfg.OutputPath, "ca.crt")
|
||||
}
|
||||
|
||||
filesWritten = append(filesWritten, keyPath, certPath, casPath)
|
||||
if err := checkOverwrite(cfg.OverwriteDestination, filesWritten...); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
|
|
|
@ -473,6 +473,12 @@ const (
|
|||
ProtocolMySQL = "mysql"
|
||||
// ProtocolMongoDB is the MongoDB database protocol.
|
||||
ProtocolMongoDB = "mongodb"
|
||||
// ProtocolCockroachDB is the CockroachDB database protocol.
|
||||
//
|
||||
// Technically it's the same as the Postgres protocol but it's used to
|
||||
// differentiate between Cockroach and Postgres databases e.g. when
|
||||
// selecting a CLI client to use.
|
||||
ProtocolCockroachDB = "cockroachdb"
|
||||
)
|
||||
|
||||
// DatabaseProtocols is a list of all supported database protocols.
|
||||
|
@ -480,6 +486,7 @@ var DatabaseProtocols = []string{
|
|||
ProtocolPostgres,
|
||||
ProtocolMySQL,
|
||||
ProtocolMongoDB,
|
||||
ProtocolCockroachDB,
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
@ -38,6 +38,13 @@ func DatabaseRoleMatchers(dbProtocol string, user, database string) services.Rol
|
|||
return services.RoleMatchers{
|
||||
&services.DatabaseUserMatcher{User: user},
|
||||
}
|
||||
case defaults.ProtocolCockroachDB:
|
||||
// Cockroach uses the same wire protocol as Postgres but handling of
|
||||
// databases is different and there's no way to prevent cross-database
|
||||
// queries so only apply RBAC to db_users.
|
||||
return services.RoleMatchers{
|
||||
&services.DatabaseUserMatcher{User: user},
|
||||
}
|
||||
default:
|
||||
return services.RoleMatchers{
|
||||
&services.DatabaseUserMatcher{User: user},
|
||||
|
|
|
@ -23,7 +23,6 @@ import (
|
|||
"net"
|
||||
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
"github.com/gravitational/teleport/lib/srv/db/common"
|
||||
"github.com/gravitational/teleport/lib/srv/db/common/role"
|
||||
|
@ -183,7 +182,7 @@ func (e *Engine) checkAccess(ctx context.Context, sessionCtx *common.Session) er
|
|||
}
|
||||
|
||||
dbRoleMatchers := role.DatabaseRoleMatchers(
|
||||
defaults.ProtocolPostgres,
|
||||
sessionCtx.Database.GetProtocol(),
|
||||
sessionCtx.DatabaseUser,
|
||||
sessionCtx.DatabaseName,
|
||||
)
|
||||
|
|
|
@ -644,7 +644,7 @@ func (s *Server) dispatch(sessionCtx *common.Session, streamWriter events.Stream
|
|||
return nil, trace.Wrap(err)
|
||||
}
|
||||
switch sessionCtx.Database.GetProtocol() {
|
||||
case defaults.ProtocolPostgres:
|
||||
case defaults.ProtocolPostgres, defaults.ProtocolCockroachDB:
|
||||
return &postgres.Engine{
|
||||
Auth: s.cfg.Auth,
|
||||
Audit: audit,
|
||||
|
|
|
@ -320,9 +320,11 @@ func (a *AuthCommand) GenerateKeys() error {
|
|||
|
||||
// GenerateAndSignKeys generates a new keypair and signs it for role
|
||||
func (a *AuthCommand) GenerateAndSignKeys(clusterAPI auth.ClientI) error {
|
||||
switch {
|
||||
case a.outputFormat == identityfile.FormatDatabase || a.outputFormat == identityfile.FormatMongo:
|
||||
switch a.outputFormat {
|
||||
case identityfile.FormatDatabase, identityfile.FormatMongo, identityfile.FormatCockroach:
|
||||
return a.generateDatabaseKeys(clusterAPI)
|
||||
}
|
||||
switch {
|
||||
case a.genUser != "" && a.genHost == "":
|
||||
return a.generateUserKeys(clusterAPI)
|
||||
case a.genUser == "" && a.genHost != "":
|
||||
|
@ -425,6 +427,12 @@ func (a *AuthCommand) generateDatabaseKeysForKey(clusterAPI auth.ClientI, key *c
|
|||
if len(principals) == 0 {
|
||||
return trace.BadParameter("at least one hostname must be specified via --host flag")
|
||||
}
|
||||
// For CockroachDB node certificates, CommonName must be "node":
|
||||
//
|
||||
// https://www.cockroachlabs.com/docs/v21.1/cockroach-cert#node-key-and-certificates
|
||||
if a.outputFormat == identityfile.FormatCockroach {
|
||||
principals = append([]string{"node"}, principals...)
|
||||
}
|
||||
subject := pkix.Name{CommonName: principals[0]}
|
||||
if a.outputFormat == identityfile.FormatMongo {
|
||||
// Include Organization attribute in MongoDB certificates as well.
|
||||
|
@ -485,6 +493,11 @@ func (a *AuthCommand) generateDatabaseKeysForKey(clusterAPI auth.ClientI, key *c
|
|||
"files": strings.Join(filesWritten, ", "),
|
||||
"output": a.output,
|
||||
})
|
||||
case identityfile.FormatCockroach:
|
||||
cockroachAuthSignTpl.Execute(os.Stdout, map[string]interface{}{
|
||||
"files": strings.Join(filesWritten, ", "),
|
||||
"output": a.output,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -521,6 +534,15 @@ net:
|
|||
mode: requireTLS
|
||||
certificateKeyFile: /path/to/{{.output}}.crt
|
||||
CAFile: /path/to/{{.output}}.cas
|
||||
`))
|
||||
cockroachAuthSignTpl = template.Must(template.New("").Parse(`Database credentials have been written to {{.files}}.
|
||||
|
||||
To enable mutual TLS on your CockroachDB server, point it to the certs
|
||||
directory using --certs-dir flag:
|
||||
|
||||
cockroach start \
|
||||
--certs-dir={{.output}} \
|
||||
# other flags...
|
||||
`))
|
||||
)
|
||||
|
||||
|
|
|
@ -376,8 +376,13 @@ func TestGenerateDatabaseKeys(t *testing.T) {
|
|||
name string
|
||||
inFormat identityfile.Format
|
||||
inHost string
|
||||
inOutDir string
|
||||
inOutFile string
|
||||
outSubject pkix.Name
|
||||
outServerNames []string
|
||||
outKeyFile string
|
||||
outCertFile string
|
||||
outCAFile string
|
||||
outKey []byte
|
||||
outCert []byte
|
||||
outCA []byte
|
||||
|
@ -386,8 +391,13 @@ func TestGenerateDatabaseKeys(t *testing.T) {
|
|||
name: "database certificate",
|
||||
inFormat: identityfile.FormatDatabase,
|
||||
inHost: "postgres.example.com",
|
||||
inOutDir: t.TempDir(),
|
||||
inOutFile: "db",
|
||||
outSubject: pkix.Name{CommonName: "postgres.example.com"},
|
||||
outServerNames: []string{"postgres.example.com"},
|
||||
outKeyFile: "db.key",
|
||||
outCertFile: "db.crt",
|
||||
outCAFile: "db.cas",
|
||||
outKey: key.Priv,
|
||||
outCert: certBytes,
|
||||
outCA: caBytes,
|
||||
|
@ -396,8 +406,13 @@ func TestGenerateDatabaseKeys(t *testing.T) {
|
|||
name: "database certificate multiple SANs",
|
||||
inFormat: identityfile.FormatDatabase,
|
||||
inHost: "mysql.external.net,mysql.internal.net,192.168.1.1",
|
||||
inOutDir: t.TempDir(),
|
||||
inOutFile: "db",
|
||||
outSubject: pkix.Name{CommonName: "mysql.external.net"},
|
||||
outServerNames: []string{"mysql.external.net", "mysql.internal.net", "192.168.1.1"},
|
||||
outKeyFile: "db.key",
|
||||
outCertFile: "db.crt",
|
||||
outCAFile: "db.cas",
|
||||
outKey: key.Priv,
|
||||
outCert: certBytes,
|
||||
outCA: caBytes,
|
||||
|
@ -406,17 +421,35 @@ func TestGenerateDatabaseKeys(t *testing.T) {
|
|||
name: "mongodb certificate",
|
||||
inFormat: identityfile.FormatMongo,
|
||||
inHost: "mongo.example.com",
|
||||
inOutDir: t.TempDir(),
|
||||
inOutFile: "mongo",
|
||||
outSubject: pkix.Name{CommonName: "mongo.example.com", Organization: []string{"example.com"}},
|
||||
outServerNames: []string{"mongo.example.com"},
|
||||
outCertFile: "mongo.crt",
|
||||
outCAFile: "mongo.cas",
|
||||
outCert: append(certBytes, key.Priv...),
|
||||
outCA: caBytes,
|
||||
},
|
||||
{
|
||||
name: "cockroachdb certificate",
|
||||
inFormat: identityfile.FormatCockroach,
|
||||
inHost: "localhost,roach1",
|
||||
inOutDir: t.TempDir(),
|
||||
outSubject: pkix.Name{CommonName: "node"},
|
||||
outServerNames: []string{"node", "localhost", "roach1"}, // "node" principal should always be added
|
||||
outKeyFile: "node.key",
|
||||
outCertFile: "node.crt",
|
||||
outCAFile: "ca.crt",
|
||||
outKey: key.Priv,
|
||||
outCert: certBytes,
|
||||
outCA: caBytes,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ac := AuthCommand{
|
||||
output: filepath.Join(t.TempDir(), "db"),
|
||||
output: filepath.Join(test.inOutDir, test.inOutFile),
|
||||
outputFormat: test.inFormat,
|
||||
signOverwrite: true,
|
||||
genHost: test.inHost,
|
||||
|
@ -434,19 +467,19 @@ func TestGenerateDatabaseKeys(t *testing.T) {
|
|||
require.Equal(t, test.outServerNames[0], authClient.dbCertsReq.ServerName)
|
||||
|
||||
if len(test.outKey) > 0 {
|
||||
keyBytes, err := ioutil.ReadFile(ac.output + ".key")
|
||||
keyBytes, err := ioutil.ReadFile(filepath.Join(test.inOutDir, test.outKeyFile))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.outKey, keyBytes, "keys match")
|
||||
}
|
||||
|
||||
if len(test.outCert) > 0 {
|
||||
certBytes, err := ioutil.ReadFile(ac.output + ".crt")
|
||||
certBytes, err := ioutil.ReadFile(filepath.Join(test.inOutDir, test.outCertFile))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.outCert, certBytes, "certificates match")
|
||||
}
|
||||
|
||||
if len(test.outCA) > 0 {
|
||||
caBytes, err := ioutil.ReadFile(ac.output + ".cas")
|
||||
caBytes, err := ioutil.ReadFile(filepath.Join(test.inOutDir, test.outCAFile))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.outCA, caBytes, "CA certificates match")
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import (
|
|||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/client"
|
||||
dbprofile "github.com/gravitational/teleport/lib/client/db"
|
||||
"github.com/gravitational/teleport/lib/client/db/postgres"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/srv/alpnproxy"
|
||||
"github.com/gravitational/teleport/lib/tlsca"
|
||||
|
@ -227,7 +228,7 @@ func onDatabaseConfig(cf *CLIConf) error {
|
|||
var host string
|
||||
var port int
|
||||
switch database.Protocol {
|
||||
case defaults.ProtocolPostgres:
|
||||
case defaults.ProtocolPostgres, defaults.ProtocolCockroachDB:
|
||||
host, port = tc.PostgresProxyHostPort()
|
||||
case defaults.ProtocolMySQL:
|
||||
host, port = tc.MySQLProxyHostPort()
|
||||
|
@ -324,6 +325,7 @@ func onDatabaseConnect(cf *CLIConf) error {
|
|||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
log.Debug(cmd.String())
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdin = os.Stdin
|
||||
|
@ -449,6 +451,14 @@ func pickActiveDatabase(cf *CLIConf) (*tlsca.RouteToDatabase, error) {
|
|||
}
|
||||
for _, db := range profile.Databases {
|
||||
if db.ServiceName == name {
|
||||
// If database user or name were provided on the CLI,
|
||||
// override the default ones.
|
||||
if cf.DatabaseUser != "" {
|
||||
db.Username = cf.DatabaseUser
|
||||
}
|
||||
if cf.DatabaseName != "" {
|
||||
db.Database = cf.DatabaseName
|
||||
}
|
||||
return &db, nil
|
||||
}
|
||||
}
|
||||
|
@ -477,46 +487,54 @@ func getConnectCommand(cf *CLIConf, tc *client.TeleportClient, profile *client.P
|
|||
opt(&options)
|
||||
}
|
||||
|
||||
// In TLS routing mode a local proxy is started on demand so connect to it.
|
||||
host, port := tc.DatabaseProxyHostPort(*db)
|
||||
if options.localProxyPort != 0 && options.localProxyHost != "" {
|
||||
host = options.localProxyHost
|
||||
port = options.localProxyPort
|
||||
}
|
||||
|
||||
switch db.Protocol {
|
||||
case defaults.ProtocolPostgres:
|
||||
return getPostgresCommand(db, profile.Cluster, cf.DatabaseUser, cf.DatabaseName, options), nil
|
||||
return getPostgresCommand(tc, profile, db, host, port, options), nil
|
||||
|
||||
case defaults.ProtocolCockroachDB:
|
||||
return getCockroachCommand(tc, profile, db, host, port, options), nil
|
||||
|
||||
case defaults.ProtocolMySQL:
|
||||
return getMySQLCommand(db, profile.Cluster, cf.DatabaseUser, cf.DatabaseName, options), nil
|
||||
return getMySQLCommand(profile, db, options), nil
|
||||
|
||||
case defaults.ProtocolMongoDB:
|
||||
host, port := tc.WebProxyHostPort()
|
||||
if options.localProxyPort != 0 && options.localProxyHost != "" {
|
||||
host = options.localProxyHost
|
||||
port = options.localProxyPort
|
||||
}
|
||||
return getMongoCommand(host, port, profile.DatabaseCertPath(db.ServiceName), options.caPath, cf.DatabaseName), nil
|
||||
return getMongoCommand(profile, db, host, port, options), nil
|
||||
}
|
||||
|
||||
return nil, trace.BadParameter("unsupported database protocol: %v", db)
|
||||
}
|
||||
|
||||
func getPostgresCommand(db *tlsca.RouteToDatabase, cluster, user, name string, options connectionCommandOpts) *exec.Cmd {
|
||||
connString := []string{fmt.Sprintf("service=%v-%v", cluster, db.ServiceName)}
|
||||
if user != "" {
|
||||
connString = append(connString, fmt.Sprintf("user=%v", user))
|
||||
}
|
||||
if name != "" {
|
||||
connString = append(connString, fmt.Sprintf("dbname=%v", name))
|
||||
}
|
||||
if options.localProxyPort != 0 {
|
||||
connString = append(connString, fmt.Sprintf("port=%v", options.localProxyPort))
|
||||
}
|
||||
if options.localProxyHost != "" {
|
||||
connString = append(connString, fmt.Sprintf("host=%v", options.localProxyHost))
|
||||
}
|
||||
return exec.Command(postgresBin, strings.Join(connString, " "))
|
||||
func getPostgresCommand(tc *client.TeleportClient, profile *client.ProfileStatus, db *tlsca.RouteToDatabase, host string, port int, options connectionCommandOpts) *exec.Cmd {
|
||||
return exec.Command(postgresBin,
|
||||
postgres.GetConnString(dbprofile.New(tc, *db, *profile, host, port)))
|
||||
}
|
||||
|
||||
func getMySQLCommand(db *tlsca.RouteToDatabase, cluster, user, name string, options connectionCommandOpts) *exec.Cmd {
|
||||
args := []string{fmt.Sprintf("--defaults-group-suffix=_%v-%v", cluster, db.ServiceName)}
|
||||
if user != "" {
|
||||
args = append(args, "--user", user)
|
||||
func getCockroachCommand(tc *client.TeleportClient, profile *client.ProfileStatus, db *tlsca.RouteToDatabase, host string, port int, options connectionCommandOpts) *exec.Cmd {
|
||||
// If cockroach CLI client is not available, fallback to psql.
|
||||
if _, err := exec.LookPath(cockroachBin); err != nil {
|
||||
log.Debugf("Couldn't find %q client in PATH, falling back to %q: %v.",
|
||||
cockroachBin, postgresBin, err)
|
||||
return exec.Command(postgresBin,
|
||||
postgres.GetConnString(dbprofile.New(tc, *db, *profile, host, port)))
|
||||
}
|
||||
if name != "" {
|
||||
args = append(args, "--database", name)
|
||||
return exec.Command(cockroachBin, "sql", "--url",
|
||||
postgres.GetConnString(dbprofile.New(tc, *db, *profile, host, port)))
|
||||
}
|
||||
|
||||
func getMySQLCommand(profile *client.ProfileStatus, db *tlsca.RouteToDatabase, options connectionCommandOpts) *exec.Cmd {
|
||||
args := []string{fmt.Sprintf("--defaults-group-suffix=_%v-%v", profile.Cluster, db.ServiceName)}
|
||||
if db.Username != "" {
|
||||
args = append(args, "--user", db.Username)
|
||||
}
|
||||
if db.Database != "" {
|
||||
args = append(args, "--database", db.Database)
|
||||
}
|
||||
|
||||
if options.localProxyPort != 0 {
|
||||
|
@ -532,21 +550,21 @@ func getMySQLCommand(db *tlsca.RouteToDatabase, cluster, user, name string, opti
|
|||
return exec.Command(mysqlBin, args...)
|
||||
}
|
||||
|
||||
func getMongoCommand(host string, port int, certPath, caPath, name string) *exec.Cmd {
|
||||
func getMongoCommand(profile *client.ProfileStatus, db *tlsca.RouteToDatabase, host string, port int, options connectionCommandOpts) *exec.Cmd {
|
||||
args := []string{
|
||||
"--host", host,
|
||||
"--port", strconv.Itoa(port),
|
||||
"--ssl",
|
||||
"--sslPEMKeyFile", certPath,
|
||||
"--sslPEMKeyFile", profile.DatabaseCertPath(db.ServiceName),
|
||||
}
|
||||
|
||||
if caPath != "" {
|
||||
if options.caPath != "" {
|
||||
// caPath is set only if mongo connects to the Teleport Proxy via ALPN SNI Local Proxy
|
||||
// and connection is terminated by proxy identity certificate.
|
||||
args = append(args, []string{"--sslCAFile", caPath}...)
|
||||
args = append(args, []string{"--sslCAFile", options.caPath}...)
|
||||
}
|
||||
if name != "" {
|
||||
args = append(args, name)
|
||||
if db.Database != "" {
|
||||
args = append(args, db.Database)
|
||||
}
|
||||
return exec.Command(mongoBin, args...)
|
||||
}
|
||||
|
@ -561,6 +579,8 @@ const (
|
|||
const (
|
||||
// postgresBin is the Postgres client binary name.
|
||||
postgresBin = "psql"
|
||||
// cockroachBin is the Cockroach client binary name.
|
||||
cockroachBin = "cockroach"
|
||||
// mysqlBin is the MySQL client binary name.
|
||||
mysqlBin = "mysql"
|
||||
// mongoBin is the Mongo client binary name.
|
||||
|
|
|
@ -128,7 +128,7 @@ func toALPNProtocol(dbProtocol string) (alpncommon.Protocol, error) {
|
|||
switch dbProtocol {
|
||||
case defaults.ProtocolMySQL:
|
||||
return alpncommon.ProtocolMySQL, nil
|
||||
case defaults.ProtocolPostgres:
|
||||
case defaults.ProtocolPostgres, defaults.ProtocolCockroachDB:
|
||||
return alpncommon.ProtocolPostgres, nil
|
||||
case defaults.ProtocolMongoDB:
|
||||
return alpncommon.ProtocolMongoDB, nil
|
||||
|
|
Loading…
Reference in a new issue