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.
- #random_password ⇒ 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
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 |
# File 'lib/instance/login_user_manager.rb', line 177 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} -p #{random_password} -m #{Shellwords.escape(username)}") case result.exitstatus when 0 home_dir = Shellwords.escape(Etc.getpwnam(username).dir) # Locking account to prevent warning os SUSE(it complains on unlocking non-locked account) modify_user(username, true, shell) 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
342 343 344 345 346 |
# File 'lib/instance/login_user_manager.rb', line 342 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
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 284 285 286 287 288 289 290 291 |
# File 'lib/instance/login_user_manager.rb', line 257 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
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 240 241 |
# File 'lib/instance/login_user_manager.rb', line 214 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 |
#random_password ⇒ Object
157 158 159 160 161 |
# File 'lib/instance/login_user_manager.rb', line 157 def random_password letters = [('a'..'z'),('A'..'Z')].map{|i| i.to_a}.flatten password = (0..32).map{ letters[rand(letters.length)] }.join Shellwords.escape(password.crypt("rightscale")) 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
358 359 360 361 362 363 364 |
# File 'lib/instance/login_user_manager.rb', line 358 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
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 |
# File 'lib/instance/login_user_manager.rb', line 314 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
300 301 302 303 304 |
# File 'lib/instance/login_user_manager.rb', line 300 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 |