gravitational/teleport · warning

managed host users can not be converted to or from a static

Error message

managed host users can not be converted to or from a static host user

What it means

errStaticConversion is returned when ResolveGroups detects an attempt to convert a host user between auto-provisioned (managed) mode and static host user mode. Teleport forbids in-place conversion to prevent unexpected account state changes; the caller (UpsertUser) logs and aborts the update.

Source

Thrown at lib/srv/usermgmt.go:292

	if errors.Is(err, host.ErrInvalidSudoers) {
		u.log.WarnContext(context.Background(), "Invalid sudoers entry. If using a login managed by a static host user resource, inspect its configured sudoers field for invalid entries. Otherwise, inspect the host_sudoers field for roles targeting this host.", "error", err, "host_username", name)
		return trace.BadParameter("invalid sudoers entry for login %q, inspect roles' host_sudoers field or static host user's sudoers field for invalid syntax", name)
	}
	return trace.Wrap(err)
}

func (u *HostSudoersManagement) RemoveSudoers(name string) error {
	if err := u.backend.RemoveSudoersFile(name); err != nil {
		return trace.Wrap(err)
	}
	return nil
}

// errUnmanagedUser is returned when attempting to modify or interact with a user that is not managed by Teleport.
var errUnmanagedUser = errors.New("user not managed by teleport")

// errStaticConversion is returned when attempting to convert a managed host user to or from a static host user
var errStaticConversion = errors.New("managed host users can not be converted to or from a static host user")

func (u *HostUserManagement) updateUser(hostUser HostUser, ui *decisionpb.HostUsersInfo) error {
	ctx := u.ctx
	log := u.log.With(
		"host_username", hostUser.Name,
		"mode", ui.GetMode(),
		"uid", hostUser.UID,
		"gid", hostUser.GID,
	)

	if ui.GetMode() == decisionpb.HostUserMode_HOST_USER_MODE_KEEP {
		_, hasKeepGroup := hostUser.Groups[apiconstants.TeleportKeepGroup]
		if !hasKeepGroup {
			home, err := u.backend.GetDefaultHomeDirectory(hostUser.Name)
			if err != nil {
				return trace.Wrap(err)
			}

View on GitHub (pinned to 1283425b60)

Solutions

  1. Remove the existing host user on the server and let Teleport recreate it in the desired mode
  2. Align the role's host user mode with the existing user's current mode instead of converting
  3. Plan migration explicitly: switch mode, then manually reconcile accounts on hosts

Example fix

// before: flipping mode for existing user
host_users:
  mode: static
// after: delete existing managed user on host first, then apply static mode,
// or keep mode consistent with existing user state
host_users:
  mode: keep  # unchanged until accounts reconciled
Defensive patterns

Strategy: validation

Validate before calling

// ensure the user's current mode matches the target mode before upsert
currentlyStatic := isStaticHostUser(hostUser)
targetStatic := ui.GetMode() == decisionpb.HostUsersMode_HOST_USERS_MODE_STATIC
if currentlyStatic != targetStatic {
    return trace.BadParameter("recreate user %s to change static/managed mode", hostUser.Name)
}

Type guard

func isStaticConversionErr(err error) bool { return errors.Is(err, errStaticConversion) }

Try / catch

if _, err := ResolveGroups(log, hostUser, ui, options.takeOwnership); err != nil {
    if errors.Is(err, errStaticConversion) {
        log.DebugContext(ctx, "cannot convert between auto-provisioned and static host users")
        return nil
    }
    return trace.Wrap(err)
}

Prevention

When it happens

Trigger: ResolveGroups (lib/srv/usermgmt.go:788) sees a user currently in static mode while the role/config declares it managed, or vice versa: (inStaticMode && managedUser) || (!inStaticMode && staticUser).

Common situations: Operator flips a role's host user mode (host_users mode static vs dynamic) for users that already exist on servers; migrating hosts between static host user definitions and auto-provisioning without cleanup.

Related errors


AI-assisted analysis of gravitational/teleport@1283425b60 (2026-09-02). Data as JSON: /api/errors/79a739f86b18c596. Report an issue: GitHub.