require 'puppet/util'
require '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
Copyright 2K16 - 2K18 Indonesian Hacker Rulez