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,102 @@
1
+ #
2
+ # Copyright (c) 2010-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
+ require 'fileutils'
24
+
25
+ module RightScale
26
+
27
+ # collection of Json utilities
28
+ module JsonUtilities
29
+ # Load JSON from given file
30
+ #
31
+ # === Parameters
32
+ # path(String):: Path to JSON file
33
+ #
34
+ # === Return
35
+ # json(String):: Resulting JSON string
36
+ #
37
+ # === Raise
38
+ # Errno::ENOENT:: Invalid path
39
+ # JSON Exception:: Invalid JSON content
40
+ def self.read_json(path)
41
+ File.open(path, "r") do |f|
42
+ f.flock(File::LOCK_EX)
43
+ return JSON.load(f)
44
+ end
45
+ end
46
+
47
+ # Serialize object to JSON and write result to file, override existing file if any.
48
+ # Note: Do not serialize object if it's a string, allows passing raw JSON.
49
+ #
50
+ # === Parameters
51
+ # path(String):: Path to file being written
52
+ # contents(Object|String):: Object to be serialized into JSON or JSON string
53
+ #
54
+ # === Return
55
+ # true:: Always return true
56
+ def self.write_json(path, contents)
57
+ contents = contents.to_json unless contents.is_a?(String)
58
+ dir = File.dirname(path)
59
+ FileUtils.mkdir_p(dir) unless File.directory?(dir)
60
+ File.open(path, 'w') do |f|
61
+ f.flock(File::LOCK_EX)
62
+ f.write(contents)
63
+ end
64
+ true
65
+ end
66
+
67
+ # Merges any existing JSON file with given contents or else writes a new
68
+ # JSON file while file is locked.
69
+ #
70
+ # === Parameters
71
+ # path(String):: Path to file being written
72
+ # contents(Object|String):: Object to be serialized into JSON or JSON string
73
+ #
74
+ # === Block
75
+ # custom merge callback which takes (left_data, right_data) where left
76
+ # represents current the disk image.
77
+ #
78
+ # === Return
79
+ # true:: Always return true
80
+ def self.merge_json(path, contents)
81
+ contents = JSON.load(contents) if contents.is_a?(String)
82
+ dir = File.dirname(path)
83
+ FileUtils.mkdir_p(dir) unless File.directory?(dir)
84
+ File.open(path, 'a+') do |f|
85
+ f.flock(File::LOCK_EX)
86
+ if File.size(path) > 0
87
+ begin
88
+ previous_contents = JSON.load(f)
89
+ yield(previous_contents, contents)
90
+ rescue JSONError
91
+ # ignored
92
+ end
93
+ f.seek(0)
94
+ f.truncate(0)
95
+ end
96
+ f.write(contents.to_json)
97
+ end
98
+ true
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,533 @@
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
+ require 'set'
24
+
25
+ module RightScale
26
+ class LoginManager
27
+ class SystemConflict < SecurityError; end
28
+
29
+ include RightSupport::Ruby::EasySingleton
30
+
31
+ CONFIG_YAML_FILE = File.normalize_path(File.join(RightScale::Platform.filesystem.right_link_static_state_dir, 'features.yml'))
32
+
33
+ CONFIG=\
34
+ if File.exists?(CONFIG_YAML_FILE)
35
+ RightSupport::Config.features(CONFIG_YAML_FILE)
36
+ else
37
+ RightSupport::Config.features({})
38
+ end
39
+
40
+
41
+ RIGHTSCALE_KEYS_FILE = '/home/rightscale/.ssh/authorized_keys'
42
+ ACTIVE_TAG = 'rs_login:state=active'
43
+ RESTRICTED_TAG = 'rs_login:state=restricted'
44
+ COMMENT = /^\s*#/
45
+ SSH_DEFAULT_KEYS = [ File.join(RightScale::Platform.filesystem.ssh_cfg_dir, 'ssh_host_rsa_key'),
46
+ File.join(RightScale::Platform.filesystem.ssh_cfg_dir, 'ssh_host_dsa_key'),
47
+ File.join(RightScale::Platform.filesystem.ssh_cfg_dir, 'ssh_host_ecdsa_key') ]
48
+
49
+ def initialize
50
+ require 'etc'
51
+ end
52
+
53
+ # Can the login manager function on this platform?
54
+ #
55
+ # == Returns:
56
+ # @return [TrueClass] if LoginManager works on this platform
57
+ # @return [FalseClass] if LoginManager does not work on this platform
58
+ #
59
+ def supported_by_platform?
60
+ right_platform = RightScale::Platform.linux?
61
+ # avoid calling user_exists? on unsupported platform(s)
62
+ right_platform && LoginUserManager.user_exists?('rightscale') && CONFIG['managed_login']['enable']
63
+ end
64
+
65
+ # Enact the login policy specified in new_policy for this system. The policy becomes
66
+ # effective immediately and controls which public keys are trusted for SSH access to
67
+ # the superuser account.
68
+ #
69
+ # == Parameters:
70
+ # @param [RightScale::LoginPolicy] New login policy
71
+ # @param [String] Serialized instance agent identity
72
+ #
73
+ # == Yields:
74
+ # @yield [String] audit content yielded to the block provided
75
+ #
76
+ # == Returns:
77
+ # @return [TrueClass] if supported by given platform
78
+ # @return [FalseClass] if not supported by given platform
79
+ #
80
+ def update_policy(new_policy, agent_identity)
81
+ return false unless supported_by_platform?
82
+
83
+ update_users(new_policy.users, agent_identity, new_policy) do |audit_content|
84
+ yield audit_content if block_given?
85
+ end
86
+
87
+ true
88
+ end
89
+
90
+ # Returns prefix command for public key record
91
+ #
92
+ # == Parameters:
93
+ # @param [String] account's username
94
+ # @param [String] account's email address
95
+ # @param [String] account's uuid
96
+ # @param [Boolean] designates whether the account has superuser privileges
97
+ # @param [String] optional profile_data to be included
98
+ #
99
+ # == Returns:
100
+ # @return [String] command string
101
+ #
102
+ def get_key_prefix(username, email, uuid, superuser, profile_data = nil)
103
+ if profile_data
104
+ profile = " --profile #{Shellwords.escape(profile_data).gsub('"', '\\"')}"
105
+ else
106
+ profile = ""
107
+ end
108
+
109
+ superuser = superuser ? " --superuser" : ""
110
+
111
+ %Q{command="rs_thunk --username #{username} --uuid #{uuid}#{superuser} --email #{email}#{profile}" }
112
+ end
113
+
114
+ # Returns current SSH host keys
115
+ #
116
+ # == Returns:
117
+ # @return [Array<String>] Base64 encoded SSH public keys
118
+ def get_ssh_host_keys()
119
+ # Try to read the sshd_config file first
120
+ keys = File.readlines(
121
+ File.join(RightScale::Platform.filesystem.ssh_cfg_dir, 'sshd_config')).map do |l|
122
+ key = nil
123
+ /^\s*HostKey\s+([^ ].*)/.match(l) { |m| key = m.captures[0] }
124
+ key
125
+ end.compact
126
+
127
+ # If the config file was empty, try these defaults
128
+ keys = keys.empty? ? SSH_DEFAULT_KEYS : keys
129
+
130
+ # Assume the public keys are just the public keys with '.pub' extended and
131
+ # read in each existing key.
132
+ keys.map { |k| k="#{k}.pub"; File.exists?(k) ? File.read(k) : nil }.compact
133
+ end
134
+
135
+ protected
136
+
137
+ # For any user with a public key fingerprint but no public key, obtain the public key
138
+ # from the old policy or by querying RightScale using the fingerprints
139
+ # Remove a user if no public keys are available for it
140
+ #
141
+ # == Parameters:
142
+ # @param [Array<LoginUsers>] Login users whose public keys are to be populated
143
+ # @param [String] Serialized instance agent identity
144
+ # @param [RightScale::LoginPolicy] New login policy
145
+ #
146
+ # == Yields:
147
+ # @yield [String] audit content yielded to the block provided
148
+ #
149
+ # == Returns:
150
+ # @return [TrueClass] always returns true
151
+ #
152
+ def update_users(users, agent_identity, new_policy)
153
+ # Create cache of public keys from stored instance state
154
+ # but there won't be any on initial launch
155
+ public_keys_cache = {}
156
+ if old_policy = InstanceState.login_policy
157
+ public_keys_cache = old_policy.users.inject({}) do |keys, user|
158
+ user.public_key_fingerprints ||= user.public_keys.map { |key| fingerprint(key, user.username) }
159
+ user.public_keys.zip(user.public_key_fingerprints).each { |(k, f)| keys[f] = k if f }
160
+ keys
161
+ end
162
+ end
163
+
164
+ # See if there are any missing keys and if so, send a request to retrieve them
165
+ # Then make one more pass to populate any missing keys and reject any that are still not populated
166
+ unless (missing = populate_public_keys(users, public_keys_cache)).empty?
167
+ payload = {:agent_identity => agent_identity, :public_key_fingerprints => missing.map { |(u, f)| f }}
168
+ request = RightScale::IdempotentRequest.new("/key_server/retrieve_public_keys", payload)
169
+
170
+ request.callback do |public_keys|
171
+ missing = populate_public_keys(users, public_keys, remove_if_missing = true)
172
+ finalize_policy(new_policy, agent_identity, users, missing.map { |(u, f)| u }.uniq) do |audit_content|
173
+ yield audit_content
174
+ end
175
+ end
176
+
177
+ request.errback do |error|
178
+ Log.error("Failed to retrieve public keys for users #{missing.map { |(u, f)| u.username }.uniq.inspect}: #{error}")
179
+ missing = populate_public_keys(users, {}, remove_if_missing = true)
180
+ finalize_policy(new_policy, agent_identity, users, missing.map { |(u, f)| u }.uniq) do |audit_content|
181
+ yield audit_content
182
+ end
183
+ end
184
+
185
+ request.run
186
+ else
187
+ finalize_policy(new_policy, agent_identity, users, missing.map { |(u, f)| u }.uniq) do |audit_content|
188
+ yield audit_content
189
+ end
190
+ end
191
+
192
+ true
193
+ end
194
+
195
+ # Manipulates the authorized_keys file to match the given login policy
196
+ # Schedules expiration of users from policy and audits the policy in
197
+ # a human-readable format
198
+ #
199
+ # == Parameters:
200
+ # @param [RightScale::LoginPolicy] New login policy
201
+ # @param [String] Serialized instance agent identity
202
+ # @param [Array<LoginUsers>] Array of updated users
203
+ # @param [Array<LoginUsers>] Array of users with public keys missing
204
+ #
205
+ # == Yields:
206
+ # @yield [String] audit content yielded to the block provided
207
+ #
208
+ # == Returns:
209
+ # @return [TrueClass] always returns true
210
+ #
211
+ def finalize_policy(new_policy, agent_identity, new_policy_users, missing)
212
+ manage_existing_users(new_policy_users)
213
+
214
+ user_lines = login_users_to_authorized_keys(new_policy_users)
215
+
216
+ InstanceState.login_policy = new_policy
217
+
218
+ write_keys_file(user_lines, RIGHTSCALE_KEYS_FILE, { :user => 'rightscale', :group => 'rightscale' })
219
+
220
+ tags = [ACTIVE_TAG, RESTRICTED_TAG]
221
+ AgentTagManager.instance.add_tags(tags)
222
+
223
+ # Schedule a timer to handle any expiration that is planned to happen in the future
224
+ schedule_expiry(new_policy, agent_identity)
225
+
226
+ # Yield a human-readable description of the policy, e.g. for an audit entry
227
+ yield describe_policy(new_policy_users, new_policy_users.select { |u| u.superuser }, missing)
228
+
229
+ true
230
+ end
231
+
232
+ # Populate missing public keys from old public keys using associated fingerprints
233
+ # Also populate any missing fingerprints where possible
234
+ #
235
+ # == Parameters:
236
+ # @param [Array<LoginUser>] Login users whose public keys are to be updated if nil
237
+ # @param [Hash<String, String>] Public keys with fingerprint as key and public key as value
238
+ # @param [Boolean] Whether to remove a user's public key if it cannot be obtained
239
+ # and the user itself if none of its public keys can be obtained
240
+ #
241
+ # == Returns:
242
+ # @return [Array<LoginUser,String] User and fingerprint for each missing public key
243
+ #
244
+ def populate_public_keys(users, public_keys_cache, remove_if_missing = false)
245
+ missing = []
246
+ users.reject! do |user|
247
+ reject = false
248
+
249
+ # Create any missing fingerprints from the public keys so that fingerprints
250
+ # are as populated as possible
251
+ user.public_key_fingerprints ||= user.public_keys.map { |key| fingerprint(key, user.username) }
252
+ user.public_key_fingerprints = user.public_keys.zip(user.public_key_fingerprints).map do |(k, f)|
253
+ f || fingerprint(k, user.username)
254
+ end
255
+
256
+ # Where possible use cache of old public keys to populate any missing ones
257
+ public_keys = user.public_keys.zip(user.public_key_fingerprints).inject([]) do |keys, (k, f)|
258
+ if f
259
+ if k ||= public_keys_cache[f]
260
+ keys << k
261
+ else
262
+ if remove_if_missing
263
+ Log.error("Failed to obtain public key with fingerprint #{f.inspect} for user #{user.username}, " +
264
+ "removing it from login policy")
265
+ else
266
+ keys << k
267
+ end
268
+ missing << [user, f]
269
+ end
270
+ else
271
+ Log.error("Failed to obtain public key with fingerprint #{f.inspect} for user #{user.username}, " +
272
+ "removing it from login policy")
273
+ end
274
+ keys
275
+ end
276
+
277
+ # Reject user if none of its public keys could be populated
278
+ # This will not happen unless remove_if_missing is true
279
+ if public_keys.empty?
280
+ reject = true
281
+ else
282
+ user.public_keys = public_keys
283
+ end
284
+ reject
285
+ end
286
+ missing
287
+ end
288
+
289
+ # Create fingerprint for public key
290
+ #
291
+ # == Parameters:
292
+ # @param [String] RSA public key
293
+ # @param [String] Name of user owning this key
294
+ #
295
+ # == Return:
296
+ # @return [String] Fingerprint for key if it could create it
297
+ # @return [NilClass] if it could not create it
298
+ #
299
+ def fingerprint(public_key, username)
300
+ LoginUser.fingerprint(public_key) if public_key
301
+ rescue Exception => e
302
+ Log.error("Failed to create public key fingerprint for user #{username}", e)
303
+ nil
304
+ end
305
+
306
+ # Returns array of public keys of specified authorized_keys file
307
+ #
308
+ # == Parameters:
309
+ # @param [String] path to authorized_keys file
310
+ #
311
+ # == Returns:
312
+ #
313
+ # @return [Array<Array(String, String, String)>] array of authorized_key parameters: algorith, public key, comment
314
+ #
315
+ def load_keys(path)
316
+ file_lines = read_keys_file(path)
317
+
318
+ keys = []
319
+ file_lines.map do |l|
320
+ components = LoginPolicy.parse_public_key(l)
321
+
322
+ if components
323
+ #preserve algorithm, key and comments; discard options (the 0th element)
324
+ keys << [ components[1], components[2], components[3] ]
325
+ elsif l =~ COMMENT
326
+ next
327
+ else
328
+ RightScale::Log.error("Malformed (or not SSH2) entry in authorized_keys file: #{l}")
329
+ next
330
+ end
331
+ end
332
+
333
+ keys
334
+ end
335
+
336
+ # Return a verbose, human-readable description of the login policy, suitable
337
+ # for appending to an audit entry. Contains formatting such as newlines and tabs.
338
+ #
339
+ # == Parameters:
340
+ # @param [Array<LoginUser>] All LoginUsers
341
+ # @param [Array<LoginUser>] Subset of LoginUsers who are authorized to act as superusers
342
+ # @param [LoginPolicy] Effective login policy
343
+ # @param [Array<LoginUser>] Users for which a public key could not be obtained
344
+ #
345
+ # == Returns:
346
+ # @return [String] description
347
+ #
348
+ def describe_policy(users, superusers, missing = [])
349
+ normal_users = users - superusers
350
+
351
+ audit = "#{users.size} authorized users (#{normal_users.size} normal, #{superusers.size} superuser).\n"
352
+ audit << "Public key missing for #{missing.map { |u| u.username }.join(", ") }.\n" if missing.size > 0
353
+
354
+ #unless normal_users.empty?
355
+ # audit += "\nNormal users:\n"
356
+ # normal_users.each do |u|
357
+ # audit += " #{u.common_name.ljust(40)} #{u.username}\n"
358
+ # end
359
+ #end
360
+ #
361
+ #unless superusers.empty?
362
+ # audit += "\nSuperusers:\n"
363
+ # superusers.each do |u|
364
+ # audit += " #{u.common_name.ljust(40)} #{u.username}\n"
365
+ # end
366
+ #end
367
+
368
+ return audit
369
+ end
370
+
371
+ # Given a LoginPolicy, add an EventMachine timer to handle the
372
+ # expiration of LoginUsers whose expiry time occurs in the future.
373
+ # This ensures that their login privilege expires on time and in accordance
374
+ # with the policy (so long as the agent is running). Expiry is handled by
375
+ # taking the policy exactly as it was received and passing it to #update_policy,
376
+ # which already knows how to filter out users who are expired at the time the policy
377
+ # is applied.
378
+ #
379
+ # == Parameters:
380
+ # @param [LoginPolicy] Policy for which expiry is to be scheduled
381
+ # @param [String] Serialized instance agent identity
382
+ #
383
+ # == Returns:
384
+ # @return [TrueClass] if expiry was scheduled
385
+ # @return [FalseClass] if expiry was not scheduled
386
+ #
387
+ def schedule_expiry(policy, agent_identity)
388
+ if @expiry_timer
389
+ @expiry_timer.cancel
390
+ @expiry_timer = nil
391
+ end
392
+
393
+ # Find the next expiry time that is in the future
394
+ now = Time.now
395
+ next_expiry = policy.users.map { |u| u.expires_at }.compact.select { |t| t > now }.min
396
+ return false unless next_expiry
397
+ delay = next_expiry.to_i - Time.now.to_i + 1
398
+
399
+ #Clip timer to one day (86,400 sec) to work around EM timer bug involving
400
+ #32-bit integer. This works because update_policy is idempotent and can
401
+ #be safely called at any time. It will "reconverge" if it is called when
402
+ #no permissions have changed.
403
+ delay = [delay, 86_400].min
404
+
405
+ return false unless delay > 0
406
+ @expiry_timer = EventMachine::Timer.new(delay) do
407
+ update_policy(policy, agent_identity)
408
+ end
409
+
410
+ return true
411
+ end
412
+
413
+ # Given a list of LoginUsers, compute an authorized_keys file that encompasses all
414
+ # of the users and has a suitable options field that invokes rs_thunk with the right
415
+ # command-line params for that user. Omit any users who are expired.
416
+ #
417
+ # == Parameters:
418
+ # @param [Array<LoginUser>] array of updated users list
419
+ #
420
+ # == Returns:
421
+ # @return [Array<String>] public key lines of user accounts
422
+ #
423
+ def login_users_to_authorized_keys(new_users)
424
+ now = Time.now
425
+
426
+ user_lines = []
427
+
428
+ new_users.each do |u|
429
+ if u.expires_at.nil? || u.expires_at > now
430
+ u.public_keys.each do |k|
431
+ user_lines << "#{get_key_prefix(u.username, u.common_name, u.uuid, u.superuser, u.profile_data)} #{k}"
432
+ end
433
+ end
434
+ end
435
+
436
+ return user_lines.sort
437
+ end
438
+
439
+ # @TODO docs
440
+ def manage_existing_users(new_policy_users)
441
+ now = Time.now
442
+
443
+ previous = {}
444
+ if InstanceState.login_policy
445
+ InstanceState.login_policy.users.each do |user|
446
+ previous[user.uuid] = user
447
+ end
448
+ end
449
+
450
+ current = {}
451
+ new_policy_users.each do |user|
452
+ current[user.uuid] = user
453
+ end
454
+
455
+ added = current.keys - previous.keys
456
+ removed = previous.keys - current.keys
457
+ stayed = current.keys & previous.keys
458
+
459
+ removed.each do |k|
460
+ begin
461
+ user = current[k] || previous[k]
462
+ LoginUserManager.manage_user(user.uuid, user.superuser, :disable => true)
463
+ rescue Exception => e
464
+ RightScale::Log.error "Failed to disable user '#{user.uuid}': #{e}" unless e.is_a?(ArgumentError)
465
+ end
466
+ end
467
+
468
+ (added + stayed).each do |k|
469
+ begin
470
+ user = current[k] || previous[k]
471
+ disable = !!(user.expires_at) && (now >= user.expires_at)
472
+ LoginUserManager.manage_user(user.uuid, user.superuser, :disable => disable)
473
+ rescue Exception => e
474
+ RightScale::Log.error "Failed to manage existing user '#{user.uuid}': #{e}" unless e.is_a?(ArgumentError)
475
+ end
476
+ end
477
+ rescue Exception => e
478
+ RightScale::Log.error "Failed to manage existing users: #{e}"
479
+ end
480
+
481
+ # === OS specific methods
482
+
483
+ # Reads specified keys file if it exists
484
+ #
485
+ # == Parameters
486
+ # @param [String] path to authorized_keys file
487
+ #
488
+ # == Return
489
+ # @return [Array<String>] list of lines of authorized_keys file
490
+ #
491
+ def read_keys_file(path)
492
+ return [] unless File.exists?(path)
493
+ File.readlines(path).map! { |l| l.chomp.strip }
494
+ end
495
+
496
+ # Replace the contents of specified keys file
497
+ #
498
+ # == Parameters:
499
+ # @param [Array<String>] list of lines that authorized_keys file should contain
500
+ # @param [String] path to authorized_keys file
501
+ # @param [Hash] additional parameters for user/group
502
+ #
503
+ # == Returns:
504
+ # @return [TrueClass] always returns true
505
+ #
506
+ def write_keys_file(keys, keys_file, chown_params = nil)
507
+ dir = File.dirname(keys_file)
508
+ FileUtils.mkdir_p(dir)
509
+ FileUtils.chmod(0700, dir)
510
+
511
+ File.open(keys_file, 'w') do |f|
512
+ f.puts "#" * 78
513
+ f.puts "# USE CAUTION WHEN EDITING THIS FILE BY HAND"
514
+ f.puts "# This file is generated based on the RightScale dashboard permission"
515
+ f.puts "# 'server_login'. You can add trusted public keys to the file, but"
516
+ f.puts "# it is regenerated every 24 hours and keys may be added or removed"
517
+ f.puts "# without notice if they correspond to a dashboard user."
518
+ f.puts "#"
519
+ f.puts "# Instead of editing this file, you probably want to do one of the"
520
+ f.puts "# following:"
521
+ f.puts "# - Edit dashboard permissions (Settings > Account > Users)"
522
+ f.puts "# - Change your personal public key (Settings > User > SSH)"
523
+ f.puts "#"
524
+
525
+ keys.each { |k| f.puts k }
526
+ end
527
+
528
+ FileUtils.chmod(0600, keys_file)
529
+ FileUtils.chown_R(chown_params[:user], chown_params[:group], File.dirname(keys_file)) if chown_params
530
+ return true
531
+ end
532
+ end
533
+ end