Repository: ansible/ansible

Issue:
Force user creation to be local
_From @rrauenza on October 2, 2015 23:18_

##### ISSUE TYPE
Feature Request

##### COMPONENT NAME
user module

##### ANSIBLE VERSION
N/A

##### SUMMARY
Sometimes LDAP/NIS user names conflict with names we want to have local (like mysql, postgres).  I'm worked around it other ways, but was told recently about luseradd.

I wonder if there would be interest in adding a force=yes to the user module such that is uses luseradd instead of useradd.


_Copied from original issue: ansible/ansible-modules-core#2193_


Discussion:
_From @bcoca on October 2, 2015 23:45_

luseradd is a new tool present in very few systems.

_From @mshaddock on October 23, 2015 19:55_

Actually, luseradd has been in RedHat Linux since at least RHEL 3.7, so it isn't that new.  Unfortunately, I don't have access to any older non-RedHat Linux systems, so I can't comment on those.

_From @Jmainguy on October 27, 2015 17:22_

@bcoca @mshaddock What about adding force with the note that it will only work on systems with luseradd installed. (And also adding that to the error message when predictably somebody uses it without having luseradd installed), what do yall think?

_From @dlbewley on February 19, 2016 23:21_

This is a problem for me, and I would find this feature useful as well. 

Perhaps a `local=yes` option to the user module.

_From @Jmainguy on February 21, 2016 14:40_

@bcoca any idea on the above idea from @dlbewley.

_From @rrauenza on February 23, 2016 20:0_

FYI, as a workaround I wrote this role (CentOS 7).  I found I also needed to flush sssd and nscd, as well as kick crond (I think it still had the ldap user id cached somehow.)

(Based on http://serverfault.com/questions/725594/filter-ldap-user-through-pam-so-it-appears-to-not-exist-at-all)

```
- name: Disable ldap users
  ini_file: dest=/etc/sssd/sssd.conf section='nss' option=filter_users value={{ filter_ldap_users | join(",") }}
  register: sssd_users

- name: Disable ldap groups
  ini_file: dest=/etc/sssd/sssd.conf section='nss' option=filter_groups value={{ filter_ldap_groups | join(",") }}
  register: sssd_groups

- name: Restart SSSD
  service: name=sssd state=restarted
  when: sssd_groups.changed or sssd_users.changed
  notify: Restart crond

- name: Flush NSCD cache
  shell: "for db in /var/db/nscd/*; do nscd -i $(basename $db); done"
  when: sssd_groups.changed or sssd_users.changed
  notify: Restart crond

- name: Flush SSSD cache
  command: /usr/sbin/sss_cache -E
  when: sssd_groups.changed or sssd_users.changed
  notify: Restart crond
```

_From @ansibot on August 1, 2016 2:25_

@grossws, ping. This issue is still waiting on your response.
[click here for bot help](https://github.com/jctanner/ansibullbot/blob/ISSUE_TRIAGE_CLASS/ISSUE_HELP.md)

<!--- boilerplate: issue_renotify_maintainer --->

_From @digi691 on September 9, 2016 20:5_

The libuser package that provides luseradd and lgroupadd is available from RHEL4 to current, Ubuntu 12.04 to current, and Debian Wheezy to current.  This looks to be well supported for current maintained/supported Distribution versions.  I might be interested in issuing a PR for this feature for the user and group module as I would find it useful in our environment as well. @bcoca, would want to make sure prior to putting time into this, if it would be considered given your previous comments.

_From @ansibot on September 12, 2016 19:36_

@grossws, ping. This issue is still waiting on your response.
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)

<!--- boilerplate: issue_renotify_maintainer --->

_From @ansibot on October 6, 2016 15:49_

@ansible, ping. This issue is still waiting on your response.
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)

<!--- boilerplate: issue_renotify_maintainer --->

_From @TheFriendlyCoder on October 20, 2016 12:16_

It would be nice if this same feature request could be extended to include support for creating local groups in the same fashion as well. We recently hit a problem where an attempt was made to create a new local group using the 'group' module, only to find out the group in question already existed in ldap. The 'group' task got skipped because the group already existed, and then subsequent tasks which attempted to add users to this group produced erroneous results (ie: it appears as though the 'user' module will complete successfully when trying to add a user to an ldap group, even though the results of the operation do not correctly add the user to the group - local or otherwise).

If the 'group' module also had a 'local=yes' option then issues like this could be avoided.

_From @TheFriendlyCoder on October 20, 2016 12:24_

NOTE: I suspect, at it's core, the problem here lies with the system tools ansible is using under the hood. Based on my ad-hoc tests on CentOS 7.2, using 'usermod' to add a local user to an ldap group complete's successfully (ie: a 0 return code) however the operation doesn't actually add the user to the group. Further, I suspect the mechanism used by the 'group' module in Ansible is first checking to see if the specified group exists on the system, and since a group does exist - albeit in ldap - the task simply gets skipped and thus 'groupadd' is never actually being run. Again, in my test environment I have confirmed that trying to use groupadd to create a local group when an existing ldap group exists with the same name fails.

