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.
Files changed (199) hide show
  1. data/actors/agent_manager.rb +88 -0
  2. data/actors/instance_scheduler.rb +321 -0
  3. data/actors/instance_services.rb +64 -0
  4. data/actors/instance_setup.rb +567 -0
  5. data/bin/cloud +25 -0
  6. data/bin/cook_runner +44 -0
  7. data/bin/deploy +120 -0
  8. data/bin/enroll +385 -0
  9. data/bin/rad +32 -0
  10. data/bin/rchk +29 -0
  11. data/bin/rnac +39 -0
  12. data/bin/rs_connect +33 -0
  13. data/bin/rs_log_level +31 -0
  14. data/bin/rs_ohai +28 -0
  15. data/bin/rs_reenroll +31 -0
  16. data/bin/rs_run_recipe +34 -0
  17. data/bin/rs_run_right_script +34 -0
  18. data/bin/rs_shutdown +33 -0
  19. data/bin/rs_tag +33 -0
  20. data/bin/rs_thunk +33 -0
  21. data/bin/rstat +31 -0
  22. data/bin/system +16 -0
  23. data/ext/Rakefile +18 -0
  24. data/init/config.yml +5 -0
  25. data/init/init.rb +79 -0
  26. data/lib/chef/ohai_setup.rb +51 -0
  27. data/lib/chef/plugins/cloud.rb +91 -0
  28. data/lib/chef/plugins/cloudstack.rb +23 -0
  29. data/lib/chef/plugins/ec2.rb +23 -0
  30. data/lib/chef/plugins/linux/block_device2.rb +24 -0
  31. data/lib/chef/plugins/rackspace.rb +23 -0
  32. data/lib/chef/plugins/rightscale.rb +125 -0
  33. data/lib/chef/plugins/windows/network.rb +114 -0
  34. data/lib/chef/plugins.rb +74 -0
  35. data/lib/chef/providers/dns_dnsmadeeasy_provider.rb +81 -0
  36. data/lib/chef/providers/dns_resource.rb +100 -0
  37. data/lib/chef/providers/executable_schedule_provider.rb +70 -0
  38. data/lib/chef/providers/executable_schedule_resource.rb +144 -0
  39. data/lib/chef/providers/remote_recipe_provider.rb +86 -0
  40. data/lib/chef/providers/remote_recipe_resource.rb +101 -0
  41. data/lib/chef/providers/right_link_tag_provider.rb +73 -0
  42. data/lib/chef/providers/right_link_tag_resource.rb +59 -0
  43. data/lib/chef/providers/right_script_provider.rb +190 -0
  44. data/lib/chef/providers/right_script_resource.rb +113 -0
  45. data/lib/chef/providers/rs_shutdown_provider.rb +75 -0
  46. data/lib/chef/providers/rs_shutdown_resource.rb +55 -0
  47. data/lib/chef/providers/server_collection_provider.rb +66 -0
  48. data/lib/chef/providers/server_collection_resource.rb +93 -0
  49. data/lib/chef/providers/windows/powershell_provider.rb +151 -0
  50. data/lib/chef/providers/windows/powershell_resource.rb +111 -0
  51. data/lib/chef/providers/windows/unsupported_provider.rb +51 -0
  52. data/lib/chef/right_providers.rb +55 -0
  53. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ChefNodeCmdlet.csproj +104 -0
  54. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ChefNodeCmdlet.dll-Help.xml +141 -0
  55. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Exceptions.cs +182 -0
  56. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeCommand.cs +58 -0
  57. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeRequest.cs +46 -0
  58. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeResponse.cs +45 -0
  59. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceCommand.cs +58 -0
  60. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceRequest.cs +46 -0
  61. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceResponse.cs +45 -0
  62. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceCommand.cs +58 -0
  63. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceRequest.cs +46 -0
  64. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceResponse.cs +45 -0
  65. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionCommand.cs +178 -0
  66. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionRequest.cs +67 -0
  67. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionResponse.cs +58 -0
  68. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueCommandBase.cs +142 -0
  69. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueRequestBase.cs +64 -0
  70. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueResponseBase.cs +69 -0
  71. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/JsonTransport.cs +110 -0
  72. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/PipeClient.cs +158 -0
  73. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/PipeServer.cs +142 -0
  74. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Properties/AssemblyInfo.cs +16 -0
  75. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ProtocolConstants.cs +55 -0
  76. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ProtocolUtilities.cs +77 -0
  77. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ReadMe.txt +53 -0
  78. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeCommand.cs +59 -0
  79. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeRequest.cs +46 -0
  80. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeResponse.cs +58 -0
  81. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceCommand.cs +59 -0
  82. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceRequest.cs +46 -0
  83. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceResponse.cs +40 -0
  84. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceCommand.cs +59 -0
  85. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceRequest.cs +46 -0
  86. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceResponse.cs +40 -0
  87. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueCommandBase.cs +293 -0
  88. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueRequestBase.cs +75 -0
  89. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueResponseBase.cs +45 -0
  90. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Transport.cs +91 -0
  91. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet.sln +35 -0
  92. data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/Program.cs +374 -0
  93. data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/Properties/AssemblyInfo.cs +16 -0
  94. data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/TestChefNodeCmdlet.csproj +65 -0
  95. data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/Program.cs +136 -0
  96. data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/Properties/AssemblyInfo.cs +36 -0
  97. data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/ReadMe.txt +46 -0
  98. data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/TestNextActionCmdlet.csproj +68 -0
  99. data/lib/chef/windows/bin/Newtonsoft.Json.dll +0 -0
  100. data/lib/chef/windows/chef_node_server.rb +463 -0
  101. data/lib/chef/windows/dynamic_powershell_provider.rb +296 -0
  102. data/lib/chef/windows/pipe_server.rb +283 -0
  103. data/lib/chef/windows/powershell_host.rb +285 -0
  104. data/lib/chef/windows/powershell_pipe_server.rb +136 -0
  105. data/lib/chef/windows/powershell_provider_base.rb +92 -0
  106. data/lib/chef/windows/scripts/run_loop.ps1 +105 -0
  107. data/lib/clouds/cloud.rb +557 -0
  108. data/lib/clouds/cloud_factory.rb +250 -0
  109. data/lib/clouds/cloud_utilities.rb +244 -0
  110. data/lib/clouds/clouds/azure.rb +106 -0
  111. data/lib/clouds/clouds/cloudstack.rb +114 -0
  112. data/lib/clouds/clouds/ec2.rb +113 -0
  113. data/lib/clouds/clouds/eucalyptus.rb +46 -0
  114. data/lib/clouds/clouds/google.rb +102 -0
  115. data/lib/clouds/clouds/none.rb +76 -0
  116. data/lib/clouds/clouds/openstack.rb +30 -0
  117. data/lib/clouds/clouds/rackspace-ng.rb +54 -0
  118. data/lib/clouds/clouds/rackspace.rb +78 -0
  119. data/lib/clouds/clouds/softlayer.rb +91 -0
  120. data/lib/clouds/metadata_formatter.rb +108 -0
  121. data/lib/clouds/metadata_provider.rb +128 -0
  122. data/lib/clouds/metadata_source.rb +87 -0
  123. data/lib/clouds/metadata_sources/certificate_metadata_source.rb +207 -0
  124. data/lib/clouds/metadata_sources/config_drive_metadata_source.rb +129 -0
  125. data/lib/clouds/metadata_sources/file_metadata_source.rb +74 -0
  126. data/lib/clouds/metadata_sources/http_metadata_source.rb +277 -0
  127. data/lib/clouds/metadata_sources/selective_metadata_source.rb +122 -0
  128. data/lib/clouds/metadata_tree_climber.rb +144 -0
  129. data/lib/clouds/metadata_writer.rb +155 -0
  130. data/lib/clouds/metadata_writers/dictionary_metadata_writer.rb +72 -0
  131. data/lib/clouds/metadata_writers/ruby_metadata_writer.rb +76 -0
  132. data/lib/clouds/metadata_writers/shell_metadata_writer.rb +121 -0
  133. data/lib/clouds/register_clouds.rb +34 -0
  134. data/lib/clouds.rb +32 -0
  135. data/lib/gem_dependencies.rb +83 -0
  136. data/lib/git_hooks/commit-msg.rb +7 -0
  137. data/lib/instance/agent_config.rb +168 -0
  138. data/lib/instance/agent_watcher.rb +233 -0
  139. data/lib/instance/audit_cook_stub.rb +104 -0
  140. data/lib/instance/audit_proxy.rb +247 -0
  141. data/lib/instance/bundle_queue.rb +104 -0
  142. data/lib/instance/cook/agent_connection.rb +109 -0
  143. data/lib/instance/cook/audit_logger.rb +165 -0
  144. data/lib/instance/cook/audit_stub.rb +142 -0
  145. data/lib/instance/cook/ca-bundle.crt +2794 -0
  146. data/lib/instance/cook/chef_state.rb +211 -0
  147. data/lib/instance/cook/cook.rb +306 -0
  148. data/lib/instance/cook/cook_state.rb +298 -0
  149. data/lib/instance/cook/cookbook_path_mapping.rb +66 -0
  150. data/lib/instance/cook/cookbook_repo_retriever.rb +190 -0
  151. data/lib/instance/cook/executable_sequence.rb +765 -0
  152. data/lib/instance/cook/external_parameter_gatherer.rb +190 -0
  153. data/lib/instance/cook/repose_downloader.rb +349 -0
  154. data/lib/instance/cook/shutdown_request_proxy.rb +121 -0
  155. data/lib/instance/cook.rb +41 -0
  156. data/lib/instance/downloader.rb +208 -0
  157. data/lib/instance/duplicable.rb +67 -0
  158. data/lib/instance/exceptions.rb +49 -0
  159. data/lib/instance/executable_sequence_proxy.rb +278 -0
  160. data/lib/instance/instance_commands.rb +577 -0
  161. data/lib/instance/instance_state.rb +633 -0
  162. data/lib/instance/json_utilities.rb +102 -0
  163. data/lib/instance/login_manager.rb +533 -0
  164. data/lib/instance/login_user_manager.rb +522 -0
  165. data/lib/instance/message_encoder.rb +118 -0
  166. data/lib/instance/multi_thread_bundle_queue.rb +232 -0
  167. data/lib/instance/operation_context.rb +60 -0
  168. data/lib/instance/options_bag.rb +65 -0
  169. data/lib/instance/payload_formatter.rb +46 -0
  170. data/lib/instance/policy.rb +53 -0
  171. data/lib/instance/policy_audit.rb +100 -0
  172. data/lib/instance/policy_manager.rb +146 -0
  173. data/lib/instance/reenroll_manager.rb +104 -0
  174. data/lib/instance/right_scripts_cookbook.rb +181 -0
  175. data/lib/instance/shutdown_request.rb +221 -0
  176. data/lib/instance/single_thread_bundle_queue.rb +189 -0
  177. data/lib/instance/volume_management.rb +450 -0
  178. data/lib/instance.rb +50 -0
  179. data/lib/repo_conf_generators/apt_conf_generators.rb +106 -0
  180. data/lib/repo_conf_generators/gem_conf_generators.rb +80 -0
  181. data/lib/repo_conf_generators/rightscale_conf_generators.rb +254 -0
  182. data/lib/repo_conf_generators/rightscale_key.pub +17 -0
  183. data/lib/repo_conf_generators/yum_conf_generators.rb +225 -0
  184. data/lib/repo_conf_generators.rb +30 -0
  185. data/lib/run_shell.rb +28 -0
  186. data/scripts/agent_checker.rb +571 -0
  187. data/scripts/agent_controller.rb +247 -0
  188. data/scripts/agent_deployer.rb +148 -0
  189. data/scripts/bundle_runner.rb +336 -0
  190. data/scripts/cloud_controller.rb +176 -0
  191. data/scripts/log_level_manager.rb +142 -0
  192. data/scripts/ohai_runner.rb +33 -0
  193. data/scripts/reenroller.rb +193 -0
  194. data/scripts/server_importer.rb +293 -0
  195. data/scripts/shutdown_client.rb +183 -0
  196. data/scripts/system_configurator.rb +367 -0
  197. data/scripts/tagger.rb +381 -0
  198. data/scripts/thunker.rb +356 -0
  199. 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