1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
|
require_relative '../../../puppet/util'
require_relative '../../../puppet/util/user_attr'
require 'date'
Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd, :source => :useradd do
desc "User and role management on Solaris, via `useradd` and `roleadd`."
defaultfor :osfamily => :solaris
commands :add => "useradd", :delete => "userdel", :modify => "usermod", :password => "passwd", :role_add => "roleadd", :role_delete => "roledel", :role_modify => "rolemod"
options :home, :flag => "-d", :method => :dir
options :comment, :method => :gecos
options :groups, :flag => "-G"
options :shell, :flag => "-s"
options :roles, :flag => "-R"
options :auths, :flag => "-A"
options :profiles, :flag => "-P"
options :password_min_age, :flag => "-n"
options :password_max_age, :flag => "-x"
options :password_warn_days, :flag => "-w"
verify :gid, "GID must be an integer" do |value|
value.is_a? Integer
end
verify :groups, "Groups must be comma-separated" do |value|
value !~ /\s/
end
def shell=(value)
check_valid_shell
set("shell", value)
end
has_features :manages_homedir, :allows_duplicates, :manages_solaris_rbac, :manages_roles, :manages_passwords, :manages_password_age, :manages_shell
def check_valid_shell
unless File.exist?(@resource.should(:shell))
raise(Puppet::Error, "Shell #{@resource.should(:shell)} must exist")
end
unless File.executable?(@resource.should(:shell).to_s)
raise(Puppet::Error, "Shell #{@resource.should(:shell)} must be executable")
end
end
#must override this to hand the keyvalue pairs
def add_properties
cmd = []
Puppet::Type.type(:user).validproperties.each do |property|
#skip the password because we can't create it with the solaris useradd
next if [:ensure, :password, :password_min_age, :password_max_age, :password_warn_days].include?(property)
# 1680 Now you can set the hashed passwords on solaris:lib/puppet/provider/user/user_role_add.rb
# the value needs to be quoted, mostly because -c might
# have spaces in it
value = @resource.should(property)
if value && value != ""
if property == :keys
cmd += build_keys_cmd(value)
else
cmd << flag(property) << value
end
end
end
cmd
end
def user_attributes
@user_attributes ||= UserAttr.get_attributes_by_name(@resource[:name])
end
def flush
@user_attributes = nil
end
def command(cmd)
cmd = ("role_#{cmd}").intern if is_role? or (!exists? and @resource[:ensure] == :role)
super(cmd)
end
def is_role?
user_attributes and user_attributes[:type] == "role"
end
def run(cmd, msg)
execute(cmd)
rescue Puppet::ExecutionFailure => detail
raise Puppet::Error, "Could not #{msg} #{@resource.class.name} #{@resource.name}: #{detail}", detail.backtrace
end
def transition(type)
cmd = [command(:modify)]
cmd << "-K" << "type=#{type}"
cmd += add_properties
cmd << @resource[:name]
end
def create
if is_role?
run(transition("normal"), "transition role to")
else
run(addcmd, "create")
cmd = passcmd
if cmd
run(cmd, "change password policy for")
end
end
# added to handle case when password is specified
self.password = @resource[:password] if @resource[:password]
end
def destroy
run(deletecmd, "delete "+ (is_role? ? "role" : "user"))
end
def create_role
if exists? and !is_role?
run(transition("role"), "transition user to")
else
run(addcmd, "create role")
end
end
def roles
user_attributes[:roles] if user_attributes
end
def auths
user_attributes[:auths] if user_attributes
end
def profiles
user_attributes[:profiles] if user_attributes
end
def project
user_attributes[:project] if user_attributes
end
def managed_attributes
[:name, :type, :roles, :auths, :profiles, :project]
end
def remove_managed_attributes
managed = managed_attributes
user_attributes.select { |k,v| !managed.include?(k) }.inject({}) { |hash, array| hash[array[0]] = array[1]; hash }
end
def keys
if user_attributes
#we have to get rid of all the keys we are managing another way
remove_managed_attributes
end
end
def build_keys_cmd(keys_hash)
cmd = []
keys_hash.each do |k,v|
cmd << "-K" << "#{k}=#{v}"
end
cmd
end
def keys=(keys_hash)
run([command(:modify)] + build_keys_cmd(keys_hash) << @resource[:name], "modify attribute key pairs")
end
# This helper makes it possible to test this on stub data without having to
# do too many crazy things!
def target_file_path
"/etc/shadow"
end
private :target_file_path
#Read in /etc/shadow, find the line for this user (skipping comments, because who knows) and return it
#No abstraction, all esoteric knowledge of file formats, yay
def shadow_entry
return @shadow_entry if defined? @shadow_entry
@shadow_entry = File.readlines(target_file_path).
reject { |r| r =~ /^[^\w]/ }.
# PUP-229: don't suppress the empty fields
collect { |l| l.chomp.split(':', -1) }.
find { |user, _| user == @resource[:name] }
end
def password
return :absent unless shadow_entry
shadow_entry[1]
end
def password_min_age
return :absent unless shadow_entry
shadow_entry[3].empty? ? -1 : shadow_entry[3]
end
def password_max_age
return :absent unless shadow_entry
shadow_entry[4].empty? ? -1 : shadow_entry[4]
end
def password_warn_days
return :absent unless shadow_entry
shadow_entry[5].empty? ? -1 : shadow_entry[5]
end
def has_sensitive_data?(property = nil)
false
end
# Read in /etc/shadow, find the line for our used and rewrite it with the
# new pw. Smooth like 80 grit sandpaper.
#
# Now uses the `replace_file` mechanism to minimize the chance that we lose
# data, but it is still terrible. We still skip platform locking, so a
# concurrent `vipw -s` session will have no idea we risk data loss.
def password=(cryptopw)
begin
shadow = File.read(target_file_path)
# Go Mifune loves the race here where we can lose data because
# /etc/shadow changed between reading it and writing it.
# --daniel 2012-02-05
Puppet::Util.replace_file(target_file_path, 0640) do |fh|
shadow.each_line do |line|
line_arr = line.split(':')
if line_arr[0] == @resource[:name]
line_arr[1] = cryptopw
line_arr[2] = (Date.today - Date.new(1970,1,1)).to_i.to_s
line = line_arr.join(':')
end
fh.print line
end
end
rescue => detail
self.fail Puppet::Error, "Could not write replace #{target_file_path}: #{detail}", detail
end
end
end
|