diff --git a/LICENSE b/LICENSE index 1e89a0b..8a9dff9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ - Copyright (c) 2014 Salt Stack Formulas + Copyright (c) 2014-2015 Salt Stack Formulas Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11,4 +11,3 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - diff --git a/README.rst b/README.rst index 2518089..4d3279a 100644 --- a/README.rst +++ b/README.rst @@ -27,3 +27,17 @@ and associated keys. Also configures sudo access, and absent users. Ensures the sudo group exists, the sudo package is installed and the sudo file is configured. + +``users.bashrc`` +---------------- + +Ensures the bashrc file exists in the users home directory. Set manage_bashrc: +True in pillar per user. Defaults to False + +``users.vimrc`` +--------------- + +Ensures the vimrc file exists in the users home directory. Set manage_vimrc: +True in pillar per user. Defaults to False +This depends on the vim-formula to be installed + diff --git a/pillar.example b/pillar.example index 93eeb74..500604b 100644 --- a/pillar.example +++ b/pillar.example @@ -9,11 +9,22 @@ users: password: $6$w............. home: /custom/buser createhome: True + manage_vimrc: False + manage_bashrc: False expire: 16426 sudouser: True + # sudo_rules doesn't need the username as a prefix for the rule + # this is added automatically by the formula. + # ---------------------------------------------------------------------- + # In case your sudo_rules have a colon please have in mind to not leave + # spaces around it. For example: + # ALL=(ALL) NOPASSWD: ALL <--- THIS WILL NOT WORK (Besides syntax is ok) + # ALL=(ALL) NOPASSWD:ALL <--- THIS WILL WORK sudo_rules: - - 'ALL=(root) /usr/bin/find' - - 'ALL=(otheruser) /usr/bin/script.sh' + - ALL=(root) /usr/bin/find + - ALL=(otheruser) /usr/bin/script.sh + sudo_defaults: + - '!requiretty' shell: /bin/bash prime_group: name: primarygroup @@ -21,13 +32,40 @@ users: groups: - users ssh_key_type: rsa + # You can inline the private keys ... ssh_keys: privkey: PRIVATEKEY pubkey: PUBLICKEY + # ... or you can pull them from a different pillar, + # for example one called "ssh_keys": + ssh_keys_pillar: + id_rsa: "ssh_keys" + another_key_pair: "ssh_keys" ssh_auth: - PUBLICKEY ssh_auth.absent: - PUBLICKEY_TO_BE_REMOVED + # Generates an authorized_keys file for the user + # with the given keys + ssh_auth_file: + - PUBLICKEY + # If you prefer to keep public keys as files rather + # than inline in pillar, this works. + ssh_auth_sources: + - salt://keys/buser.id_rsa.pub + # Manage the ~/.ssh/config file + ssh_config: + all: + hostname: "*" + options: + - "StrictHostKeyChecking no" + - "UserKnownHostsFile=/dev/null" + importanthost: + hostname: "needcheck.example.com" + options: + - "StrictHostKeyChecking yes" + + google_2fa: True google_auth: ssh: | SOMEGAUTHHASHVAL diff --git a/users/bashrc.sls b/users/bashrc.sls new file mode 100644 index 0000000..fc268f4 --- /dev/null +++ b/users/bashrc.sls @@ -0,0 +1,27 @@ +{% from "users/map.jinja" import users with context %} +include: + - users + +{% for name, user in pillar.get('users', {}).items() if user.absent is not defined or not user.absent %} +{%- if user == None -%} +{%- set user = {} -%} +{%- endif -%} +{%- set home = user.get('home', "/home/%s" % name) -%} +{%- set manage = user.get('manage_bashrc', False) -%} +{%- if 'prime_group' in user and 'name' in user['prime_group'] %} +{%- set user_group = user.prime_group.name -%} +{%- else -%} +{%- set user_group = name -%} +{%- endif %} +{%- if manage -%} +users_{{ name }}_user_bashrc: + file.managed: + - name: {{ home }}/.bashrc + - user: {{ name }} + - group: {{ user_group }} + - mode: 644 + - source: + - salt://users/files/bashrc/{{ name }}/bashrc + - salt://users/files/bashrc/bashrc +{% endif %} +{% endfor %} diff --git a/users/files/bashrc/bashrc b/users/files/bashrc/bashrc new file mode 100644 index 0000000..d6ccfde --- /dev/null +++ b/users/files/bashrc/bashrc @@ -0,0 +1,9 @@ +# +# ~/.bashrc +# +# +# If not running interactively, don't do anything +[[ $- != *i* ]] && return + +alias ls='ls --color=auto' +PS1='[\u@\h \W]\$ ' diff --git a/users/files/vimrc/vimrc b/users/files/vimrc/vimrc new file mode 100644 index 0000000..fef9e87 --- /dev/null +++ b/users/files/vimrc/vimrc @@ -0,0 +1,160 @@ +" URL: http://vim.wikia.com/wiki/Example_vimrc +" Authors: http://vim.wikia.com/wiki/Vim_on_Freenode +" Description: A minimal, but feature rich, example .vimrc. If you are a +" newbie, basing your first .vimrc on this file is a good choice. +" If you're a more advanced user, building your own .vimrc based +" on this file is still a good idea. + +"------------------------------------------------------------ +" Features {{{1 +" +" These options and commands enable some very useful features in Vim, that +" no user should have to live without. + +" Set 'nocompatible' to ward off unexpected things that your distro might +" have made, as well as sanely reset options when re-sourcing .vimrc +set nocompatible + +" Attempt to determine the type of a file based on its name and possibly its +" contents. Use this to allow intelligent auto-indenting for each filetype, +" and for plugins that are filetype specific. +filetype indent plugin on + +" Enable syntax highlighting +syntax on + + +"------------------------------------------------------------ +" Must have options {{{1 +" +" These are highly recommended options. + +" Vim with default settings does not allow easy switching between multiple files +" in the same editor window. Users can use multiple split windows or multiple +" tab pages to edit multiple files, but it is still best to enable an option to +" allow easier switching between files. +" +" One such option is the 'hidden' option, which allows you to re-use the same +" window and switch from an unsaved buffer without saving it first. Also allows +" you to keep an undo history for multiple files when re-using the same window +" in this way. Note that using persistent undo also lets you undo in multiple +" files even in the same window, but is less efficient and is actually designed +" for keeping undo history after closing Vim entirely. Vim will complain if you +" try to quit without saving, and swap files will keep you safe if your computer +" crashes. +set hidden + +" Note that not everyone likes working this way (with the hidden option). +" Alternatives include using tabs or split windows instead of re-using the same +" window as mentioned above, and/or either of the following options: +" set confirm +" set autowriteall + +" Better command-line completion +set wildmenu + +" Show partial commands in the last line of the screen +set showcmd + +" Highlight searches (use to temporarily turn off highlighting; see the +" mapping of below) +set hlsearch + +" Modelines have historically been a source of security vulnerabilities. As +" such, it may be a good idea to disable them and use the securemodelines +" script, . +" set nomodeline + + +"------------------------------------------------------------ +" Usability options {{{1 +" +" These are options that users frequently set in their .vimrc. Some of them +" change Vim's behaviour in ways which deviate from the true Vi way, but +" which are considered to add usability. Which, if any, of these options to +" use is very much a personal preference, but they are harmless. + +" Use case insensitive search, except when using capital letters +set ignorecase +set smartcase + +" Allow backspacing over autoindent, line breaks and start of insert action +set backspace=indent,eol,start + +" When opening a new line and no filetype-specific indenting is enabled, keep +" the same indent as the line you're currently on. Useful for READMEs, etc. +set autoindent + +" Stop certain movements from always going to the first character of a line. +" While this behaviour deviates from that of Vi, it does what most users +" coming from other editors would expect. +set nostartofline + +" Display the cursor position on the last line of the screen or in the status +" line of a window +set ruler + +" Always display the status line, even if only one window is displayed +set laststatus=2 + +" Instead of failing a command because of unsaved changes, instead raise a +" dialogue asking if you wish to save changed files. +set confirm + +" Use visual bell instead of beeping when doing something wrong +set visualbell + +" And reset the terminal code for the visual bell. If visualbell is set, and +" this line is also included, vim will neither flash nor beep. If visualbell +" is unset, this does nothing. +set t_vb= + +" Enable use of the mouse for all modes +set mouse=a + +" Set the command window height to 2 lines, to avoid many cases of having to +" "press to continue" +set cmdheight=2 + +" Display line numbers on the left +set number + +" Quickly time out on keycodes, but never time out on mappings +set notimeout ttimeout ttimeoutlen=200 + +" Use to toggle between 'paste' and 'nopaste' +set pastetoggle= + + +"------------------------------------------------------------ +" Indentation options {{{1 +" +" Indentation settings according to personal preference. + +" Indentation settings for using 4 spaces instead of tabs. +" Do not change 'tabstop' from its default value of 8 with this setup. +set shiftwidth=4 +set softtabstop=4 +set expandtab + +" Indentation settings for using hard tabs for indent. Display tabs as +" four characters wide. +"set shiftwidth=4 +"set tabstop=4 + + +"------------------------------------------------------------ +" Mappings {{{1 +" +" Useful mappings + +" Map Y to act like D and C, i.e. to yank until EOL, rather than act as yy, +" which is the default +map Y y$ + +" Map (redraw screen) to also turn off search highlighting until the +" next search +nnoremap :nohl + + +"------------------------------------------------------------ diff --git a/users/googleauth.sls b/users/googleauth.sls index 55260f6..9e6a9ff 100644 --- a/users/googleauth.sls +++ b/users/googleauth.sls @@ -1,15 +1,31 @@ # vim: sts=2 ts=2 sw=2 et ai {% from "users/map.jinja" import users with context %} -googleauth-package: +users_googleauth-package: pkg.installed: - name: {{ users.googleauth_package }} - require: - file: {{ users.googleauth_dir }} -{{ users.googleauth_dir }}: - file: - - directory +users_{{ users.googleauth_dir }}: + file.directory: + - name: {{ users.googleauth_dir }} - user: root - group: {{ users.root_group }} - mode: 600 + +{% for name, user in pillar.get('users', {}).items() if user.absent is not defined or not user.absent %} +{%- if 'google_auth' in user %} +{%- for svc in user['google_auth'] %} +{%- if user.get('google_2fa', True) %} +users_googleauth-pam-{{ svc }}-{{ name }}: + file.replace: + - name: /etc/pam.d/{{ svc }} + - pattern: "^@include common-auth" + - repl: "auth [success=done new_authtok_reqd=done default=die] pam_google_authenticator.so user=root secret={{ users.googleauth_dir }}/${USER}_{{ svc }} echo_verification_code\n@include common-auth" + - unless: grep pam_google_authenticator.so /etc/pam.d/{{ svc }} + - backup: .bak +{%- endif %} +{%- endfor %} +{%- endif %} +{%- endfor %} diff --git a/users/init.sls b/users/init.sls index e18643d..21b125b 100644 --- a/users/init.sls +++ b/users/init.sls @@ -38,13 +38,13 @@ include: {%- endif %} {% for group in user.get('groups', []) %} -{{ name }}_{{ group }}_group: +users_{{ name }}_{{ group }}_group: group: - name: {{ group }} - present {% endfor %} -{{ name }}_user: +users_{{ name }}_user: {% if user.get('createhome', True) %} file.directory: - name: {{ home }} @@ -101,6 +101,7 @@ include: - group: {{ group }} {% endfor %} + {% if 'ssh_keys' in user or 'ssh_auth' in user or 'ssh_auth.absent' in user %} user_keydir_{{ name }}: file.directory: @@ -119,7 +120,7 @@ user_keydir_{{ name }}: {% if 'ssh_keys' in user %} {% set key_type = 'id_' + user.get('ssh_key_type', 'rsa') %} -user_{{ name }}_private_key: +users_user_{{ name }}_private_key: file.managed: - name: {{ user.get('home', '/home/{0}'.format(name)) }}/.ssh/{{ key_type }} - user: {{ name }} @@ -128,11 +129,11 @@ user_{{ name }}_private_key: - show_diff: False - contents_pillar: users:{{ name }}:ssh_keys:privkey - require: - - user: {{ name }}_user + - user: users_{{ name }}_user {% for group in user.get('groups', []) %} - - group: {{ name }}_{{ group }}_group + - group: users_{{ name }}_{{ group }}_group {% endfor %} -user_{{ name }}_public_key: +users_user_{{ name }}_public_key: file.managed: - name: {{ user.get('home', '/home/{0}'.format(name)) }}/.ssh/{{ key_type }}.pub - user: {{ name }} @@ -141,45 +142,106 @@ user_{{ name }}_public_key: - show_diff: False - contents_pillar: users:{{ name }}:ssh_keys:pubkey - require: - - user: {{ name }}_user + - user: users_{{ name }}_user {% for group in user.get('groups', []) %} - - group: {{ name }}_{{ group }}_group + - group: users_{{ name }}_{{ group }}_group {% endfor %} {% endif %} +{% if 'ssh_auth_file' in user %} +users_authorized_keys_{{ name }}: + file.managed: + - name: {{ home }}/.ssh/authorized_keys + - user: {{ name }} + - group: {{ name }} + - mode: 600 + - contents: | + {% for auth in user.ssh_auth_file -%} + {{ auth }} + {% endfor -%} +{% endif %} {% if 'ssh_auth' in user %} {% for auth in user['ssh_auth'] %} -ssh_auth_{{ name }}_{{ loop.index0 }}: +users_ssh_auth_{{ name }}_{{ loop.index0 }}: ssh_auth.present: - user: {{ name }} - name: {{ auth }} - require: - - file: {{ name }}_user - - user: {{ name }}_user + - file: users_{{ name }}_user + - user: users_{{ name }}_user +{% endfor %} +{% endif %} + +{% if 'ssh_keys_pillar' in user %} +{% for key_name, pillar_name in user['ssh_keys_pillar'].iteritems() %} +users_ssh_keys_files_{{ name }}_{{ key_name }}_pub: + file.managed: + - name: {{ user.get('home', '/home/{0}'.format(name)) }}/.ssh/{{ key_name + }}.pub + - contents: | + {{ pillar[pillar_name][key_name]['pubkey'] }} +users_ssh_keys_files_{{ name }}_{{ key_name }}_priv: + file.managed: + - name: {{ user.get('home', '/home/{0}'.format(name)) }}/.ssh/{{ key_name + }} + - contents: | + {{ pillar[pillar_name][key_name]['privkey'] | indent(8) }} +{% endfor %} +{% endif %} + +{% if 'ssh_auth_sources' in user %} +{% for pubkey_file in user['ssh_auth_sources'] %} +users_ssh_auth_source_{{ name }}_{{ loop.index0 }}: + ssh_auth.present: + - user: {{ name }} + - source: {{ pubkey_file }} + - require: + - file: users_{{ name }}_user + - user: users_{{ name }}_user {% endfor %} {% endif %} {% if 'ssh_auth.absent' in user %} {% for auth in user['ssh_auth.absent'] %} -ssh_auth_delete_{{ name }}_{{ loop.index0 }}: +users_ssh_auth_delete_{{ name }}_{{ loop.index0 }}: ssh_auth.absent: - user: {{ name }} - name: {{ auth }} - require: - - file: {{ name }}_user - - user: {{ name }}_user + - file: users_{{ name }}_user + - user: users_{{ name }}_user {% endfor %} {% endif %} +{% if 'ssh_config' in user %} +users_ssh_config_{{ name }}: + file.managed: + - name: {{ home }}/.ssh/config + - user: {{ name }} + - group: {{ user_group }} + - mode: 640 + - contents: | + # Managed by Saltstack + # Do Not Edit + {% for label, setting in user.ssh_config.items() %} + # {{ label }} + Host {{ setting.get('hostname') }} + {%- for opts in setting.get('options') %} + {{ opts }} + {%- endfor %} + {% endfor -%} +{% endif %} + {% if 'sudouser' in user and user['sudouser'] %} -sudoer-{{ name }}: +users_sudoer-{{ name }}: file.managed: - name: {{ users.sudoers_dir }}/{{ name }} - user: root - group: {{ users.root_group }} - mode: '0440' +{% if 'sudo_rules' in user or 'sudo_defaults' in user %} {% if 'sudo_rules' in user %} {% for rule in user['sudo_rules'] %} "validate {{ name }} sudo rule {{ loop.index0 }} {{ name }} {{ rule }}": @@ -191,46 +253,71 @@ sudoer-{{ name }}: # Specify the rule via an env var to avoid shell quoting issues. - rule: "{{ name }} {{ rule }}" - require_in: - - file: {{ users.sudoers_dir }}/{{ name }} + - file: users_{{ users.sudoers_dir }}/{{ name }} {% endfor %} +{% endif %} +{% if 'sudo_defaults' in user %} +{% for entry in user['sudo_defaults'] %} +"validate {{ name }} sudo Defaults {{ loop.index0 }} {{ name }} {{ entry }}": + cmd.run: + - name: 'visudo -cf - <<<"$rule" | { read output; if [[ $output != "stdin: parsed OK" ]] ; then echo $output ; fi }' + - stateful: True + - shell: {{ users.visudo_shell }} + - env: + # Specify the rule via an env var to avoid shell quoting issues. + - rule: "Defaults:{{ name }} {{ entry }}" + - require_in: + - file: users_{{ users.sudoers_dir }}/{{ name }} +{% endfor %} +{% endif %} -{{ users.sudoers_dir }}/{{ name }}: +users_{{ users.sudoers_dir }}/{{ name }}: file.managed: + - name: {{ users.sudoers_dir }}/{{ name }} - contents: | + {%- if 'sudo_defaults' in user %} + {%- for entry in user['sudo_defaults'] %} + Defaults:{{ name }} {{ entry }} + {%- endfor %} + {%- endif %} + {%- if 'sudo_rules' in user %} {%- for rule in user['sudo_rules'] %} {{ name }} {{ rule }} {%- endfor %} + {%- endif %} - require: - - file: sudoer-defaults - - file: sudoer-{{ name }} + - file: users_sudoer-defaults + - file: users_sudoer-{{ name }} {% endif %} {% else %} -{{ users.sudoers_dir }}/{{ name }}: +users_{{ users.sudoers_dir }}/{{ name }}: file.absent: - name: {{ users.sudoers_dir }}/{{ name }} {% endif %} {%- if 'google_auth' in user %} {%- for svc in user['google_auth'] %} -googleauth-{{ svc }}-{{ name }}: +users_googleauth-{{ svc }}-{{ name }}: file.managed: - replace: false - name: {{ users.googleauth_dir }}/{{ name }}_{{ svc }} - contents_pillar: 'users:{{ name }}:google_auth:{{ svc }}' - user: root - group: {{ users.root_group }} - - mode: 600 + - mode: 400 - require: - - pkg: googleauth-package + - pkg: users_googleauth-package {%- endfor %} {%- endif %} {% endfor %} + {% for name, user in pillar.get('users', {}).iteritems() if user.absent is defined and user.absent %} -{{ name }}: +users_absent_user_{{ name }}: {% if 'purge' in user or 'force' in user %} user.absent: + - name: {{ name }} {% if 'purge' in user %} - purge: {{ user['purge'] }} {% endif %} @@ -238,22 +325,24 @@ googleauth-{{ svc }}-{{ name }}: - force: {{ user['force'] }} {% endif %} {% else %} - user.absent + user.absent: + - name: {{ name }} {% endif -%} -{{ users.sudoers_dir }}/{{ name }}: +users_{{ users.sudoers_dir }}/{{ name }}: file.absent: - name: {{ users.sudoers_dir }}/{{ name }} {% endfor %} {% for user in pillar.get('absent_users', []) %} -{{ user }}: +users_absent_user_2_{{ user }}: user.absent -{{ users.sudoers_dir }}/{{ user }}: +users_2_{{ users.sudoers_dir }}/{{ user }}: file.absent: - name: {{ users.sudoers_dir }}/{{ user }} {% endfor %} {% for group in pillar.get('absent_groups', []) %} -{{ group }}: - group.absent +users_absent_group_{{ group }}: + group.absent: + - name: {{ group }} {% endfor %} diff --git a/users/sudo.sls b/users/sudo.sls index 5d852c9..2953ad2 100644 --- a/users/sudo.sls +++ b/users/sudo.sls @@ -2,31 +2,31 @@ {% from "users/map.jinja" import users with context %} # Ensure availability of bash -bash-package: +users_bash-package: pkg.installed: - name: {{ users.bash_package }} -sudo-group: +users_sudo-group: group.present: - name: sudo - system: True -sudo-package: +users_sudo-package: pkg.installed: - name: {{ users.sudo_package }} - require: - - group: sudo-group + - group: users_sudo-group - file: {{ users.sudoers_dir }} -{{ users.sudoers_dir }}: - file: - - directory +users_{{ users.sudoers_dir }}: + file.directory: + - name: {{ users.sudoers_dir }} -sudoer-defaults: +users_sudoer-defaults: file.append: - name: {{ users.sudoers_file }} - require: - - pkg: sudo-package + - pkg: users_sudo-package - text: - Defaults env_reset - Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" diff --git a/users/vimrc.sls b/users/vimrc.sls new file mode 100644 index 0000000..e678bb6 --- /dev/null +++ b/users/vimrc.sls @@ -0,0 +1,28 @@ +{% from "users/map.jinja" import users with context %} +include: + - users + - vim + +{% for name, user in pillar.get('users', {}).items() if user.absent is not defined or not user.absent %} +{%- if user == None -%} +{%- set user = {} -%} +{%- endif -%} +{%- set home = user.get('home', "/home/%s" % name) -%} +{%- set manage = user.get('manage_vimrc', False) -%} +{%- if 'prime_group' in user and 'name' in user['prime_group'] %} +{%- set user_group = user.prime_group.name -%} +{%- else -%} +{%- set user_group = name -%} +{%- endif %} +{%- if manage -%} +users_{{ name }}_user_vimrc: + file.managed: + - name: {{ home }}/.vimrc + - user: {{ name }} + - group: {{ user_group }} + - mode: 644 + - source: + - salt://users/files/vimrc/{{ name }}/vimrc + - salt://users/files/vimrc/vimrc +{% endif %} +{% endfor %}