teleport/integration/hostuser_test.go

782 lines
24 KiB
Go

//go:build linux
// +build linux
/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package integration
import (
"bufio"
"bytes"
"context"
"fmt"
"os"
"os/exec"
"os/user"
"path/filepath"
"slices"
"strings"
"testing"
"time"
"github.com/google/uuid"
"github.com/gravitational/trace"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
labelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/label/v1"
userprovisioningpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/userprovisioning/v2"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/types/userprovisioning"
"github.com/gravitational/teleport/integration/helpers"
"github.com/gravitational/teleport/lib/auth/testauthority"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/backend/lite"
"github.com/gravitational/teleport/lib/service/servicecfg"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/services/local"
"github.com/gravitational/teleport/lib/srv"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/utils/host"
)
const testuser = "teleport-testuser"
const testgroup = "teleport-testgroup"
func getUserShells(path string) (map[string]string, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
scanner := bufio.NewScanner(f)
userShells := make(map[string]string)
for scanner.Scan() {
parts := strings.Split(scanner.Text(), ":")
userShells[parts[0]] = parts[len(parts)-1]
}
return userShells, nil
}
func TestRootHostUsersBackend(t *testing.T) {
utils.RequireRoot(t)
sudoersTestDir := t.TempDir()
usersbk := srv.HostUsersProvisioningBackend{}
sudoersbk := srv.HostSudoersProvisioningBackend{
SudoersPath: sudoersTestDir,
HostUUID: "hostuuid",
}
t.Run("Test CreateGroup", func(t *testing.T) {
t.Cleanup(func() { cleanupUsersAndGroups(nil, []string{testgroup}) })
err := usersbk.CreateGroup(testgroup, "")
require.NoError(t, err)
err = usersbk.CreateGroup(testgroup, "")
require.True(t, trace.IsAlreadyExists(err))
_, err = usersbk.LookupGroup(testgroup)
require.NoError(t, err)
})
t.Run("Test CreateUser and group", func(t *testing.T) {
t.Cleanup(func() { cleanupUsersAndGroups([]string{testuser}, []string{testgroup}) })
err := usersbk.CreateGroup(testgroup, "")
require.NoError(t, err)
testHome := filepath.Join("/home", testuser)
err = usersbk.CreateUser(testuser, []string{testgroup}, host.UserOpts{Home: testHome})
require.NoError(t, err)
tuser, err := usersbk.Lookup(testuser)
require.NoError(t, err)
group, err := usersbk.LookupGroup(testgroup)
require.NoError(t, err)
tuserGids, err := tuser.GroupIds()
require.NoError(t, err)
require.Contains(t, tuserGids, group.Gid)
err = usersbk.CreateUser(testuser, []string{}, host.UserOpts{Home: testHome})
require.True(t, trace.IsAlreadyExists(err))
require.NoFileExists(t, testHome)
err = usersbk.CreateHomeDirectory(testHome, tuser.Uid, tuser.Gid)
require.NoError(t, err)
t.Cleanup(func() {
os.RemoveAll(testHome)
})
require.FileExists(t, filepath.Join(testHome, ".bashrc"))
})
t.Run("Test DeleteUser", func(t *testing.T) {
t.Cleanup(func() { cleanupUsersAndGroups([]string{testuser}, nil) })
err := usersbk.CreateUser(testuser, nil, host.UserOpts{})
require.NoError(t, err)
_, err = usersbk.Lookup(testuser)
require.NoError(t, err)
err = usersbk.DeleteUser(testuser)
require.NoError(t, err)
_, err = user.Lookup(testuser)
require.Equal(t, err, user.UnknownUserError(testuser))
})
t.Run("Test GetAllUsers", func(t *testing.T) {
checkUsers := []string{"teleport-usera", "teleport-userb", "teleport-userc"}
t.Cleanup(func() { cleanupUsersAndGroups(checkUsers, nil) })
for _, u := range checkUsers {
err := usersbk.CreateUser(u, []string{}, host.UserOpts{})
require.NoError(t, err)
}
users, err := usersbk.GetAllUsers()
require.NoError(t, err)
require.Subset(t, users, append(checkUsers, "root"))
})
t.Run("Test sudoers", func(t *testing.T) {
if _, err := exec.LookPath("visudo"); err != nil {
t.Skip("visudo not found on path")
}
validSudoersEntry := []byte("root ALL=(ALL) ALL")
err := sudoersbk.CheckSudoers(validSudoersEntry)
require.NoError(t, err)
invalidSudoersEntry := []byte("yipee i broke sudo!!!!")
err = sudoersbk.CheckSudoers(invalidSudoersEntry)
require.Contains(t, err.Error(), "visudo: invalid sudoers file")
// test sudoers entry containing . or ~
require.NoError(t, sudoersbk.WriteSudoersFile("user.name", validSudoersEntry))
_, err = os.Stat(filepath.Join(sudoersTestDir, "teleport-hostuuid-user_name"))
require.NoError(t, err)
require.NoError(t, sudoersbk.RemoveSudoersFile("user.name"))
_, err = os.Stat(filepath.Join(sudoersTestDir, "teleport-hostuuid-user_name"))
require.True(t, os.IsNotExist(err))
})
t.Run("Test CreateHomeDirectory does not follow symlinks", func(t *testing.T) {
t.Cleanup(func() { cleanupUsersAndGroups([]string{testuser}, nil) })
err := usersbk.CreateUser(testuser, nil, host.UserOpts{})
require.NoError(t, err)
tuser, err := usersbk.Lookup(testuser)
require.NoError(t, err)
require.NoError(t, os.WriteFile("/etc/skel/testfile", []byte("test\n"), 0o700))
bashrcPath := filepath.Join("/home", testuser, ".bashrc")
require.NoFileExists(t, bashrcPath)
testHome := filepath.Join("/home", testuser)
require.NoError(t, os.MkdirAll(testHome, 0o700))
require.NoError(t, os.Symlink("/tmp/ignoreme", bashrcPath))
require.NoFileExists(t, "/tmp/ignoreme")
err = usersbk.CreateHomeDirectory(testHome, tuser.Uid, tuser.Gid)
t.Cleanup(func() {
os.RemoveAll(testHome)
})
require.ErrorIs(t, err, os.ErrExist)
require.NoFileExists(t, "/tmp/ignoreme")
})
}
func getUserGroups(t *testing.T, u *user.User) []string {
var userGroups []string
userGids, err := u.GroupIds()
require.NoError(t, err)
for _, gid := range userGids {
group, err := user.LookupGroupId(gid)
require.NoError(t, err)
userGroups = append(userGroups, group.Name)
}
return userGroups
}
func requireUserInGroups(t *testing.T, u *user.User, requiredGroups []string) {
require.Subset(t, getUserGroups(t, u), requiredGroups)
}
func cleanupUsersAndGroups(users []string, groups []string) {
for _, group := range groups {
cmd := exec.Command("groupdel", group)
err := cmd.Run()
if err != nil {
log.Debugf("Error deleting group %s: %s", group, err)
}
}
for _, user := range users {
host.UserDel(user)
}
}
func sudoersPath(username, uuid string) string {
return fmt.Sprintf("/etc/sudoers.d/teleport-%s-%s", uuid, username)
}
func TestRootHostUsers(t *testing.T) {
utils.RequireRoot(t)
ctx := context.Background()
bk, err := lite.New(ctx, backend.Params{"path": t.TempDir()})
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, bk.Close()) })
presence := local.NewPresenceService(bk)
t.Run("test create temporary user without home dir", func(t *testing.T) {
users := srv.NewHostUsers(context.Background(), presence, "host_uuid")
testGroups := []string{"group1", "group2"}
closer, err := users.UpsertUser(testuser, services.HostUsersInfo{Groups: testGroups, Mode: services.HostUserModeDrop})
require.NoError(t, err)
testGroups = append(testGroups, types.TeleportDropGroup)
t.Cleanup(func() { cleanupUsersAndGroups([]string{testuser}, testGroups) })
u, err := user.Lookup(testuser)
require.NoError(t, err)
requireUserInGroups(t, u, testGroups)
require.Equal(t, string(os.PathSeparator), u.HomeDir)
require.NoError(t, closer.Close())
_, err = user.Lookup(testuser)
require.Equal(t, err, user.UnknownUserError(testuser))
})
t.Run("test create user with uid and gid", func(t *testing.T) {
users := srv.NewHostUsers(context.Background(), presence, "host_uuid")
testUID := "1234"
testGID := "1337"
_, err := user.LookupGroupId(testGID)
require.ErrorIs(t, err, user.UnknownGroupIdError(testGID))
closer, err := users.UpsertUser(testuser, services.HostUsersInfo{
Mode: services.HostUserModeDrop,
UID: testUID,
GID: testGID,
})
require.NoError(t, err)
t.Cleanup(func() { cleanupUsersAndGroups([]string{testuser}, []string{types.TeleportDropGroup}) })
group, err := user.LookupGroupId(testGID)
require.NoError(t, err)
require.Equal(t, testuser, group.Name)
u, err := user.Lookup(testuser)
require.NoError(t, err)
require.Equal(t, u.Uid, testUID)
require.Equal(t, u.Gid, testGID)
require.NoError(t, closer.Close())
_, err = user.Lookup(testuser)
require.Equal(t, err, user.UnknownUserError(testuser))
})
t.Run("test create permanent user", func(t *testing.T) {
users := srv.NewHostUsers(context.Background(), presence, "host_uuid")
expectedHome := filepath.Join("/home", testuser)
require.NoDirExists(t, expectedHome)
closer, err := users.UpsertUser(testuser, services.HostUsersInfo{Mode: services.HostUserModeKeep})
require.NoError(t, err)
require.Nil(t, closer)
t.Cleanup(func() { cleanupUsersAndGroups([]string{testuser}, []string{types.TeleportKeepGroup}) })
u, err := user.Lookup(testuser)
require.NoError(t, err)
require.Equal(t, expectedHome, u.HomeDir)
require.DirExists(t, expectedHome)
t.Cleanup(func() {
os.RemoveAll(expectedHome)
})
})
t.Run("test create sudoers enabled users", func(t *testing.T) {
if _, err := exec.LookPath("visudo"); err != nil {
t.Skip("Visudo not found on path")
}
uuid := "host_uuid"
users := srv.NewHostUsers(context.Background(), presence, uuid)
sudoers := srv.NewHostSudoers(uuid)
t.Cleanup(func() {
os.Remove(sudoersPath(testuser, uuid))
cleanupUsersAndGroups([]string{testuser}, nil)
})
closer, err := users.UpsertUser(testuser,
services.HostUsersInfo{
Mode: services.HostUserModeDrop,
})
require.NoError(t, err)
err = sudoers.WriteSudoers(testuser, []string{"ALL=(ALL) ALL"})
require.NoError(t, err)
_, err = os.Stat(sudoersPath(testuser, uuid))
require.NoError(t, err)
// delete the user and ensure the sudoers file got deleted
require.NoError(t, closer.Close())
require.NoError(t, sudoers.RemoveSudoers(testuser))
_, err = os.Stat(sudoersPath(testuser, uuid))
require.True(t, os.IsNotExist(err))
// ensure invalid sudoers entries dont get written
err = sudoers.WriteSudoers(testuser,
[]string{"badsudoers entry!!!"},
)
require.Error(t, err)
_, err = os.Stat(sudoersPath(testuser, uuid))
require.True(t, os.IsNotExist(err))
})
t.Run("test delete all users in teleport service group", func(t *testing.T) {
users := srv.NewHostUsers(context.Background(), presence, "host_uuid")
users.SetHostUserDeletionGrace(0)
deleteableUsers := []string{"teleport-user1", "teleport-user2", "teleport-user3"}
for _, user := range deleteableUsers {
_, err := users.UpsertUser(user, services.HostUsersInfo{Mode: services.HostUserModeDrop})
require.NoError(t, err)
}
// this user should not be in the service group as it was created with mode keep.
closer, err := users.UpsertUser("teleport-user4", services.HostUsersInfo{
Mode: services.HostUserModeKeep,
})
require.NoError(t, err)
require.Nil(t, closer)
t.Cleanup(func() {
cleanupUsersAndGroups(
[]string{"teleport-user1", "teleport-user2", "teleport-user3", "teleport-user4"},
[]string{types.TeleportDropGroup, types.TeleportKeepGroup})
})
err = users.DeleteAllUsers()
require.NoError(t, err)
_, err = user.Lookup("teleport-user4")
require.NoError(t, err)
for _, us := range deleteableUsers {
_, err := user.Lookup(us)
require.Equal(t, err, user.UnknownUserError(us))
}
})
t.Run("test update changed groups", func(t *testing.T) {
tests := []struct {
name string
firstGroups []string
secondGroups []string
}{
{
name: "add groups",
secondGroups: []string{"group1", "group2"},
},
{
name: "delete groups",
firstGroups: []string{"group1", "group2"},
},
{
name: "change groups",
firstGroups: []string{"group1", "group2"},
secondGroups: []string{"group2", "group3"},
},
{
name: "no change",
firstGroups: []string{"group1", "group2"},
secondGroups: []string{"group2", "group1"},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Cleanup(func() { cleanupUsersAndGroups([]string{testuser}, slices.Concat(tc.firstGroups, tc.secondGroups)) })
// Verify that the user is created with the first set of groups.
users := srv.NewHostUsers(context.Background(), presence, "host_uuid")
_, err := users.UpsertUser(testuser, services.HostUsersInfo{
Groups: tc.firstGroups,
Mode: services.HostUserModeKeep,
})
require.NoError(t, err)
u, err := user.Lookup(testuser)
require.NoError(t, err)
requireUserInGroups(t, u, tc.firstGroups)
// Verify that the user is updated with the second set of groups.
_, err = users.UpsertUser(testuser, services.HostUsersInfo{
Groups: tc.secondGroups,
Mode: services.HostUserModeKeep,
})
require.NoError(t, err)
u, err = user.Lookup(testuser)
require.NoError(t, err)
requireUserInGroups(t, u, tc.secondGroups)
// Verify that the appropriate groups form the first set were deleted.
userGroups := getUserGroups(t, u)
for _, group := range tc.firstGroups {
if !slices.Contains(tc.secondGroups, group) {
require.NotContains(t, userGroups, group)
}
}
})
}
})
t.Run("Test default shell assignment", func(t *testing.T) {
defaultShellUser := "default-shell"
namedShellUser := "named-shell"
absoluteShellUser := "absolute-shell"
t.Cleanup(func() { cleanupUsersAndGroups([]string{defaultShellUser, namedShellUser, absoluteShellUser}, nil) })
// Create a user with a named shell expected to be available in the PATH
users := srv.NewHostUsers(context.Background(), presence, "host_uuid")
_, err := users.UpsertUser(namedShellUser, services.HostUsersInfo{
Mode: services.HostUserModeKeep,
Shell: "bash",
})
require.NoError(t, err)
// Create a user with an absolute path to a shell
_, err = users.UpsertUser(absoluteShellUser, services.HostUsersInfo{
Mode: services.HostUserModeKeep,
Shell: "/usr/bin/bash",
})
require.NoError(t, err)
// Create a user with the host default shell (default behavior)
_, err = users.UpsertUser(defaultShellUser, services.HostUsersInfo{
Mode: services.HostUserModeKeep,
Shell: "zsh",
})
require.NoError(t, err)
_, err = user.Lookup(namedShellUser)
require.NoError(t, err)
_, err = user.Lookup(absoluteShellUser)
require.NoError(t, err)
_, err = user.Lookup(defaultShellUser)
require.NoError(t, err)
// Verify users have the correct shell assigned
userShells, err := getUserShells("/etc/passwd")
require.NoError(t, err)
// Using bash and sh for this test because they should be present on predictable paths for most reasonable places we might
// be running integration tests
expectedShell := "/usr/bin/bash"
assert.Equal(t, expectedShell, userShells[namedShellUser])
assert.Equal(t, expectedShell, userShells[absoluteShellUser])
assert.NotEqual(t, expectedShell, userShells[defaultShellUser])
// User's shell should not be overwritten when updating, only when creating a new host user
_, err = users.UpsertUser(namedShellUser, services.HostUsersInfo{
Mode: services.HostUserModeKeep,
Shell: "sh",
})
require.NoError(t, err)
userShells, err = getUserShells("/etc/passwd")
require.NoError(t, err)
assert.Equal(t, expectedShell, userShells[namedShellUser])
})
}
func TestRootLoginAsHostUser(t *testing.T) {
utils.RequireRoot(t)
// Create test instance.
privateKey, publicKey, err := testauthority.New().GenerateKeyPair()
require.NoError(t, err)
instance := helpers.NewInstance(t, helpers.InstanceConfig{
ClusterName: helpers.Site,
HostID: uuid.New().String(),
NodeName: Host,
Priv: privateKey,
Pub: publicKey,
Log: utils.NewLoggerForTests(),
})
// Create a user that can create a host user.
username := "test-user"
login := utils.GenerateLocalUsername(t)
groups := []string{"foo", "bar"}
role, err := types.NewRole("ssh-host-user", types.RoleSpecV6{
Options: types.RoleOptions{
CreateHostUserMode: types.CreateHostUserMode_HOST_USER_MODE_KEEP,
},
Allow: types.RoleConditions{
Logins: []string{login},
HostGroups: groups,
NodeLabels: types.Labels{types.Wildcard: []string{types.Wildcard}},
},
})
require.NoError(t, err)
instance.Secrets.Users[username] = &helpers.User{
Username: username,
AllowedLogins: []string{login},
Roles: []types.Role{role},
}
require.NoError(t, instance.Create(t, nil, true, nil))
require.NoError(t, instance.Start())
t.Cleanup(func() {
require.NoError(t, instance.StopAll())
})
tests := []struct {
name string
command []string
stdinText string
}{
{
name: "non-interactive session",
command: []string{"whoami"},
},
{
name: "interactive session",
stdinText: "whoami\nexit\n",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
stdin := bytes.NewBufferString(tc.stdinText)
stdout := utils.NewSyncBuffer()
t.Cleanup(func() {
require.NoError(t, stdout.Close())
})
client, err := instance.NewClient(helpers.ClientConfig{
TeleportUser: username,
Login: login,
Cluster: helpers.Site,
Host: Host,
Port: helpers.Port(t, instance.SSH),
Stdin: stdin,
Stdout: stdout,
})
require.NoError(t, err)
// Run an SSH session to completion.
t.Cleanup(func() { cleanupUsersAndGroups([]string{login}, groups) })
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
t.Cleanup(cancel)
err = client.SSH(ctx, tc.command)
require.NoError(t, err)
// Check for correct result from whoami command.
require.Contains(t, stdout.String(), login)
// Verify that a host user was created.
u, err := user.Lookup(login)
require.NoError(t, err)
createdGroups, err := u.GroupIds()
require.NoError(t, err)
for _, group := range createdGroups {
_, err := user.LookupGroupId(group)
require.NoError(t, err)
}
})
}
}
func TestRootStaticHostUsers(t *testing.T) {
utils.RequireRoot(t)
// Create test instance.
privateKey, publicKey, err := testauthority.New().GenerateKeyPair()
require.NoError(t, err)
instance := helpers.NewInstance(t, helpers.InstanceConfig{
ClusterName: helpers.Site,
HostID: uuid.New().String(),
NodeName: Host,
Priv: privateKey,
Pub: publicKey,
Log: utils.NewLoggerForTests(),
})
require.NoError(t, instance.Create(t, nil, false, nil))
require.NoError(t, instance.Start())
t.Cleanup(func() {
require.NoError(t, instance.StopAll())
})
nodeCfg := servicecfg.MakeDefaultConfig()
nodeCfg.SSH.Labels = map[string]string{
"foo": "bar",
}
_, err = instance.StartNode(nodeCfg)
require.NoError(t, err)
// Create host user resources.
groups := []string{"foo", "bar"}
goodLogin := utils.GenerateLocalUsername(t)
goodUser := userprovisioning.NewStaticHostUser(goodLogin, &userprovisioningpb.StaticHostUserSpec{
Matchers: []*userprovisioningpb.Matcher{
{
NodeLabels: []*labelv1.Label{
{
Name: "foo",
Values: []string{"bar"},
},
},
Groups: groups,
Sudoers: []string{"All = (root) NOPASSWD: /usr/bin/systemctl restart nginx.service"},
},
},
})
goodLoginWithShell := utils.GenerateLocalUsername(t)
goodUserWithShell := userprovisioning.NewStaticHostUser(goodLoginWithShell, &userprovisioningpb.StaticHostUserSpec{
Matchers: []*userprovisioningpb.Matcher{
{
NodeLabels: []*labelv1.Label{
{
Name: "foo",
Values: []string{"bar"},
},
},
Groups: groups,
DefaultShell: "bash",
},
},
})
nonMatchingLogin := utils.GenerateLocalUsername(t)
nonMatchingUser := userprovisioning.NewStaticHostUser(nonMatchingLogin, &userprovisioningpb.StaticHostUserSpec{
Matchers: []*userprovisioningpb.Matcher{
{
NodeLabels: []*labelv1.Label{
{
Name: "foo",
Values: []string{"baz"},
},
},
Groups: groups,
},
},
})
conflictingLogin := utils.GenerateLocalUsername(t)
conflictingUser := userprovisioning.NewStaticHostUser(conflictingLogin, &userprovisioningpb.StaticHostUserSpec{
Matchers: []*userprovisioningpb.Matcher{
{
NodeLabels: []*labelv1.Label{
{
Name: "foo",
Values: []string{"bar"},
},
},
Groups: groups,
},
{
NodeLabelsExpression: `labels["foo"] == "bar"`,
Groups: groups,
},
},
})
clt := instance.Process.GetAuthServer()
for _, hostUser := range []*userprovisioningpb.StaticHostUser{goodUser, goodUserWithShell, nonMatchingUser, conflictingUser} {
_, err := clt.UpsertStaticHostUser(context.Background(), hostUser)
require.NoError(t, err)
}
t.Cleanup(func() { cleanupUsersAndGroups([]string{goodLogin, nonMatchingLogin, conflictingLogin}, groups) })
// Test that a node picks up new host users from the cache.
testStaticHostUsers(t, nodeCfg.HostUUID, goodLogin, goodLoginWithShell, nonMatchingLogin, conflictingLogin, groups)
cleanupUsersAndGroups([]string{goodLogin, nonMatchingLogin, conflictingLogin}, groups)
require.NoError(t, instance.StopNodes())
_, err = instance.StartNode(nodeCfg)
require.NoError(t, err)
// Test that a new node picks up existing host users on startup.
testStaticHostUsers(t, nodeCfg.HostUUID, goodLogin, goodLoginWithShell, nonMatchingLogin, conflictingLogin, groups)
// Check that a deleted resource doesn't affect the host user.
require.NoError(t, clt.DeleteStaticHostUser(context.Background(), goodLogin))
require.NoError(t, clt.DeleteStaticHostUser(context.Background(), goodLoginWithShell))
var lookupErr error
var homeDirErr error
var sudoerErr error
require.Never(t, func() bool {
_, lookupErr = user.Lookup(goodLogin)
_, homeDirErr = os.Stat("/home/" + goodLogin)
_, sudoerErr = os.Stat(sudoersPath(goodLogin, nodeCfg.HostUUID))
return lookupErr != nil || homeDirErr != nil || sudoerErr != nil
}, 5*time.Second, time.Second,
"lookup err: %v\nhome dir err: %v\nsudoer err: %v\n",
lookupErr, homeDirErr, sudoerErr)
}
func testStaticHostUsers(t *testing.T, nodeUUID, goodLogin, goodLoginWithShell, nonMatchingLogin, conflictingLogin string, groups []string) {
t.Cleanup(func() {
os.Remove(sudoersPath(goodLogin, nodeUUID))
})
// Check that the good user was correctly applied.
require.EventuallyWithT(t, func(collect *assert.CollectT) {
// Check that the user was created.
existingUser, err := user.Lookup(goodLogin)
assert.NoError(collect, err)
assert.DirExists(collect, existingUser.HomeDir)
// Check that the user has the right groups, including teleport-static.
groupIDs, err := existingUser.GroupIds()
assert.NoError(collect, err)
userGroups := make([]string, 0, len(groupIDs))
for _, gid := range groupIDs {
group, err := user.LookupGroupId(gid)
assert.NoError(collect, err)
userGroups = append(userGroups, group.Name)
}
assert.Subset(collect, userGroups, groups)
assert.Contains(collect, userGroups, types.TeleportStaticGroup)
// Check that the sudoers file was created.
assert.FileExists(collect, sudoersPath(goodLogin, nodeUUID))
userShells, err := getUserShells("/etc/passwd")
assert.NoError(collect, err)
assert.Equal(collect, "/usr/bin/bash", userShells[goodLoginWithShell])
}, 10*time.Second, time.Second)
// Check that the nonmatching and conflicting users were not created.
var nonmatchingUserErr error
var conflictingUserErr error
require.Never(t, func() bool {
_, nonmatchingUserErr = user.Lookup(nonMatchingLogin)
_, conflictingUserErr = user.Lookup(conflictingLogin)
return nonmatchingUserErr == nil && conflictingUserErr == nil
}, 5*time.Second, time.Second,
"nonmatching user error: %v\nconflicting user error: %v\n",
nonmatchingUserErr, conflictingUserErr,
)
}