Rush has uploaded a new change for review.
https://gerrit.wikimedia.org/r/129501
Change subject: admin module for user/group/permissions cleanup
......................................................................
admin module for user/group/permissions cleanup
Users/groups/perms are defined are meant to be defined in yaml.
Applied like:
yaml:
groups:
test1:
gid: 1
members: [foo]
test2:
gid: 2
members: []
users:
foo:
uid: 1
...
.pp
node /foo/ {
#groups create their users (can overlap) and permissions
#as well as system groups and handling user membership
class { 'admin': groups => ['test', 'test2'], }
}
More info see: admin/README
Change-Id: I6982bfced50a22faac37246dff4d52ff163a34ed
---
A modules/admin/README
A modules/admin/data/.gitignore
A modules/admin/files/home/skel/.gitignore
A modules/admin/files/sudoers
A modules/admin/files/user_cleanup.sh
A modules/admin/lib/puppet/parser/functions/unique_users.rb
A modules/admin/manifests/group.pp
A modules/admin/manifests/groupmembers.pp
A modules/admin/manifests/init.pp
A modules/admin/manifests/sudo.pp
A modules/admin/manifests/user.pp
A modules/admin/manifests/yamlgroup.pp
A modules/admin/manifests/yamluser.pp
A modules/admin/templates/sudoers.erb
14 files changed, 686 insertions(+), 0 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/operations/puppet
refs/changes/01/129501/1
diff --git a/modules/admin/README b/modules/admin/README
new file mode 100644
index 0000000..a5e6e50
--- /dev/null
+++ b/modules/admin/README
@@ -0,0 +1,170 @@
+This module is meant to manage all users, groups, and permissions (sudo).
+
+All managed resources should be defined in yaml.
+
+see: admin/data/data.yaml
+
+-- Examples --
+
+Adding a group:
+
+ groups:
+ mygroup:
+ ensure: present
+ gid: 551
+ members: [foo, bar]
+
+Managing members for a default system group:
+
+-> For groups without a set GID we do not attempt creation
+
+ groups:
+ adm:
+ members: [foo, bar]
+
+Removing a member from a group:
+
+-> Removing 'bar' user from mygroup means removal from members array
+
+ groups:
+ mygroup:
+ ensure: present
+ gid: 551
+ members: [foo, bar] -> members: [foo]
+
+Removing a group:
+
+-> absenting a group will remove it where it was applied
+
+ groups:
+ mygroup:
+ foo:
+ ensure: absent
+ gid: 679
+ members: []
+
+Adding user 'foo':
+
+-> Since assignment is group centric this user won't be created anywhere yet
+
+ users:
+ foo:
+ uid: 1146
+ gid: 500
+ realname: Foo Bar
+ ssh_keys: [ssh-rsa mykeyhash foobar@mac]
+
+Adding user 'foo' to adm:
+
+ groups:
+ adm:
+ members: [foo]
+
+Removing user foo:
+
+-> absented users cannot be members of a group -- other than absent --
+-> users who are not a member of a supplemtary group are removed
+-> Therefore, removing a user from all groups means they will be removed
+ everywhere they existed because of those groups.
+
+ groups:
+ adm:
+ members: [foo, bar] -> members: [bar]
+
+-> User garbage collection logs to syslog and console:
+
+ logger: /usr/local/bin/user_cleanup.sh removing
user:foo:x:1001:4183::/home/foo:/bin/sh
+
+ notice: /Stage[main]/Admin/Exec[user_cleanup]/returns:
/usr/local/bin/user_cleanup.sh \
+ removing user:foo:x:1146:1146::/home/foo:/bin/sh
+
+-> However, if you want to ensure a user is especially missing globally
+-> add the user to the meta 'absent' group
+
+ groups:
+ absent:
+ members: [foo]
+
+ users:
+ foo:
+ ensure: absent
+ uid: 510
+ gid: 500
+ realname: Foo Bar
+ ssh_keys: [ssh-rsa mykeyhash foobar@mac]
+
+-> absent group users:
+-> * are _always_ included in every batch of assignments
+-> * should never have 'ensure: present'
+-> * cannot be a member of any other group
+
+Assigning groups/users:
+
+ node /myhost/ {
+ class { 'admin': groups => ['mygroup'], }
+ }
+
+ or (including managed members of a system group):
+
+ node /myhost/ {
+ class { 'admin': groups => ['mygroup', 'adm'], }
+ }
+
+Assigning sudo permissions to a group:
+
+ groups:
+ adm:
+ members: [foo, bar]
+ privs: [ALL=(ALL:ALL) ALL]
+
+ Creates: '/etc/sudoers.d/adm'
+
+ # This file is managed by Puppet!
+ %adm ALL=(ALL:ALL) ALL
+
+Removing sudo permissions from a group:
+
+-> if you remove a group the permissions are removed as well
+-> the 'absent' keyword will also remove all sudo permissions while
+-> retaining the group and members
+
+ bar:
+ gid: 680
+ privs: [absent]
+ members: [a, b, c]
+
+Users can be given sudo permissions in the same way:
+
+-> this is a limited use approach. these permissions would apply across the
entire env.
+
+ foo:
+ ensure: present
+ privs: [ALL=(ALL:ALL) ALL]
+
+Assigning one-off (single user, single case) sudo permissions:
+
+ admin::sudo { "foo_user_only_should_do_x":
+ user=>'bob',
+ comment=>'this is good karma',
+ privs=>['ALL = NOPASSWD: ALL'],
+ }
+
+ Creates '/etc/sudoers.d/foo_user_only_should_do_x':
+
+ # This file is managed by Puppet!
+ #this is good karma
+ bob ALL = NOPASSWD: ALL
+
+Getting your /home/ stuff wherever you are:
+
+-> if you define a dir for your username in '${module}/files/home' all
contents are managed
+
+ ├── files
+ │ ├── home
+ │ │ ├── foo
+ │ │ │ └── .vimrc
+
+
+#Notes:
+#* Groups with no members get root by default
+#* admin::user and admin::group are not dependent on yaml
diff --git a/modules/admin/data/.gitignore b/modules/admin/data/.gitignore
new file mode 100644
index 0000000..a82d1b4
--- /dev/null
+++ b/modules/admin/data/.gitignore
@@ -0,0 +1 @@
+#placeholder
diff --git a/modules/admin/files/home/skel/.gitignore
b/modules/admin/files/home/skel/.gitignore
new file mode 100644
index 0000000..ad8fd3b
--- /dev/null
+++ b/modules/admin/files/home/skel/.gitignore
@@ -0,0 +1 @@
+#place holder for git
diff --git a/modules/admin/files/sudoers b/modules/admin/files/sudoers
new file mode 100644
index 0000000..7e90fa6
--- /dev/null
+++ b/modules/admin/files/sudoers
@@ -0,0 +1,29 @@
+#
+# This file MUST be edited with the 'visudo' command as root.
+#
+# Please consider adding local content in /etc/sudoers.d/ instead of
+# directly modifying this file.
+#
+# See the man page for details on how to write a sudoers file.
+#
+Defaults env_reset
+Defaults
secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+
+# Host alias specification
+
+# User alias specification
+
+# Cmnd alias specification
+
+# User privilege specification
+root ALL=(ALL:ALL) ALL
+
+# Members of the admin group may gain root privileges
+%admin ALL=(ALL) ALL
+
+# Allow members of group sudo to execute any command
+%sudo ALL=(ALL:ALL) ALL
+
+# See sudoers(5) for more information on "#include" directives:
+
+#includedir /etc/sudoers.d
diff --git a/modules/admin/files/user_cleanup.sh
b/modules/admin/files/user_cleanup.sh
new file mode 100644
index 0000000..bebe8b5
--- /dev/null
+++ b/modules/admin/files/user_cleanup.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+# This is a user garbage collection script that removes
+# users who do not have a supplementary group that also have
+# a UID above the ID_BOUNDARY. Removals are logged to syslog.
+# with 'dryrun' as first arg exits 1 if cleanup is needed
+
+PASSWD='/etc/passwd'
+ID_BOUNDRY='500'
+
+function log() {
+ logger $1
+ echo $1
+}
+
+IFS=$'\r\n' PASSWD_USERS=($(cat $PASSWD))
+for var in "${PASSWD_USERS[@]}"
+do
+ username=`grep ${var} /etc/passwd | cut -d ':' -f 1`
+ uid=`grep ${var} /etc/passwd | cut -d ':' -f 3`
+ if [[ "$uid" -gt "$ID_BOUNDRY" ]]; then
+ if [[ `id $username` != *","* ]]; then
+ if [ "${1}" == "dryrun" ]
+ then
+ exit 1
+ fi
+ log "${0} removing user:""${var}"
+ /usr/sbin/deluser --remove-home --backup-to=/tmp $username &> /dev/null
+ fi
+ fi
+done
diff --git a/modules/admin/lib/puppet/parser/functions/unique_users.rb
b/modules/admin/lib/puppet/parser/functions/unique_users.rb
new file mode 100644
index 0000000..541868e
--- /dev/null
+++ b/modules/admin/lib/puppet/parser/functions/unique_users.rb
@@ -0,0 +1,13 @@
+ module Puppet::Parser::Functions
+ newfunction(:unique_users, :type => :rvalue) do |args|
+ myhash = args[0]
+ applied_groups = args[1]
+ users = Array.new
+ for group in applied_groups
+ if myhash['groups'].key?(group)
+ users.push(myhash['groups'][group]['members'])
+ end
+ end
+ return users.flatten(1).uniq
+ end
+ end
diff --git a/modules/admin/manifests/group.pp b/modules/admin/manifests/group.pp
new file mode 100644
index 0000000..af45090
--- /dev/null
+++ b/modules/admin/manifests/group.pp
@@ -0,0 +1,42 @@
+# A defined type for system group mangement
+#
+# === Parameters
+#
+# [*name*]
+# Group name
+#
+# [*ensure*]
+# Add or remove the user group [ "present" | "absent"]
+#
+# [*gid*]
+# Sets the group id
+#
+# [*privs*]
+# An array of priviledges to setup via admin::sudo
+#
+
+define admin::group(
+ $ensure = 'present',
+ $gid = undef,
+ $privs = [],
+)
+ {
+
+ #sans specified $gid we assume system group and do not create
+ if ($ensure == 'absent') or ($gid) {
+ group { $name:
+ ensure => $ensure,
+ name => $name,
+ allowdupe => false,
+ gid => $gid,
+ }
+ }
+
+ if !empty($privs) {
+ admin::sudo { $name:
+ ensure => $ensure,
+ privs => $privs,
+ is_group => true,
+ }
+ }
+}
diff --git a/modules/admin/manifests/groupmembers.pp
b/modules/admin/manifests/groupmembers.pp
new file mode 100644
index 0000000..efbe50e
--- /dev/null
+++ b/modules/admin/manifests/groupmembers.pp
@@ -0,0 +1,42 @@
+# A defined type for managing system group members
+#
+# === Parameters
+#
+# [*name*]
+# Group name
+#
+# [*yamlhash*]
+# Gash that contains valid group data
+#
+# [*default_member*]
+# User to be added to a group when explicit members array is empty
+
+define admin::groupmembers(
+ $yamlhash={},
+ $default_member='root',
+)
+ {
+ include admin
+
+ $gdata = $yamlhash['groups'][$name]
+ $members = $gdata['members']
+
+ if !empty($members) {
+ $joined_user_list = join($members,",")
+ }
+ else {
+ $joined_user_list = $default_member
+ }
+
+ #this list is inclusive. anyone not defined is removed.
+ #check for group existence and if so compare current users
+ $group_nonexistent="getent group ${name} | xargs test -z"
+ $members_match="getent group ${name} | cut -d ':' -f 4 | grep -E
^${joined_user_list}$"
+ exec { "${name}_ensure_members":
+ command => "/usr/bin/gpasswd ${name} -M ${joined_user_list}",
+ path => "/usr/bin:/bin",
+ unless => "$group_nonexistent || $members_match",
+ logoutput => true,
+ }
+
+}
diff --git a/modules/admin/manifests/init.pp b/modules/admin/manifests/init.pp
new file mode 100644
index 0000000..44ac5ff
--- /dev/null
+++ b/modules/admin/manifests/init.pp
@@ -0,0 +1,65 @@
+# Creates groups, users, and sudo permissions all from yaml for valid passed
group name
+#
+# === Parameters
+#
+# [*$groups*]
+# Array of valid groups (defined in yaml) to create with associated members
+#
+# [*$always_groups*]
+# Array of valid groups to always run
+#
+
+class admin(
+ $groups=[],
+ $always_groups=['absent', 'ops'],
+)
+ {
+
+ $module_path = get_module_path($module_name)
+ $data = loadyaml("${module_path}/data/data.yaml")
+ $uinfo = $data['users']
+ $users = keys($uinfo)
+
+ #making sure to include always_groups
+ $all_groups = split(inline_template("<%= (always_groups+groups).join(',')
%>"),',')
+
+ #this custom function eliminates the need for virtual users
+ $user_set = unique_users($data, $all_groups)
+
+ file { '/usr/local/bin/user_cleanup.sh':
+ ensure => file,
+ mode => '0755',
+ source => 'puppet:///modules/admin/user_cleanup.sh',
+ before => Exec[user_cleanup],
+ }
+
+ file { '/etc/sudoers':
+ ensure => file,
+ mode => '0440',
+ source => 'puppet:///modules/admin/sudoers',
+ tag => 'sudoers',
+ }
+
+ admin::yamlgroup { $all_groups:
+ yamlhash => $data,
+ before => Admin::Yamluser[$user_set],
+ }
+
+ admin::yamluser { $user_set:
+ yamlhash => $data,
+ before => Admin::Groupmembers[$all_groups],
+ }
+
+ admin::groupmembers { $all_groups:
+ yamlhash => $data,
+ before => Exec[user_cleanup],
+ }
+
+ #declarative gotcha: non-defined users can get left behind
+ #here we cleanup anyone not in a supplementary group above a certain UID
+ exec { "user_cleanup":
+ command => "/usr/local/bin/user_cleanup.sh",
+ unless => "/usr/local/bin/user_cleanup.sh dryrun",
+ logoutput => true,
+ }
+}
diff --git a/modules/admin/manifests/sudo.pp b/modules/admin/manifests/sudo.pp
new file mode 100644
index 0000000..fca748b
--- /dev/null
+++ b/modules/admin/manifests/sudo.pp
@@ -0,0 +1,76 @@
+# A defined type for user/group sudo privilege management
+#
+# === Parameters
+#
+# [*name*]
+# If this is for a user or group this is the user or group name.
+# If this is a one-off it is the name of the on-system sudo file
+#
+# [*ensure*]
+# Add or remove the priv definition in /etc/sudoers.d [ "present" | "absent"]
+#
+# [*user*]
+# The username of the user to be given priviledges.
+#
+# WARNING: Use for user oneoffs. Sudo privs should be handled in
+# the main user/group definition in almost all cases.
+#
+# [*comment*]
+# In case of a non-user definition/non-group definition priv a comment
+# can be provided.
+#
+# [*privs*]
+# An array of lines to be included in a sudoers.d/ file
+#
+# [*is_group*]
+# Boolean value to determine if this is a group.
+# Group declarations in sudo terms need a '%' prepend
+#
+
+define admin::sudo(
+ $ensure='present',
+ $user=undef,
+ $comment=undef,
+ $privs=[],
+ $is_group=false,
+)
+ {
+
+ if ($user) and ($is_group) {
+ fail("${user} specified as group")
+ }
+
+ if ($user) {
+ $priv_holder = $user
+ }
+ else {
+ $priv_holder = $name
+ }
+
+ if member($privs, 'absent') {
+ $final_ensure = 'absent'
+ }
+ else {
+ $final_ensure = $ensure
+ }
+
+ #WARNING: if path supplied is an existing dir Puppet will swallow this
silently
+ $filepath = "/etc/sudoers.d/${name}"
+ file { $filepath:
+ ensure => $final_ensure,
+ owner => 'root',
+ group => 'root',
+ mode => '0440',
+ content => template('admin/sudoers.erb'),
+ tag => 'sudoers',
+ }
+
+ #messing up sudo can have dire consquences. here we are linting
+ #the final sudo file. if bad, remove and throw an exception.
+ exec { "${name}_sudo_linting":
+ command => "rm -f /etc/sudoers.d/${name} && false",
+ unless => "test -e ${filepath} && /usr/sbin/visudo -cf ${filepath}
|| exit 0",
+ path => '/bin:/usr/bin',
+ subscribe => File[$filepath],
+ }
+}
diff --git a/modules/admin/manifests/user.pp b/modules/admin/manifests/user.pp
new file mode 100644
index 0000000..bda23a1
--- /dev/null
+++ b/modules/admin/manifests/user.pp
@@ -0,0 +1,133 @@
+# A defined type for user account management.
+#
+# WARNING: this is designed to NOT play well with local modifications.
+#
+# === Parameters
+#
+# [*name*]
+# The user of the user to be created.
+#
+# [*ensure*]
+# Add or remove the user account [ "present" | "absent"]
+#
+# [*uid*]
+# The UID to set for the new account. Must be globally unique.
+#
+# [*gid*]
+# Sets the primary group of this user.
+#
+# NOTE: User created files default to this group
+#
+# [*groups*]
+# An array of additional groups to add the user to.
+#
+# NOTE: user membership should almost exclusively be handled in the
+# external definition format (yaml)
+#
+# WARNING: setting a group here means anywhere this user exists the
+# group _has_ to exist also. More than likely they should be added
+# to the appropriate group in Admin::Groups
+#
+# [*comment*]
+# Typicaly the realname for the user.
+#
+# [*shell*]
+# The login shell.
+#
+# [*privs*]
+# An array of priviledges to setup via admin::sudo
+# Rarely should a user differ from an established group.
+#
+# [*ssh_keys*]
+# An array of strings containing the SSH public keys.
+#
+
+define admin::user (
+ $ensure = 'present',
+ $uid = undef,
+ $gid = undef,
+ $groups = [],
+ $comment = '',
+ $shell = '/bin/bash',
+ $privs = undef,
+ $ssh_keys = [],
+ )
+{
+ validate_re($ensure, '^(present|absent)$')
+
+ $ensure_dir = $ensure ? {
+ 'absent' => 'absent',
+ 'present' => 'directory',
+ }
+
+ user { $name:
+ ensure => $ensure,
+ name => $name,
+ uid => $uid,
+ comment => $realname,
+ gid => $gid,
+ groups => [],
+ shell => $shell,
+ managehome => false, # we do it manually below
+ allowdupe => false,
+ }
+
+ #This is all absented by the above /home/${user} cleanup
+ #Puppet chokes if we try to absent subfiles to /home/${user}
+ if $ensure == 'present' {
+
+ file { "/home/${name}":
+ ensure => $ensure_dir,
+ source => [
+ "puppet:///modules/admin/home/${name}/",
+ 'puppet:///modules/admin/home/skel/',
+ ],
+ sourceselect => 'first',
+ recurse => 'remote',
+ mode => '0644',
+ owner => $name,
+ group => $gid,
+ force => true,
+ tag => 'user-home',
+ require => User[$name],
+ }
+
+ # XXX: move under /etc/ssh/userkeys
+ # we want to exclusively manage ssh keys in puppet
+ if !empty($ssh_keys) {
+
+ if !is_array($ssh_keys) {
+ fail("${name} does not have a correct ssh_keys array:
${ssh_keys}")
+ }
+
+ $ssh_authorized_keys = join($ssh_keys, "\n")
+
+ file { "/home/${name}/.ssh":
+ ensure => $ensure_dir,
+ owner => $name,
+ group => $gid,
+ mode => '0700',
+ force => true,
+ tag => 'user-ssh',
+ require => File["/home/${name}"],
+ }
+
+ file { "/home/${name}/.ssh/authorized_keys":
+ ensure => $ensure,
+ owner => $name,
+ group => $gid,
+ mode => '0600',
+ content => $ssh_authorized_keys,
+ force => true,
+ tag => 'user-ssh',
+ require => File["/home/${name}/.ssh"],
+ }
+ }
+ }
+ if !empty($privs) {
+ admin::sudo { $name:
+ ensure => $ensure,
+ privs => $privs,
+ }
+ }
+}
diff --git a/modules/admin/manifests/yamlgroup.pp
b/modules/admin/manifests/yamlgroup.pp
new file mode 100644
index 0000000..b064adb
--- /dev/null
+++ b/modules/admin/manifests/yamlgroup.pp
@@ -0,0 +1,29 @@
+# A defined type for group creation / user realization from yaml
+#
+# === Parameters
+#
+# [*name*]
+# Yaml group name
+#
+# [*yamlhash*]
+# Hash that contains valid group data
+
+define admin::yamlgroup(
+ $yamlhash={},
+)
+ {
+ include admin
+
+ #explicit error as otherwise it goes forward later
+ #complaining of 'invalid hash' which is hard to track down
+ if !has_key($yamlhash['groups'], $name) {
+ fail("${name} is not a valid group name")
+ }
+
+ $gdata = $yamlhash['groups'][$name]
+ admin::group { $name:
+ ensure => $gdata['ensure'],
+ gid => $gdata['gid'],
+ privs => $gdata['privs'],
+ }
+}
diff --git a/modules/admin/manifests/yamluser.pp
b/modules/admin/manifests/yamluser.pp
new file mode 100644
index 0000000..75dbf02
--- /dev/null
+++ b/modules/admin/manifests/yamluser.pp
@@ -0,0 +1,42 @@
+# A defined type for user creation from yaml
+#
+# === Parameters
+#
+# [*name*]
+# Yaml user name
+#
+# [*yamlhash*]
+# Hash with valid user data
+
+define admin::yamluser(
+ $yamlhash={},
+)
+ {
+
+ $uinfo = $yamlhash['users'][$name]
+
+ if has_key($uinfo, 'gid') {
+ $group_id = $uinfo['gid']
+ }
+ else {
+ $group_id = $uinfo['uid']
+ }
+
+ if has_key($uinfo, 'ssh_keys') {
+ $key_set = $uinfo['ssh_keys']
+ }
+ else {
+ $key_set = []
+ }
+
+ admin::user { $name:
+ ensure => $uinfo['ensure'],
+ uid => $uinfo['uid'],
+ gid => $group_id,
+ groups => $uinfo['groups'],
+ comment => $uinfo['realname'],
+ shell => $uinfo['shell'],
+ privs => $uinfo['privs'],
+ ssh_keys => $key_set,
+ }
+}
diff --git a/modules/admin/templates/sudoers.erb
b/modules/admin/templates/sudoers.erb
new file mode 100644
index 0000000..e8a83b8
--- /dev/null
+++ b/modules/admin/templates/sudoers.erb
@@ -0,0 +1,12 @@
+# This file is managed by Puppet!
+
+<%- if @comment %>
+#<%= @comment %>
+<%- end %>
+<%- privs.each do |privilege| -%>
+<%- if @is_group == true %>
+%<%= @priv_holder %> <%= privilege %>
+<%- else %>
+<%= @priv_holder %> <%= privilege %>
+<%- end -%>
+<%- end -%>
--
To view, visit https://gerrit.wikimedia.org/r/129501
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I6982bfced50a22faac37246dff4d52ff163a34ed
Gerrit-PatchSet: 1
Gerrit-Project: operations/puppet
Gerrit-Branch: production
Gerrit-Owner: Rush <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits