Skip to content

Commit

Permalink
split user keys for db access
Browse files Browse the repository at this point in the history
  • Loading branch information
nklaassen committed Aug 6, 2024
1 parent ce700b7 commit a57db58
Show file tree
Hide file tree
Showing 23 changed files with 201 additions and 104 deletions.
27 changes: 19 additions & 8 deletions api/utils/keypaths/keypaths.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,14 @@ const (
// │ │ └── appC.key --> private key for app service "appC"
// │ ├── foo-db --> Database access certs for user "foo"
// │ │ ├── root --> Database access certs for cluster "root"
// │ │ │ ├── dbA-x509.pem --> TLS cert for database service "dbA"
// │ │ │ ├── dbB-x509.pem --> TLS cert for database service "dbB"
// │ │ │ ├── dbA.crt --> TLS cert for database service "dbA"
// │ │ │ ├── dbA.key --> private key for database service "dbA"
// │ │ │ ├── dbB.crt --> TLS cert for database service "dbB"
// │ │ │ ├── dbB.key --> private key for database service "dbB"
// │ │ │ └── dbC-wallet --> Oracle Client wallet Configuration directory.
// │ │ ├── leaf --> Database access certs for cluster "leaf"
// │ │ │ └── dbC-x509.pem --> TLS cert for database service "dbC"
// │ │ │ ├── dbC.crt --> TLS cert for database service "dbC"
// │ │ │ └── dbC.key --> private key for database service "dbC"
// │ │ └── proxy-localca.pem --> Self-signed TLS Routing local proxy CA
// │ ├── foo-kube --> Kubernetes certs for user "foo"
// │ | ├── root --> Kubernetes certs for Teleport cluster "root"
Expand Down Expand Up @@ -274,27 +277,35 @@ func DatabaseDir(baseDir, proxy, username string) string {
return filepath.Join(ProxyKeyDir(baseDir, proxy), username+dbDirSuffix)
}

// DatabaseCertDir returns the path to the user's database cert directory
// DatabaseCredentialDir returns the path to the user's database cert directory
// for the given proxy and cluster.
//
// <baseDir>/keys/<proxy>/<username>-db/<cluster>
func DatabaseCertDir(baseDir, proxy, username, cluster string) string {
func DatabaseCredentialDir(baseDir, proxy, username, cluster string) string {
return filepath.Join(DatabaseDir(baseDir, proxy, username), cluster)
}

// DatabaseCertPath returns the path to the user's TLS certificate
// for the given proxy, cluster, and database.
//
// <baseDir>/keys/<proxy>/<username>-db/<cluster>/<dbname>-x509.pem
// <baseDir>/keys/<proxy>/<username>-db/<cluster>/<dbname>.crt
func DatabaseCertPath(baseDir, proxy, username, cluster, dbname string) string {
return filepath.Join(DatabaseCertDir(baseDir, proxy, username, cluster), dbname+fileExtTLSCertLegacy)
return filepath.Join(DatabaseCredentialDir(baseDir, proxy, username, cluster), dbname+fileExtTLSCert)
}

// DatabaseKeyPath returns the path to the user's TLS private key
// for the given proxy, cluster, and database.
//
// <baseDir>/keys/<proxy>/<username>-db/<cluster>/<dbname>.key
func DatabaseKeyPath(baseDir, proxy, username, cluster, dbname string) string {
return filepath.Join(DatabaseCredentialDir(baseDir, proxy, username, cluster), dbname+fileExtTLSKey)
}

// DatabaseOracleWalletDirectory returns the path to the user's Oracle Wallet configuration directory.
// for the given proxy, cluster and database.
// <baseDir>/keys/<proxy>/<username>-db/<cluster>/dbname-wallet/
func DatabaseOracleWalletDirectory(baseDir, proxy, username, cluster, dbname string) string {
return filepath.Join(DatabaseCertDir(baseDir, proxy, username, cluster), dbname+oracleWalletDirSuffix)
return filepath.Join(DatabaseCredentialDir(baseDir, proxy, username, cluster), dbname+oracleWalletDirSuffix)
}

// KubeDir returns the path to the user's kube directory
Expand Down
21 changes: 15 additions & 6 deletions lib/client/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,12 +544,18 @@ func VirtualPathCAParams(caType types.CertAuthType) VirtualPathParams {
}
}

// VirtualPathDatabaseParams returns parameters for selecting specific database
// certificates.
func VirtualPathDatabaseParams(databaseName string) VirtualPathParams {
// VirtualPathDatabaseCertParams returns parameters for selecting a specific database
// certificate by name.
func VirtualPathDatabaseCertParams(databaseName string) VirtualPathParams {
return VirtualPathParams{databaseName}
}

// VirtualPathDatabaseKeyParams returns parameters for selecting a specific database
// key by name.
func VirtualPathDatabaseKeyParams(databaseName string) VirtualPathParams {
return VirtualPathParams{"DB", databaseName}
}

// VirtualPathAppCertParams returns parameters for selecting specific app cert by name.
func VirtualPathAppCertParams(appName string) VirtualPathParams {
return VirtualPathParams{appName}
Expand Down Expand Up @@ -1468,8 +1474,8 @@ func (tc *TeleportClient) IssueUserCertsWithMFA(ctx context.Context, params Reis
}
defer clusterClient.Close()

key, _, err := clusterClient.IssueUserCertsWithMFA(ctx, params, tc.NewMFAPrompt(mfaPromptOpts...))
return key, trace.Wrap(err)
keyRing, _, err := clusterClient.IssueUserCertsWithMFA(ctx, params, tc.NewMFAPrompt(mfaPromptOpts...))
return keyRing, trace.Wrap(err)
}

// CreateAccessRequestV2 registers a new access request with the auth server.
Expand Down Expand Up @@ -3713,7 +3719,10 @@ func (tc *TeleportClient) SSHLogin(ctx context.Context, sshLoginFunc SSHLoginFun
keyRing.KubeTLSCerts[tc.KubernetesCluster] = response.TLSCert
}
if tc.DatabaseService != "" {
keyRing.DBTLSCerts[tc.DatabaseService] = response.TLSCert
keyRing.DBTLSCredentials[tc.DatabaseService] = TLSCredential{
Cert: response.TLSCert,
PrivateKey: priv,
}
}

// Store the requested cluster name in the keyring.
Expand Down
12 changes: 11 additions & 1 deletion lib/client/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -824,12 +824,22 @@ func TestVirtualPathNames(t *testing.T) {
{
name: "database",
kind: VirtualPathDatabase,
params: VirtualPathDatabaseParams("foo"),
params: VirtualPathDatabaseCertParams("foo"),
expected: []string{
"TSH_VIRTUAL_PATH_DB_FOO",
"TSH_VIRTUAL_PATH_DB",
},
},
{
name: "database key",
kind: VirtualPathKey,
params: VirtualPathDatabaseKeyParams("foo"),
expected: []string{
"TSH_VIRTUAL_PATH_KEY_DB_FOO",
"TSH_VIRTUAL_PATH_KEY_DB",
"TSH_VIRTUAL_PATH_KEY",
},
},
{
name: "app",
kind: VirtualPathAppCert,
Expand Down
5 changes: 3 additions & 2 deletions lib/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import (
tracessh "github.com/gravitational/teleport/api/observability/tracing/ssh"
"github.com/gravitational/teleport/api/types"
apievents "github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/api/utils/keys"
"github.com/gravitational/teleport/api/utils/retryutils"
"github.com/gravitational/teleport/lib/auth/authclient"
"github.com/gravitational/teleport/lib/defaults"
Expand Down Expand Up @@ -205,10 +206,10 @@ const (
// makeDatabaseClientPEM returns appropriate client PEM file contents for the
// specified database type. Some databases only need certificate in the PEM
// file, others both certificate and key.
func makeDatabaseClientPEM(proto string, cert []byte, pk *KeyRing) ([]byte, error) {
func makeDatabaseClientPEM(proto string, cert []byte, pk *keys.PrivateKey) ([]byte, error) {
// MongoDB expects certificate and key pair in the same pem file.
if proto == defaults.ProtocolMongoDB {
keyPEM, err := pk.PrivateKey.SoftwarePrivateKeyPEM()
keyPEM, err := pk.SoftwarePrivateKeyPEM()
if err == nil {
return append(cert, keyPEM...), nil
} else if !trace.IsBadParameter(err) {
Expand Down
5 changes: 4 additions & 1 deletion lib/client/client_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ func (s *testAuthority) makeSignedKeyRing(t *testing.T, idx KeyRingIndex, makeEx
keyRing.Cert = cert
keyRing.TLSCert = tlsCert
keyRing.TrustedCerts = []authclient.TrustedCerts{s.trustedCerts}
keyRing.DBTLSCerts["example-db"] = tlsCert
keyRing.DBTLSCredentials["example-db"] = TLSCredential{
Cert: tlsCert,
PrivateKey: priv,
}
return keyRing
}

Expand Down
27 changes: 21 additions & 6 deletions lib/client/cluster_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,14 @@ func (c *ClusterClient) generateUserCerts(ctx context.Context, cachePolicy CertC
Cert: certs.TLS,
}
case proto.UserCertsRequest_Database:
dbCert, err := makeDatabaseClientPEM(params.RouteToDatabase.Protocol, certs.TLS, keyRing)
dbCert, err := makeDatabaseClientPEM(params.RouteToDatabase.Protocol, certs.TLS, privKey)
if err != nil {
return nil, trace.Wrap(err)
}
keyRing.DBTLSCerts[params.RouteToDatabase.ServiceName] = dbCert
keyRing.DBTLSCredentials[params.RouteToDatabase.ServiceName] = TLSCredential{
Cert: dbCert,
PrivateKey: privKey,
}
case proto.UserCertsRequest_Kubernetes:
keyRing.KubeTLSCerts[params.KubernetesCluster] = certs.TLS
case proto.UserCertsRequest_WindowsDesktop:
Expand Down Expand Up @@ -359,6 +362,15 @@ func (c *ClusterClient) prepareUserCertsRequest(ctx context.Context, params Reis
if err != nil {
return nil, nil, trace.Wrap(err)
}
case proto.UserCertsRequest_Database:
privateKey, err = keyRing.GenerateKey(ctx, c.tc, cryptosuites.UserDatabase)
if err != nil {
return nil, nil, trace.Wrap(err)
}
tlsPublicKey, err = keys.MarshalPublicKey(privateKey.Public())
if err != nil {
return nil, nil, trace.Wrap(err)
}
default:
// TODO(nklaassen): split the SSH and TLS key for remaining protocols.
privateKey = keyRing.PrivateKey
Expand Down Expand Up @@ -664,14 +676,17 @@ func PerformMFACeremony(ctx context.Context, params PerformMFACeremonyParams) (*
keyRing.KubeTLSCerts[certsReq.KubernetesCluster] = newCerts.TLS

case proto.UserCertsRequest_Database:
dbCert, err := makeDatabaseClientPEM(certsReq.RouteToDatabase.Protocol, newCerts.TLS, keyRing)
dbCert, err := makeDatabaseClientPEM(certsReq.RouteToDatabase.Protocol, newCerts.TLS, params.PrivateKey)
if err != nil {
return nil, nil, trace.Wrap(err)
}
if keyRing.DBTLSCerts == nil {
keyRing.DBTLSCerts = make(map[string][]byte)
if keyRing.DBTLSCredentials == nil {
keyRing.DBTLSCredentials = make(map[string]TLSCredential)
}
keyRing.DBTLSCredentials[certsReq.RouteToDatabase.ServiceName] = TLSCredential{
Cert: dbCert,
PrivateKey: params.PrivateKey,
}
keyRing.DBTLSCerts[certsReq.RouteToDatabase.ServiceName] = dbCert

case proto.UserCertsRequest_WindowsDesktop:
if keyRing.WindowsDesktopCerts == nil {
Expand Down
13 changes: 8 additions & 5 deletions lib/client/db/dbcmd/dbcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,9 @@ func (c *CLICommandBuilder) getMariaDBArgs() []string {
}

sslCertPath := c.profile.DatabaseCertPathForCluster(c.tc.SiteName, c.db.ServiceName)
sslKeyPath := c.profile.DatabaseKeyPathForCluster(c.tc.SiteName, c.db.ServiceName)

args = append(args, []string{"--ssl-key", c.profile.KeyPath()}...)
args = append(args, []string{"--ssl-key", sslKeyPath}...)
args = append(args, []string{"--ssl-ca", c.profile.CACertPathForCluster(c.rootCluster)}...)
args = append(args, []string{"--ssl-cert", sslCertPath}...)

Expand Down Expand Up @@ -589,7 +590,7 @@ func (c *CLICommandBuilder) getRedisCommand() *exec.Cmd {
if !c.options.noTLS {
args = append(args,
"--tls",
"--key", c.profile.KeyPath(),
"--key", c.profile.DatabaseKeyPathForCluster(c.tc.SiteName, c.db.ServiceName),
"--cert", c.profile.DatabaseCertPathForCluster(c.tc.SiteName, c.db.ServiceName))

if c.tc.InsecureSkipVerify {
Expand Down Expand Up @@ -691,7 +692,9 @@ func (c *CLICommandBuilder) getOpenSearchCommand() (*exec.Cmd, error) {
func (c *CLICommandBuilder) getOpenSearchCLICommand() (*exec.Cmd, error) {
cfg := opensearch.ConfigNoTLS(c.host, c.port)
if !c.options.noTLS {
cfg = opensearch.ConfigTLS(c.host, c.port, c.options.caPath, c.profile.DatabaseCertPathForCluster(c.tc.SiteName, c.db.ServiceName), c.profile.KeyPath())
cfg = opensearch.ConfigTLS(c.host, c.port, c.options.caPath,
c.profile.DatabaseCertPathForCluster(c.tc.SiteName, c.db.ServiceName),
c.profile.DatabaseKeyPathForCluster(c.tc.SiteName, c.db.ServiceName))
}

baseDir := path.Join(c.profile.Dir, c.profile.Cluster, c.db.ServiceName)
Expand Down Expand Up @@ -825,7 +828,7 @@ func (c *CLICommandBuilder) getElasticsearchAlternativeCommands() []CommandAlter
} else {
args := []string{
fmt.Sprintf("https://%v:%v/", c.host, c.port),
"--key", c.profile.KeyPath(),
"--key", c.profile.DatabaseKeyPathForCluster(c.tc.SiteName, c.db.ServiceName),
"--cert", c.profile.DatabaseCertPathForCluster(c.tc.SiteName, c.db.ServiceName),
}

Expand Down Expand Up @@ -870,7 +873,7 @@ func (c *CLICommandBuilder) getOpenSearchAlternativeCommands() []CommandAlternat
} else {
args := []string{
fmt.Sprintf("https://%v:%v/", c.host, c.port),
"--key", c.profile.KeyPath(),
"--key", c.profile.DatabaseKeyPathForCluster(c.tc.SiteName, c.db.ServiceName),
"--cert", c.profile.DatabaseCertPathForCluster(c.tc.SiteName, c.db.ServiceName),
}

Expand Down
2 changes: 1 addition & 1 deletion lib/client/db/dbcmd/dbcmd_clickhouse.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (c *CLICommandBuilder) getClickhouseHTTPCommand() (*exec.Cmd, error) {
} else {
args := []string{
fmt.Sprintf("https://%v:%v/", c.host, c.port),
"--key", c.profile.KeyPath(),
"--key", c.profile.DatabaseKeyPathForCluster(c.tc.SiteName, c.db.ServiceName),
"--cert", c.profile.DatabaseCertPathForCluster(c.tc.SiteName, c.db.ServiceName),
}

Expand Down
Loading

0 comments on commit a57db58

Please sign in to comment.