_From @TheFriendlyCoder on October 20, 2016 12:35_

Also, another 2cents: I can see how skipping the 'group' task could make sense if the group exists on the system, whether the group is local or not, however I'd say that the 'user' module should probably fail if / when it attempts to add a user to a group - local or otherwise - and the user doesn't actually get added to the group. At least then problems like this could be detected as the plays are running, instead of finding problems with the configuration after the fact in production.

_From @ansibot on October 22, 2016 9:18_

@ansible, ping. This issue is still waiting on your response.
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)

<!--- boilerplate: issue_renotify_maintainer --->

_From @ansibot on December 7, 2016 17:56_

This repository has been locked. All new issues and pull requests should be filed in https://github.com/ansible/ansible

Please read through the <a href="http://docs.ansible.com/ansible/dev_guide/repomerge.html#move-issues-and-prs-to-new-repo">repomerge</a> page in the dev guide. The guide contains links to tools which automatically move your issue or pull request to the ansible/ansible repo.

<!-- boilerplate: repomerge -->
cc @sfromm
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/modules/system/user.py b/lib/ansible/modules/system/user.py
--- a/lib/ansible/modules/system/user.py
+++ b/lib/ansible/modules/system/user.py
@@ -197,6 +197,15 @@
         description:
             - An expiry time for the user in epoch, it will be ignored on platforms that do not support this.
               Currently supported on Linux and FreeBSD.
+    local:
+        version_added: "2.4"
+        required: false
+        default: "False"
+        description:
+            - Forces the use of "local" command alternatives on platforms that implement it.
+              This is useful in environments that use centralized authentification when you want to manipulate the local users.
+              I.E. it uses `luseradd` instead of `useradd`.
+            - This requires that these commands exist on the targeted host, otherwise it will be a fatal error.
 '''
 
 EXAMPLES = '''
@@ -252,7 +261,6 @@
 except:
     HAVE_SPWD=False
 
-
 class User(object):
     """
     This is a generic User manipulation class that is subclassed
@@ -305,6 +313,7 @@ def __init__(self, module):
         self.home    = module.params['home']
         self.expires = None
         self.groups = None
+        self.local = module.params['local']
 
         if module.params['groups'] is not None:
             self.groups = ','.join(module.params['groups'])
@@ -332,7 +341,12 @@ def execute_command(self, cmd, use_unsafe_shell=False, data=None, obey_checkmode
             return self.module.run_command(cmd, use_unsafe_shell=use_unsafe_shell, data=data)
 
     def remove_user_userdel(self):
-        cmd = [self.module.get_bin_path('userdel', True)]
+        if self.local:
+            command_name = 'luserdel'
+        else:
+            command_name = 'userdel'
+
+        cmd = [self.module.get_bin_path(command_name, True)]
         if self.force:
             cmd.append('-f')
         if self.remove:
@@ -341,7 +355,13 @@ def remove_user_userdel(self):
 
         return self.execute_command(cmd)
 
-    def create_user_useradd(self, command_name='useradd'):
+    def create_user_useradd(self):
+
+        if self.local:
+            command_name = 'luseradd'
+        else:
+            command_name = 'useradd'
+
         cmd = [self.module.get_bin_path(command_name, True)]
 
         if self.uid is not None:
@@ -417,7 +437,13 @@ def create_user_useradd(self, command_name='useradd'):
 
     def _check_usermod_append(self):
         # check if this version of usermod can append groups
-        usermod_path = self.module.get_bin_path('usermod', True)
+
+        if self.local:
+            command_name = 'lusermod'
+        else:
+            command_name = 'usermod'
+
+        usermod_path = self.module.get_bin_path(command_name, True)
 
         # for some reason, usermod --help cannot be used by non root
         # on RH/Fedora, due to lack of execute bit for others
@@ -439,7 +465,13 @@ def _check_usermod_append(self):
 
 
     def modify_user_usermod(self):
-        cmd = [self.module.get_bin_path('usermod', True)]
+
+        if self.local:
+            command_name = 'lusermod'
+        else:
+            command_name = 'usermod'
+
+        cmd = [self.module.get_bin_path(command_name, True)]
         info = self.user_info()
         has_append = self._check_usermod_append()
 
@@ -2141,6 +2173,7 @@ def main():
             ssh_key_passphrase=dict(default=None, type='str', no_log=True),
             update_password=dict(default='always',choices=['always','on_create'],type='str'),
             expires=dict(default=None, type='float'),
+            local=dict(type='bool'),
         ),
         supports_check_mode=True
     )


Test patch: