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 @@ -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,
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 @@ -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
Expand Down Expand Up @@ -265,6 +265,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 @@ -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)),
Expand Down Expand Up @@ -563,14 +566,26 @@ 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 {
return result, trace.Wrap(err)
} else if autoUser.IsEnabled() {
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 @@ -999,6 +1014,7 @@ func AccessInfoFromLocalCertificate(cert *ssh.Certificate) (*AccessInfo, error)
}

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

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

return &AccessInfo{
Username: identity.Username,
Roles: roles,
Traits: traits,
AllowedResourceIDs: allowedResourceIDs,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
}
}
88 changes: 77 additions & 11 deletions lib/services/role_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2082,6 +2082,7 @@ func makeAccessCheckerWithRoleSet(roleSet RoleSet) AccessChecker {
roleNames[i] = role.GetName()
}
accessInfo := &AccessInfo{
Username: "alice",
Roles: roleNames,
Traits: nil,
AllowedResourceIDs: nil,
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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)
})
}
}
Expand Down Expand Up @@ -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)

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 @@ -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
}
Expand Down
5 changes: 4 additions & 1 deletion tool/tsh/common/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 18 additions & 2 deletions tool/tsh/common/tsh.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -2865,7 +2869,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 @@ -2876,6 +2880,18 @@ func formatUsersForDB(database types.Database, accessChecker services.AccessChec
return "(none)"
}

// Add a note for auto-provisioned user.
if database.SupportsAutoUsers() && database.GetAdminUser().Name != "" {
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)"
}()
}
}

if len(dbUsers.Denied) == 0 {
return fmt.Sprintf("%v", dbUsers.Allowed)
}
Expand Down
Loading