From b797d592edff96e8f9ff77b1f4e0f54ef37e55fb Mon Sep 17 00:00:00 2001 From: Gabriel Corado Date: Thu, 3 Mar 2022 10:10:21 -0300 Subject: [PATCH 1/8] feat(tctl): sign command to generate database access credentials --- tool/tctl/common/auth_command.go | 39 +++++++++++- tool/tctl/common/auth_command_test.go | 89 +++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 3 deletions(-) diff --git a/tool/tctl/common/auth_command.go b/tool/tctl/common/auth_command.go index 6dce83157fa70..876262d764000 100644 --- a/tool/tctl/common/auth_command.go +++ b/tool/tctl/common/auth_command.go @@ -68,6 +68,7 @@ type AuthCommand struct { leafCluster string kubeCluster string appName string + dbName string signOverwrite bool rotateGracePeriod time.Duration @@ -120,6 +121,7 @@ func (a *AuthCommand) Initialize(app *kingpin.Application, config *service.Confi a.authSign.Flag("leaf-cluster", `Leaf cluster to generate identity file for when --format is set to "kubernetes"`).StringVar(&a.leafCluster) a.authSign.Flag("kube-cluster-name", `Kubernetes cluster to generate identity file for when --format is set to "kubernetes"`).StringVar(&a.kubeCluster) a.authSign.Flag("app-name", `Application to generate identity file for`).StringVar(&a.appName) + a.authSign.Flag("db-name", `Database to generate identity file for`).StringVar(&a.dbName) a.authRotate = auth.Command("rotate", "Rotate certificate authorities in the cluster") a.authRotate.Flag("grace-period", "Grace period keeps previous certificate authorities signatures valid, if set to 0 will force users to relogin and nodes to re-register."). @@ -593,10 +595,14 @@ func (a *AuthCommand) generateUserKeys(clusterAPI auth.ClientI) error { return trace.Wrap(err) } - var routeToApp proto.RouteToApp - var certUsage proto.UserCertsRequest_CertUsage + var ( + routeToApp proto.RouteToApp + routeToDatabase proto.RouteToDatabase + certUsage proto.UserCertsRequest_CertUsage + ) - if a.appName != "" { + switch { + case a.appName != "": server, err := getApplicationServer(context.TODO(), clusterAPI, a.appName) if err != nil { return trace.Wrap(err) @@ -618,6 +624,17 @@ func (a *AuthCommand) generateUserKeys(clusterAPI auth.ClientI) error { SessionID: appSession.GetName(), } certUsage = proto.UserCertsRequest_App + case a.dbName != "": + server, err := getDatabaseServer(context.TODO(), clusterAPI, a.dbName) + if err != nil { + return trace.Wrap(err) + } + + routeToDatabase = proto.RouteToDatabase{ + ServiceName: a.dbName, + Protocol: server.GetDatabase().GetProtocol(), + } + certUsage = proto.UserCertsRequest_Database } reqExpiry := time.Now().UTC().Add(a.genTTL) @@ -631,6 +648,7 @@ func (a *AuthCommand) generateUserKeys(clusterAPI auth.ClientI) error { KubernetesCluster: a.kubeCluster, RouteToApp: routeToApp, Usage: certUsage, + RouteToDatabase: routeToDatabase, }) if err != nil { return trace.Wrap(err) @@ -817,3 +835,18 @@ func getApplicationServer(ctx context.Context, clusterAPI auth.ClientI, appName } return nil, trace.NotFound("app %q not found", appName) } + +func getDatabaseServer(ctx context.Context, clientAPI auth.ClientI, dbName string) (types.DatabaseServer, error) { + servers, err := clientAPI.GetDatabaseServers(ctx, apidefaults.Namespace) + if err != nil { + return nil, trace.Wrap(err) + } + + for _, server := range servers { + if server.GetName() == dbName { + return server, nil + } + } + + return nil, trace.NotFound("database %q not found", dbName) +} diff --git a/tool/tctl/common/auth_command_test.go b/tool/tctl/common/auth_command_test.go index 078160132d496..90aa4318157f1 100644 --- a/tool/tctl/common/auth_command_test.go +++ b/tool/tctl/common/auth_command_test.go @@ -32,6 +32,7 @@ import ( "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/client/identityfile" + "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/kube/kubeconfig" "github.com/gravitational/teleport/lib/service" "github.com/gravitational/teleport/lib/services" @@ -238,6 +239,7 @@ type mockClient struct { remoteClusters []types.RemoteCluster kubeServices []types.Server appServices []types.AppServer + dbServices []types.DatabaseServer appSession types.WebSession } @@ -273,6 +275,10 @@ func (c *mockClient) CreateAppSession(ctx context.Context, req types.CreateAppSe return c.appSession, nil } +func (c *mockClient) GetDatabaseServers(context.Context, string, ...services.MarshalOption) ([]types.DatabaseServer, error) { + return c.dbServices, nil +} + func TestCheckKubeCluster(t *testing.T) { const teleportCluster = "local-teleport" clusterName, err := services.NewClusterNameWithRandomID(types.ClusterNameSpecV2{ @@ -633,3 +639,86 @@ func TestGenerateAppCertificates(t *testing.T) { }) } } + +func TestGenerateDatabaseUserCertificates(t *testing.T) { + tests := map[string]struct { + clusterName string + dbName string + dbServices []types.DatabaseServer + withError bool + }{ + "DatabaseExists": { + clusterName: "example.com", + dbName: "db-1", + dbServices: []types.DatabaseServer{ + &types.DatabaseServerV3{ + Metadata: types.Metadata{ + Name: "db-1", + }, + Spec: types.DatabaseServerSpecV3{ + Hostname: "example.com", + Database: &types.DatabaseV3{ + Spec: types.DatabaseSpecV3{ + Protocol: defaults.ProtocolPostgres, + }, + }, + }, + }, + }, + }, + "DatabaseNotFound": { + clusterName: "example.com", + dbName: "db-2", + dbServices: []types.DatabaseServer{}, + withError: true, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + clusterName, err := services.NewClusterNameWithRandomID( + types.ClusterNameSpecV2{ + ClusterName: test.clusterName, + }) + require.NoError(t, err) + + authClient := &mockClient{ + clusterName: clusterName, + userCerts: &proto.Certs{ + SSH: []byte("SSH cert"), + TLS: []byte("TLS cert"), + }, + dbServices: test.dbServices, + } + + certsDir := t.TempDir() + output := filepath.Join(certsDir, test.dbName) + ac := AuthCommand{ + output: output, + outputFormat: identityfile.FormatTLS, + signOverwrite: true, + genTTL: time.Hour, + dbName: test.dbName, + } + + err = ac.generateUserKeys(authClient) + if test.withError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + expectedRouteToDatabase := proto.RouteToDatabase{ + ServiceName: test.dbName, + Protocol: defaults.ProtocolPostgres, + } + require.Equal(t, proto.UserCertsRequest_Database, authClient.userCertsReq.Usage) + require.Equal(t, expectedRouteToDatabase, authClient.userCertsReq.RouteToDatabase) + + certBytes, err := ioutil.ReadFile(filepath.Join(certsDir, test.dbName+".crt")) + require.NoError(t, err) + require.Equal(t, authClient.userCerts.TLS, certBytes, "certificates match") + }) + } +} From b0302d9c1bdf773590f6463a79a9f54e22b6bff4 Mon Sep 17 00:00:00 2001 From: Gabriel Corado Date: Fri, 4 Mar 2022 09:36:12 -0300 Subject: [PATCH 2/8] feat(tctl): make auth sign parameters app-name and db-name mutually exclusive --- tool/tctl/common/auth_command.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tool/tctl/common/auth_command.go b/tool/tctl/common/auth_command.go index 876262d764000..0a075ac43d9d8 100644 --- a/tool/tctl/common/auth_command.go +++ b/tool/tctl/common/auth_command.go @@ -120,8 +120,8 @@ func (a *AuthCommand) Initialize(app *kingpin.Application, config *service.Confi a.authSign.Flag("kube-cluster", `Leaf cluster to generate identity file for when --format is set to "kubernetes"`).Hidden().StringVar(&a.leafCluster) a.authSign.Flag("leaf-cluster", `Leaf cluster to generate identity file for when --format is set to "kubernetes"`).StringVar(&a.leafCluster) a.authSign.Flag("kube-cluster-name", `Kubernetes cluster to generate identity file for when --format is set to "kubernetes"`).StringVar(&a.kubeCluster) - a.authSign.Flag("app-name", `Application to generate identity file for`).StringVar(&a.appName) - a.authSign.Flag("db-name", `Database to generate identity file for`).StringVar(&a.dbName) + a.authSign.Flag("app-name", `Application to generate identity file for. Mutually exclusive with "--db-name".`).StringVar(&a.appName) + a.authSign.Flag("db-name", `Database to generate identity file for. Mutually exclusive with "--app-name".`).StringVar(&a.dbName) a.authRotate = auth.Command("rotate", "Rotate certificate authorities in the cluster") a.authRotate.Flag("grace-period", "Grace period keeps previous certificate authorities signatures valid, if set to 0 will force users to relogin and nodes to re-register."). @@ -601,6 +601,11 @@ func (a *AuthCommand) generateUserKeys(clusterAPI auth.ClientI) error { certUsage proto.UserCertsRequest_CertUsage ) + // `appName` and `dbName` are mutually exclusive. + if a.appName != "" && a.dbName != "" { + return trace.BadParameter("only --app-name or --db-name can be set, not both") + } + switch { case a.appName != "": server, err := getApplicationServer(context.TODO(), clusterAPI, a.appName) From af424f4c88369a71f8a67777ec83544e831626e4 Mon Sep 17 00:00:00 2001 From: Gabriel Corado Date: Fri, 4 Mar 2022 09:46:00 -0300 Subject: [PATCH 3/8] feat(tctl): add flag db-user to auth sign command --- tool/tctl/common/auth_command.go | 3 ++ tool/tctl/common/auth_command_test.go | 40 ++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/tool/tctl/common/auth_command.go b/tool/tctl/common/auth_command.go index 0a075ac43d9d8..56e095750821e 100644 --- a/tool/tctl/common/auth_command.go +++ b/tool/tctl/common/auth_command.go @@ -69,6 +69,7 @@ type AuthCommand struct { kubeCluster string appName string dbName string + dbUser string signOverwrite bool rotateGracePeriod time.Duration @@ -122,6 +123,7 @@ func (a *AuthCommand) Initialize(app *kingpin.Application, config *service.Confi a.authSign.Flag("kube-cluster-name", `Kubernetes cluster to generate identity file for when --format is set to "kubernetes"`).StringVar(&a.kubeCluster) a.authSign.Flag("app-name", `Application to generate identity file for. Mutually exclusive with "--db-name".`).StringVar(&a.appName) a.authSign.Flag("db-name", `Database to generate identity file for. Mutually exclusive with "--app-name".`).StringVar(&a.dbName) + a.authSign.Flag("db-user", `Database user placed on the identity file. Only used when "--db-name" is set.`).StringVar(&a.dbUser) a.authRotate = auth.Command("rotate", "Rotate certificate authorities in the cluster") a.authRotate.Flag("grace-period", "Grace period keeps previous certificate authorities signatures valid, if set to 0 will force users to relogin and nodes to re-register."). @@ -638,6 +640,7 @@ func (a *AuthCommand) generateUserKeys(clusterAPI auth.ClientI) error { routeToDatabase = proto.RouteToDatabase{ ServiceName: a.dbName, Protocol: server.GetDatabase().GetProtocol(), + Username: a.dbUser, } certUsage = proto.UserCertsRequest_Database } diff --git a/tool/tctl/common/auth_command_test.go b/tool/tctl/common/auth_command_test.go index 90aa4318157f1..025c238af4ed2 100644 --- a/tool/tctl/common/auth_command_test.go +++ b/tool/tctl/common/auth_command_test.go @@ -642,14 +642,17 @@ func TestGenerateAppCertificates(t *testing.T) { func TestGenerateDatabaseUserCertificates(t *testing.T) { tests := map[string]struct { - clusterName string - dbName string - dbServices []types.DatabaseServer - withError bool + clusterName string + dbName string + dbUser string + expectedDbProtocol string + dbServices []types.DatabaseServer + withError bool }{ "DatabaseExists": { - clusterName: "example.com", - dbName: "db-1", + clusterName: "example.com", + dbName: "db-1", + expectedDbProtocol: defaults.ProtocolPostgres, dbServices: []types.DatabaseServer{ &types.DatabaseServerV3{ Metadata: types.Metadata{ @@ -666,6 +669,27 @@ func TestGenerateDatabaseUserCertificates(t *testing.T) { }, }, }, + "DatabaseWithUserExists": { + clusterName: "example.com", + dbName: "db-user-1", + dbUser: "mongo-user", + expectedDbProtocol: defaults.ProtocolMongoDB, + dbServices: []types.DatabaseServer{ + &types.DatabaseServerV3{ + Metadata: types.Metadata{ + Name: "db-user-1", + }, + Spec: types.DatabaseServerSpecV3{ + Hostname: "example.com", + Database: &types.DatabaseV3{ + Spec: types.DatabaseSpecV3{ + Protocol: defaults.ProtocolMongoDB, + }, + }, + }, + }, + }, + }, "DatabaseNotFound": { clusterName: "example.com", dbName: "db-2", @@ -699,6 +723,7 @@ func TestGenerateDatabaseUserCertificates(t *testing.T) { signOverwrite: true, genTTL: time.Hour, dbName: test.dbName, + dbUser: test.dbUser, } err = ac.generateUserKeys(authClient) @@ -711,7 +736,8 @@ func TestGenerateDatabaseUserCertificates(t *testing.T) { expectedRouteToDatabase := proto.RouteToDatabase{ ServiceName: test.dbName, - Protocol: defaults.ProtocolPostgres, + Protocol: test.expectedDbProtocol, + Username: test.dbUser, } require.Equal(t, proto.UserCertsRequest_Database, authClient.userCertsReq.Usage) require.Equal(t, expectedRouteToDatabase, authClient.userCertsReq.RouteToDatabase) From 9645eb6b6c916ec420e2b2632dad55de6cbc0b41 Mon Sep 17 00:00:00 2001 From: Gabriel Corado Date: Fri, 4 Mar 2022 14:49:52 -0300 Subject: [PATCH 4/8] test(tctl): remove references to deprecated package ioutil --- tool/tctl/common/auth_command_test.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tool/tctl/common/auth_command_test.go b/tool/tctl/common/auth_command_test.go index 025c238af4ed2..9498d862deccc 100644 --- a/tool/tctl/common/auth_command_test.go +++ b/tool/tctl/common/auth_command_test.go @@ -18,7 +18,6 @@ import ( "bytes" "context" "crypto/x509/pkix" - "io/ioutil" "os" "path/filepath" "testing" @@ -43,7 +42,7 @@ import ( func TestAuthSignKubeconfig(t *testing.T) { t.Parallel() - tmpDir, err := ioutil.TempDir("", "auth_command_test") + tmpDir, err := os.MkdirTemp("", "auth_command_test") if err != nil { t.Fatal(err) } @@ -522,19 +521,19 @@ func TestGenerateDatabaseKeys(t *testing.T) { require.Equal(t, test.outServerNames[0], authClient.dbCertsReq.ServerName) if len(test.outKey) > 0 { - keyBytes, err := ioutil.ReadFile(filepath.Join(test.inOutDir, test.outKeyFile)) + keyBytes, err := os.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(filepath.Join(test.inOutDir, test.outCertFile)) + certBytes, err := os.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(filepath.Join(test.inOutDir, test.outCAFile)) + caBytes, err := os.ReadFile(filepath.Join(test.inOutDir, test.outCAFile)) require.NoError(t, err) require.Equal(t, test.outCA, caBytes, "CA certificates match") } @@ -633,7 +632,7 @@ func TestGenerateAppCertificates(t *testing.T) { require.Equal(t, proto.UserCertsRequest_App, authClient.userCertsReq.Usage) require.Equal(t, expectedRouteToApp, authClient.userCertsReq.RouteToApp) - certBytes, err := ioutil.ReadFile(filepath.Join(tc.outDir, tc.outFileBase+".crt")) + certBytes, err := os.ReadFile(filepath.Join(tc.outDir, tc.outFileBase+".crt")) require.NoError(t, err) require.Equal(t, authClient.userCerts.TLS, certBytes, "certificates match") }) @@ -742,7 +741,7 @@ func TestGenerateDatabaseUserCertificates(t *testing.T) { require.Equal(t, proto.UserCertsRequest_Database, authClient.userCertsReq.Usage) require.Equal(t, expectedRouteToDatabase, authClient.userCertsReq.RouteToDatabase) - certBytes, err := ioutil.ReadFile(filepath.Join(certsDir, test.dbName+".crt")) + certBytes, err := os.ReadFile(filepath.Join(certsDir, test.dbName+".crt")) require.NoError(t, err) require.Equal(t, authClient.userCerts.TLS, certBytes, "certificates match") }) From 5f115e599086c2cfd56d9210c784a0a3ad0916a9 Mon Sep 17 00:00:00 2001 From: Gabriel Corado Date: Fri, 4 Mar 2022 14:55:04 -0300 Subject: [PATCH 5/8] test(tctl): update test to check error type --- tool/tctl/common/auth_command_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tool/tctl/common/auth_command_test.go b/tool/tctl/common/auth_command_test.go index 9498d862deccc..94d92a96242e4 100644 --- a/tool/tctl/common/auth_command_test.go +++ b/tool/tctl/common/auth_command_test.go @@ -646,7 +646,7 @@ func TestGenerateDatabaseUserCertificates(t *testing.T) { dbUser string expectedDbProtocol string dbServices []types.DatabaseServer - withError bool + expectedErr error }{ "DatabaseExists": { clusterName: "example.com", @@ -693,7 +693,7 @@ func TestGenerateDatabaseUserCertificates(t *testing.T) { clusterName: "example.com", dbName: "db-2", dbServices: []types.DatabaseServer{}, - withError: true, + expectedErr: trace.NotFound(""), }, } @@ -726,8 +726,9 @@ func TestGenerateDatabaseUserCertificates(t *testing.T) { } err = ac.generateUserKeys(authClient) - if test.withError { + if test.expectedErr != nil { require.Error(t, err) + require.IsType(t, test.expectedErr, err) return } From 49b7a1d9a680734003f33b76d584a82ba6106b9f Mon Sep 17 00:00:00 2001 From: Gabriel Corado Date: Fri, 4 Mar 2022 14:57:57 -0300 Subject: [PATCH 6/8] chore(tctl): add godoc to `getDatabaseServer` function --- tool/tctl/common/auth_command.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tool/tctl/common/auth_command.go b/tool/tctl/common/auth_command.go index 56e095750821e..3bdc5bbaab76e 100644 --- a/tool/tctl/common/auth_command.go +++ b/tool/tctl/common/auth_command.go @@ -844,6 +844,8 @@ func getApplicationServer(ctx context.Context, clusterAPI auth.ClientI, appName return nil, trace.NotFound("app %q not found", appName) } +// getDatabaseServer fetches a single `DatabaseServer` by name using the +// provided `auth.ClientI`. func getDatabaseServer(ctx context.Context, clientAPI auth.ClientI, dbName string) (types.DatabaseServer, error) { servers, err := clientAPI.GetDatabaseServers(ctx, apidefaults.Namespace) if err != nil { From 66902bb45d6dbc60f5d1f0c60b32cb53ce4e3a37 Mon Sep 17 00:00:00 2001 From: Gabriel Corado Date: Thu, 10 Mar 2022 18:24:19 -0300 Subject: [PATCH 7/8] refactor(tctl): rename database-related flags in auth sign --- tool/tctl/common/auth_command.go | 19 +++++++------ tool/tctl/common/auth_command_test.go | 39 ++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/tool/tctl/common/auth_command.go b/tool/tctl/common/auth_command.go index 13f7dc11a01b5..6140e9d7e73c7 100644 --- a/tool/tctl/common/auth_command.go +++ b/tool/tctl/common/auth_command.go @@ -68,6 +68,7 @@ type AuthCommand struct { leafCluster string kubeCluster string appName string + db string dbName string dbUser string signOverwrite bool @@ -122,8 +123,9 @@ func (a *AuthCommand) Initialize(app *kingpin.Application, config *service.Confi a.authSign.Flag("leaf-cluster", `Leaf cluster to generate identity file for when --format is set to "kubernetes"`).StringVar(&a.leafCluster) a.authSign.Flag("kube-cluster-name", `Kubernetes cluster to generate identity file for when --format is set to "kubernetes"`).StringVar(&a.kubeCluster) a.authSign.Flag("app-name", `Application to generate identity file for. Mutually exclusive with "--db-name".`).StringVar(&a.appName) - a.authSign.Flag("db-name", `Database to generate identity file for. Mutually exclusive with "--app-name".`).StringVar(&a.dbName) - a.authSign.Flag("db-user", `Database user placed on the identity file. Only used when "--db-name" is set.`).StringVar(&a.dbUser) + a.authSign.Flag("db", `Database to generate identity file for. Mutually exclusive with "--app-name".`).StringVar(&a.db) + a.authSign.Flag("db-user", `Database user placed on the identity file. Only used when "--db" is set.`).StringVar(&a.dbUser) + a.authSign.Flag("db-name", `Database name placed on the identity file. Only used when "--db" is set.`).StringVar(&a.dbName) a.authRotate = auth.Command("rotate", "Rotate certificate authorities in the cluster") a.authRotate.Flag("grace-period", "Grace period keeps previous certificate authorities signatures valid, if set to 0 will force users to relogin and nodes to re-register."). @@ -605,9 +607,9 @@ func (a *AuthCommand) generateUserKeys(ctx context.Context, clusterAPI auth.Clie certUsage proto.UserCertsRequest_CertUsage ) - // `appName` and `dbName` are mutually exclusive. - if a.appName != "" && a.dbName != "" { - return trace.BadParameter("only --app-name or --db-name can be set, not both") + // `appName` and `db` are mutually exclusive. + if a.appName != "" && a.db != "" { + return trace.BadParameter("only --app-name or --db can be set, not both") } switch { @@ -633,15 +635,16 @@ func (a *AuthCommand) generateUserKeys(ctx context.Context, clusterAPI auth.Clie SessionID: appSession.GetName(), } certUsage = proto.UserCertsRequest_App - case a.dbName != "": - server, err := getDatabaseServer(context.TODO(), clusterAPI, a.dbName) + case a.db != "": + server, err := getDatabaseServer(context.TODO(), clusterAPI, a.db) if err != nil { return trace.Wrap(err) } routeToDatabase = proto.RouteToDatabase{ - ServiceName: a.dbName, + ServiceName: a.db, Protocol: server.GetDatabase().GetProtocol(), + Database: a.dbName, Username: a.dbUser, } certUsage = proto.UserCertsRequest_Database diff --git a/tool/tctl/common/auth_command_test.go b/tool/tctl/common/auth_command_test.go index 887f5f2ed1fb7..44e064d7e291e 100644 --- a/tool/tctl/common/auth_command_test.go +++ b/tool/tctl/common/auth_command_test.go @@ -680,8 +680,10 @@ func TestGenerateAppCertificates(t *testing.T) { } func TestGenerateDatabaseUserCertificates(t *testing.T) { + ctx := context.Background() tests := map[string]struct { clusterName string + db string dbName string dbUser string expectedDbProtocol string @@ -690,7 +692,7 @@ func TestGenerateDatabaseUserCertificates(t *testing.T) { }{ "DatabaseExists": { clusterName: "example.com", - dbName: "db-1", + db: "db-1", expectedDbProtocol: defaults.ProtocolPostgres, dbServices: []types.DatabaseServer{ &types.DatabaseServerV3{ @@ -710,7 +712,7 @@ func TestGenerateDatabaseUserCertificates(t *testing.T) { }, "DatabaseWithUserExists": { clusterName: "example.com", - dbName: "db-user-1", + db: "db-user-1", dbUser: "mongo-user", expectedDbProtocol: defaults.ProtocolMongoDB, dbServices: []types.DatabaseServer{ @@ -729,9 +731,30 @@ func TestGenerateDatabaseUserCertificates(t *testing.T) { }, }, }, + "DatabaseWithDatabaseNameExists": { + clusterName: "example.com", + db: "db-user-1", + dbName: "root-database", + expectedDbProtocol: defaults.ProtocolMongoDB, + dbServices: []types.DatabaseServer{ + &types.DatabaseServerV3{ + Metadata: types.Metadata{ + Name: "db-user-1", + }, + Spec: types.DatabaseServerSpecV3{ + Hostname: "example.com", + Database: &types.DatabaseV3{ + Spec: types.DatabaseSpecV3{ + Protocol: defaults.ProtocolMongoDB, + }, + }, + }, + }, + }, + }, "DatabaseNotFound": { clusterName: "example.com", - dbName: "db-2", + db: "db-2", dbServices: []types.DatabaseServer{}, expectedErr: trace.NotFound(""), }, @@ -755,17 +778,18 @@ func TestGenerateDatabaseUserCertificates(t *testing.T) { } certsDir := t.TempDir() - output := filepath.Join(certsDir, test.dbName) + output := filepath.Join(certsDir, test.db) ac := AuthCommand{ output: output, outputFormat: identityfile.FormatTLS, signOverwrite: true, genTTL: time.Hour, + db: test.db, dbName: test.dbName, dbUser: test.dbUser, } - err = ac.generateUserKeys(authClient) + err = ac.generateUserKeys(ctx, authClient) if test.expectedErr != nil { require.Error(t, err) require.IsType(t, test.expectedErr, err) @@ -775,14 +799,15 @@ func TestGenerateDatabaseUserCertificates(t *testing.T) { require.NoError(t, err) expectedRouteToDatabase := proto.RouteToDatabase{ - ServiceName: test.dbName, + ServiceName: test.db, Protocol: test.expectedDbProtocol, + Database: test.dbName, Username: test.dbUser, } require.Equal(t, proto.UserCertsRequest_Database, authClient.userCertsReq.Usage) require.Equal(t, expectedRouteToDatabase, authClient.userCertsReq.RouteToDatabase) - certBytes, err := os.ReadFile(filepath.Join(certsDir, test.dbName+".crt")) + certBytes, err := os.ReadFile(filepath.Join(certsDir, test.db+".crt")) require.NoError(t, err) require.Equal(t, authClient.userCerts.TLS, certBytes, "certificates match") }) From 618fecc4be38dba66587812a5b6fcdc6b82e3ca9 Mon Sep 17 00:00:00 2001 From: Gabriel Corado Date: Mon, 4 Apr 2022 13:30:16 -0300 Subject: [PATCH 8/8] refactor(tctl): rename flag from `db` to `db-service` --- tool/tctl/common/auth_command.go | 20 ++++++++++---------- tool/tctl/common/auth_command_test.go | 18 +++++++++--------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/tool/tctl/common/auth_command.go b/tool/tctl/common/auth_command.go index 98c9f27d6ab0f..64d7fda309ea2 100644 --- a/tool/tctl/common/auth_command.go +++ b/tool/tctl/common/auth_command.go @@ -67,7 +67,7 @@ type AuthCommand struct { leafCluster string kubeCluster string appName string - db string + dbService string dbName string dbUser string signOverwrite bool @@ -121,10 +121,10 @@ func (a *AuthCommand) Initialize(app *kingpin.Application, config *service.Confi a.authSign.Flag("kube-cluster", `Leaf cluster to generate identity file for when --format is set to "kubernetes"`).Hidden().StringVar(&a.leafCluster) a.authSign.Flag("leaf-cluster", `Leaf cluster to generate identity file for when --format is set to "kubernetes"`).StringVar(&a.leafCluster) a.authSign.Flag("kube-cluster-name", `Kubernetes cluster to generate identity file for when --format is set to "kubernetes"`).StringVar(&a.kubeCluster) - a.authSign.Flag("app-name", `Application to generate identity file for. Mutually exclusive with "--db-name".`).StringVar(&a.appName) - a.authSign.Flag("db", `Database to generate identity file for. Mutually exclusive with "--app-name".`).StringVar(&a.db) - a.authSign.Flag("db-user", `Database user placed on the identity file. Only used when "--db" is set.`).StringVar(&a.dbUser) - a.authSign.Flag("db-name", `Database name placed on the identity file. Only used when "--db" is set.`).StringVar(&a.dbName) + a.authSign.Flag("app-name", `Application to generate identity file for. Mutually exclusive with "--db-service".`).StringVar(&a.appName) + a.authSign.Flag("db-service", `Database to generate identity file for. Mutually exclusive with "--app-name".`).StringVar(&a.dbService) + a.authSign.Flag("db-user", `Database user placed on the identity file. Only used when "--db-service" is set.`).StringVar(&a.dbUser) + a.authSign.Flag("db-name", `Database name placed on the identity file. Only used when "--db-service" is set.`).StringVar(&a.dbName) a.authRotate = auth.Command("rotate", "Rotate certificate authorities in the cluster") a.authRotate.Flag("grace-period", "Grace period keeps previous certificate authorities signatures valid, if set to 0 will force users to relogin and nodes to re-register."). @@ -607,8 +607,8 @@ func (a *AuthCommand) generateUserKeys(ctx context.Context, clusterAPI auth.Clie ) // `appName` and `db` are mutually exclusive. - if a.appName != "" && a.db != "" { - return trace.BadParameter("only --app-name or --db can be set, not both") + if a.appName != "" && a.dbService != "" { + return trace.BadParameter("only --app-name or --db-service can be set, not both") } switch { @@ -634,14 +634,14 @@ func (a *AuthCommand) generateUserKeys(ctx context.Context, clusterAPI auth.Clie SessionID: appSession.GetName(), } certUsage = proto.UserCertsRequest_App - case a.db != "": - server, err := getDatabaseServer(context.TODO(), clusterAPI, a.db) + case a.dbService != "": + server, err := getDatabaseServer(context.TODO(), clusterAPI, a.dbService) if err != nil { return trace.Wrap(err) } routeToDatabase = proto.RouteToDatabase{ - ServiceName: a.db, + ServiceName: a.dbService, Protocol: server.GetDatabase().GetProtocol(), Database: a.dbName, Username: a.dbUser, diff --git a/tool/tctl/common/auth_command_test.go b/tool/tctl/common/auth_command_test.go index 5556d00a05295..ccd96fe3debd2 100644 --- a/tool/tctl/common/auth_command_test.go +++ b/tool/tctl/common/auth_command_test.go @@ -679,7 +679,7 @@ func TestGenerateDatabaseUserCertificates(t *testing.T) { ctx := context.Background() tests := map[string]struct { clusterName string - db string + dbService string dbName string dbUser string expectedDbProtocol string @@ -688,7 +688,7 @@ func TestGenerateDatabaseUserCertificates(t *testing.T) { }{ "DatabaseExists": { clusterName: "example.com", - db: "db-1", + dbService: "db-1", expectedDbProtocol: defaults.ProtocolPostgres, dbServices: []types.DatabaseServer{ &types.DatabaseServerV3{ @@ -708,7 +708,7 @@ func TestGenerateDatabaseUserCertificates(t *testing.T) { }, "DatabaseWithUserExists": { clusterName: "example.com", - db: "db-user-1", + dbService: "db-user-1", dbUser: "mongo-user", expectedDbProtocol: defaults.ProtocolMongoDB, dbServices: []types.DatabaseServer{ @@ -729,7 +729,7 @@ func TestGenerateDatabaseUserCertificates(t *testing.T) { }, "DatabaseWithDatabaseNameExists": { clusterName: "example.com", - db: "db-user-1", + dbService: "db-user-1", dbName: "root-database", expectedDbProtocol: defaults.ProtocolMongoDB, dbServices: []types.DatabaseServer{ @@ -750,7 +750,7 @@ func TestGenerateDatabaseUserCertificates(t *testing.T) { }, "DatabaseNotFound": { clusterName: "example.com", - db: "db-2", + dbService: "db-2", dbServices: []types.DatabaseServer{}, expectedErr: trace.NotFound(""), }, @@ -774,13 +774,13 @@ func TestGenerateDatabaseUserCertificates(t *testing.T) { } certsDir := t.TempDir() - output := filepath.Join(certsDir, test.db) + output := filepath.Join(certsDir, test.dbService) ac := AuthCommand{ output: output, outputFormat: identityfile.FormatTLS, signOverwrite: true, genTTL: time.Hour, - db: test.db, + dbService: test.dbService, dbName: test.dbName, dbUser: test.dbUser, } @@ -795,7 +795,7 @@ func TestGenerateDatabaseUserCertificates(t *testing.T) { require.NoError(t, err) expectedRouteToDatabase := proto.RouteToDatabase{ - ServiceName: test.db, + ServiceName: test.dbService, Protocol: test.expectedDbProtocol, Database: test.dbName, Username: test.dbUser, @@ -803,7 +803,7 @@ func TestGenerateDatabaseUserCertificates(t *testing.T) { require.Equal(t, proto.UserCertsRequest_Database, authClient.userCertsReq.Usage) require.Equal(t, expectedRouteToDatabase, authClient.userCertsReq.RouteToDatabase) - certBytes, err := os.ReadFile(filepath.Join(certsDir, test.db+".crt")) + certBytes, err := os.ReadFile(filepath.Join(certsDir, test.dbService+".crt")) require.NoError(t, err) require.Equal(t, authClient.userCerts.TLS, certBytes, "certificates match") })