Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/client/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,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,
Expand Down
31 changes: 31 additions & 0 deletions lib/client/profile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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())
}
32 changes: 26 additions & 6 deletions lib/services/access_checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ type AccessChecker interface {
GetKubeResources(cluster types.KubeCluster) (allowed, denied []types.KubernetesResource)

// EnumerateDatabaseUsers specializes EnumerateEntities to enumerate db_users.
EnumerateDatabaseUsers(database types.Database, extraUsers ...string) EnumerationResult
EnumerateDatabaseUsers(database types.Database, extraUsers ...string) (EnumerationResult, error)

// GetAllowedLoginsForResource returns all of the allowed logins for the passed resource.
//
Expand All @@ -250,6 +250,8 @@ type AccessInfo struct {
// access restrictions should be applied. Used for search-based access
// requests.
AllowedResourceIDs []types.ResourceID
// Username is the Teleport username.
Username string
}

// accessChecker implements the AccessChecker interface.
Expand Down Expand Up @@ -325,7 +327,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)),
Expand Down Expand Up @@ -534,14 +537,26 @@ func (a *accessChecker) CheckDatabaseRoles(database types.Database) (create bool
}

// 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() != "" {
result := NewEnumerationResult()
createAutoUser, _, err := a.CheckDatabaseRoles(database)
if err != nil {
return result, trace.Wrap(err)
} else if createAutoUser {
result.allowedDeniedMap[a.info.Username] = true
return result, nil
}
}

listFn := func(role types.Role, condition types.RoleConditionType) []string {
return role.GetDatabaseUsers(condition)
}
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.
Expand Down Expand Up @@ -968,6 +983,7 @@ func AccessInfoFromLocalCertificate(cert *ssh.Certificate) (*AccessInfo, error)
}

return &AccessInfo{
Username: cert.KeyId,
Roles: roles,
Traits: traits,
AllowedResourceIDs: allowedResourceIDs,
Expand Down Expand Up @@ -1014,6 +1030,7 @@ func AccessInfoFromRemoteCertificate(cert *ssh.Certificate, roleMap types.RoleMa
}

return &AccessInfo{
Username: cert.KeyId,
Roles: roles,
Traits: traits,
AllowedResourceIDs: allowedResourceIDs,
Expand Down Expand Up @@ -1047,6 +1064,7 @@ func AccessInfoFromLocalIdentity(identity tlsca.Identity, access UserGetter) (*A
}

return &AccessInfo{
Username: identity.Username,
Roles: roles,
Traits: traits,
AllowedResourceIDs: allowedResourceIDs,
Expand Down Expand Up @@ -1096,6 +1114,7 @@ func AccessInfoFromRemoteIdentity(identity tlsca.Identity, roleMap types.RoleMap
allowedResourceIDs := identity.AllowedResourceIDs

return &AccessInfo{
Username: identity.Username,
Roles: roles,
Traits: traits,
AllowedResourceIDs: allowedResourceIDs,
Expand Down Expand Up @@ -1140,7 +1159,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,
}
}
46 changes: 45 additions & 1 deletion lib/services/role_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1990,6 +1990,7 @@ func makeAccessCheckerWithRoleSet(roleSet RoleSet) AccessChecker {
roleNames[i] = role.GetName()
}
accessInfo := &AccessInfo{
Username: "alice",
Roles: roleNames,
Traits: nil,
AllowedResourceIDs: nil,
Expand Down Expand Up @@ -3820,6 +3821,17 @@ func TestRoleSetEnumerateDatabaseUsers(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{
Expand Down Expand Up @@ -3870,6 +3882,22 @@ func TestRoleSetEnumerateDatabaseUsers(t *testing.T) {
},
}

roleAutoUser := &types.RoleV6{
Metadata: types.Metadata{Name: "auto-user", Namespace: apidefaults.Namespace},
Spec: types.RoleSpecV6{
Options: types.RoleOptions{
CreateDatabaseUser: types.NewBoolOption(true),
},
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
Expand Down Expand Up @@ -3916,11 +3944,22 @@ func TestRoleSetEnumerateDatabaseUsers(t *testing.T) {
wildcardDenied: true,
},
},
{
name: "auto-user provisioning enabled",
roles: RoleSet{roleAutoUser},
server: dbAutoUser,
enumResult: EnumerationResult{
allowedDeniedMap: map[string]bool{"alice": true},
wildcardAllowed: false,
wildcardDenied: false,
},
},
}
for _, tc := range testCases {
accessChecker := makeAccessCheckerWithRoleSet(tc.roles)
t.Run(tc.name, func(t *testing.T) {
enumResult := accessChecker.EnumerateDatabaseUsers(tc.server)
enumResult, err := accessChecker.EnumerateDatabaseUsers(tc.server)
require.NoError(t, err)
require.Equal(t, tc.enumResult, enumResult)
})
}
Expand Down Expand Up @@ -7384,6 +7423,10 @@ func (u mockCurrentUser) GetTraits() map[string][]string {
return u.traits
}

func (u mockCurrentUser) GetName() string {
return "mockCurrentUser"
}

func TestNewAccessCheckerForRemoteCluster(t *testing.T) {
user := mockCurrentUser{
roles: []string{"dev", "admin"},
Expand All @@ -7409,6 +7452,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)

Expand Down
5 changes: 4 additions & 1 deletion lib/teleterm/clusters/cluster_databases.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,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
}
Expand Down
20 changes: 18 additions & 2 deletions tool/tsh/tsh.go
Original file line number Diff line number Diff line change
Expand Up @@ -2776,7 +2776,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() {
Expand Down Expand Up @@ -2830,7 +2834,7 @@ 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) {
// may happen if fetching the role set failed for any reason.
if accessChecker == nil {
return "(unknown)"
Expand All @@ -2841,6 +2845,18 @@ func formatUsersForDB(database types.Database, accessChecker services.AccessChec
return "(none)"
}

// Add a note for auto-provisioned user.
if database.SupportsAutoUsers() && database.GetAdminUser() != "" {
createAutoUser, _, err := accessChecker.CheckDatabaseRoles(database)
if err != nil {
log.Warnf("Failed to CheckDatabaseRoles for database %v: %v.", database.GetName(), err)
} else if createAutoUser {
defer func() {
users = users + " (Auto-provisioned)"
}()
}
}

if len(dbUsers.Denied) == 0 {
return fmt.Sprintf("%v", dbUsers.Allowed)
}
Expand Down
58 changes: 57 additions & 1 deletion tool/tsh/tsh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4379,6 +4379,18 @@ func TestListDatabasesWithUsers(t *testing.T) {
})
require.NoError(t, err)

dbWithAutoUser, 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{
Expand All @@ -4403,6 +4415,21 @@ func TestListDatabasesWithUsers(t *testing.T) {
},
},
}
roleAutoUser := &types.RoleV6{
Metadata: types.Metadata{Name: "auto-user", Namespace: apidefaults.Namespace},
Spec: types.RoleSpecV6{
Options: types.RoleOptions{
CreateDatabaseUser: types.NewBoolOption(true),
},
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
Expand Down Expand Up @@ -4463,14 +4490,43 @@ func TestListDatabasesWithUsers(t *testing.T) {
},
wantText: "[dev]",
},
{
name: "db with admin user and role with auto-user",
database: dbWithAutoUser,
roles: services.RoleSet{roleAutoUser},
wantUsers: &dbUsers{
Allowed: []string{"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: "[*]",
},
}

for _, tt := range tests {
tt := tt
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)
Expand Down