right_link 5.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/actors/agent_manager.rb +88 -0
- data/actors/instance_scheduler.rb +321 -0
- data/actors/instance_services.rb +64 -0
- data/actors/instance_setup.rb +567 -0
- data/bin/cloud +25 -0
- data/bin/cook_runner +44 -0
- data/bin/deploy +120 -0
- data/bin/enroll +385 -0
- data/bin/rad +32 -0
- data/bin/rchk +29 -0
- data/bin/rnac +39 -0
- data/bin/rs_connect +33 -0
- data/bin/rs_log_level +31 -0
- data/bin/rs_ohai +28 -0
- data/bin/rs_reenroll +31 -0
- data/bin/rs_run_recipe +34 -0
- data/bin/rs_run_right_script +34 -0
- data/bin/rs_shutdown +33 -0
- data/bin/rs_tag +33 -0
- data/bin/rs_thunk +33 -0
- data/bin/rstat +31 -0
- data/bin/system +16 -0
- data/ext/Rakefile +18 -0
- data/init/config.yml +5 -0
- data/init/init.rb +79 -0
- data/lib/chef/ohai_setup.rb +51 -0
- data/lib/chef/plugins/cloud.rb +91 -0
- data/lib/chef/plugins/cloudstack.rb +23 -0
- data/lib/chef/plugins/ec2.rb +23 -0
- data/lib/chef/plugins/linux/block_device2.rb +24 -0
- data/lib/chef/plugins/rackspace.rb +23 -0
- data/lib/chef/plugins/rightscale.rb +125 -0
- data/lib/chef/plugins/windows/network.rb +114 -0
- data/lib/chef/plugins.rb +74 -0
- data/lib/chef/providers/dns_dnsmadeeasy_provider.rb +81 -0
- data/lib/chef/providers/dns_resource.rb +100 -0
- data/lib/chef/providers/executable_schedule_provider.rb +70 -0
- data/lib/chef/providers/executable_schedule_resource.rb +144 -0
- data/lib/chef/providers/remote_recipe_provider.rb +86 -0
- data/lib/chef/providers/remote_recipe_resource.rb +101 -0
- data/lib/chef/providers/right_link_tag_provider.rb +73 -0
- data/lib/chef/providers/right_link_tag_resource.rb +59 -0
- data/lib/chef/providers/right_script_provider.rb +190 -0
- data/lib/chef/providers/right_script_resource.rb +113 -0
- data/lib/chef/providers/rs_shutdown_provider.rb +75 -0
- data/lib/chef/providers/rs_shutdown_resource.rb +55 -0
- data/lib/chef/providers/server_collection_provider.rb +66 -0
- data/lib/chef/providers/server_collection_resource.rb +93 -0
- data/lib/chef/providers/windows/powershell_provider.rb +151 -0
- data/lib/chef/providers/windows/powershell_resource.rb +111 -0
- data/lib/chef/providers/windows/unsupported_provider.rb +51 -0
- data/lib/chef/right_providers.rb +55 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ChefNodeCmdlet.csproj +104 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ChefNodeCmdlet.dll-Help.xml +141 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Exceptions.cs +182 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeCommand.cs +58 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeResponse.cs +45 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceCommand.cs +58 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceResponse.cs +45 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceCommand.cs +58 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceResponse.cs +45 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionCommand.cs +178 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionRequest.cs +67 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionResponse.cs +58 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueCommandBase.cs +142 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueRequestBase.cs +64 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueResponseBase.cs +69 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/JsonTransport.cs +110 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/PipeClient.cs +158 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/PipeServer.cs +142 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Properties/AssemblyInfo.cs +16 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ProtocolConstants.cs +55 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ProtocolUtilities.cs +77 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ReadMe.txt +53 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeCommand.cs +59 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeResponse.cs +58 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceCommand.cs +59 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceResponse.cs +40 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceCommand.cs +59 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceResponse.cs +40 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueCommandBase.cs +293 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueRequestBase.cs +75 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueResponseBase.cs +45 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Transport.cs +91 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet.sln +35 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/Program.cs +374 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/Properties/AssemblyInfo.cs +16 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/TestChefNodeCmdlet.csproj +65 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/Program.cs +136 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/Properties/AssemblyInfo.cs +36 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/ReadMe.txt +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/TestNextActionCmdlet.csproj +68 -0
- data/lib/chef/windows/bin/Newtonsoft.Json.dll +0 -0
- data/lib/chef/windows/chef_node_server.rb +463 -0
- data/lib/chef/windows/dynamic_powershell_provider.rb +296 -0
- data/lib/chef/windows/pipe_server.rb +283 -0
- data/lib/chef/windows/powershell_host.rb +285 -0
- data/lib/chef/windows/powershell_pipe_server.rb +136 -0
- data/lib/chef/windows/powershell_provider_base.rb +92 -0
- data/lib/chef/windows/scripts/run_loop.ps1 +105 -0
- data/lib/clouds/cloud.rb +557 -0
- data/lib/clouds/cloud_factory.rb +250 -0
- data/lib/clouds/cloud_utilities.rb +244 -0
- data/lib/clouds/clouds/azure.rb +106 -0
- data/lib/clouds/clouds/cloudstack.rb +114 -0
- data/lib/clouds/clouds/ec2.rb +113 -0
- data/lib/clouds/clouds/eucalyptus.rb +46 -0
- data/lib/clouds/clouds/google.rb +102 -0
- data/lib/clouds/clouds/none.rb +76 -0
- data/lib/clouds/clouds/openstack.rb +30 -0
- data/lib/clouds/clouds/rackspace-ng.rb +54 -0
- data/lib/clouds/clouds/rackspace.rb +78 -0
- data/lib/clouds/clouds/softlayer.rb +91 -0
- data/lib/clouds/metadata_formatter.rb +108 -0
- data/lib/clouds/metadata_provider.rb +128 -0
- data/lib/clouds/metadata_source.rb +87 -0
- data/lib/clouds/metadata_sources/certificate_metadata_source.rb +207 -0
- data/lib/clouds/metadata_sources/config_drive_metadata_source.rb +129 -0
- data/lib/clouds/metadata_sources/file_metadata_source.rb +74 -0
- data/lib/clouds/metadata_sources/http_metadata_source.rb +277 -0
- data/lib/clouds/metadata_sources/selective_metadata_source.rb +122 -0
- data/lib/clouds/metadata_tree_climber.rb +144 -0
- data/lib/clouds/metadata_writer.rb +155 -0
- data/lib/clouds/metadata_writers/dictionary_metadata_writer.rb +72 -0
- data/lib/clouds/metadata_writers/ruby_metadata_writer.rb +76 -0
- data/lib/clouds/metadata_writers/shell_metadata_writer.rb +121 -0
- data/lib/clouds/register_clouds.rb +34 -0
- data/lib/clouds.rb +32 -0
- data/lib/gem_dependencies.rb +83 -0
- data/lib/git_hooks/commit-msg.rb +7 -0
- data/lib/instance/agent_config.rb +168 -0
- data/lib/instance/agent_watcher.rb +233 -0
- data/lib/instance/audit_cook_stub.rb +104 -0
- data/lib/instance/audit_proxy.rb +247 -0
- data/lib/instance/bundle_queue.rb +104 -0
- data/lib/instance/cook/agent_connection.rb +109 -0
- data/lib/instance/cook/audit_logger.rb +165 -0
- data/lib/instance/cook/audit_stub.rb +142 -0
- data/lib/instance/cook/ca-bundle.crt +2794 -0
- data/lib/instance/cook/chef_state.rb +211 -0
- data/lib/instance/cook/cook.rb +306 -0
- data/lib/instance/cook/cook_state.rb +298 -0
- data/lib/instance/cook/cookbook_path_mapping.rb +66 -0
- data/lib/instance/cook/cookbook_repo_retriever.rb +190 -0
- data/lib/instance/cook/executable_sequence.rb +765 -0
- data/lib/instance/cook/external_parameter_gatherer.rb +190 -0
- data/lib/instance/cook/repose_downloader.rb +349 -0
- data/lib/instance/cook/shutdown_request_proxy.rb +121 -0
- data/lib/instance/cook.rb +41 -0
- data/lib/instance/downloader.rb +208 -0
- data/lib/instance/duplicable.rb +67 -0
- data/lib/instance/exceptions.rb +49 -0
- data/lib/instance/executable_sequence_proxy.rb +278 -0
- data/lib/instance/instance_commands.rb +577 -0
- data/lib/instance/instance_state.rb +633 -0
- data/lib/instance/json_utilities.rb +102 -0
- data/lib/instance/login_manager.rb +533 -0
- data/lib/instance/login_user_manager.rb +522 -0
- data/lib/instance/message_encoder.rb +118 -0
- data/lib/instance/multi_thread_bundle_queue.rb +232 -0
- data/lib/instance/operation_context.rb +60 -0
- data/lib/instance/options_bag.rb +65 -0
- data/lib/instance/payload_formatter.rb +46 -0
- data/lib/instance/policy.rb +53 -0
- data/lib/instance/policy_audit.rb +100 -0
- data/lib/instance/policy_manager.rb +146 -0
- data/lib/instance/reenroll_manager.rb +104 -0
- data/lib/instance/right_scripts_cookbook.rb +181 -0
- data/lib/instance/shutdown_request.rb +221 -0
- data/lib/instance/single_thread_bundle_queue.rb +189 -0
- data/lib/instance/volume_management.rb +450 -0
- data/lib/instance.rb +50 -0
- data/lib/repo_conf_generators/apt_conf_generators.rb +106 -0
- data/lib/repo_conf_generators/gem_conf_generators.rb +80 -0
- data/lib/repo_conf_generators/rightscale_conf_generators.rb +254 -0
- data/lib/repo_conf_generators/rightscale_key.pub +17 -0
- data/lib/repo_conf_generators/yum_conf_generators.rb +225 -0
- data/lib/repo_conf_generators.rb +30 -0
- data/lib/run_shell.rb +28 -0
- data/scripts/agent_checker.rb +571 -0
- data/scripts/agent_controller.rb +247 -0
- data/scripts/agent_deployer.rb +148 -0
- data/scripts/bundle_runner.rb +336 -0
- data/scripts/cloud_controller.rb +176 -0
- data/scripts/log_level_manager.rb +142 -0
- data/scripts/ohai_runner.rb +33 -0
- data/scripts/reenroller.rb +193 -0
- data/scripts/server_importer.rb +293 -0
- data/scripts/shutdown_client.rb +183 -0
- data/scripts/system_configurator.rb +367 -0
- data/scripts/tagger.rb +381 -0
- data/scripts/thunker.rb +356 -0
- metadata +418 -0
@@ -0,0 +1,522 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2009-2011 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
module RightScale
|
24
|
+
class LoginUserManager
|
25
|
+
include RightSupport::Ruby::EasySingleton
|
26
|
+
|
27
|
+
PROFILE_CHECKSUM = "profile.md5"
|
28
|
+
|
29
|
+
MIN_UID = 10_000
|
30
|
+
MAX_UID = 2**32 - 1
|
31
|
+
MAX_UUID = MAX_UID - MIN_UID
|
32
|
+
|
33
|
+
# List of directories that commonly contain user and group management utilities
|
34
|
+
SBIN_PATHS = ['/usr/bin', '/usr/sbin', '/bin', '/sbin']
|
35
|
+
|
36
|
+
# List of viable default shells. Useful because Ubuntu's adduser seems to require a -s parameter.
|
37
|
+
DEFAULT_SHELLS = ['/bin/bash', '/usr/bin/bash', '/bin/sh', '/usr/bin/sh', '/bin/dash', '/bin/tcsh']
|
38
|
+
|
39
|
+
# Map a universally-unique integer RightScale user ID to a locally-unique Unix UID.
|
40
|
+
def uuid_to_uid(uuid)
|
41
|
+
uuid = Integer(uuid)
|
42
|
+
if uuid >= 0 && uuid <= MAX_UUID
|
43
|
+
10_000 + uuid
|
44
|
+
else
|
45
|
+
raise RangeError, "#{uuid} is not within (0..#{MAX_UUID})"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Pick a username that does not yet exist on the system. If the given
|
50
|
+
# username does not exist, it is returned; else we add a "_1" suffix
|
51
|
+
# and continue incrementing the number until we arrive at a username
|
52
|
+
# that does not yet exist.
|
53
|
+
#
|
54
|
+
# === Parameters
|
55
|
+
# ideal(String):: the user's ideal (chosen) username
|
56
|
+
#
|
57
|
+
# === Return
|
58
|
+
# username(String):: username with possible postfix
|
59
|
+
def pick_username(ideal)
|
60
|
+
name = ideal
|
61
|
+
i = 0
|
62
|
+
|
63
|
+
while user_exists?(name)
|
64
|
+
i += 1
|
65
|
+
name = "#{ideal}_#{i}"
|
66
|
+
end
|
67
|
+
|
68
|
+
name
|
69
|
+
end
|
70
|
+
|
71
|
+
# Ensure that a given user exists and that his group membership is correct.
|
72
|
+
#
|
73
|
+
# === Parameters
|
74
|
+
# username(String):: preferred username of RightScale user
|
75
|
+
# uuid(String):: RightScale user's UUID
|
76
|
+
# superuser(Boolean):: whether the user should have sudo privileges
|
77
|
+
#
|
78
|
+
# === Block
|
79
|
+
# If a block is given AND the user needs to be created, yields to the block
|
80
|
+
# with the to-be-created account's username, before creating it. This gives
|
81
|
+
# the caller a chance to provide interactive feedback to the user.
|
82
|
+
#
|
83
|
+
# === Return
|
84
|
+
# username(String):: user's actual username (may vary from preferred username)
|
85
|
+
#
|
86
|
+
# === Raise
|
87
|
+
# (LoginManager::SystemConflict):: if an existing non-RightScale-managed UID prevents us from creating a user
|
88
|
+
def create_user(username, uuid, superuser)
|
89
|
+
uid = LoginUserManager.uuid_to_uid(uuid)
|
90
|
+
|
91
|
+
if uid_exists?(uid, ['rightscale'])
|
92
|
+
username = uid_to_username(uid)
|
93
|
+
elsif !uid_exists?(uid)
|
94
|
+
username = pick_username(username)
|
95
|
+
yield(username) if block_given?
|
96
|
+
add_user(username, uid)
|
97
|
+
modify_group('rightscale', :add, username)
|
98
|
+
|
99
|
+
# NB it is SUPER IMPORTANT to pass :force=>true here. Due to an oddity in Ruby's Etc
|
100
|
+
# extension, a user who has recently been added, won't seem to be a member of
|
101
|
+
# any groups until the SECOND time we enumerate his group membership.
|
102
|
+
manage_user(uuid, superuser, :force=>true)
|
103
|
+
else
|
104
|
+
raise RightScale::LoginManager::SystemConflict, "A user with UID #{uid} already exists and is " +
|
105
|
+
"not managed by RightScale"
|
106
|
+
end
|
107
|
+
|
108
|
+
username
|
109
|
+
end
|
110
|
+
|
111
|
+
# If the given user exists and is RightScale-managed, then ensure his login information and
|
112
|
+
# group membership are correct. If force == true, then management tasks are performed
|
113
|
+
# irrespective of the user's group membership status.
|
114
|
+
#
|
115
|
+
# === Parameters
|
116
|
+
# uuid(String):: RightScale user's UUID
|
117
|
+
# superuser(Boolean):: whether the user should have sudo privileges
|
118
|
+
# force(Boolean):: if true, performs group management even if the user does NOT belong to 'rightscale'
|
119
|
+
#
|
120
|
+
# === Options
|
121
|
+
# :force:: if true, then the user will be updated even if they do not belong to the RightScale group
|
122
|
+
# :disable:: if true, then the user will be prevented from logging in
|
123
|
+
#
|
124
|
+
# === Return
|
125
|
+
# username(String):: if the user exists, returns his actual username
|
126
|
+
# false:: if the user does not exist
|
127
|
+
def manage_user(uuid, superuser, options={})
|
128
|
+
uid = LoginUserManager.uuid_to_uid(uuid)
|
129
|
+
username = uid_to_username(uid)
|
130
|
+
force = options[:force] || false
|
131
|
+
disable = options[:disable] || false
|
132
|
+
|
133
|
+
if ( force && uid_exists?(uid) ) || uid_exists?(uid, ['rightscale'])
|
134
|
+
modify_user(username, disable)
|
135
|
+
action = superuser ? :add : :remove
|
136
|
+
modify_group('rightscale_sudo', action, username) if group_exists?('rightscale_sudo')
|
137
|
+
|
138
|
+
username
|
139
|
+
else
|
140
|
+
false
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Fetches username from account's UID.
|
145
|
+
#
|
146
|
+
# === Parameters
|
147
|
+
# uid(String):: linux account UID
|
148
|
+
#
|
149
|
+
# === Return
|
150
|
+
# username(String):: account's username or empty string
|
151
|
+
def uid_to_username(uid)
|
152
|
+
uid = Integer(uid)
|
153
|
+
Etc.getpwuid(uid).name
|
154
|
+
end
|
155
|
+
|
156
|
+
# Create a Unix user with the "useradd" command.
|
157
|
+
#
|
158
|
+
# === Parameters
|
159
|
+
# username(String):: username
|
160
|
+
# uid(String):: account's UID
|
161
|
+
# expired_at(Time):: account's expiration date; default nil
|
162
|
+
# shell(String):: account's login shell; default nil (use systemwide default)
|
163
|
+
#
|
164
|
+
#
|
165
|
+
# === Raise
|
166
|
+
# (RightScale::LoginManager::SystemConflict):: if the user could not be created for some reason
|
167
|
+
#
|
168
|
+
# === Return
|
169
|
+
# true:: always returns true
|
170
|
+
def add_user(username, uid, shell=nil)
|
171
|
+
uid = Integer(uid)
|
172
|
+
shell ||= DEFAULT_SHELLS.detect { |sh| File.exists?(sh) }
|
173
|
+
|
174
|
+
useradd = find_sbin('useradd')
|
175
|
+
|
176
|
+
unless shell.nil?
|
177
|
+
dash_s = "-s #{Shellwords.escape(shell)}"
|
178
|
+
end
|
179
|
+
|
180
|
+
result = sudo("#{useradd} #{dash_s} -u #{uid} -m #{Shellwords.escape(username)}")
|
181
|
+
|
182
|
+
case result.exitstatus
|
183
|
+
when 0
|
184
|
+
home_dir = Shellwords.escape(Etc.getpwnam(username).dir)
|
185
|
+
|
186
|
+
sudo("chmod 0771 #{Shellwords.escape(home_dir)}")
|
187
|
+
|
188
|
+
RightScale::Log.info "LoginUserManager created #{username} successfully"
|
189
|
+
else
|
190
|
+
raise RightScale::LoginManager::SystemConflict, "Failed to create user #{username}"
|
191
|
+
end
|
192
|
+
|
193
|
+
true
|
194
|
+
end
|
195
|
+
|
196
|
+
# Modify a user with the "usermod" command.
|
197
|
+
#
|
198
|
+
# === Parameters
|
199
|
+
# username(String):: username
|
200
|
+
# uid(String):: account's UID
|
201
|
+
# locked(true,false):: if true, prevent the user from logging in
|
202
|
+
# shell(String):: account's login shell; default nil (use systemwide default)
|
203
|
+
#
|
204
|
+
# === Return
|
205
|
+
# true:: always returns true
|
206
|
+
def modify_user(username, locked=false, shell=nil)
|
207
|
+
shell ||= DEFAULT_SHELLS.detect { |sh| File.exists?(sh) }
|
208
|
+
|
209
|
+
usermod = find_sbin('usermod')
|
210
|
+
|
211
|
+
if locked
|
212
|
+
# the man page claims that "1" works here, but testing proves that it doesn't.
|
213
|
+
# use 1970 instead.
|
214
|
+
dash_e = "-e 1970-01-01 -L"
|
215
|
+
else
|
216
|
+
dash_e = "-e 99999 -U"
|
217
|
+
end
|
218
|
+
|
219
|
+
unless shell.nil?
|
220
|
+
dash_s = "-s #{Shellwords.escape(shell)}"
|
221
|
+
end
|
222
|
+
|
223
|
+
result = sudo("#{usermod} #{dash_e} #{dash_s} #{Shellwords.escape(username)}")
|
224
|
+
|
225
|
+
case result.exitstatus
|
226
|
+
when 0
|
227
|
+
RightScale::Log.info "LoginUserManager modified #{username} successfully"
|
228
|
+
else
|
229
|
+
RightScale::Log.error "Failed to modify user #{username}"
|
230
|
+
end
|
231
|
+
|
232
|
+
true
|
233
|
+
end
|
234
|
+
|
235
|
+
# Adds or removes a user from an OS group; does nothing if the user
|
236
|
+
# is already in the correct membership state.
|
237
|
+
#
|
238
|
+
# === Parameters
|
239
|
+
# group(String):: group name
|
240
|
+
# operation(Symbol):: :add or :remove
|
241
|
+
# username(String):: username to add/remove
|
242
|
+
#
|
243
|
+
# === Raise
|
244
|
+
# Raises ArgumentError
|
245
|
+
#
|
246
|
+
# === Return
|
247
|
+
# result(Boolean):: true if user was added/removed; false if
|
248
|
+
#
|
249
|
+
def modify_group(group, operation, username)
|
250
|
+
#Ensure group/user exist; this raises ArgumentError if either does not exist
|
251
|
+
Etc.getgrnam(group)
|
252
|
+
Etc.getpwnam(username)
|
253
|
+
|
254
|
+
groups = Set.new
|
255
|
+
Etc.group { |g| groups << g.name if g.mem.include?(username) }
|
256
|
+
|
257
|
+
case operation
|
258
|
+
when :add
|
259
|
+
return false if groups.include?(group)
|
260
|
+
groups << group
|
261
|
+
when :remove
|
262
|
+
return false unless groups.include?(group)
|
263
|
+
groups.delete(group)
|
264
|
+
else
|
265
|
+
raise ArgumentError, "Unknown operation #{operation}; expected :add or :remove"
|
266
|
+
end
|
267
|
+
|
268
|
+
groups = Shellwords.escape(groups.to_a.join(','))
|
269
|
+
username = Shellwords.escape(username)
|
270
|
+
|
271
|
+
usermod = find_sbin('usermod')
|
272
|
+
|
273
|
+
result = sudo("#{usermod} -G #{groups} #{username}")
|
274
|
+
|
275
|
+
case result.exitstatus
|
276
|
+
when 0
|
277
|
+
RightScale::Log.info "Successfully performed group-#{operation} of #{username} to #{group}"
|
278
|
+
return true
|
279
|
+
else
|
280
|
+
RightScale::Log.error "Failed group-#{operation} of #{username} to #{group}"
|
281
|
+
return false
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# Checks if user with specified name exists in the system.
|
286
|
+
#
|
287
|
+
# === Parameter
|
288
|
+
# name(String):: username
|
289
|
+
#
|
290
|
+
# === Return
|
291
|
+
# exist_status(Boolean):: true if user exists; otherwise false
|
292
|
+
def user_exists?(name)
|
293
|
+
Etc.getpwnam(name).name == name
|
294
|
+
rescue ArgumentError
|
295
|
+
false
|
296
|
+
end
|
297
|
+
|
298
|
+
# Check if user with specified Unix UID exists in the system, and optionally
|
299
|
+
# whether he belongs to all of the specified groups.
|
300
|
+
#
|
301
|
+
# === Parameters
|
302
|
+
# uid(String):: account's UID
|
303
|
+
#
|
304
|
+
# === Return
|
305
|
+
# exist_status(Boolean):: true if exists; otherwise false
|
306
|
+
def uid_exists?(uid, groups=[])
|
307
|
+
uid = Integer(uid)
|
308
|
+
user_exists = Etc.getpwuid(uid).uid == uid
|
309
|
+
if groups.empty?
|
310
|
+
user_belongs = true
|
311
|
+
else
|
312
|
+
mem = Set.new
|
313
|
+
username = Etc.getpwuid(uid).name
|
314
|
+
Etc.group { |g| mem << g.name if g.mem.include?(username) }
|
315
|
+
user_belongs = groups.all? { |g| mem.include?(g) }
|
316
|
+
end
|
317
|
+
|
318
|
+
user_exists && user_belongs
|
319
|
+
rescue ArgumentError
|
320
|
+
false
|
321
|
+
end
|
322
|
+
|
323
|
+
# Check if group with specified name exists in the system.
|
324
|
+
#
|
325
|
+
# === Parameters
|
326
|
+
# name(String):: group's name
|
327
|
+
#
|
328
|
+
# === Block
|
329
|
+
# If a block is given, it will be yielded to with various status messages
|
330
|
+
# suitable for display to the user.
|
331
|
+
#
|
332
|
+
# === Return
|
333
|
+
# exist_status(Boolean):: true if exists; otherwise false
|
334
|
+
def group_exists?(name)
|
335
|
+
groups = Set.new
|
336
|
+
Etc.group { |g| groups << g.name }
|
337
|
+
groups.include?(name)
|
338
|
+
end
|
339
|
+
|
340
|
+
def setup_profile(username, home_dir, custom_data, force)
|
341
|
+
return false if custom_data.nil? || custom_data.empty?
|
342
|
+
|
343
|
+
checksum_path = File.join('.rightscale', PROFILE_CHECKSUM)
|
344
|
+
return false if !force && File.exists?(File.join(home_dir, checksum_path))
|
345
|
+
|
346
|
+
t0 = Time.now.to_i
|
347
|
+
yield("Performing profile setup for #{username}...") if block_given?
|
348
|
+
|
349
|
+
tmpdir = Dir.mktmpdir
|
350
|
+
file_path = File.join(tmpdir, File.basename(custom_data))
|
351
|
+
if download_files(custom_data, file_path) && extract_files(username, file_path, home_dir)
|
352
|
+
save_checksum(username, file_path, checksum_path, home_dir)
|
353
|
+
t1 = Time.now.to_i
|
354
|
+
yield("Setup complete (#{t1 - t0} sec)") if block_given? && (t1 - t0 >= 2)
|
355
|
+
end
|
356
|
+
|
357
|
+
return true
|
358
|
+
rescue Exception => e
|
359
|
+
yield("Failed to create profile for #{username}; continuing") if block_given?
|
360
|
+
yield("#{e.class.name}: #{e.message} - #{e.backtrace.first}") if block_given?
|
361
|
+
Log.error("#{e.class.name}: #{e.message} - #{e.backtrace.first}")
|
362
|
+
return false
|
363
|
+
ensure
|
364
|
+
FileUtils.rm_rf(tmpdir) if tmpdir && File.exists?(tmpdir)
|
365
|
+
end
|
366
|
+
|
367
|
+
# Set some of the environment variables that would normally be set if a user
|
368
|
+
# were to login to an interactive shell. This is useful when simulating an
|
369
|
+
# interactive login, e.g. for purposes of running a user-specified command
|
370
|
+
# via SSH.
|
371
|
+
#
|
372
|
+
# === Parameters
|
373
|
+
# username(String):: user's name
|
374
|
+
#
|
375
|
+
# === Return
|
376
|
+
# true:: always returns true
|
377
|
+
def simulate_login(username)
|
378
|
+
info = Etc.getpwnam(username)
|
379
|
+
ENV['USER'] = info.name
|
380
|
+
ENV['HOME'] = info.dir
|
381
|
+
ENV['SHELL'] = info.shell
|
382
|
+
true
|
383
|
+
end
|
384
|
+
|
385
|
+
protected
|
386
|
+
|
387
|
+
# Run a command as root, jumping through a sudo gate if necessary.
|
388
|
+
#
|
389
|
+
# === Parameters
|
390
|
+
# cmd(String):: the command to execute
|
391
|
+
#
|
392
|
+
# === Return
|
393
|
+
# exitstatus(Process::Status):: the exitstatus of the process
|
394
|
+
def sudo(cmd)
|
395
|
+
cmd = "sudo #{cmd}" unless Process.euid == 0
|
396
|
+
|
397
|
+
RightScale::Log.info("LoginUserManager command: #{cmd}")
|
398
|
+
output = %x(#{cmd})
|
399
|
+
result = $?
|
400
|
+
RightScale::Log.info("LoginUserManager result: #{$?.exitstatus}; output: #{cmd}")
|
401
|
+
|
402
|
+
result
|
403
|
+
end
|
404
|
+
|
405
|
+
# Search through some directories to find the location of a binary. Necessary because different
|
406
|
+
# Linux distributions put their user-management utilities in slightly different places.
|
407
|
+
#
|
408
|
+
# === Parameters
|
409
|
+
# cmd(String):: name of command to search for, e.g. 'usermod'
|
410
|
+
#
|
411
|
+
# === Return
|
412
|
+
# path(String):: the absolute path to the command
|
413
|
+
#
|
414
|
+
# === Raise
|
415
|
+
# (LoginManager::SystemConflict):: if the command can't be found
|
416
|
+
#
|
417
|
+
def find_sbin(cmd)
|
418
|
+
path = SBIN_PATHS.detect do |dir|
|
419
|
+
File.exists?(File.join(dir, cmd))
|
420
|
+
end
|
421
|
+
|
422
|
+
raise RightScale::LoginManager::SystemConflict, "Failed to find a suitable implementation of '#{cmd}'." unless path
|
423
|
+
|
424
|
+
File.join(path, cmd)
|
425
|
+
end
|
426
|
+
|
427
|
+
# Downloads a file from specified URL
|
428
|
+
#
|
429
|
+
# === Parameters
|
430
|
+
# url(String):: URL to file
|
431
|
+
# path(String):: downloaded file path
|
432
|
+
#
|
433
|
+
# === Return
|
434
|
+
# downloaded(Boolean):: true if downloaded and saved successfully
|
435
|
+
def download_files(url, path)
|
436
|
+
client = RightSupport::Net::HTTPClient.new
|
437
|
+
response = client.get(url, :timeout => 10)
|
438
|
+
File.open(path, "wb") { |file| file.write(response) } unless response.empty?
|
439
|
+
File.exists?(path)
|
440
|
+
rescue Exception => e
|
441
|
+
Log.error("#{e.class.name}: #{e.message} - #{e.backtrace.first}")
|
442
|
+
false
|
443
|
+
end
|
444
|
+
|
445
|
+
# Extracts an archive and moves files to destination directory
|
446
|
+
# Supported archive types are:
|
447
|
+
# .tar.bz2 / .tbz
|
448
|
+
# .tar.gz / .tgz
|
449
|
+
# .zip
|
450
|
+
#
|
451
|
+
# === Parameters
|
452
|
+
# username(String):: account's username
|
453
|
+
# filename(String):: archive's path
|
454
|
+
# destination_path(String):: path where extracted files should be
|
455
|
+
# moved
|
456
|
+
#
|
457
|
+
# === Return
|
458
|
+
# extracted(Boolean):: true if archive is extracted successfully
|
459
|
+
def extract_files(username, filename, destination_path)
|
460
|
+
escaped_filename = Shellwords.escape(filename)
|
461
|
+
|
462
|
+
case filename
|
463
|
+
when /(?:\.tar\.bz2|\.tbz)$/
|
464
|
+
result = sudo("tar jxf #{escaped_filename} -C #{destination_path}")
|
465
|
+
when /(?:\.tar\.gz|\.tgz)$/
|
466
|
+
result = sudo("tar zxf #{escaped_filename} -C #{destination_path}")
|
467
|
+
when /\.zip$/
|
468
|
+
result = sudo("unzip -o #{escaped_filename} -d #{destination_path}")
|
469
|
+
else
|
470
|
+
raise ArgumentError, "Don't know how to extract #{filename}'"
|
471
|
+
end
|
472
|
+
|
473
|
+
extracted = result.success?
|
474
|
+
chowned = change_owner(username, username, destination_path)
|
475
|
+
|
476
|
+
extracted && chowned
|
477
|
+
end
|
478
|
+
|
479
|
+
# Calculates MD5 checksum for specified file and saves it
|
480
|
+
#
|
481
|
+
# === Parameters
|
482
|
+
# username(String):: account's username
|
483
|
+
# target(String):: path to file
|
484
|
+
# checksum_path(String):: relative path to checksum file
|
485
|
+
# destination(String):: path to file where checksum should be saved
|
486
|
+
#
|
487
|
+
# === Return
|
488
|
+
# nil
|
489
|
+
def save_checksum(username, target, checksum_path, destination)
|
490
|
+
checksum = Digest::MD5.file(target).to_s
|
491
|
+
|
492
|
+
temp_dir = File.join(File.dirname(target), File.dirname(checksum_path))
|
493
|
+
temp_path = File.join(File.dirname(target), checksum_path)
|
494
|
+
|
495
|
+
FileUtils.mkdir_p(temp_dir)
|
496
|
+
FileUtils.chmod_R(0771, temp_dir) # need +x to others for File.exists? => true
|
497
|
+
File.open(temp_path, "w") { |f| f.write(checksum) }
|
498
|
+
|
499
|
+
change_owner(username, username, temp_dir)
|
500
|
+
sudo("mv #{temp_dir} #{destination}")
|
501
|
+
rescue Exception => e
|
502
|
+
STDERR.puts "Failed to save checksum for #{username} profile"
|
503
|
+
STDERR.puts "#{e.class.name}: #{e.message} - #{e.backtrace.first}"
|
504
|
+
Log.error("#{e.class.name}: #{e.message} - #{e.backtrace.first}")
|
505
|
+
end
|
506
|
+
|
507
|
+
# Changes owner of directories and files from given path
|
508
|
+
#
|
509
|
+
# === Parameters
|
510
|
+
# username(String):: desired owner's username
|
511
|
+
# group(String):: desired group name
|
512
|
+
# path(String):: path for owner changing
|
513
|
+
#
|
514
|
+
# === Return
|
515
|
+
# chowned(Boolean):: true if owner changed successfully
|
516
|
+
def change_owner(username, group, path)
|
517
|
+
result = sudo("chown -R #{Shellwords.escape(username)}:#{Shellwords.escape(group)} #{path}")
|
518
|
+
|
519
|
+
result.success?
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2012 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'base64'
|
24
|
+
require 'encryptor'
|
25
|
+
|
26
|
+
module RightScale
|
27
|
+
module MessageEncoder
|
28
|
+
# creates an encoder for the given secret
|
29
|
+
# @param [String] identity needed for SecureSerialization usually of the form (rs-instance-1111-1111)
|
30
|
+
# @param [String] secret for encoding/decoding or nil to use agent's certificate
|
31
|
+
def for_agent(identity, secret=nil)
|
32
|
+
SecureSerializerEncoder.new(identity, secret)
|
33
|
+
end
|
34
|
+
module_function :for_agent
|
35
|
+
|
36
|
+
class SecretSerializer
|
37
|
+
def initialize(serializer, identity, secret)
|
38
|
+
@serializer = serializer
|
39
|
+
@identity = identity
|
40
|
+
@secret = secret
|
41
|
+
end
|
42
|
+
|
43
|
+
# Encodes the given serializable object to text.
|
44
|
+
#
|
45
|
+
# @param [Object] data in form of any serializable object
|
46
|
+
# @return [String] text representing encoded data
|
47
|
+
def dump(data)
|
48
|
+
serialized_data = @serializer.dump(data)
|
49
|
+
encrypted_data = ::Encryptor.encrypt(serialized_data, :key => @secret)
|
50
|
+
printable_data = ::Base64.encode64(encrypted_data)
|
51
|
+
|
52
|
+
# adhere to the SecureSerializer format in case we want to roll this
|
53
|
+
# implementation into that class and distinguish 'secure' encryption
|
54
|
+
# from 'secret' by the presence or absence of 'signature'.
|
55
|
+
#
|
56
|
+
# FIX: do we want to roll them together because it will introduce a
|
57
|
+
# dependency on the encryptor gem?
|
58
|
+
return @serializer.dump({'id' => @identity, 'data' => printable_data, 'encrypted' => true}, :json)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Loads an encoded serializable object from text.
|
62
|
+
#
|
63
|
+
# @param [String] text to decode
|
64
|
+
# @return [Object] decoded object
|
65
|
+
def load(text)
|
66
|
+
hash = @serializer.load(text)
|
67
|
+
printable_data = hash['data'] # the only relevant field in this case
|
68
|
+
encrypted_data = ::Base64.decode64(printable_data)
|
69
|
+
decrypted_data = ::Encryptor.decrypt(encrypted_data, :key => @secret)
|
70
|
+
return @serializer.load(decrypted_data)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Encode/Decode using the secure serializer
|
75
|
+
class SecureSerializerEncoder
|
76
|
+
# @param [String] identity needed for SecureSerialization usually of the form (rs-instance-1111-1111)
|
77
|
+
# @param [String] secret for encoding/decoding or nil to use agent's certificate
|
78
|
+
def initialize(identity, secret=nil)
|
79
|
+
@serializer = serializer_for_instance(identity, secret)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Encodes the given object into a hash.
|
83
|
+
#
|
84
|
+
# @param [Object] data in form of any serializable object
|
85
|
+
# @return [Hash] hash containing dumped data
|
86
|
+
def encode(data)
|
87
|
+
@serializer.dump(data)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Decodes the given hash into the original object.
|
91
|
+
#
|
92
|
+
# @param [Hash] data as a hash containing encoded data
|
93
|
+
# @return [Object] decoded object
|
94
|
+
def decode(data)
|
95
|
+
@serializer.load(data)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# creates a secure serializer that produce a packet that can be
|
101
|
+
# encoded/decoded by the instance.
|
102
|
+
#
|
103
|
+
# @param [String] identity needed for SecureSerialization usually of the form (rs-instance-1111-1111)
|
104
|
+
# @param [String] secret for encoding/decoding or nil to use agent's certificate
|
105
|
+
def serializer_for_instance(agent_id, secret=nil)
|
106
|
+
if secret
|
107
|
+
SecretSerializer.new(Serializer.new, agent_id, secret)
|
108
|
+
else
|
109
|
+
agent_type = 'instance'
|
110
|
+
cert = Certificate.load(AgentConfig.certs_file("#{agent_type}.cert"))
|
111
|
+
key = RsaKeyPair.load(AgentConfig.certs_file("#{agent_type}.key"))
|
112
|
+
store = StaticCertificateStore.new(cert, key, cert, cert)
|
113
|
+
SecureSerializer.new(Serializer.new, agent_id, store)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|