mirror of
https://github.com/gravitational/teleport
synced 2024-10-19 16:53:57 +00:00
Allow host users to be created with a specific UID or GID (#29305)
* Allow setting HostUserUID/GID traits * Create users with specified UID/GID * rename the traits, fix typo * Document host user creation with specific UID/GID * resolve comments * Resolve comments * Update doc and help strings
This commit is contained in:
parent
9fc2679ea2
commit
2fbe27b7e8
|
@ -384,6 +384,14 @@ const (
|
|||
// TraitGCPServiceAccounts is the name of the role variable used to store
|
||||
// allowed GCP service accounts.
|
||||
TraitGCPServiceAccounts = "gcp_service_accounts"
|
||||
|
||||
// TraitHostUserUID is the name of the variable used to specify
|
||||
// the UID to create host user account with.
|
||||
TraitHostUserUID = "host_user_uid"
|
||||
|
||||
// TraitHostUserGID is the name of the variable used to specify
|
||||
// the GID to create host user account with.
|
||||
TraitHostUserGID = "host_user_gid"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -107,6 +107,10 @@ type User interface {
|
|||
SetAzureIdentities(azureIdentities []string)
|
||||
// SetGCPServiceAccounts sets a list of GCP service accounts for the user
|
||||
SetGCPServiceAccounts(accounts []string)
|
||||
// SetHostUserUID sets the UID for host users
|
||||
SetHostUserUID(uid string)
|
||||
// SetHostUserGID sets the GID for host users
|
||||
SetHostUserGID(gid string)
|
||||
// GetCreatedBy returns information about user
|
||||
GetCreatedBy() CreatedBy
|
||||
// SetCreatedBy sets created by information
|
||||
|
@ -340,6 +344,16 @@ func (u *UserV2) SetGCPServiceAccounts(accounts []string) {
|
|||
u.setTrait(constants.TraitGCPServiceAccounts, accounts)
|
||||
}
|
||||
|
||||
// SetHostUserUID sets the host user UID
|
||||
func (u *UserV2) SetHostUserUID(uid string) {
|
||||
u.setTrait(constants.TraitHostUserUID, []string{uid})
|
||||
}
|
||||
|
||||
// SetHostUserGID sets the host user GID
|
||||
func (u *UserV2) SetHostUserGID(uid string) {
|
||||
u.setTrait(constants.TraitHostUserGID, []string{uid})
|
||||
}
|
||||
|
||||
// GetStatus returns login status of the user
|
||||
func (u *UserV2) GetStatus() LoginStatus {
|
||||
return u.Spec.Status
|
||||
|
|
|
@ -1699,6 +1699,8 @@ $ tctl users add [<flags>] <account>
|
|||
| `--gcp-service-accounts` | none | Comma-separated strings | List of allowed GCP service accounts for the new user |
|
||||
| `--azure-identities` | none | Comma-separated strings | List of Azure managed identities to allow the user to assume. Must be the full URIs of the identities |
|
||||
| `--ttl` | 1h | relative duration like 5s, 2m, or 3h, **maximum 48h** | Set expiration time for token |
|
||||
| `--host-user-uid` | none | Unix UID | UID for auto provisioned host users to use |
|
||||
| `--host-user-gid` | none | Unix GID | GID for auto provisioned host users to use |
|
||||
|
||||
#### Global flags
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ since it must execute these commands in order to create transient users:
|
|||
- `visudo`
|
||||
- (!docs/pages/includes/tctl.mdx!)
|
||||
|
||||
## Step 1/2. Configure a role
|
||||
## Step 1/3. Configure a role
|
||||
|
||||
First, create a role with `create_host_user_mode` set to `drop` or `keep`.
|
||||
|
||||
|
@ -132,7 +132,43 @@ ssh_service:
|
|||
|
||||
(!docs/pages/includes/add-role-to-user.mdx role="auto-users"!)
|
||||
|
||||
## Step 2/2 Test host user creation
|
||||
## Step 2/3. [Optional] Configure the UID and GID for the created users
|
||||
|
||||
If the user has the `host_user_uid` and `host_user_gid` traits
|
||||
specified, when the host user is being created the UID and GID will be
|
||||
set to those values.
|
||||
|
||||
These values can either be set manually when creating or updating the
|
||||
user through `tctl`, or it can be set via SSO attributes of the same
|
||||
name.
|
||||
|
||||
If a group with the specified GID does not already exist, a group will
|
||||
be created with the same login name as the user being created.
|
||||
|
||||
```yaml
|
||||
kind: user
|
||||
metadata:
|
||||
name: some_teleport_user
|
||||
spec:
|
||||
...
|
||||
traits:
|
||||
logins:
|
||||
- root
|
||||
- alex
|
||||
host_user_gid:
|
||||
# gid and uid values must be quoted.
|
||||
- "1234"
|
||||
host_user_uid:
|
||||
- "5678"
|
||||
```
|
||||
|
||||
<Admonition type="warning">
|
||||
|
||||
If multiple entries are specified in the `host_user_uid` or `host_user_gid` only the first entry will be used.
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Step 3/3 Test host user creation
|
||||
|
||||
When you connect to a remote Node via `tsh`, and host user creation is enabled, the
|
||||
Teleport SSH Service will automatically create a user on the host:
|
||||
|
|
|
@ -66,14 +66,14 @@ func TestRootHostUsersBackend(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Test CreateGroup", func(t *testing.T) {
|
||||
err := backend.CreateGroup(testgroup)
|
||||
err := backend.CreateGroup(testgroup, "")
|
||||
require.NoError(t, err)
|
||||
err = backend.CreateGroup(testgroup)
|
||||
err = backend.CreateGroup(testgroup, "")
|
||||
require.True(t, trace.IsAlreadyExists(err))
|
||||
})
|
||||
|
||||
t.Run("Test CreateUser and group", func(t *testing.T) {
|
||||
err := backend.CreateUser(testuser, []string{testgroup})
|
||||
err := backend.CreateUser(testuser, []string{testgroup}, "", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
tuser, err := backend.Lookup(testuser)
|
||||
|
@ -86,7 +86,7 @@ func TestRootHostUsersBackend(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.Contains(t, tuserGids, group.Gid)
|
||||
|
||||
err = backend.CreateUser(testuser, []string{})
|
||||
err = backend.CreateUser(testuser, []string{}, "", "")
|
||||
require.True(t, trace.IsAlreadyExists(err))
|
||||
|
||||
})
|
||||
|
@ -107,7 +107,7 @@ func TestRootHostUsersBackend(t *testing.T) {
|
|||
}
|
||||
})
|
||||
for _, u := range checkUsers {
|
||||
err := backend.CreateUser(u, []string{})
|
||||
err := backend.CreateUser(u, []string{}, "", "")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
|
@ -190,6 +190,39 @@ func TestRootHostUsers(t *testing.T) {
|
|||
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.CreateUser(testuser, &services.HostUsersInfo{
|
||||
Mode: types.CreateHostUserMode_HOST_USER_MODE_DROP,
|
||||
UID: testUID,
|
||||
GID: testGID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(cleanupUsersAndGroups([]string{testuser}, []string{types.TeleportServiceGroup}))
|
||||
|
||||
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 sudoers enabled users", func(t *testing.T) {
|
||||
if _, err := exec.LookPath("visudo"); err != nil {
|
||||
t.Skip("Visudo not found on path")
|
||||
|
|
|
@ -838,6 +838,10 @@ type HostUsersInfo struct {
|
|||
// Mode determines if a host user should be deleted after a session
|
||||
// ends or not.
|
||||
Mode types.CreateHostUserMode
|
||||
// UID is the UID that the host user will be created with
|
||||
UID string
|
||||
// GID is the GID that the host user will be created with
|
||||
GID string
|
||||
}
|
||||
|
||||
// HostUsers returns host user information matching a server or nil if
|
||||
|
@ -927,10 +931,24 @@ func (a *accessChecker) HostUsers(s types.Server) (*HostUsersInfo, error) {
|
|||
sudoers = finalSudoers
|
||||
}
|
||||
|
||||
traits := a.Traits()
|
||||
var gid string
|
||||
gidL := traits[constants.TraitHostUserGID]
|
||||
if len(gidL) >= 1 {
|
||||
gid = gidL[0]
|
||||
}
|
||||
var uid string
|
||||
uidL := traits[constants.TraitHostUserUID]
|
||||
if len(uidL) >= 1 {
|
||||
uid = uidL[0]
|
||||
}
|
||||
|
||||
return &HostUsersInfo{
|
||||
Groups: utils.StringsSliceFromSet(groups),
|
||||
Sudoers: sudoers,
|
||||
Mode: mode,
|
||||
UID: uid,
|
||||
GID: gid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -64,10 +64,12 @@ type HostUsersBackend interface {
|
|||
Lookup(name string) (*user.User, error)
|
||||
// LookupGroup retrieves a group by name.
|
||||
LookupGroup(group string) (*user.Group, error)
|
||||
// LookupGroupByID retrieves a group by its ID.
|
||||
LookupGroupByID(gid string) (*user.Group, error)
|
||||
// CreateGroup creates a group on a host.
|
||||
CreateGroup(group string) error
|
||||
CreateGroup(group string, gid string) error
|
||||
// CreateUser creates a user on a host.
|
||||
CreateUser(name string, groups []string) error
|
||||
CreateUser(name string, groups []string, uid, gid string) error
|
||||
// DeleteUser deletes a user from a host.
|
||||
DeleteUser(name string) error
|
||||
// CheckSudoers ensures that a sudoers file to be written is valid
|
||||
|
@ -224,8 +226,15 @@ func (u *HostUserManagement) CreateUser(name string, ui *services.HostUsersInfo)
|
|||
if err := u.storage.UpsertHostUserInteractionTime(u.ctx, name, time.Now()); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
if ui.GID != "" {
|
||||
// if gid is specified a group must already exist
|
||||
err := u.backend.CreateGroup(name, ui.GID)
|
||||
if err != nil && !trace.IsAlreadyExists(err) {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
err = u.backend.CreateUser(name, groups)
|
||||
err = u.backend.CreateUser(name, groups, ui.UID, ui.GID)
|
||||
if err != nil && !trace.IsAlreadyExists(err) {
|
||||
return trace.WrapWithMessage(err, "error while creating user")
|
||||
}
|
||||
|
@ -288,7 +297,7 @@ func (u *HostUserManagement) createGroupIfNotExist(group string) error {
|
|||
if err != nil && !isUnknownGroupError(err, group) {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
err = u.backend.CreateGroup(group)
|
||||
err = u.backend.CreateGroup(group, "")
|
||||
if trace.IsAlreadyExists(err) {
|
||||
return nil
|
||||
}
|
||||
|
@ -303,6 +312,7 @@ func (u *HostUserManagement) createGroupIfNotExist(group string) error {
|
|||
// See github issue - https://github.com/golang/go/issues/40334
|
||||
func isUnknownGroupError(err error, groupName string) bool {
|
||||
return errors.Is(err, user.UnknownGroupError(groupName)) ||
|
||||
errors.Is(err, user.UnknownGroupIdError(groupName)) ||
|
||||
strings.HasSuffix(err.Error(), syscall.ENOENT.Error()) ||
|
||||
strings.HasSuffix(err.Error(), syscall.ESRCH.Error())
|
||||
}
|
||||
|
|
|
@ -59,6 +59,11 @@ func (*HostUsersProvisioningBackend) LookupGroup(name string) (*user.Group, erro
|
|||
return user.LookupGroup(name)
|
||||
}
|
||||
|
||||
// LookupGroup host group information lookup by GID
|
||||
func (*HostUsersProvisioningBackend) LookupGroupByID(gid string) (*user.Group, error) {
|
||||
return user.LookupGroupId(gid)
|
||||
}
|
||||
|
||||
// GetAllUsers returns a full list of users present on a system
|
||||
func (*HostUsersProvisioningBackend) GetAllUsers() ([]string, error) {
|
||||
users, _, err := host.GetAllUsers()
|
||||
|
@ -66,14 +71,14 @@ func (*HostUsersProvisioningBackend) GetAllUsers() ([]string, error) {
|
|||
}
|
||||
|
||||
// CreateGroup creates a group on a host
|
||||
func (*HostUsersProvisioningBackend) CreateGroup(name string) error {
|
||||
_, err := host.GroupAdd(name)
|
||||
func (*HostUsersProvisioningBackend) CreateGroup(name string, gid string) error {
|
||||
_, err := host.GroupAdd(name, gid)
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
// CreateUser creates a user on a host
|
||||
func (*HostUsersProvisioningBackend) CreateUser(name string, groups []string) error {
|
||||
_, err := host.UserAdd(name, groups)
|
||||
func (*HostUsersProvisioningBackend) CreateUser(name string, groups []string, uid, gid string) error {
|
||||
_, err := host.UserAdd(name, groups, uid, gid)
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
|
|
|
@ -40,6 +40,10 @@ type testHostUserBackend struct {
|
|||
groups map[string]string
|
||||
// sudoers: user -> entries
|
||||
sudoers map[string]string
|
||||
// userUID: user -> uid
|
||||
userUID map[string]string
|
||||
// userGID: user -> gid
|
||||
userGID map[string]string
|
||||
}
|
||||
|
||||
func newTestUserMgmt() *testHostUserBackend {
|
||||
|
@ -47,6 +51,8 @@ func newTestUserMgmt() *testHostUserBackend {
|
|||
users: map[string][]string{},
|
||||
groups: map[string]string{},
|
||||
sudoers: map[string]string{},
|
||||
userUID: map[string]string{},
|
||||
userGID: map[string]string{},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,6 +80,13 @@ func (tm *testHostUserBackend) LookupGroup(groupname string) (*user.Group, error
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (tm *testHostUserBackend) LookupGroupByID(gid string) (*user.Group, error) {
|
||||
return &user.Group{
|
||||
Gid: tm.groups[gid],
|
||||
Name: gid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (tm *testHostUserBackend) UserGIDs(u *user.User) ([]string, error) {
|
||||
ids := make([]string, 0, len(tm.users[u.Username]))
|
||||
for _, id := range tm.users[u.Username] {
|
||||
|
@ -82,7 +95,7 @@ func (tm *testHostUserBackend) UserGIDs(u *user.User) ([]string, error) {
|
|||
return ids, nil
|
||||
}
|
||||
|
||||
func (tm *testHostUserBackend) CreateGroup(group string) error {
|
||||
func (tm *testHostUserBackend) CreateGroup(group, gid string) error {
|
||||
_, ok := tm.groups[group]
|
||||
if ok {
|
||||
return trace.AlreadyExists("Group %q, already exists", group)
|
||||
|
@ -91,12 +104,14 @@ func (tm *testHostUserBackend) CreateGroup(group string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (tm *testHostUserBackend) CreateUser(user string, groups []string) error {
|
||||
func (tm *testHostUserBackend) CreateUser(user string, groups []string, uid, gid string) error {
|
||||
_, ok := tm.users[user]
|
||||
if ok {
|
||||
return trace.AlreadyExists("Group %q, already exists", user)
|
||||
}
|
||||
tm.users[user] = groups
|
||||
tm.userUID[user] = uid
|
||||
tm.userGID[user] = gid
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -167,8 +182,8 @@ func TestUserMgmt_CreateTemporaryUser(t *testing.T) {
|
|||
require.NoError(t, closer.Close())
|
||||
require.NotContains(t, backend.users, "bob")
|
||||
|
||||
backend.CreateGroup("testgroup")
|
||||
backend.CreateUser("simon", []string{})
|
||||
backend.CreateGroup("testgroup", "")
|
||||
backend.CreateUser("simon", []string{}, "", "")
|
||||
|
||||
// try to create a temporary user for simon
|
||||
closer, err = users.CreateUser("simon", userinfo)
|
||||
|
@ -215,12 +230,12 @@ func TestUserMgmtSudoers_CreateTemporaryUser(t *testing.T) {
|
|||
}
|
||||
// test user already exists but teleport-service group has not yet
|
||||
// been created
|
||||
backend.CreateUser("testuser", nil)
|
||||
backend.CreateUser("testuser", nil, "", "")
|
||||
_, err := users.CreateUser("testuser", &services.HostUsersInfo{
|
||||
Mode: types.CreateHostUserMode_HOST_USER_MODE_DROP,
|
||||
})
|
||||
require.True(t, trace.IsAlreadyExists(err))
|
||||
backend.CreateGroup(types.TeleportServiceGroup)
|
||||
backend.CreateGroup(types.TeleportServiceGroup, "")
|
||||
// IsAlreadyExists error when teleport-service group now exists
|
||||
_, err = users.CreateUser("testuser", &services.HostUsersInfo{
|
||||
Mode: types.CreateHostUserMode_HOST_USER_MODE_DROP,
|
||||
|
@ -258,12 +273,12 @@ func TestUserMgmt_DeleteAllTeleportSystemUsers(t *testing.T) {
|
|||
|
||||
for _, user := range usersDB {
|
||||
for _, group := range user.groups {
|
||||
mgmt.CreateGroup(group)
|
||||
mgmt.CreateGroup(group, "")
|
||||
}
|
||||
if slices.Contains(user.groups, types.TeleportServiceGroup) {
|
||||
users.CreateUser(user.user, &services.HostUsersInfo{Groups: user.groups})
|
||||
} else {
|
||||
mgmt.CreateUser(user.user, user.groups)
|
||||
mgmt.CreateUser(user.user, user.groups, "", "")
|
||||
}
|
||||
}
|
||||
require.NoError(t, users.DeleteAllUsers())
|
||||
|
|
|
@ -33,13 +33,20 @@ const GroupExistExit = 9
|
|||
const UserExistExit = 9
|
||||
const UserLoggedInExit = 8
|
||||
|
||||
// GroupAdd creates a group on a host using `groupadd`
|
||||
func GroupAdd(groupname string) (exitCode int, err error) {
|
||||
// GroupAdd creates a group on a host using `groupadd` optionally
|
||||
// specifying the GID to create the group with.
|
||||
func GroupAdd(groupname string, gid string) (exitCode int, err error) {
|
||||
groupaddBin, err := exec.LookPath("groupadd")
|
||||
if err != nil {
|
||||
return -1, trace.Wrap(err, "cant find groupadd binary")
|
||||
}
|
||||
cmd := exec.Command(groupaddBin, groupname)
|
||||
var args []string
|
||||
if gid != "" {
|
||||
args = append(args, "--gid", gid)
|
||||
}
|
||||
args = append(args, groupname)
|
||||
|
||||
cmd := exec.Command(groupaddBin, args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
log.Debugf("%s output: %s", cmd.Path, string(output))
|
||||
if cmd.ProcessState.ExitCode() == GroupExistExit {
|
||||
|
@ -49,7 +56,7 @@ func GroupAdd(groupname string) (exitCode int, err error) {
|
|||
}
|
||||
|
||||
// UserAdd creates a user on a host using `useradd`
|
||||
func UserAdd(username string, groups []string) (exitCode int, err error) {
|
||||
func UserAdd(username string, groups []string, uid, gid string) (exitCode int, err error) {
|
||||
useraddBin, err := exec.LookPath("useradd")
|
||||
if err != nil {
|
||||
return -1, trace.Wrap(err, "cant find useradd binary")
|
||||
|
@ -59,6 +66,13 @@ func UserAdd(username string, groups []string) (exitCode int, err error) {
|
|||
if len(groups) != 0 {
|
||||
args = append(args, "--groups", strings.Join(groups, ","))
|
||||
}
|
||||
if uid != "" {
|
||||
args = append(args, "--uid", uid)
|
||||
}
|
||||
if gid != "" {
|
||||
args = append(args, "--gid", gid)
|
||||
}
|
||||
|
||||
cmd := exec.Command(useraddBin, args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
log.Debugf("%s output: %s", cmd.Path, string(output))
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -55,6 +56,10 @@ type UserCommand struct {
|
|||
allowedAzureIdentities []string
|
||||
allowedGCPServiceAccounts []string
|
||||
allowedRoles []string
|
||||
hostUserUID string
|
||||
hostUserUIDProvided bool
|
||||
hostUserGID string
|
||||
hostUserGIDProvided bool
|
||||
|
||||
ttl time.Duration
|
||||
|
||||
|
@ -88,6 +93,8 @@ func (u *UserCommand) Initialize(app *kingpin.Application, config *servicecfg.Co
|
|||
u.userAdd.Flag("aws-role-arns", "List of allowed AWS role ARNs for the new user").StringsVar(&u.allowedAWSRoleARNs)
|
||||
u.userAdd.Flag("azure-identities", "List of allowed Azure identities for the new user").StringsVar(&u.allowedAzureIdentities)
|
||||
u.userAdd.Flag("gcp-service-accounts", "List of allowed GCP service accounts for the new user").StringsVar(&u.allowedGCPServiceAccounts)
|
||||
u.userAdd.Flag("host-user-uid", "UID for auto provisioned host users to use").IsSetByUser(&u.hostUserUIDProvided).StringVar(&u.hostUserUID)
|
||||
u.userAdd.Flag("host-user-gid", "GID for auto provisioned host users to use").IsSetByUser(&u.hostUserGIDProvided).StringVar(&u.hostUserGID)
|
||||
|
||||
u.userAdd.Flag("roles", "List of roles for the new user to assume").Required().StringsVar(&u.allowedRoles)
|
||||
|
||||
|
@ -121,6 +128,8 @@ func (u *UserCommand) Initialize(app *kingpin.Application, config *servicecfg.Co
|
|||
StringsVar(&u.allowedAzureIdentities)
|
||||
u.userUpdate.Flag("set-gcp-service-accounts", "List of allowed GCP service accounts for the user, replaces current service accounts").
|
||||
StringsVar(&u.allowedGCPServiceAccounts)
|
||||
u.userUpdate.Flag("set-host-user-uid", "UID for auto provisioned host users to use. Value can be reset by providing an empty string").IsSetByUser(&u.hostUserUIDProvided).StringVar(&u.hostUserUID)
|
||||
u.userUpdate.Flag("set-host-user-gid", "GID for auto provisioned host users to use. Value can be reset by providing an empty string").IsSetByUser(&u.hostUserGIDProvided).StringVar(&u.hostUserGID)
|
||||
|
||||
u.userList = users.Command("ls", "Lists all user accounts.")
|
||||
u.userList.Flag("format", "Output format, 'text' or 'json'").Hidden().Default(teleport.Text).StringVar(&u.format)
|
||||
|
@ -250,6 +259,17 @@ func (u *UserCommand) Add(ctx context.Context, client auth.ClientI) error {
|
|||
}
|
||||
}
|
||||
|
||||
if u.hostUserUIDProvided && u.hostUserUID != "" {
|
||||
if _, err := strconv.Atoi(u.hostUserUID); err != nil {
|
||||
return trace.BadParameter("host user UID must be a numeric ID")
|
||||
}
|
||||
}
|
||||
if u.hostUserGIDProvided && u.hostUserGID != "" {
|
||||
if _, err := strconv.Atoi(u.hostUserGID); err != nil {
|
||||
return trace.BadParameter("host user GID must be a numeric ID")
|
||||
}
|
||||
}
|
||||
|
||||
traits := map[string][]string{
|
||||
constants.TraitLogins: u.allowedLogins,
|
||||
constants.TraitWindowsLogins: u.allowedWindowsLogins,
|
||||
|
@ -261,6 +281,8 @@ func (u *UserCommand) Add(ctx context.Context, client auth.ClientI) error {
|
|||
constants.TraitAWSRoleARNs: flattenSlice(u.allowedAWSRoleARNs),
|
||||
constants.TraitAzureIdentities: azureIdentities,
|
||||
constants.TraitGCPServiceAccounts: gcpServiceAccounts,
|
||||
constants.TraitHostUserUID: {u.hostUserUID},
|
||||
constants.TraitHostUserGID: {u.hostUserGID},
|
||||
}
|
||||
|
||||
user, err := types.NewUser(u.login)
|
||||
|
@ -416,6 +438,22 @@ func (u *UserCommand) Update(ctx context.Context, client auth.ClientI) error {
|
|||
updateMessages["GCP service accounts"] = accounts
|
||||
}
|
||||
|
||||
if u.hostUserUIDProvided && u.hostUserUID != "" {
|
||||
if _, err := strconv.Atoi(u.hostUserUID); err != nil {
|
||||
return trace.BadParameter("host user UID must be a numeric ID")
|
||||
}
|
||||
|
||||
user.SetHostUserUID(u.hostUserUID)
|
||||
updateMessages["Host user UID"] = []string{u.hostUserUID}
|
||||
}
|
||||
if u.hostUserGIDProvided && u.hostUserGID != "" {
|
||||
if _, err := strconv.Atoi(u.hostUserGID); err != nil {
|
||||
return trace.BadParameter("host user GID must be a numeric ID")
|
||||
}
|
||||
user.SetHostUserGID(u.hostUserGID)
|
||||
updateMessages["Host user GID"] = []string{u.hostUserGID}
|
||||
}
|
||||
|
||||
if len(updateMessages) == 0 {
|
||||
return trace.BadParameter("Nothing to update. Please provide at least one --set flag.")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue