diff --git a/README.rst b/README.rst index 543a712..39f9cf7 100644 --- a/README.rst +++ b/README.rst @@ -45,9 +45,25 @@ True' in pillar per user. Defaults to False. Ensures the vimrc file exists in the users home directory. Sets 'manage_vimrc: True' in pillar per user. Defaults to False. -This depends on the vim-formula to be installed. +This depends on the vim-formula being available and pillar `users:use_vim_formula: True`. ``users.user_files`` --------------- Permits the abitrary management of files. See pillar.example for configuration details. + +Overriding default values +========================= + +In order to separate actual user account definitions from configuration the pillar ``users-formula`` was introduced: + +.. code-block:: yaml + + users: + myuser: + # stuff + + users-formula: + lookup: + root_group: toor + shell: '/bin/zsh' diff --git a/pillar.example b/pillar.example index c45d5ac..f58f05e 100644 --- a/pillar.example +++ b/pillar.example @@ -1,9 +1,22 @@ +users-formula: + use_vim_formula: True + lookup: # override the defauls in map.jinja + root_group: root + +# group initialization +groups: + foo: + state: present + gid: 500 + system: False + users: ## Minimal required pillar values auser: fullname: A User ## Full list of pillar values + allow_gid_change: False buser: fullname: B User password: $6$w............. @@ -22,9 +35,13 @@ users: workphone: "(555) 555-5555" homephone: "(555) 555-5551" manage_vimrc: False + allow_gid_change: True manage_bashrc: False manage_profile: False expire: 16426 + # Disables user management except sudo rules. + # Useful for setting sudo rules for system accounts created by package instalation + sudoonly: False sudouser: True # sudo_rules doesn't need the username as a prefix for the rule # this is added automatically by the formula. @@ -55,6 +72,13 @@ users: ssh_keys: privkey: PRIVATEKEY pubkey: PUBLICKEY + # or you can provide path to key on Salt fileserver + privkey: salt://path_to_PRIVATEKEY + pubkey: salt://path_to_PUBLICKEY + # you can provide multiple keys, the keyname is taken as filename + # make sure your public keys suffix is .pub + foobar: PRIVATEKEY + foobar.pub: PUBLICKEY # ... or you can pull them from a different pillar, # for example one called "ssh_keys": ssh_keys_pillar: @@ -75,10 +99,18 @@ users: # than inline in pillar, this works. ssh_auth_sources: - salt://keys/buser.id_rsa.pub + ssh_auth_sources.absent: + - salt://keys/deleteduser.id_rsa.pub # PUBLICKEY_FILE_TO_BE_REMOVED # Manage the ~/.ssh/config file ssh_known_hosts: importanthost: + port: 22 fingerprint: 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48 + key: PUBLICKEY + enc: ssh-rsa + hash_known_hosts: True + timeout: 5 + fingerprint_hash_type: sha256 ssh_known_hosts.absent: - notimportanthost ssh_config: @@ -98,7 +130,11 @@ users: gitconfig: user.name: B User user.email: buser@example.com - url."https://".insteadOf: "git://" + "url.https://.insteadOf": "git://" + + gitconfig.absent: + - push.default + - color\..+ google_2fa: True google_auth: @@ -113,6 +149,8 @@ users: 33333333 44444444 55555555 + # unique: True allows user to have non unique uid + unique: False uid: 1001 user_files: @@ -121,6 +159,13 @@ users: # should be a salt fileserver path either with or without 'salt://' # if not present, it defaults to 'salt://users/files/user/ source: users/files/default + template: jinja + # You can specify octal mode for files and symlinks that will be copied. Since version 2016.11.0 + # it's possible to use 'keep' for file_mode, to preserve file original mode, thus you can save + # execution bit for example. + file_mode: keep + sym_mode: 640 + exclude_pat: "*.gitignore" ## Absent user cuser: @@ -133,3 +178,17 @@ users: absent_users: - donald - bad_guy + +groups: + badguys: + absent: True + niceguys: + gid: 4242 + system: False + addusers: root + delusers: toor + ssl-cert: + system: True + members: + - www-data + - openldap diff --git a/users/bashrc.sls b/users/bashrc.sls index 4d4ca4d..a4fa3f9 100644 --- a/users/bashrc.sls +++ b/users/bashrc.sls @@ -21,7 +21,8 @@ users_{{ name }}_user_bashrc: - user: {{ name }} - group: {{ user_group }} - mode: 644 - - source: + - template: jinja + - source: - salt://users/files/bashrc/{{ name }}/bashrc - salt://users/files/bashrc/bashrc {% endif %} diff --git a/users/defaults.yaml b/users/defaults.yaml new file mode 100644 index 0000000..2b69c19 --- /dev/null +++ b/users/defaults.yaml @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# vim: ft=yaml + +users-formula: + use_vim_formula: False + +users: + allow_gid_change: True + createhome: True + diff --git a/users/init.sls b/users/init.sls index b873e04..2cf2af0 100644 --- a/users/init.sls +++ b/users/init.sls @@ -5,11 +5,31 @@ {% set used_user_files = [] %} {% set used_polkit = [] %} +{% for group, setting in salt['pillar.get']('groups', {}).items() %} +{% if setting.absent is defined and setting.absent or setting.get('state', "present") == 'absent' %} +users_group_absent_{{ group }}: + group.absent: + - name: {{ group }} +{% else %} +users_group_present_{{ group }}: + group.present: + - name: {{ group }} + - gid: {{ setting.get('gid', "null") }} + - system: {{ setting.get('system',"False") }} + - members: {{ setting.get('members')|json }} + - addusers: {{ setting.get('addusers')|json }} + - delusers: {{ setting.get('delusers')|json }} +{% endif %} +{% endfor %} + {%- for name, user in pillar.get('users', {}).items() if user.absent is not defined or not user.absent %} {%- if user == None -%} {%- set user = {} -%} {%- endif -%} +{%- if 'sudoonly' in user and user['sudoonly'] %} +{%- set _dummy=user.update({'sudouser': True}) %} +{%- endif %} {%- if 'sudouser' in user and user['sudouser'] %} {%- do used_sudo.append(1) %} {%- endif %} @@ -47,6 +67,7 @@ include: {%- endif -%} {%- set current = salt.user.info(name) -%} {%- set home = user.get('home', current.get('home', "/home/%s" % name)) -%} +{%- set createhome = user.get('createhome') -%} {%- if 'prime_group' in user and 'name' in user['prime_group'] %} {%- set user_group = user.prime_group.name -%} @@ -54,6 +75,7 @@ include: {%- set user_group = name -%} {%- endif %} +{%- if not ( 'sudoonly' in user and user['sudoonly'] ) %} {% for group in user.get('groups', []) %} users_{{ name }}_{{ group }}_group: group.present: @@ -63,13 +85,24 @@ users_{{ name }}_{{ group }}_group: {% endif %} {% endfor %} +{# in case home subfolder doesn't exist, create it before the user exists #} +{% if createhome -%} +users_{{ name }}_user_prereq: + file.directory: + - name: {{ salt['file.dirname'](home) }} + - makedirs: True + - prereq: + - user: users_{{ name }}_user +{%- endif %} + users_{{ name }}_user: - {% if user.get('createhome', True) %} + {% if createhome -%} file.directory: - name: {{ home }} - user: {{ user.get('homedir_owner', name) }} - group: {{ user.get('homedir_group', user_group) }} - mode: {{ user.get('user_dir_mode', '0750') }} + - makedirs: True - require: - user: users_{{ name }}_user - group: {{ user_group }} @@ -123,11 +156,15 @@ users_{{ name }}_user: - workphone: {{ user['workphone'] }} {% endif %} {% if 'homephone' in user %} - - homephone: {{ user['workphone'] }} + - homephone: {{ user['homephone'] }} {% endif %} - {% if not user.get('createhome', True) %} - - createhome: False + - createhome: {{ createhome }} + {% if not user.get('unique', True) %} + - unique: False {% endif %} + {%- if grains['saltversioninfo'] >= [2018, 3, 1] %} + - allow_gid_change: {{ users.allow_gid_change if 'allow_gid_change' not in user else user['allow_gid_change'] }} + {%- endif %} {% if 'expire' in user -%} {% if grains['kernel'].endswith('BSD') and user['expire'] < 157766400 %} @@ -141,6 +178,18 @@ users_{{ name }}_user: - expire: {{ user['expire'] }} {% endif %} {% endif -%} + {% if 'mindays' in user %} + - mindays: {{ user.get('mindays', None) }} + {% endif %} + {% if 'maxdays' in user %} + - maxdays: {{ user.get('maxdays', None) }} + {% endif %} + {% if 'inactdays' in user %} + - inactdays: {{ user.get('inactdays', None) }} + {% endif %} + {% if 'warndays' in user %} + - warndays: {{ user.get('warndays', None) }} + {% endif %} - remove_groups: {{ user.get('remove_groups', 'False') }} - groups: - {{ user_group }} @@ -173,6 +222,7 @@ user_keydir_{{ name }}: - group: {{ user_group }} - makedirs: True - mode: 700 + - dir_mode: 700 - require: - user: {{ name }} - group: {{ user_group }} @@ -182,35 +232,40 @@ user_keydir_{{ name }}: {% endif %} {% if 'ssh_keys' in user %} - {% set key_type = 'id_' + user.get('ssh_key_type', 'rsa') %} -users_user_{{ name }}_private_key: + {% for _key in user.ssh_keys.keys() %} + {% if _key == 'privkey' %} + {% set key_name = 'id_' + user.get('ssh_key_type', 'rsa') %} + {% elif _key == 'pubkey' %} + {% set key_name = 'id_' + user.get('ssh_key_type', 'rsa') + '.pub' %} + {% else %} + {% set key_name = _key %} + {% endif %} +users_{{ name }}_{{ key_name }}_key: file.managed: - - name: {{ home }}/.ssh/{{ key_type }} - - user: {{ name }} - - group: {{ user_group }} - - mode: 600 - - show_diff: False - - contents_pillar: users:{{ name }}:ssh_keys:privkey - - require: - - user: users_{{ name }}_user - {% for group in user.get('groups', []) %} - - group: users_{{ name }}_{{ group }}_group - {% endfor %} -users_user_{{ name }}_public_key: - file.managed: - - name: {{ home }}/.ssh/{{ key_type }}.pub + - name: {{ home }}/.ssh/{{ key_name }} - user: {{ name }} - group: {{ user_group }} + {% if key_name.endswith(".pub") %} - mode: 644 + {% else %} + - mode: 600 + {% endif %} - show_diff: False - - contents_pillar: users:{{ name }}:ssh_keys:pubkey + {%- set key_value = salt['pillar.get']('users:'+name+':ssh_keys:'+_key) %} + {%- if 'salt://' in key_value[:7] %} + - source: {{ key_value }} + {%- else %} + - contents_pillar: users:{{ name }}:ssh_keys:{{ _key }} + {%- endif %} - require: - user: users_{{ name }}_user {% for group in user.get('groups', []) %} - group: users_{{ name }}_{{ group }}_group {% endfor %} + {% endfor %} {% endif %} + {% if 'ssh_auth_file' in user or 'ssh_auth_pillar' in user %} users_authorized_keys_{{ name }}: file.managed: @@ -224,8 +279,9 @@ users_authorized_keys_{{ name }}: {{ auth }} {% endfor -%} {% else %} + - contents: | {%- for key_name, pillar_name in user['ssh_auth_pillar'].items() %} - - contents_pillar: {{ pillar_name }}:{{ key_name }}:pubkey + {{ salt['pillar.get'](pillar_name + ':' + key_name + ':pubkey', '') }} {%- endfor %} {% endif %} {% endif %} @@ -280,7 +336,23 @@ users_ssh_auth_source_{{ name }}_{{ loop.index0 }}: - user: {{ name }} - source: {{ pubkey_file }} - require: + {% if createhome -%} - file: users_{{ name }}_user + {% endif -%} + - user: users_{{ name }}_user +{% endfor %} +{% endif %} + +{% if 'ssh_auth_sources.absent' in user %} +{% for pubkey_file in user['ssh_auth_sources.absent'] %} +users_ssh_auth_source_delete_{{ name }}_{{ loop.index0 }}: + ssh_auth.absent: + - user: {{ name }} + - source: {{ pubkey_file }} + - require: + {% if createhome -%} + - file: users_{{ name }}_user + {% endif -%} - user: users_{{ name }}_user {% endfor %} {% endif %} @@ -292,7 +364,9 @@ users_ssh_auth_delete_{{ name }}_{{ loop.index0 }}: - user: {{ name }} - name: {{ auth }} - require: + {% if createhome -%} - file: users_{{ name }}_user + {% endif -%} - user: users_{{ name }}_user {% endfor %} {% endif %} @@ -334,8 +408,14 @@ users_ssh_known_hosts_{{ name }}_{{ loop.index0 }}: {% if 'enc' in host %} - enc: {{ host['enc'] }} {% endif -%} - {% if 'hash_hostname' in host %} - - hash_hostname: {{ host['hash_hostname'] }} + {% if 'hash_known_hosts' in host %} + - hash_known_hosts: {{ host['hash_known_hosts'] }} + {% endif -%} + {% if 'timeout' in host %} + - timeout: {{ host['timeout'] }} + {% endif -%} + {% if 'fingerprint_hash_type' in host %} + - fingerprint_hash_type: {{ host['fingerprint_hash_type'] }} {% endif -%} {% endfor %} {% endif %} @@ -348,13 +428,15 @@ users_ssh_known_hosts_delete_{{ name }}_{{ loop.index0 }}: - name: {{ host }} {% endfor %} {% endif %} +{% endif %} +{% set sudoers_d_filename = name|replace('.','_') %} {% if 'sudouser' in user and user['sudouser'] %} users_sudoer-{{ name }}: file.managed: - replace: False - - name: {{ users.sudoers_dir }}/{{ name }} + - name: {{ users.sudoers_dir }}/{{ sudoers_d_filename }} - user: root - group: {{ users.root_group }} - mode: '0440' @@ -393,7 +475,7 @@ users_sudoer-{{ name }}: users_{{ users.sudoers_dir }}/{{ name }}: file.managed: - replace: True - - name: {{ users.sudoers_dir }}/{{ name }} + - name: {{ users.sudoers_dir }}/{{ sudoers_d_filename }} - contents: | {%- if 'sudo_defaults' in user %} {%- for entry in user['sudo_defaults'] %} @@ -414,14 +496,14 @@ users_{{ users.sudoers_dir }}/{{ name }}: - file: users_sudoer-defaults - file: users_sudoer-{{ name }} cmd.wait: - - name: visudo -cf {{ users.sudoers_dir }}/{{ name }} || ( rm -rvf {{ users.sudoers_dir }}/{{ name }}; exit 1 ) + - name: visudo -cf {{ users.sudoers_dir }}/{{ sudoers_d_filename }} || ( rm -rvf {{ users.sudoers_dir }}/{{ sudoers_d_filename }}; exit 1 ) - watch: - - file: {{ users.sudoers_dir }}/{{ name }} + - file: {{ users.sudoers_dir }}/{{ sudoers_d_filename }} {% endif %} {% else %} -users_{{ users.sudoers_dir }}/{{ name }}: +users_{{ users.sudoers_dir }}/{{ sudoers_d_filename }}: file.absent: - - name: {{ users.sudoers_dir }}/{{ name }} + - name: {{ users.sudoers_dir }}/{{ sudoers_d_filename }} {% endif %} {%- if 'google_auth' in user %} @@ -439,10 +521,6 @@ users_googleauth-{{ svc }}-{{ name }}: {%- endfor %} {%- endif %} -# -# if not salt['cmd.has_exec']('git') -# fails even if git is installed -# # this doesn't work (Salt bug), therefore need to run state.apply twice #include: # - users @@ -452,10 +530,12 @@ users_googleauth-{{ svc }}-{{ name }}: # - require_in: # - sls: users # +{% if salt['cmd.has_exec']('git') %} + {% if 'gitconfig' in user %} {% for key, value in user['gitconfig'].items() %} users_{{ name }}_user_gitconfig_{{ loop.index0 }}: - {% if grains['saltversioninfo'] >= (2015, 8, 0, 0) %} + {% if grains['saltversioninfo'] >= [2015, 8, 0, 0] %} git.config_set: {% else %} git.config: @@ -463,7 +543,7 @@ users_{{ name }}_user_gitconfig_{{ loop.index0 }}: - name: {{ key }} - value: "{{ value }}" - user: {{ name }} - {% if grains['saltversioninfo'] >= (2015, 8, 0, 0) %} + {% if grains['saltversioninfo'] >= [2015, 8, 0, 0] %} - global: True {% else %} - is_global: True @@ -471,6 +551,19 @@ users_{{ name }}_user_gitconfig_{{ loop.index0 }}: {% endfor %} {% endif %} +{% if 'gitconfig.absent' in user and grains['saltversioninfo'] >= [2015, 8, 0, 0] %} +{% for key in user.get('gitconfig.absent') %} +users_{{ name }}_user_gitconfig_absent_{{ key }}: + git.config_unset: + - name: '{{ key }}' + - user: {{ name }} + - global: True + - all: True +{% endfor %} +{% endif %} + +{% endif %} + {% endfor %} diff --git a/users/map.jinja b/users/map.jinja index 1237066..67b7df8 100644 --- a/users/map.jinja +++ b/users/map.jinja @@ -1,5 +1,22 @@ # vim: sts=2 ts=2 sw=2 et ai -{% set users = salt['grains.filter_by']({ + +{# import defaults.yaml as defaults #} +{% import_yaml 'users/defaults.yaml' as defaults %} + +{# set Os-family specific settings #} +{% set users = salt['grains.filter_by']( + defaults, + merge=salt['grains.filter_by']({ + 'MacOS': { + 'sudoers_dir': '/etc/sudoers.d', + 'sudoers_file': '/etc/sudoers', + 'googleauth_dir': '/etc/google_authenticator.d', + 'shell': '/bin/bash', + 'visudo_shell': '/bin/bash', + 'bash_package': 'bash', + 'sudo_package': 'sudo', + 'googleauth_package': 'google-authenticator-libpam', + }, 'Debian': { 'sudoers_dir': '/etc/sudoers.d', 'sudoers_file': '/etc/sudoers', @@ -23,7 +40,7 @@ 'bash_package': 'app-shells/bash', 'sudo_package': 'app-admin/sudo', 'googleauth_package': 'libpam-google-authenticator', - }, + }, 'FreeBSD': { 'sudoers_dir': '/usr/local/etc/sudoers.d', 'sudoers_file': '/usr/local/etc/sudoers', @@ -34,7 +51,29 @@ 'bash_package': 'bash', 'sudo_package': 'sudo', 'googleauth_package': 'pam_google_authenticator', - }, + }, + 'OpenBSD': { + 'sudoers_dir': '/etc/sudoers.d', + 'sudoers_file': '/etc/sudoers', + 'googleauth_dir': '/etc/google_authenticator.d', + 'root_group': 'wheel', + 'shell': '/bin/csh', + 'visudo_shell': '/usr/local/bin/bash', + 'bash_package': 'bash', + 'sudo_package': 'sudo', + 'googleauth_package': 'pam_google_authenticator', + }, + 'Solaris': { + 'sudoers_dir': '/opt/local/etc/sudoers.d', + 'sudoers_file': '/opt/local/etc/sudoers', + 'googleauth_dir': '/opt/local/etc/google_authenticator.d', + 'root_group': 'root', + 'shell': '/bin/bash', + 'visudo_shell': '/bin/bash', + 'bash_package': 'bash', + 'sudo_package': 'sudo', + 'googleauth_package': 'libpam-google-authenticator', + }, 'default': { 'sudoers_dir': '/etc/sudoers.d', 'sudoers_file': '/etc/sudoers', @@ -47,5 +86,12 @@ 'googleauth_package': 'libpam-google-authenticator', 'polkit_dir': '/etc/polkit-1/localauthority.conf.d', 'polkit_defaults': 'unix-group:sudo;' - }, -}, merge=salt['pillar.get']('users:lookup')) %} + }, + }, merge=salt['pillar.get']('users-formula:lookup')), + base='users', +) %} + +{% if grains.os == 'MacOS' %} + {% set group = salt['cmd.run']("stat -f '%Sg' /dev/console") %} + {% do users.update({'root_group': group,}) %} +{% endif %} \ No newline at end of file diff --git a/users/profile.sls b/users/profile.sls index 55ac8e2..3c30d8c 100644 --- a/users/profile.sls +++ b/users/profile.sls @@ -21,6 +21,7 @@ users_{{ name }}_user_profile: - user: {{ name }} - group: {{ user_group }} - mode: 644 + - template: jinja - source: - salt://users/files/profile/{{ name }}/profile - salt://users/files/profile/profile diff --git a/users/sudo.sls b/users/sudo.sls index 092d004..e87acbb 100644 --- a/users/sudo.sls +++ b/users/sudo.sls @@ -11,6 +11,7 @@ users_sudo-package: - name: {{ users.sudo_package }} - require: - file: {{ users.sudoers_dir }} + - unless: test "`uname`" = "Darwin" users_{{ users.sudoers_dir }}: file.directory: diff --git a/users/user_files.sls b/users/user_files.sls index 461628b..c66ca40 100644 --- a/users/user_files.sls +++ b/users/user_files.sls @@ -9,6 +9,10 @@ include: {%- set user_files = salt['pillar.get'](('users:' ~ username ~ ':user_files'), {'enabled': False}) -%} {%- set user_group = salt['pillar.get'](('users:' ~ username ~ ':prime_group:name'), username) -%} {%- set user_home = salt['pillar.get'](('users:' ~ username ~ ':home'), current.get('home', '/home/' ~ username )) -%} +{%- set user_files_template = salt['pillar.get'](('users:' ~ username ~ ':user_files:template'), None) -%} +{%- set user_files_file_mode = salt['pillar.get'](('users:' ~ username ~ ':user_files:file_mode'), False) -%} +{%- set user_files_sym_mode = salt['pillar.get'](('users:' ~ username ~ ':user_files:sym_mode'), False) -%} +{%- set user_files_exclude_pat = salt['pillar.get'](('users:' ~ username ~ ':user_files:exclude_pat'), False) -%} {%- if user_files.enabled -%} {%- if user_files.source is defined -%} @@ -34,7 +38,19 @@ users_userfiles_{{ username }}_recursive: - source: {{ file_source }} - user: {{ username }} - group: {{ user_group }} + {% if user_files_template -%} + - template: {{ user_files_template }} + {% endif -%} - clean: False + {% if user_files_file_mode -%} + - file_mode: {{ user_files_file_mode }} + {% endif -%} + {% if user_files_sym_mode -%} + - sym_mode: {{ user_files_sym_mode }} + {% endif -%} + {% if user_files_exclude_pat -%} + - exclude_pat: "{{ user_files_exclude_pat }}" + {% endif -%} - include_empty: True - keep_symlinks: True - require: diff --git a/users/vimrc.sls b/users/vimrc.sls index 5404738..d8a378d 100644 --- a/users/vimrc.sls +++ b/users/vimrc.sls @@ -1,4 +1,7 @@ {% from "users/map.jinja" import users with context %} + +{% if users.use_vim_formula %} + include: - users - vim @@ -22,8 +25,11 @@ users_{{ name }}_user_vimrc: - user: {{ name }} - group: {{ user_group }} - mode: 644 - - source: + - template: jinja + - source: - salt://users/files/vimrc/{{ name }}/vimrc - salt://users/files/vimrc/vimrc {% endif %} {% endfor %} + +{% endif %}