right_link 5.9.0

Sign up to get free protection for your applications and to get access to all the features.
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