From c099fc5d0b23e4edc4c1c734864ea13bb71a16a7 Mon Sep 17 00:00:00 2001 From: STeve Huang Date: Wed, 1 Nov 2023 15:23:37 -0400 Subject: [PATCH 1/3] Fix an issue "Allowed Users" in `tsh db ls" shows wrong user for databases with Automatic User Provisioning enabled. --- lib/client/profile.go | 1 + lib/services/access_checker.go | 26 +++++++++++++++++++++++--- lib/services/role.go | 4 ++++ tool/tsh/common/tsh.go | 14 +++++++++++++- tool/tsh/common/tsh_test.go | 23 ++++++++++++++++++++++- 5 files changed, 63 insertions(+), 5 deletions(-) diff --git a/lib/client/profile.go b/lib/client/profile.go index 991daa9a06126..bdb6c667a5d5c 100644 --- a/lib/client/profile.go +++ b/lib/client/profile.go @@ -577,6 +577,7 @@ func ProfileNameFromProxyAddress(store ProfileStore, proxyAddr string) (string, // AccessInfo returns the complete services.AccessInfo for this profile. func (p *ProfileStatus) AccessInfo() *services.AccessInfo { return &services.AccessInfo{ + Username: p.Username, Roles: p.Roles, Traits: p.Traits, AllowedResourceIDs: p.AllowedResourceIDs, diff --git a/lib/services/access_checker.go b/lib/services/access_checker.go index 2b34a322e75dd..de35caed85927 100644 --- a/lib/services/access_checker.go +++ b/lib/services/access_checker.go @@ -265,6 +265,8 @@ type AccessInfo struct { // access restrictions should be applied. Used for search-based access // requests. AllowedResourceIDs []types.ResourceID + // Username is the Telepeort username + Username string } // accessChecker implements the AccessChecker interface. @@ -340,7 +342,8 @@ func NewAccessCheckerForRemoteCluster(ctx context.Context, localAccessInfo *Acce } remoteAccessInfo := &AccessInfo{ - Traits: remoteUser.GetTraits(), + Username: localAccessInfo.Username, + Traits: remoteUser.GetTraits(), // Will fill this in with the names of the remote/mapped roles we got // from GetCurrentUserRoles. Roles: make([]string, 0, len(remoteRoles)), @@ -564,6 +567,18 @@ func (a *accessChecker) CheckDatabaseRoles(database types.Database) (mode types. // EnumerateDatabaseUsers specializes EnumerateEntities to enumerate db_users. func (a *accessChecker) EnumerateDatabaseUsers(database types.Database, extraUsers ...string) EnumerationResult { + // When auto-user provisioning is enabled, only Teleport username is allowed. + if database.SupportsAutoUsers() && database.GetAdminUser().Name != "" { + autoUser, _, err := a.CheckDatabaseRoles(database) + if err != nil { + log.Debugf("Failed to CheckDatabaseRoles for EnumerateDatabaseUsers: %v. Assuming auto-user provisioning is not enabled.", err) + } else if autoUser.IsEnabled() && database.SupportsAutoUsers() && database.GetAdminUser().Name != "" { + result := NewEnumerationResult() + result.allowedDeniedMap[a.info.Username] = true + return result + } + } + listFn := func(role types.Role, condition types.RoleConditionType) []string { return role.GetDatabaseUsers(condition) } @@ -999,6 +1014,7 @@ func AccessInfoFromLocalCertificate(cert *ssh.Certificate) (*AccessInfo, error) } return &AccessInfo{ + Username: cert.KeyId, Roles: roles, Traits: traits, AllowedResourceIDs: allowedResourceIDs, @@ -1045,6 +1061,7 @@ func AccessInfoFromRemoteCertificate(cert *ssh.Certificate, roleMap types.RoleMa } return &AccessInfo{ + Username: cert.KeyId, Roles: roles, Traits: traits, AllowedResourceIDs: allowedResourceIDs, @@ -1078,6 +1095,7 @@ func AccessInfoFromLocalIdentity(identity tlsca.Identity, access UserGetter) (*A } return &AccessInfo{ + Username: identity.Username, Roles: roles, Traits: traits, AllowedResourceIDs: allowedResourceIDs, @@ -1127,6 +1145,7 @@ func AccessInfoFromRemoteIdentity(identity tlsca.Identity, roleMap types.RoleMap allowedResourceIDs := identity.AllowedResourceIDs return &AccessInfo{ + Username: identity.Username, Roles: roles, Traits: traits, AllowedResourceIDs: allowedResourceIDs, @@ -1171,7 +1190,8 @@ func AccessInfoFromUserState(user UserState) *AccessInfo { roles := user.GetRoles() traits := user.GetTraits() return &AccessInfo{ - Roles: roles, - Traits: traits, + Username: user.GetName(), + Roles: roles, + Traits: traits, } } diff --git a/lib/services/role.go b/lib/services/role.go index 46e0fa4207ba2..9f3e7477fbc24 100644 --- a/lib/services/role.go +++ b/lib/services/role.go @@ -934,6 +934,10 @@ func ExtractAllowedResourcesFromCert(cert *ssh.Certificate) ([]types.ResourceID, return allowedResources, trace.Wrap(err) } +func ExtractUsernameFromCert(cert *ssh.Certificate) (string, error) { + return "", nil +} + // NewRoleSet returns new RoleSet based on the roles func NewRoleSet(roles ...types.Role) RoleSet { // unauthenticated Nop role should not have any privileges diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go index 963bbeaafacb2..2c0c5a50ac63c 100644 --- a/tool/tsh/common/tsh.go +++ b/tool/tsh/common/tsh.go @@ -2865,7 +2865,18 @@ func serializeDatabasesAllClusters(dbListings []databaseListing, format string) return string(out), trace.Wrap(err) } -func formatUsersForDB(database types.Database, accessChecker services.AccessChecker) string { +func formatUsersForDB(database types.Database, accessChecker services.AccessChecker) (users string) { + // When auto-user provisioning is enabled, only username is allowed. `tsh + // db ls` will add a footnote for "(+)" to explain this. + if database.SupportsAutoUsers() && database.GetAdminUser().Name != "" { + autoUser, _, _ := accessChecker.CheckDatabaseRoles(database) + if autoUser.IsEnabled() { + defer func() { + users = users + " (Auto-provisioned)" + }() + } + } + // may happen if fetching the role set failed for any reason. if accessChecker == nil { return "(unknown)" @@ -2950,6 +2961,7 @@ func showDatabasesAsText(w io.Writer, clusterFlag string, databases []types.Data } else { t = asciitable.MakeTableWithTruncatedColumn([]string{"Name", "Description", "Allowed Users", "Labels", "Connect"}, rows, "Labels") } + fmt.Fprintln(w, t.AsBuffer().String()) } diff --git a/tool/tsh/common/tsh_test.go b/tool/tsh/common/tsh_test.go index 495247c73b69f..f95133f986f22 100644 --- a/tool/tsh/common/tsh_test.go +++ b/tool/tsh/common/tsh_test.go @@ -4514,6 +4514,17 @@ func TestListDatabasesWithUsers(t *testing.T) { }) require.NoError(t, err) + dbWithAutoUser, err := types.NewDatabaseV3(types.Metadata{ + Name: "auto-user", + }, types.DatabaseSpecV3{ + Protocol: "postgres", + URI: "localhost:5432", + AdminUser: &types.DatabaseAdminUser{ + Name: "teleport-admin", + }, + }) + require.NoError(t, err) + roleDevStage := &types.RoleV6{ Metadata: types.Metadata{Name: "dev-stage", Namespace: apidefaults.Namespace}, Spec: types.RoleSpecV6{ @@ -4598,6 +4609,14 @@ func TestListDatabasesWithUsers(t *testing.T) { }, wantText: "[dev]", }, + { + name: "database with automatic user provisioning", + database: dbWithAutoUser, + wantUsers: &dbUsers{ + Allowed: []string{"alice"}, + }, + wantText: "[alice] (+)", + }, } for _, tt := range tests { @@ -4605,7 +4624,9 @@ func TestListDatabasesWithUsers(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - accessChecker := services.NewAccessCheckerWithRoleSet(&services.AccessInfo{}, "clustername", tt.roles) + accessChecker := services.NewAccessCheckerWithRoleSet(&services.AccessInfo{ + Username: "alice", + }, "clustername", tt.roles) gotUsers := getDBUsers(tt.database, accessChecker) require.Equal(t, tt.wantUsers, gotUsers) From ea0ddc9281b6b1f9b690b40039e198d60d659863 Mon Sep 17 00:00:00 2001 From: STeve Huang Date: Thu, 16 Nov 2023 15:16:25 -0500 Subject: [PATCH 2/3] add UT --- lib/client/profile_test.go | 31 ++++++++ lib/services/access_checker.go | 16 ++-- lib/services/role.go | 4 - lib/services/role_test.go | 88 +++++++++++++++++++--- lib/teleterm/clusters/cluster_databases.go | 5 +- tool/tsh/common/db.go | 5 +- tool/tsh/common/tsh.go | 28 +++---- tool/tsh/common/tsh_test.go | 41 +++++++++- 8 files changed, 177 insertions(+), 41 deletions(-) diff --git a/lib/client/profile_test.go b/lib/client/profile_test.go index 3897e2c5e5902..5c0ba1cf22b42 100644 --- a/lib/client/profile_test.go +++ b/lib/client/profile_test.go @@ -22,6 +22,9 @@ import ( "github.com/stretchr/testify/require" "github.com/gravitational/teleport/api/profile" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/types/wrappers" + "github.com/gravitational/teleport/lib/services" ) func newTestFSProfileStore(t *testing.T) *FSProfileStore { @@ -110,3 +113,31 @@ func TestProfileNameFromProxyAddress(t *testing.T) { require.Error(t, err) }) } + +func TestProfileStatusAccessInfo(t *testing.T) { + allowedResourceIDs := []types.ResourceID{{ + ClusterName: "cluster", + Kind: types.KindNode, + Name: "uuid", + }} + traits := wrappers.Traits{ + "trait1": {"value1", "value2"}, + "trait2": {"value3", "value4"}, + } + + wantAccessInfo := &services.AccessInfo{ + Username: "alice", + Roles: []string{"role1", "role2"}, + Traits: traits, + AllowedResourceIDs: allowedResourceIDs, + } + + profileStatus := ProfileStatus{ + Username: "alice", + Roles: []string{"role1", "role2"}, + Traits: traits, + AllowedResourceIDs: allowedResourceIDs, + } + + require.Equal(t, wantAccessInfo, profileStatus.AccessInfo()) +} diff --git a/lib/services/access_checker.go b/lib/services/access_checker.go index de35caed85927..64ffc4aa3f2ce 100644 --- a/lib/services/access_checker.go +++ b/lib/services/access_checker.go @@ -236,7 +236,7 @@ type AccessChecker interface { EnumerateEntities(resource AccessCheckable, listFn roleEntitiesListFn, newMatcher roleMatcherFactoryFn, extraEntities ...string) EnumerationResult // EnumerateDatabaseUsers specializes EnumerateEntities to enumerate db_users. - EnumerateDatabaseUsers(database types.Database, extraUsers ...string) EnumerationResult + EnumerateDatabaseUsers(database types.Database, extraUsers ...string) (EnumerationResult, error) // EnumerateDatabaseNames specializes EnumerateEntities to enumerate db_names. EnumerateDatabaseNames(database types.Database, extraNames ...string) EnumerationResult @@ -265,7 +265,7 @@ type AccessInfo struct { // access restrictions should be applied. Used for search-based access // requests. AllowedResourceIDs []types.ResourceID - // Username is the Telepeort username + // Username is the Telepeort username. Username string } @@ -566,16 +566,16 @@ func (a *accessChecker) CheckDatabaseRoles(database types.Database) (mode types. } // EnumerateDatabaseUsers specializes EnumerateEntities to enumerate db_users. -func (a *accessChecker) EnumerateDatabaseUsers(database types.Database, extraUsers ...string) EnumerationResult { +func (a *accessChecker) EnumerateDatabaseUsers(database types.Database, extraUsers ...string) (EnumerationResult, error) { // When auto-user provisioning is enabled, only Teleport username is allowed. if database.SupportsAutoUsers() && database.GetAdminUser().Name != "" { + result := NewEnumerationResult() autoUser, _, err := a.CheckDatabaseRoles(database) if err != nil { - log.Debugf("Failed to CheckDatabaseRoles for EnumerateDatabaseUsers: %v. Assuming auto-user provisioning is not enabled.", err) - } else if autoUser.IsEnabled() && database.SupportsAutoUsers() && database.GetAdminUser().Name != "" { - result := NewEnumerationResult() + return result, trace.Wrap(err) + } else if autoUser.IsEnabled() { result.allowedDeniedMap[a.info.Username] = true - return result + return result, nil } } @@ -585,7 +585,7 @@ func (a *accessChecker) EnumerateDatabaseUsers(database types.Database, extraUse newMatcher := func(user string) RoleMatcher { return NewDatabaseUserMatcher(database, user) } - return a.EnumerateEntities(database, listFn, newMatcher, extraUsers...) + return a.EnumerateEntities(database, listFn, newMatcher, extraUsers...), nil } // EnumerateDatabaseNames specializes EnumerateEntities to enumerate db_names. diff --git a/lib/services/role.go b/lib/services/role.go index 9f3e7477fbc24..46e0fa4207ba2 100644 --- a/lib/services/role.go +++ b/lib/services/role.go @@ -934,10 +934,6 @@ func ExtractAllowedResourcesFromCert(cert *ssh.Certificate) ([]types.ResourceID, return allowedResources, trace.Wrap(err) } -func ExtractUsernameFromCert(cert *ssh.Certificate) (string, error) { - return "", nil -} - // NewRoleSet returns new RoleSet based on the roles func NewRoleSet(roles ...types.Role) RoleSet { // unauthenticated Nop role should not have any privileges diff --git a/lib/services/role_test.go b/lib/services/role_test.go index 564e7fabaa8d4..2087f8664b932 100644 --- a/lib/services/role_test.go +++ b/lib/services/role_test.go @@ -2082,6 +2082,7 @@ func makeAccessCheckerWithRoleSet(roleSet RoleSet) AccessChecker { roleNames[i] = role.GetName() } accessInfo := &AccessInfo{ + Username: "alice", Roles: roleNames, Traits: nil, AllowedResourceIDs: nil, @@ -3913,6 +3914,17 @@ func TestRoleSetEnumerateDatabaseUsersAndNames(t *testing.T) { URI: "uri", }) require.NoError(t, err) + dbAutoUser, err := types.NewDatabaseV3(types.Metadata{ + Name: "auto-user", + Labels: map[string]string{"env": "prod"}, + }, types.DatabaseSpecV3{ + Protocol: "postgres", + URI: "localhost:5432", + AdminUser: &types.DatabaseAdminUser{ + Name: "teleport-admin", + }, + }) + require.NoError(t, err) roleDevStage := &types.RoleV6{ Metadata: types.Metadata{Name: "dev-stage", Namespace: apidefaults.Namespace}, Spec: types.RoleSpecV6{ @@ -3968,17 +3980,39 @@ func TestRoleSetEnumerateDatabaseUsersAndNames(t *testing.T) { }, } + roleAutoUser := &types.RoleV6{ + Metadata: types.Metadata{Name: "auto-user", Namespace: apidefaults.Namespace}, + Spec: types.RoleSpecV6{ + Options: types.RoleOptions{ + CreateDatabaseUserMode: types.CreateDatabaseUserMode_DB_USER_MODE_KEEP, + }, + Allow: types.RoleConditions{ + Namespaces: []string{apidefaults.Namespace}, + DatabaseLabels: types.Labels{"env": []string{"prod"}}, + DatabaseRoles: []string{"dev"}, + DatabaseNames: []string{"*"}, + DatabaseUsers: []string{types.Wildcard}, + }, + }, + } + testCases := []struct { - name string - roles RoleSet - server types.Database - enumResult EnumerationResult + name string + roles RoleSet + server types.Database + enumDBUserResult EnumerationResult + enumDBNameResult EnumerationResult }{ { name: "deny overrides allow", roles: RoleSet{roleAllowDenySame}, server: dbStage, - enumResult: EnumerationResult{ + enumDBUserResult: EnumerationResult{ + allowedDeniedMap: map[string]bool{"root": false}, + wildcardAllowed: false, + wildcardDenied: false, + }, + enumDBNameResult: EnumerationResult{ allowedDeniedMap: map[string]bool{"root": false}, wildcardAllowed: false, wildcardDenied: false, @@ -3988,7 +4022,12 @@ func TestRoleSetEnumerateDatabaseUsersAndNames(t *testing.T) { name: "developer allowed any username in stage database except root", roles: RoleSet{roleDevStage, roleDevProd}, server: dbStage, - enumResult: EnumerationResult{ + enumDBUserResult: EnumerationResult{ + allowedDeniedMap: map[string]bool{"dev": true, "root": false}, + wildcardAllowed: true, + wildcardDenied: false, + }, + enumDBNameResult: EnumerationResult{ allowedDeniedMap: map[string]bool{"dev": true, "root": false}, wildcardAllowed: true, wildcardDenied: false, @@ -3998,7 +4037,12 @@ func TestRoleSetEnumerateDatabaseUsersAndNames(t *testing.T) { name: "developer allowed only specific username/database in prod database", roles: RoleSet{roleDevStage, roleDevProd}, server: dbProd, - enumResult: EnumerationResult{ + enumDBUserResult: EnumerationResult{ + allowedDeniedMap: map[string]bool{"dev": true, "root": false}, + wildcardAllowed: false, + wildcardDenied: false, + }, + enumDBNameResult: EnumerationResult{ allowedDeniedMap: map[string]bool{"dev": true, "root": false}, wildcardAllowed: false, wildcardDenied: false, @@ -4008,20 +4052,41 @@ func TestRoleSetEnumerateDatabaseUsersAndNames(t *testing.T) { name: "there may be users disallowed from all users", roles: RoleSet{roleDevStage, roleDevProd, roleNoDBAccess}, server: dbProd, - enumResult: EnumerationResult{ + enumDBUserResult: EnumerationResult{ allowedDeniedMap: map[string]bool{"dev": false, "root": false}, wildcardAllowed: false, wildcardDenied: true, }, + enumDBNameResult: EnumerationResult{ + allowedDeniedMap: map[string]bool{"dev": false, "root": false}, + wildcardAllowed: false, + wildcardDenied: true, + }, + }, + { + name: "auto-user provisioning enabled", + roles: RoleSet{roleAutoUser}, + server: dbAutoUser, + enumDBUserResult: EnumerationResult{ + allowedDeniedMap: map[string]bool{"alice": true}, + wildcardAllowed: false, + wildcardDenied: false, + }, + enumDBNameResult: EnumerationResult{ + allowedDeniedMap: map[string]bool{}, + wildcardAllowed: true, + wildcardDenied: false, + }, }, } for _, tc := range testCases { accessChecker := makeAccessCheckerWithRoleSet(tc.roles) t.Run(tc.name, func(t *testing.T) { - enumResult := accessChecker.EnumerateDatabaseUsers(tc.server) - require.Equal(t, tc.enumResult, enumResult) + enumResult, err := accessChecker.EnumerateDatabaseUsers(tc.server) + require.NoError(t, err) + require.Equal(t, tc.enumDBUserResult, enumResult) enumResult = accessChecker.EnumerateDatabaseNames(tc.server) - require.Equal(t, tc.enumResult, enumResult) + require.Equal(t, tc.enumDBNameResult, enumResult) }) } } @@ -7604,6 +7669,7 @@ func TestNewAccessCheckerForRemoteCluster(t *testing.T) { } accessInfo := AccessInfoFromUserState(user) + require.Equal(t, "mockCurrentUser", accessInfo.Username) accessChecker, err := NewAccessCheckerForRemoteCluster(context.Background(), accessInfo, "clustername", currentUserRoleGetter) require.NoError(t, err) diff --git a/lib/teleterm/clusters/cluster_databases.go b/lib/teleterm/clusters/cluster_databases.go index 55ac925c7012d..2527ebe2991c6 100644 --- a/lib/teleterm/clusters/cluster_databases.go +++ b/lib/teleterm/clusters/cluster_databases.go @@ -238,7 +238,10 @@ func (c *Cluster) GetAllowedDatabaseUsers(ctx context.Context, dbURI string) ([] return nil, trace.Wrap(err) } - dbUsers := accessChecker.EnumerateDatabaseUsers(db) + dbUsers, err := accessChecker.EnumerateDatabaseUsers(db) + if err != nil { + return nil, trace.Wrap(err) + } return dbUsers.Allowed(), nil } diff --git a/tool/tsh/common/db.go b/tool/tsh/common/db.go index 8d8d80f462a05..0247bb6d4cd10 100644 --- a/tool/tsh/common/db.go +++ b/tool/tsh/common/db.go @@ -1122,7 +1122,10 @@ func getDefaultDBUser(db types.Database, checker services.AccessChecker) (string // ref: https://redis.io/commands/auth extraUsers = append(extraUsers, defaults.DefaultRedisUsername) } - dbUsers := checker.EnumerateDatabaseUsers(db, extraUsers...) + dbUsers, err := checker.EnumerateDatabaseUsers(db, extraUsers...) + if err != nil { + return "", trace.Wrap(err) + } allowed := dbUsers.Allowed() if len(allowed) == 1 { return allowed[0], nil diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go index 2c0c5a50ac63c..0eb7cded8b67d 100644 --- a/tool/tsh/common/tsh.go +++ b/tool/tsh/common/tsh.go @@ -2811,7 +2811,11 @@ type databaseWithUsers struct { } func getDBUsers(db types.Database, accessChecker services.AccessChecker) *dbUsers { - users := accessChecker.EnumerateDatabaseUsers(db) + users, err := accessChecker.EnumerateDatabaseUsers(db) + if err != nil { + log.Warnf("Failed to EnumerateDatabaseUsers for database %v: %v.", db.GetName(), err) + return &dbUsers{} + } var denied []string allowed := users.Allowed() if users.WildcardAllowed() { @@ -2866,17 +2870,6 @@ func serializeDatabasesAllClusters(dbListings []databaseListing, format string) } func formatUsersForDB(database types.Database, accessChecker services.AccessChecker) (users string) { - // When auto-user provisioning is enabled, only username is allowed. `tsh - // db ls` will add a footnote for "(+)" to explain this. - if database.SupportsAutoUsers() && database.GetAdminUser().Name != "" { - autoUser, _, _ := accessChecker.CheckDatabaseRoles(database) - if autoUser.IsEnabled() { - defer func() { - users = users + " (Auto-provisioned)" - }() - } - } - // may happen if fetching the role set failed for any reason. if accessChecker == nil { return "(unknown)" @@ -2887,6 +2880,16 @@ func formatUsersForDB(database types.Database, accessChecker services.AccessChec return "(none)" } + // Add a note for auto-provisioned user. + if database.SupportsAutoUsers() && database.GetAdminUser().Name != "" { + autoUser, _, _ := accessChecker.CheckDatabaseRoles(database) + if autoUser.IsEnabled() { + defer func() { + users = users + " (Auto-provisioned)" + }() + } + } + if len(dbUsers.Denied) == 0 { return fmt.Sprintf("%v", dbUsers.Allowed) } @@ -2961,7 +2964,6 @@ func showDatabasesAsText(w io.Writer, clusterFlag string, databases []types.Data } else { t = asciitable.MakeTableWithTruncatedColumn([]string{"Name", "Description", "Allowed Users", "Labels", "Connect"}, rows, "Labels") } - fmt.Fprintln(w, t.AsBuffer().String()) } diff --git a/tool/tsh/common/tsh_test.go b/tool/tsh/common/tsh_test.go index f95133f986f22..ac92194ca2f5d 100644 --- a/tool/tsh/common/tsh_test.go +++ b/tool/tsh/common/tsh_test.go @@ -4515,7 +4515,8 @@ func TestListDatabasesWithUsers(t *testing.T) { require.NoError(t, err) dbWithAutoUser, err := types.NewDatabaseV3(types.Metadata{ - Name: "auto-user", + Name: "auto-user", + Labels: map[string]string{"env": "prod"}, }, types.DatabaseSpecV3{ Protocol: "postgres", URI: "localhost:5432", @@ -4549,6 +4550,21 @@ func TestListDatabasesWithUsers(t *testing.T) { }, }, } + roleAutoUser := &types.RoleV6{ + Metadata: types.Metadata{Name: "auto-user", Namespace: apidefaults.Namespace}, + Spec: types.RoleSpecV6{ + Options: types.RoleOptions{ + CreateDatabaseUserMode: types.CreateDatabaseUserMode_DB_USER_MODE_KEEP, + }, + Allow: types.RoleConditions{ + Namespaces: []string{apidefaults.Namespace}, + DatabaseLabels: types.Labels{"env": []string{"prod"}}, + DatabaseRoles: []string{"dev"}, + DatabaseNames: []string{"*"}, + DatabaseUsers: []string{types.Wildcard}, + }, + }, + } tests := []struct { name string @@ -4610,12 +4626,31 @@ func TestListDatabasesWithUsers(t *testing.T) { wantText: "[dev]", }, { - name: "database with automatic user provisioning", + name: "db with admin user and role with auto-user", database: dbWithAutoUser, + roles: services.RoleSet{roleAutoUser}, wantUsers: &dbUsers{ Allowed: []string{"alice"}, }, - wantText: "[alice] (+)", + wantText: "[alice] (Auto-provisioned)", + }, + { + name: "db with admin user but role without auto-user", + database: dbWithAutoUser, + roles: services.RoleSet{roleDevProd}, + wantUsers: &dbUsers{ + Allowed: []string{"dev"}, + }, + wantText: "[dev]", + }, + { + name: "db without admin user but role with auto-user", + database: dbProd, + roles: services.RoleSet{roleAutoUser}, + wantUsers: &dbUsers{ + Allowed: []string{"*"}, + }, + wantText: "[*]", }, } From c8526c1f83bcc1d9b384fe8d4d4fb785a59372a7 Mon Sep 17 00:00:00 2001 From: STeve Huang Date: Mon, 20 Nov 2023 08:49:54 -0500 Subject: [PATCH 3/3] err check and typo fix --- lib/services/access_checker.go | 2 +- tool/tsh/common/tsh.go | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/services/access_checker.go b/lib/services/access_checker.go index 64ffc4aa3f2ce..5b0713b068fb0 100644 --- a/lib/services/access_checker.go +++ b/lib/services/access_checker.go @@ -265,7 +265,7 @@ type AccessInfo struct { // access restrictions should be applied. Used for search-based access // requests. AllowedResourceIDs []types.ResourceID - // Username is the Telepeort username. + // Username is the Teleport username. Username string } diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go index 0eb7cded8b67d..77ff4deb49439 100644 --- a/tool/tsh/common/tsh.go +++ b/tool/tsh/common/tsh.go @@ -2882,8 +2882,10 @@ func formatUsersForDB(database types.Database, accessChecker services.AccessChec // Add a note for auto-provisioned user. if database.SupportsAutoUsers() && database.GetAdminUser().Name != "" { - autoUser, _, _ := accessChecker.CheckDatabaseRoles(database) - if autoUser.IsEnabled() { + autoUser, _, err := accessChecker.CheckDatabaseRoles(database) + if err != nil { + log.Warnf("Failed to CheckDatabaseRoles for database %v: %v.", database.GetName(), err) + } else if autoUser.IsEnabled() { defer func() { users = users + " (Auto-provisioned)" }()