right_link 5.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/actors/agent_manager.rb +88 -0
- data/actors/instance_scheduler.rb +321 -0
- data/actors/instance_services.rb +64 -0
- data/actors/instance_setup.rb +567 -0
- data/bin/cloud +25 -0
- data/bin/cook_runner +44 -0
- data/bin/deploy +120 -0
- data/bin/enroll +385 -0
- data/bin/rad +32 -0
- data/bin/rchk +29 -0
- data/bin/rnac +39 -0
- data/bin/rs_connect +33 -0
- data/bin/rs_log_level +31 -0
- data/bin/rs_ohai +28 -0
- data/bin/rs_reenroll +31 -0
- data/bin/rs_run_recipe +34 -0
- data/bin/rs_run_right_script +34 -0
- data/bin/rs_shutdown +33 -0
- data/bin/rs_tag +33 -0
- data/bin/rs_thunk +33 -0
- data/bin/rstat +31 -0
- data/bin/system +16 -0
- data/ext/Rakefile +18 -0
- data/init/config.yml +5 -0
- data/init/init.rb +79 -0
- data/lib/chef/ohai_setup.rb +51 -0
- data/lib/chef/plugins/cloud.rb +91 -0
- data/lib/chef/plugins/cloudstack.rb +23 -0
- data/lib/chef/plugins/ec2.rb +23 -0
- data/lib/chef/plugins/linux/block_device2.rb +24 -0
- data/lib/chef/plugins/rackspace.rb +23 -0
- data/lib/chef/plugins/rightscale.rb +125 -0
- data/lib/chef/plugins/windows/network.rb +114 -0
- data/lib/chef/plugins.rb +74 -0
- data/lib/chef/providers/dns_dnsmadeeasy_provider.rb +81 -0
- data/lib/chef/providers/dns_resource.rb +100 -0
- data/lib/chef/providers/executable_schedule_provider.rb +70 -0
- data/lib/chef/providers/executable_schedule_resource.rb +144 -0
- data/lib/chef/providers/remote_recipe_provider.rb +86 -0
- data/lib/chef/providers/remote_recipe_resource.rb +101 -0
- data/lib/chef/providers/right_link_tag_provider.rb +73 -0
- data/lib/chef/providers/right_link_tag_resource.rb +59 -0
- data/lib/chef/providers/right_script_provider.rb +190 -0
- data/lib/chef/providers/right_script_resource.rb +113 -0
- data/lib/chef/providers/rs_shutdown_provider.rb +75 -0
- data/lib/chef/providers/rs_shutdown_resource.rb +55 -0
- data/lib/chef/providers/server_collection_provider.rb +66 -0
- data/lib/chef/providers/server_collection_resource.rb +93 -0
- data/lib/chef/providers/windows/powershell_provider.rb +151 -0
- data/lib/chef/providers/windows/powershell_resource.rb +111 -0
- data/lib/chef/providers/windows/unsupported_provider.rb +51 -0
- data/lib/chef/right_providers.rb +55 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ChefNodeCmdlet.csproj +104 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ChefNodeCmdlet.dll-Help.xml +141 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Exceptions.cs +182 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeCommand.cs +58 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeResponse.cs +45 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceCommand.cs +58 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceResponse.cs +45 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceCommand.cs +58 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceResponse.cs +45 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionCommand.cs +178 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionRequest.cs +67 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionResponse.cs +58 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueCommandBase.cs +142 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueRequestBase.cs +64 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueResponseBase.cs +69 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/JsonTransport.cs +110 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/PipeClient.cs +158 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/PipeServer.cs +142 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Properties/AssemblyInfo.cs +16 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ProtocolConstants.cs +55 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ProtocolUtilities.cs +77 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ReadMe.txt +53 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeCommand.cs +59 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeResponse.cs +58 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceCommand.cs +59 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceResponse.cs +40 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceCommand.cs +59 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceResponse.cs +40 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueCommandBase.cs +293 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueRequestBase.cs +75 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueResponseBase.cs +45 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Transport.cs +91 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet.sln +35 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/Program.cs +374 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/Properties/AssemblyInfo.cs +16 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/TestChefNodeCmdlet.csproj +65 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/Program.cs +136 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/Properties/AssemblyInfo.cs +36 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/ReadMe.txt +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/TestNextActionCmdlet.csproj +68 -0
- data/lib/chef/windows/bin/Newtonsoft.Json.dll +0 -0
- data/lib/chef/windows/chef_node_server.rb +463 -0
- data/lib/chef/windows/dynamic_powershell_provider.rb +296 -0
- data/lib/chef/windows/pipe_server.rb +283 -0
- data/lib/chef/windows/powershell_host.rb +285 -0
- data/lib/chef/windows/powershell_pipe_server.rb +136 -0
- data/lib/chef/windows/powershell_provider_base.rb +92 -0
- data/lib/chef/windows/scripts/run_loop.ps1 +105 -0
- data/lib/clouds/cloud.rb +557 -0
- data/lib/clouds/cloud_factory.rb +250 -0
- data/lib/clouds/cloud_utilities.rb +244 -0
- data/lib/clouds/clouds/azure.rb +106 -0
- data/lib/clouds/clouds/cloudstack.rb +114 -0
- data/lib/clouds/clouds/ec2.rb +113 -0
- data/lib/clouds/clouds/eucalyptus.rb +46 -0
- data/lib/clouds/clouds/google.rb +102 -0
- data/lib/clouds/clouds/none.rb +76 -0
- data/lib/clouds/clouds/openstack.rb +30 -0
- data/lib/clouds/clouds/rackspace-ng.rb +54 -0
- data/lib/clouds/clouds/rackspace.rb +78 -0
- data/lib/clouds/clouds/softlayer.rb +91 -0
- data/lib/clouds/metadata_formatter.rb +108 -0
- data/lib/clouds/metadata_provider.rb +128 -0
- data/lib/clouds/metadata_source.rb +87 -0
- data/lib/clouds/metadata_sources/certificate_metadata_source.rb +207 -0
- data/lib/clouds/metadata_sources/config_drive_metadata_source.rb +129 -0
- data/lib/clouds/metadata_sources/file_metadata_source.rb +74 -0
- data/lib/clouds/metadata_sources/http_metadata_source.rb +277 -0
- data/lib/clouds/metadata_sources/selective_metadata_source.rb +122 -0
- data/lib/clouds/metadata_tree_climber.rb +144 -0
- data/lib/clouds/metadata_writer.rb +155 -0
- data/lib/clouds/metadata_writers/dictionary_metadata_writer.rb +72 -0
- data/lib/clouds/metadata_writers/ruby_metadata_writer.rb +76 -0
- data/lib/clouds/metadata_writers/shell_metadata_writer.rb +121 -0
- data/lib/clouds/register_clouds.rb +34 -0
- data/lib/clouds.rb +32 -0
- data/lib/gem_dependencies.rb +83 -0
- data/lib/git_hooks/commit-msg.rb +7 -0
- data/lib/instance/agent_config.rb +168 -0
- data/lib/instance/agent_watcher.rb +233 -0
- data/lib/instance/audit_cook_stub.rb +104 -0
- data/lib/instance/audit_proxy.rb +247 -0
- data/lib/instance/bundle_queue.rb +104 -0
- data/lib/instance/cook/agent_connection.rb +109 -0
- data/lib/instance/cook/audit_logger.rb +165 -0
- data/lib/instance/cook/audit_stub.rb +142 -0
- data/lib/instance/cook/ca-bundle.crt +2794 -0
- data/lib/instance/cook/chef_state.rb +211 -0
- data/lib/instance/cook/cook.rb +306 -0
- data/lib/instance/cook/cook_state.rb +298 -0
- data/lib/instance/cook/cookbook_path_mapping.rb +66 -0
- data/lib/instance/cook/cookbook_repo_retriever.rb +190 -0
- data/lib/instance/cook/executable_sequence.rb +765 -0
- data/lib/instance/cook/external_parameter_gatherer.rb +190 -0
- data/lib/instance/cook/repose_downloader.rb +349 -0
- data/lib/instance/cook/shutdown_request_proxy.rb +121 -0
- data/lib/instance/cook.rb +41 -0
- data/lib/instance/downloader.rb +208 -0
- data/lib/instance/duplicable.rb +67 -0
- data/lib/instance/exceptions.rb +49 -0
- data/lib/instance/executable_sequence_proxy.rb +278 -0
- data/lib/instance/instance_commands.rb +577 -0
- data/lib/instance/instance_state.rb +633 -0
- data/lib/instance/json_utilities.rb +102 -0
- data/lib/instance/login_manager.rb +533 -0
- data/lib/instance/login_user_manager.rb +522 -0
- data/lib/instance/message_encoder.rb +118 -0
- data/lib/instance/multi_thread_bundle_queue.rb +232 -0
- data/lib/instance/operation_context.rb +60 -0
- data/lib/instance/options_bag.rb +65 -0
- data/lib/instance/payload_formatter.rb +46 -0
- data/lib/instance/policy.rb +53 -0
- data/lib/instance/policy_audit.rb +100 -0
- data/lib/instance/policy_manager.rb +146 -0
- data/lib/instance/reenroll_manager.rb +104 -0
- data/lib/instance/right_scripts_cookbook.rb +181 -0
- data/lib/instance/shutdown_request.rb +221 -0
- data/lib/instance/single_thread_bundle_queue.rb +189 -0
- data/lib/instance/volume_management.rb +450 -0
- data/lib/instance.rb +50 -0
- data/lib/repo_conf_generators/apt_conf_generators.rb +106 -0
- data/lib/repo_conf_generators/gem_conf_generators.rb +80 -0
- data/lib/repo_conf_generators/rightscale_conf_generators.rb +254 -0
- data/lib/repo_conf_generators/rightscale_key.pub +17 -0
- data/lib/repo_conf_generators/yum_conf_generators.rb +225 -0
- data/lib/repo_conf_generators.rb +30 -0
- data/lib/run_shell.rb +28 -0
- data/scripts/agent_checker.rb +571 -0
- data/scripts/agent_controller.rb +247 -0
- data/scripts/agent_deployer.rb +148 -0
- data/scripts/bundle_runner.rb +336 -0
- data/scripts/cloud_controller.rb +176 -0
- data/scripts/log_level_manager.rb +142 -0
- data/scripts/ohai_runner.rb +33 -0
- data/scripts/reenroller.rb +193 -0
- data/scripts/server_importer.rb +293 -0
- data/scripts/shutdown_client.rb +183 -0
- data/scripts/system_configurator.rb +367 -0
- data/scripts/tagger.rb +381 -0
- data/scripts/thunker.rb +356 -0
- metadata +418 -0
@@ -0,0 +1,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
|