diff --git a/.ansible-lint b/.ansible-lint new file mode 100644 index 0000000..192211d --- /dev/null +++ b/.ansible-lint @@ -0,0 +1,3 @@ +skip_list: + - '106' + - '204' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..76b5516 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,47 @@ +name: CI +on: [push] +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout the codebase + uses: actions/checkout@v2 + + - name: Setup Python 3 + uses: actions/setup-python@v2 + with: + python-version: '3.x' + architecture: 'x64' + + - name: Install lint dependencies + run: pip3 install ansible ansible-lint yamllint + + - name: Lint role + run: | + ansible-lint + yamllint . + + - name: Test role + run: | + echo '[defaults]' > ansible.cfg + echo 'roles_path = ../' >> ansible.cfg + ansible-playbook -i localhost, tests/test.yml --syntax-check + ansible-playbook -i localhost, --connection=local -b tests/test.yml + ansible-playbook -i localhost, --connection=local -b tests/test.yml | grep -q 'changed=0.*failed=0' && (echo 'Idempotence test: pass' && exit 0) || (echo 'Idempotence test: fail' && exit 1) + id ansibletestuser | grep --silent "uid=2222(ansibletestuser) gid=2222(ansibletestuser) groups=2222(ansibletestuser),2(bin),100(users)" + id ansibletestuser2 | grep --silent "uid=2223(ansibletestuser2) gid=2223(ansibletestuser2) groups=2223(ansibletestuser2),2(bin),100(users)" + id ansibletestuser3 | grep --silent "uid=2224(ansibletestuser3) gid=4001(ansibletestgroup1) groups=4001(ansibletestgroup1),2(bin),100(users)" + id ansibletestuser4 | grep --silent "uid=2225(ansibletestuser4) gid=100(users) groups=100(users),2(bin)" + id ansibletestuser5 | grep --silent "uid=2226(ansibletestuser5) gid=4000(ansibletestgroup) groups=4000(ansibletestgroup),2(bin),100(users)" + grep --silent "^ansibletestgroup:" /etc/group + grep --silent "^ansibletestgroup1:" /etc/group + ls -lgd /home/ansibletestuser | awk '{exit $3!="ansibletestuser"}' + ls -lgd /home/otherdirectory | awk '{exit $3!="ansibletestuser2"}' + ls -lgd /home/ansibletestuser3 | awk '{exit $3!="ansibletestgroup1"}' + ls -lgd /home/otherdirectory1 | awk '{exit $3!="users"}' + ls -lgd /home/ansibletestuser5 | awk '{exit $3!="ansibletestgroup"}' + ls -lg /home/ansibletestuser/.profile | awk '{exit $3!="ansibletestuser"}' + ls -lg /home/otherdirectory/.profile | awk '{exit $3!="ansibletestuser2"}' + ls -lg /home/ansibletestuser3/.profile | awk '{exit $3!="ansibletestgroup1"}' + ls -lg /home/otherdirectory1/.profile | awk '{exit $3!="users"}' + ls -lgd /home/ansibletestuser5/.profile | awk '{exit $3!="ansibletestgroup"}' diff --git a/.gitignore b/.gitignore index 5ecf0da..0e4ef61 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.idea -*.DS_Store \ No newline at end of file +*.DS_Store +*.retry \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index fb6a4e4..0000000 --- a/.travis.yml +++ /dev/null @@ -1,47 +0,0 @@ ---- -sudo: required -language: python -python: "2.7" - -install: - - pip install ansible==2.7.8 - - # Add ansible.cfg to pick up roles path. - - "{ echo '[defaults]'; echo 'roles_path = ../'; } >> ansible.cfg" - -script: - # Syntax Check - - ansible-playbook -i localhost, tests/test.yml --syntax-check - - # Run tests.yml - - ansible-playbook -i localhost, --connection=local --sudo tests/test.yml - - # Run the role/playbook again, checking to make sure it's idempotent. - - > - ansible-playbook -i localhost, --connection=local --sudo tests/test.yml - | grep -q 'changed=0.*failed=0' - && (echo 'Idempotence test: pass' && exit 0) - || (echo 'Idempotence test: fail' && exit 1) - - # Lets check on the state of the users. I would invoke severspec myself however - # its a big thing to bring in on a small pull. - - id ansibletestuser | grep --silent "uid=2222(ansibletestuser) gid=2222(ansibletestuser) groups=2222(ansibletestuser),2(bin),100(users)" - - id ansibletestuser2 | grep --silent "uid=2223(ansibletestuser2) gid=2223(ansibletestuser2) groups=2223(ansibletestuser2),2(bin),100(users)" - - id ansibletestuser3 | grep --silent "uid=2224(ansibletestuser3) gid=4001(ansibletestgroup1) groups=4001(ansibletestgroup1),2(bin),100(users)" - - id ansibletestuser4 | grep --silent "uid=2225(ansibletestuser4) gid=100(users) groups=100(users),2(bin)" - - id ansibletestuser5 | grep --silent "uid=2226(ansibletestuser5) gid=4000(ansibletestgroup) groups=4000(ansibletestgroup),2(bin),100(users)" - - grep --silent "^ansibletestgroup:" /etc/group - - grep --silent "^ansibletestgroup1:" /etc/group - - ls -lgd /home/ansibletestuser | awk '{exit $3!="ansibletestuser"}' - - ls -lgd /home/otherdirectory | awk '{exit $3!="ansibletestuser2"}' - - ls -lgd /home/ansibletestuser3 | awk '{exit $3!="ansibletestgroup1"}' - - ls -lgd /home/otherdirectory1 | awk '{exit $3!="users"}' - - ls -lgd /home/ansibletestuser5 | awk '{exit $3!="ansibletestgroup"}' - - ls -lg /home/ansibletestuser/.profile | awk '{exit $3!="ansibletestuser"}' - - ls -lg /home/otherdirectory/.profile | awk '{exit $3!="ansibletestuser2"}' - - ls -lg /home/ansibletestuser3/.profile | awk '{exit $3!="ansibletestgroup1"}' - - ls -lg /home/otherdirectory1/.profile | awk '{exit $3!="users"}' - - ls -lgd /home/ansibletestuser5/.profile | awk '{exit $3!="ansibletestgroup"}' - -notifications: - webhooks: https://galaxy.ansible.com/api/v1/notifications/ diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..399a074 --- /dev/null +++ b/.yamllint @@ -0,0 +1,11 @@ +--- +extends: default + +rules: + line-length: + max: 120 + level: warning + +ignore: | + .github/workflows/ci.yml + tests/test.yml diff --git a/LICENSE b/LICENSE index 4b6926b..6204d20 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) unxnn +Copyright (c) 2020 Andrey Pronin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -17,4 +17,5 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE \ No newline at end of file +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 4c7bfb3..7063619 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,21 @@ # Ansible Role: users -[![Build Status](https://travis-ci.org/unxnn/ansible-role-users.svg?branch=master)](https://travis-ci.org/unxnn/ansible-role-users) +[![CI](https://github.com/unxnn/ansible-role-users/workflows/CI/badge.svg?event=push)](https://github.com/unxnn/ansible-role-users/actions?query=workflow%3ACI) -Role to manage users on a system. +Role to manage users on a UNIX-based system. ## Role configuration * `users_create_per_user_group` (default: true) - when creating users, also create a group with the same username and make that the user's primary group. -* `users_group` (default: users) - if users_create_per_user_group is _not_ set, +* `users_group` (default: users) - if users_create_per_user_group is _not_ set, then this is the primary group for all created users. -* `users_default_shell` (default: /bin/bash) - the default shell if none is - specified for the user. * `users_create_homedirs` (default: true) - create home directories for new users. Set this to false if you manage home directories separately. +* `users_default_shell` (default: /bin/bash) - the default shell if none is + specified for the user. +* `authorized_keys_file` (default: .ssh/authorized_keys) - Set this if the ssh server is configured to use a non standard authorized keys file. ## Creating users @@ -30,12 +31,12 @@ The following attributes are required for each user: * `uid` - The numeric user id for the user (optional). This is required for uid consistency across systems. * `gid` - The numeric group id for the group (optional). Otherwise, the - `uid` will be used. +`uid` will be used. * `password` - If a hash is provided then that will be used, but otherwise the account will be locked. * `update_password` - This can be either 'always' or 'on_create' - - `'always'` will update passwords if they differ. (default) - - `'on_create'` will only set the password for newly created users. + + `'always'` will update passwords if they differ. (default) + + `'on_create'` will only set the password for newly created users. * `group` - Optional primary group override. * `groups` - A list of supplementary groups for the user. * `append` - If yes, will only add groups, not set them to just the list in groups (optional). @@ -49,11 +50,13 @@ In addition, the following items are optional for each user: * `shell` - The user's shell. This defaults to /bin/bash. The default is configurable using the users_default_shell variable if you want to give all users the same shell, but it is different than /bin/bash. +* `is_system_user` - Set to `True` to create system user. Example: --- users: + - username: foo name: Foo Bar groups: ['admin','systemd-journal'] @@ -62,9 +65,11 @@ Example: profile: | alias ll='ls -ahl' ssh_key: + - "ssh-rsa AAAAA.... foo@server" - "ssh-rsa AAAAB.... foo2@server" groups_to_create: + - name: developers gid: 20000 @@ -72,13 +77,13 @@ Generating a password hash: # On Debian/Ubuntu (via the package "whois") mkpasswd --method=SHA-512 --rounds=4096 - + # OpenSSL (note: this will only make md5crypt. While better than plantext it should not be considered fully secure) openssl passwd -1 - + # Python (change password and salt values) python -c "import crypt, getpass, pwd; print crypt.crypt('password', '\$6\$SALT\$')" - + # Perl (change password and salt values) perl -e 'print crypt("password","\$6\$SALT\$") . "\n"' @@ -86,7 +91,7 @@ Generating a password hash: The `users_deleted` variable contains a list of users who should no longer be in the system, and these will be removed on the next ansible run. The format -is the same as for users to add, but the only required field is `username`. +is the same as for users to add, but the only required field is `username` . However, it is recommended that you also keep the `uid` field for reference so that numeric user ids are not accidentally reused. @@ -94,6 +99,7 @@ You can optionally choose to remove the user's home directory and mail spool wit the `remove` parameter, and force removal of files with the `force` parameter. users_deleted: + - username: bar uid: 1003 remove: yes diff --git a/defaults/main.yml b/defaults/main.yml index 76d00c9..7bf3737 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -1,11 +1,14 @@ --- # Create a group for every user and make that their primary group users_create_per_user_group: true + # If we're not creating a per-user group, then this is the group all users # belong to users_group: users + # The default shell for a user if none is specified users_default_shell: /bin/bash + # Create home dirs for new users? Set this to false if you manage home # directories in some other way. users_create_homedirs: true @@ -20,3 +23,5 @@ users_deleted: [] # - name: developers # gid: 20000 groups_to_create: [] + +authorized_keys_file: ".ssh/authorized_keys" diff --git a/meta/main.yml b/meta/main.yml index fe844fc..df6e944 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -5,7 +5,7 @@ galaxy_info: description: User management role company: none license: MIT - min_ansible_version: 1.3 + min_ansible_version: 2.5 platforms: - name: EL versions: diff --git a/tasks/main.yml b/tasks/main.yml index 8c67046..ff97271 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,21 +1,30 @@ --- +- name: Check visudo exists + stat: + path: /usr/sbin/visudo + register: visudo + - name: Allow admin group sudo without password lineinfile: path: /etc/sudoers state: present - regexp: '^%admin ALL=' - line: '%admin ALL=(ALL) NOPASSWD: ALL' - validate: '/usr/sbin/visudo -cf %s' + regexp: "^%admin ALL=" + line: "%admin ALL=(ALL) NOPASSWD: ALL" + validate: "/usr/sbin/visudo -cf %s" + when: visudo.stat.exists tags: ["users", "groups", "configuration"] - name: Creating groups - group: name="{{ item.name }}" gid="{{ item.gid | default(omit) }}" + group: + name: "{{ item.name }}" + gid: "{{ item.gid | default(omit) }}" with_items: "{{ groups_to_create }}" tags: ["users", "groups", "configuration"] - name: Per-user group creation - group: name="{{ item.username }}" - gid="{{ item.gid | default(item.uid) | default(omit) }}" + group: + name: "{{ item.username }}" + gid: "{{ item.gid | default(item.uid) | default(omit) }}" with_items: "{{ users }}" when: "'group' not in item and users_create_per_user_group" tags: ["users", "configuration"] @@ -35,6 +44,7 @@ createhome: "{{ 'yes' if users_create_homedirs else 'no' }}" generate_ssh_key: "{{ item.generate_ssh_key | default(omit) }}" update_password: "{{ item.update_password | default(omit) }}" + system: "{{ item.is_system_user | default(omit) | bool or omit }}" with_items: "{{ users }}" tags: ["users", "configuration"] @@ -42,11 +52,11 @@ authorized_key: user: "{{ item.0.username }}" key: "{{ item.1 }}" - path: "{{ item.0.home | default('/home/' + item.0.username) }}/.ssh/authorized_keys" + path: "{{ item.0.home | default('/home/' + item.0.username) }}/{{ authorized_keys_file }}" with_subelements: - "{{ users }}" - ssh_key - - skip_missing: yes + - skip_missing: true tags: ["users", "configuration"] - name: Setup user profiles @@ -70,7 +80,9 @@ tags: ["users", "configuration"] - name: Deleted per-user group removal - group: name="{{ item.username }}" state=absent + group: + name: "{{ item.username }}" + state: absent with_items: "{{ users_deleted }}" when: users_create_per_user_group tags: ["users", "configuration"] diff --git a/tests/test.yml b/tests/test.yml index 8080173..aee18ea 100644 --- a/tests/test.yml +++ b/tests/test.yml @@ -99,4 +99,14 @@ ssh_key: - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDUMN9/Hqi2Jgcbvx8mv4XuXLw39FUkm7CJiLlxeQrE+ridF4RErhbT5nR+F3rGTKsiBkrts3UDzvqmk2YvRoPFlyHSHGD8d1Tgncwcj9FNxGqZadlptJPQbyTYyHrDzmnhk3zU/wwJBip7frVctl1D98dtR9/M+X1zRgbT4Gr0o2iSO5cRykRrKTz0+/rQ4Z7XQ+PZmQhO4e5DeSzEzn5NDATAdDNvwfeuNYUOY2MOGIzdlpApD8E7baxw/LOkCPJtAp3Sc8KrMhgF1L7A1g2ZI7+7jq1GXkb7oBYJEczuQsaW9Mf40Y3piAutPWDp/cAcWqBWVjmA101JooJqDdE3 noone@.example.com" roles: - - ansible-role-users \ No newline at end of file + - ansible-role-users + +- hosts: localhost + remote_user: root + vars: + users: + - name: Ansible Test User6 + username: ansibletestuser6 + is_system_user: True + roles: + - ansible-role-users