Class: RightScale::LoginUserManager
- Includes:
- RightSupport::Ruby::EasySingleton
- Defined in:
- lib/instance/login_user_manager.rb
Constant Summary collapse
- PROFILE_CHECKSUM =
"profile.md5"
- MIN_UID =
10_000
- MAX_UID =
2**32 - 1
- MAX_UUID =
MAX_UID - MIN_UID
- SBIN_PATHS =
List of directories that commonly contain user and group management utilities
['/usr/bin', '/usr/sbin', '/bin', '/sbin']
- DEFAULT_SHELLS =
List of viable default shells. Useful because Ubuntu’s adduser seems to require a -s parameter.
['/bin/bash', '/usr/bin/bash', '/bin/sh', '/usr/bin/sh', '/bin/dash', '/bin/tcsh']
Instance Method Summary collapse
-
#add_user(username, uid, shell = nil) ⇒ Object
Create a Unix user with the “useradd” command.
-
#create_user(username, uuid, superuser) ⇒ Object
Ensure that a given user exists and that his group membership is correct.
-
#group_exists?(name) ⇒ Boolean
Check if group with specified name exists in the system.
-
#manage_user(uuid, superuser, options = {}) ⇒ Object
If the given user exists and is RightScale-managed, then ensure his login information and group membership are correct.
-
#modify_group(group, operation, username) ⇒ Object
Adds or removes a user from an OS group; does nothing if the user is already in the correct membership state.
-
#modify_user(username, locked = false, shell = nil) ⇒ Object
Modify a user with the “usermod” command.
-
#pick_username(ideal) ⇒ Object
Pick a username that does not yet exist on the system.
- #setup_profile(username, home_dir, custom_data, force) ⇒ Object
-
#simulate_login(username) ⇒ Object
Set some of the environment variables that would normally be set if a user were to login to an interactive shell.
-
#uid_exists?(uid, groups = []) ⇒ Boolean
Check if user with specified Unix UID exists in the system, and optionally whether he belongs to all of the specified groups.
-
#uid_to_username(uid) ⇒ Object
- uid(String)
-
linux account UID.
-
#user_exists?(name) ⇒ Boolean
Checks if user with specified name exists in the system.
-
#uuid_to_uid(uuid) ⇒ Object
Map a universally-unique integer RightScale user ID to a locally-unique Unix UID.
Instance Method Details
#add_user(username, uid, shell = nil) ⇒ Object
Create a Unix user with the “useradd” command.
Parameters
- username(String)
-
username
- uid(String)
-
account’s UID
- expired_at(Time)
-
account’s expiration date; default nil
- shell(String)
-
account’s login shell; default nil (use systemwide default)
Raise
- (RightScale::LoginManager::SystemConflict)
-
if the user could not be created for some reason
Return
- true
-
always returns true
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 |
# File 'lib/instance/login_user_manager.rb', line 170 def add_user(username, uid, shell=nil) uid = Integer(uid) shell ||= DEFAULT_SHELLS.detect { |sh| File.exists?(sh) } useradd = find_sbin('useradd') unless shell.nil? dash_s = "-s #{Shellwords.escape(shell)}" end result = sudo("#{useradd} #{dash_s} -u #{uid} -m #{Shellwords.escape(username)}") case result.exitstatus when 0 home_dir = Shellwords.escape(Etc.getpwnam(username).dir) sudo("chmod 0771 #{Shellwords.escape(home_dir)}") RightScale::Log.info "LoginUserManager created #{username} successfully" else raise RightScale::LoginManager::SystemConflict, "Failed to create user #{username}" end true end |
#create_user(username, uuid, superuser) ⇒ Object
Ensure that a given user exists and that his group membership is correct.
Parameters
- username(String)
-
preferred username of RightScale user
- uuid(String)
-
RightScale user’s UUID
- superuser(Boolean)
-
whether the user should have sudo privileges
Block
If a block is given AND the user needs to be created, yields to the block with the to-be-created account’s username, before creating it. This gives the caller a chance to provide interactive feedback to the user.
Return
- username(String)
-
user’s actual username (may vary from preferred username)
Raise
- (LoginManager::SystemConflict)
-
if an existing non-RightScale-managed UID prevents us from creating a user
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/instance/login_user_manager.rb', line 88 def create_user(username, uuid, superuser) uid = LoginUserManager.uuid_to_uid(uuid) if uid_exists?(uid, ['rightscale']) username = uid_to_username(uid) elsif !uid_exists?(uid) username = pick_username(username) yield(username) if block_given? add_user(username, uid) modify_group('rightscale', :add, username) # NB it is SUPER IMPORTANT to pass :force=>true here. Due to an oddity in Ruby's Etc # extension, a user who has recently been added, won't seem to be a member of # any groups until the SECOND time we enumerate his group membership. manage_user(uuid, superuser, :force=>true) else raise RightScale::LoginManager::SystemConflict, "A user with UID #{uid} already exists and is " + "not managed by RightScale" end username end |
#group_exists?(name) ⇒ Boolean
Check if group with specified name exists in the system.
Parameters
- name(String)
-
group’s name
Block
If a block is given, it will be yielded to with various status messages suitable for display to the user.
Return
- exist_status(Boolean)
-
true if exists; otherwise false
334 335 336 337 338 |
# File 'lib/instance/login_user_manager.rb', line 334 def group_exists?(name) groups = Set.new Etc.group { |g| groups << g.name } groups.include?(name) end |
#manage_user(uuid, superuser, options = {}) ⇒ Object
If the given user exists and is RightScale-managed, then ensure his login information and group membership are correct. If force == true, then management tasks are performed irrespective of the user’s group membership status.
Parameters
- uuid(String)
-
RightScale user’s UUID
- superuser(Boolean)
-
whether the user should have sudo privileges
- force(Boolean)
-
if true, performs group management even if the user does NOT belong to ‘rightscale’
Options
- :force
-
if true, then the user will be updated even if they do not belong to the RightScale group
- :disable
-
if true, then the user will be prevented from logging in
Return
- username(String)
-
if the user exists, returns his actual username
- false
-
if the user does not exist
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/instance/login_user_manager.rb', line 127 def manage_user(uuid, superuser, ={}) uid = LoginUserManager.uuid_to_uid(uuid) username = uid_to_username(uid) force = [:force] || false disable = [:disable] || false if ( force && uid_exists?(uid) ) || uid_exists?(uid, ['rightscale']) modify_user(username, disable) action = superuser ? :add : :remove modify_group('rightscale_sudo', action, username) if group_exists?('rightscale_sudo') username else false end end |
#modify_group(group, operation, username) ⇒ Object
Adds or removes a user from an OS group; does nothing if the user is already in the correct membership state.
Parameters
- group(String)
-
group name
- operation(Symbol)
-
:add or :remove
- username(String)
-
username to add/remove
Raise
Raises ArgumentError
Return
- result(Boolean)
-
true if user was added/removed; false if
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/instance/login_user_manager.rb', line 249 def modify_group(group, operation, username) #Ensure group/user exist; this raises ArgumentError if either does not exist Etc.getgrnam(group) Etc.getpwnam(username) groups = Set.new Etc.group { |g| groups << g.name if g.mem.include?(username) } case operation when :add return false if groups.include?(group) groups << group when :remove return false unless groups.include?(group) groups.delete(group) else raise ArgumentError, "Unknown operation #{operation}; expected :add or :remove" end groups = Shellwords.escape(groups.to_a.join(',')) username = Shellwords.escape(username) usermod = find_sbin('usermod') result = sudo("#{usermod} -G #{groups} #{username}") case result.exitstatus when 0 RightScale::Log.info "Successfully performed group-#{operation} of #{username} to #{group}" return true else RightScale::Log.error "Failed group-#{operation} of #{username} to #{group}" return false end end |
#modify_user(username, locked = false, shell = nil) ⇒ Object
Modify a user with the “usermod” command.
Parameters
- username(String)
-
username
- uid(String)
-
account’s UID
- locked(true,false)
-
if true, prevent the user from logging in
- shell(String)
-
account’s login shell; default nil (use systemwide default)
Return
- true
-
always returns true
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 |
# File 'lib/instance/login_user_manager.rb', line 206 def modify_user(username, locked=false, shell=nil) shell ||= DEFAULT_SHELLS.detect { |sh| File.exists?(sh) } usermod = find_sbin('usermod') if locked # the man page claims that "1" works here, but testing proves that it doesn't. # use 1970 instead. dash_e = "-e 1970-01-01 -L" else dash_e = "-e 99999 -U" end unless shell.nil? dash_s = "-s #{Shellwords.escape(shell)}" end result = sudo("#{usermod} #{dash_e} #{dash_s} #{Shellwords.escape(username)}") case result.exitstatus when 0 RightScale::Log.info "LoginUserManager modified #{username} successfully" else RightScale::Log.error "Failed to modify user #{username}" end true end |
#pick_username(ideal) ⇒ Object
Pick a username that does not yet exist on the system. If the given username does not exist, it is returned; else we add a “_1” suffix and continue incrementing the number until we arrive at a username that does not yet exist.
Parameters
- ideal(String)
-
the user’s ideal (chosen) username
Return
- username(String)
-
username with possible postfix
59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/instance/login_user_manager.rb', line 59 def pick_username(ideal) name = ideal i = 0 while user_exists?(name) i += 1 name = "#{ideal}_#{i}" end name end |
#setup_profile(username, home_dir, custom_data, force) ⇒ Object
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 |
# File 'lib/instance/login_user_manager.rb', line 340 def setup_profile(username, home_dir, custom_data, force) return false if custom_data.nil? || custom_data.empty? checksum_path = File.join('.rightscale', PROFILE_CHECKSUM) return false if !force && File.exists?(File.join(home_dir, checksum_path)) t0 = Time.now.to_i yield("Performing profile setup for #{username}...") if block_given? tmpdir = Dir.mktmpdir file_path = File.join(tmpdir, File.basename(custom_data)) if download_files(custom_data, file_path) && extract_files(username, file_path, home_dir) save_checksum(username, file_path, checksum_path, home_dir) t1 = Time.now.to_i yield("Setup complete (#{t1 - t0} sec)") if block_given? && (t1 - t0 >= 2) end return true rescue Exception => e yield("Failed to create profile for #{username}; continuing") if block_given? yield("#{e.class.name}: #{e.} - #{e.backtrace.first}") if block_given? Log.error("#{e.class.name}: #{e.} - #{e.backtrace.first}") return false ensure FileUtils.rm_rf(tmpdir) if tmpdir && File.exists?(tmpdir) end |
#simulate_login(username) ⇒ Object
Set some of the environment variables that would normally be set if a user were to login to an interactive shell. This is useful when simulating an interactive login, e.g. for purposes of running a user-specified command via SSH.
Parameters
- username(String)
-
user’s name
Return
- true
-
always returns true
377 378 379 380 381 382 383 |
# File 'lib/instance/login_user_manager.rb', line 377 def simulate_login(username) info = Etc.getpwnam(username) ENV['USER'] = info.name ENV['HOME'] = info.dir ENV['SHELL'] = info.shell true end |
#uid_exists?(uid, groups = []) ⇒ Boolean
Check if user with specified Unix UID exists in the system, and optionally whether he belongs to all of the specified groups.
Parameters
- uid(String)
-
account’s UID
Return
- exist_status(Boolean)
-
true if exists; otherwise false
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 |
# File 'lib/instance/login_user_manager.rb', line 306 def uid_exists?(uid, groups=[]) uid = Integer(uid) user_exists = Etc.getpwuid(uid).uid == uid if groups.empty? user_belongs = true else mem = Set.new username = Etc.getpwuid(uid).name Etc.group { |g| mem << g.name if g.mem.include?(username) } user_belongs = groups.all? { |g| mem.include?(g) } end user_exists && user_belongs rescue ArgumentError false end |
#uid_to_username(uid) ⇒ Object
- uid(String)
-
linux account UID
Return
- username(String)
-
account’s username or empty string
151 152 153 154 |
# File 'lib/instance/login_user_manager.rb', line 151 def uid_to_username(uid) uid = Integer(uid) Etc.getpwuid(uid).name end |
#user_exists?(name) ⇒ Boolean
Checks if user with specified name exists in the system.
Parameter
- name(String)
-
username
Return
- exist_status(Boolean)
-
true if user exists; otherwise false
292 293 294 295 296 |
# File 'lib/instance/login_user_manager.rb', line 292 def user_exists?(name) Etc.getpwnam(name).name == name rescue ArgumentError false end |
#uuid_to_uid(uuid) ⇒ Object
Map a universally-unique integer RightScale user ID to a locally-unique Unix UID.
40 41 42 43 44 45 46 47 |
# File 'lib/instance/login_user_manager.rb', line 40 def uuid_to_uid(uuid) uuid = Integer(uuid) if uuid >= 0 && uuid <= MAX_UUID 10_000 + uuid else raise RangeError, "#{uuid} is not within (0..#{MAX_UUID})" end end |