train 2.1.7 → 2.1.13
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/train.rb +20 -20
- data/lib/train/errors.rb +1 -1
- data/lib/train/extras.rb +2 -2
- data/lib/train/extras/command_wrapper.rb +24 -24
- data/lib/train/extras/stat.rb +27 -27
- data/lib/train/file.rb +30 -30
- data/lib/train/file/local.rb +8 -8
- data/lib/train/file/local/unix.rb +5 -5
- data/lib/train/file/local/windows.rb +1 -1
- data/lib/train/file/remote.rb +8 -8
- data/lib/train/file/remote/aix.rb +1 -1
- data/lib/train/file/remote/linux.rb +2 -2
- data/lib/train/file/remote/qnx.rb +8 -8
- data/lib/train/file/remote/unix.rb +10 -14
- data/lib/train/file/remote/windows.rb +5 -5
- data/lib/train/globals.rb +1 -1
- data/lib/train/options.rb +8 -8
- data/lib/train/platforms.rb +8 -8
- data/lib/train/platforms/common.rb +1 -1
- data/lib/train/platforms/detect/helpers/os_common.rb +36 -32
- data/lib/train/platforms/detect/helpers/os_linux.rb +12 -12
- data/lib/train/platforms/detect/helpers/os_windows.rb +27 -29
- data/lib/train/platforms/detect/scanner.rb +4 -4
- data/lib/train/platforms/detect/specifications/api.rb +8 -8
- data/lib/train/platforms/detect/specifications/os.rb +252 -252
- data/lib/train/platforms/detect/uuid.rb +5 -7
- data/lib/train/platforms/platform.rb +9 -5
- data/lib/train/plugin_test_helper.rb +12 -12
- data/lib/train/plugins.rb +5 -5
- data/lib/train/plugins/base_connection.rb +13 -13
- data/lib/train/plugins/transport.rb +7 -7
- data/lib/train/transports/azure.rb +23 -23
- data/lib/train/transports/cisco_ios_connection.rb +20 -20
- data/lib/train/transports/clients/azure/graph_rbac.rb +2 -2
- data/lib/train/transports/clients/azure/vault.rb +4 -4
- data/lib/train/transports/docker.rb +4 -10
- data/lib/train/transports/gcp.rb +23 -23
- data/lib/train/transports/helpers/azure/file_credentials.rb +8 -8
- data/lib/train/transports/helpers/azure/file_parser.rb +1 -1
- data/lib/train/transports/helpers/azure/subscription_number_file_parser.rb +1 -1
- data/lib/train/transports/local.rb +22 -22
- data/lib/train/transports/mock.rb +33 -35
- data/lib/train/transports/ssh.rb +47 -47
- data/lib/train/transports/ssh_connection.rb +28 -28
- data/lib/train/transports/vmware.rb +32 -34
- data/lib/train/transports/winrm.rb +37 -37
- data/lib/train/transports/winrm_connection.rb +12 -12
- data/lib/train/version.rb +1 -1
- metadata +2 -2
@@ -1,8 +1,6 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require 'securerandom'
|
5
|
-
require 'json'
|
1
|
+
require "digest/sha1"
|
2
|
+
require "securerandom"
|
3
|
+
require "json"
|
6
4
|
|
7
5
|
module Train::Platforms::Detect
|
8
6
|
class UUID
|
@@ -24,10 +22,10 @@ module Train::Platforms::Detect
|
|
24
22
|
else
|
25
23
|
if @platform[:uuid_command]
|
26
24
|
result = @backend.run_command(@platform[:uuid_command])
|
27
|
-
return uuid_from_string(result.stdout.chomp) if result.exit_status
|
25
|
+
return uuid_from_string(result.stdout.chomp) if result.exit_status == 0 && !result.stdout.empty?
|
28
26
|
end
|
29
27
|
|
30
|
-
raise
|
28
|
+
raise "Could not find platform uuid! Please set a uuid_command for your platform."
|
31
29
|
end
|
32
30
|
end
|
33
31
|
end
|
@@ -44,7 +44,7 @@ module Train::Platforms
|
|
44
44
|
@cleaned_name = nil if force
|
45
45
|
@cleaned_name ||= begin
|
46
46
|
name = (@platform[:name] || @name)
|
47
|
-
name.downcase!.tr!(
|
47
|
+
name.downcase!.tr!(" ", "_") if name =~ /[A-Z ]/
|
48
48
|
name
|
49
49
|
end
|
50
50
|
end
|
@@ -59,7 +59,7 @@ module Train::Platforms
|
|
59
59
|
if respond_to?(name)
|
60
60
|
send(name)
|
61
61
|
else
|
62
|
-
|
62
|
+
"unknown"
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
@@ -73,6 +73,10 @@ module Train::Platforms
|
|
73
73
|
@platform
|
74
74
|
end
|
75
75
|
|
76
|
+
def cisco_ios? # TODO: kinda a hack. needed to prevent tests from corrupting.
|
77
|
+
false
|
78
|
+
end
|
79
|
+
|
76
80
|
# Add generic family? and platform methods to an existing platform
|
77
81
|
#
|
78
82
|
# This is done later to add any custom
|
@@ -84,8 +88,8 @@ module Train::Platforms
|
|
84
88
|
# Add in family methods
|
85
89
|
family_list = Train::Platforms.families
|
86
90
|
family_list.each_value do |k|
|
87
|
-
next if respond_to?(k.name +
|
88
|
-
define_singleton_method(k.name +
|
91
|
+
next if respond_to?(k.name + "?")
|
92
|
+
define_singleton_method(k.name + "?") do
|
89
93
|
family_hierarchy.include?(k.name)
|
90
94
|
end
|
91
95
|
end
|
@@ -99,7 +103,7 @@ module Train::Platforms
|
|
99
103
|
end
|
100
104
|
|
101
105
|
# Create method for name if its not already true
|
102
|
-
m = name +
|
106
|
+
m = name + "?"
|
103
107
|
return if respond_to?(m)
|
104
108
|
define_singleton_method(m) do
|
105
109
|
true
|
@@ -3,27 +3,27 @@
|
|
3
3
|
|
4
4
|
# Load Train. We certainly need the plugin system, and also several other parts
|
5
5
|
# that are tightly coupled. Train itself is fairly light, and non-invasive.
|
6
|
-
require
|
6
|
+
require "train"
|
7
7
|
|
8
8
|
# You can select from a number of test harnesses. Since Train is closely related
|
9
9
|
# to InSpec, and InSpec uses Spec-style controls in profile code, you will
|
10
10
|
# probably want to use something like minitest/spec, which provides Spec-style
|
11
11
|
# tests.
|
12
|
-
require
|
13
|
-
require
|
12
|
+
require "minitest/spec"
|
13
|
+
require "minitest/autorun"
|
14
14
|
|
15
15
|
# Data formats commonly used in testing
|
16
|
-
require
|
17
|
-
require
|
16
|
+
require "json"
|
17
|
+
require "ostruct"
|
18
18
|
|
19
19
|
# Utilities often needed
|
20
|
-
require
|
21
|
-
require
|
22
|
-
require
|
20
|
+
require "fileutils"
|
21
|
+
require "tmpdir"
|
22
|
+
require "pathname"
|
23
23
|
|
24
24
|
# You might want to put some debugging tools here. We run tests to find bugs,
|
25
25
|
# after all.
|
26
|
-
require
|
26
|
+
require "byebug"
|
27
27
|
|
28
28
|
# Configure MiniTest to expose things like `let`
|
29
29
|
class Module
|
@@ -38,11 +38,11 @@ module TrainPluginBaseHelper
|
|
38
38
|
plugin_test_helper_path = Pathname.new(caller_locations(4, 1).first.absolute_path)
|
39
39
|
plugin_src_root = plugin_test_helper_path.parent.parent
|
40
40
|
base.let(:plugin_src_path) { plugin_src_root }
|
41
|
-
base.let(:plugin_fixtures_path) { File.join(plugin_src_root,
|
41
|
+
base.let(:plugin_fixtures_path) { File.join(plugin_src_root, "test", "fixtures") }
|
42
42
|
end
|
43
43
|
|
44
|
-
let(:train_src_path) { File.expand_path(File.join(__FILE__,
|
45
|
-
let(:train_fixtures_path) { File.join(train_src_path,
|
44
|
+
let(:train_src_path) { File.expand_path(File.join(__FILE__, "..", "..")) }
|
45
|
+
let(:train_fixtures_path) { File.join(train_src_path, "test", "fixtures") }
|
46
46
|
let(:registry) { Train::Plugins.registry }
|
47
47
|
end
|
48
48
|
|
data/lib/train/plugins.rb
CHANGED
@@ -3,11 +3,11 @@
|
|
3
3
|
# Author:: Dominik Richter (<dominik.richter@gmail.com>)
|
4
4
|
# Author:: Christoph Hartmann (<chris@lollyrock.com>)
|
5
5
|
|
6
|
-
require
|
6
|
+
require "train/errors"
|
7
7
|
|
8
8
|
module Train
|
9
9
|
class Plugins
|
10
|
-
require
|
10
|
+
require "train/plugins/transport"
|
11
11
|
|
12
12
|
class << self
|
13
13
|
# Retrieve the current plugin registry, containing all plugin names
|
@@ -30,10 +30,10 @@ module Train
|
|
30
30
|
# @return [Transport] the versioned transport base class
|
31
31
|
def self.plugin(version = 1)
|
32
32
|
if version != 1
|
33
|
-
|
34
|
-
|
33
|
+
raise ClientError,
|
34
|
+
"Only understand train plugin version 1. You are trying to "\
|
35
35
|
"initialize a train plugin #{version}, which is not supported "\
|
36
|
-
|
36
|
+
"in the current release of train."
|
37
37
|
end
|
38
38
|
::Train::Plugins::Transport
|
39
39
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
3
|
+
require "train/errors"
|
4
|
+
require "train/extras"
|
5
|
+
require "train/file"
|
6
|
+
require "logger"
|
7
7
|
|
8
8
|
class Train::Plugins::Transport
|
9
9
|
# A Connection instance can be generated and re-generated, given new
|
@@ -21,7 +21,7 @@ class Train::Plugins::Transport
|
|
21
21
|
# @yield [self] yields itself for block-style invocation
|
22
22
|
def initialize(options = nil)
|
23
23
|
@options = options || {}
|
24
|
-
@logger = @options.delete(:logger) || Logger.new(
|
24
|
+
@logger = @options.delete(:logger) || Logger.new($stdout, level: :fatal)
|
25
25
|
Train::Platforms::Detect::Specifications::OS.load
|
26
26
|
Train::Platforms::Detect::Specifications::Api.load
|
27
27
|
|
@@ -64,12 +64,12 @@ class Train::Plugins::Transport
|
|
64
64
|
# Enable caching types for Train. Currently we support
|
65
65
|
# :api_call, :file and :command types
|
66
66
|
def enable_cache(type)
|
67
|
-
|
67
|
+
raise Train::UnknownCacheType, "#{type} is not a valid cache type" unless @cache_enabled.keys.include?(type.to_sym)
|
68
68
|
@cache_enabled[type.to_sym] = true
|
69
69
|
end
|
70
70
|
|
71
71
|
def disable_cache(type)
|
72
|
-
|
72
|
+
raise Train::UnknownCacheType, "#{type} is not a valid cache type" unless @cache_enabled.keys.include?(type.to_sym)
|
73
73
|
@cache_enabled[type.to_sym] = false
|
74
74
|
clear_cache(type.to_sym)
|
75
75
|
end
|
@@ -81,13 +81,13 @@ class Train::Plugins::Transport
|
|
81
81
|
|
82
82
|
def to_json
|
83
83
|
{
|
84
|
-
|
84
|
+
"files" => Hash[@cache[:file].map { |x, y| [x, y.to_json] }],
|
85
85
|
}
|
86
86
|
end
|
87
87
|
|
88
88
|
def load_json(j)
|
89
|
-
require
|
90
|
-
j[
|
89
|
+
require "train/transports/mock"
|
90
|
+
j["files"].each do |path, jf|
|
91
91
|
@cache[:file][path] = Train::Transports::Mock::Connection::File.from_json(jf)
|
92
92
|
end
|
93
93
|
end
|
@@ -137,7 +137,7 @@ class Train::Plugins::Transport
|
|
137
137
|
#
|
138
138
|
# @return [LoginCommand] array of command line tokens
|
139
139
|
def login_command
|
140
|
-
|
140
|
+
raise NotImplementedError, "#{self.class} does not implement #login_command()"
|
141
141
|
end
|
142
142
|
|
143
143
|
# Block and return only when the remote host is prepared and ready to
|
@@ -161,7 +161,7 @@ class Train::Plugins::Transport
|
|
161
161
|
#
|
162
162
|
# @return [CommandResult] contains the result of running the command
|
163
163
|
def run_command_via_connection(_command, &_data_handler)
|
164
|
-
|
164
|
+
raise NotImplementedError, "#{self.class} does not implement #run_command_via_connection()"
|
165
165
|
end
|
166
166
|
|
167
167
|
# Interact with files on the target. Read, write, and get metadata
|
@@ -170,7 +170,7 @@ class Train::Plugins::Transport
|
|
170
170
|
# @param [String] path which is being inspected
|
171
171
|
# @return [FileCommon] file object that allows for interaction
|
172
172
|
def file_via_connection(_path, *_args)
|
173
|
-
|
173
|
+
raise NotImplementedError, "#{self.class} does not implement #file_via_connection(...)"
|
174
174
|
end
|
175
175
|
|
176
176
|
def clear_cache(type)
|
@@ -3,17 +3,17 @@
|
|
3
3
|
# Author:: Dominik Richter (<dominik.richter@gmail.com>)
|
4
4
|
# Author:: Christoph Hartmann (<chris@lollyrock.com>)
|
5
5
|
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
6
|
+
require "logger"
|
7
|
+
require "train/errors"
|
8
|
+
require "train/extras"
|
9
|
+
require "train/options"
|
10
10
|
|
11
11
|
class Train::Plugins
|
12
12
|
class Transport
|
13
13
|
include Train::Extras
|
14
14
|
Train::Options.attach(self)
|
15
15
|
|
16
|
-
require
|
16
|
+
require "train/plugins/base_connection"
|
17
17
|
|
18
18
|
# Initialize a new Transport object
|
19
19
|
#
|
@@ -21,7 +21,7 @@ class Train::Plugins
|
|
21
21
|
# @return [Transport] the transport object
|
22
22
|
def initialize(options = {})
|
23
23
|
@options = merge_options({}, options || {})
|
24
|
-
@logger = @options[:logger] || Logger.new(
|
24
|
+
@logger = @options[:logger] || Logger.new($stdout, level: :fatal)
|
25
25
|
end
|
26
26
|
|
27
27
|
# Create a connection to the target. Options may be provided
|
@@ -30,7 +30,7 @@ class Train::Plugins
|
|
30
30
|
# @param [Hash] _options = nil provide optional configuration params
|
31
31
|
# @return [Connection] the connection for this configuration
|
32
32
|
def connection(_options = nil)
|
33
|
-
|
33
|
+
raise Train::ClientError, "#{self.class} does not implement #connection()"
|
34
34
|
end
|
35
35
|
|
36
36
|
# Register the inheriting class with as a train plugin using the
|
@@ -1,27 +1,27 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
3
|
+
require "train/plugins"
|
4
|
+
require "ms_rest_azure"
|
5
|
+
require "azure_mgmt_resources"
|
6
|
+
require "azure_graph_rbac"
|
7
|
+
require "azure_mgmt_key_vault"
|
8
|
+
require "socket"
|
9
|
+
require "timeout"
|
10
|
+
require "train/transports/helpers/azure/file_credentials"
|
11
|
+
require "train/transports/clients/azure/graph_rbac"
|
12
|
+
require "train/transports/clients/azure/vault"
|
13
13
|
|
14
14
|
module Train::Transports
|
15
15
|
class Azure < Train.plugin(1)
|
16
|
-
name
|
17
|
-
option :tenant_id, default: ENV[
|
18
|
-
option :client_id, default: ENV[
|
19
|
-
option :client_secret, default: ENV[
|
20
|
-
option :subscription_id, default: ENV[
|
21
|
-
option :msi_port, default: ENV[
|
16
|
+
name "azure"
|
17
|
+
option :tenant_id, default: ENV["AZURE_TENANT_ID"]
|
18
|
+
option :client_id, default: ENV["AZURE_CLIENT_ID"]
|
19
|
+
option :client_secret, default: ENV["AZURE_CLIENT_SECRET"]
|
20
|
+
option :subscription_id, default: ENV["AZURE_SUBSCRIPTION_ID"]
|
21
|
+
option :msi_port, default: ENV["AZURE_MSI_PORT"] || "50342"
|
22
22
|
|
23
23
|
# This can provide the client id and secret
|
24
|
-
option :credentials_file, default: ENV[
|
24
|
+
option :credentials_file, default: ENV["AZURE_CRED_FILE"]
|
25
25
|
|
26
26
|
def connection(_ = nil)
|
27
27
|
@connection ||= Connection.new(@options)
|
@@ -30,7 +30,7 @@ module Train::Transports
|
|
30
30
|
class Connection < BaseConnection
|
31
31
|
attr_reader :options
|
32
32
|
|
33
|
-
DEFAULT_FILE = ::File.join(Dir.home,
|
33
|
+
DEFAULT_FILE = ::File.join(Dir.home, ".azure", "credentials")
|
34
34
|
|
35
35
|
def initialize(options)
|
36
36
|
@apis = {}
|
@@ -51,14 +51,14 @@ module Train::Transports
|
|
51
51
|
@options[:msi_port] = @options[:msi_port].to_i unless @options[:msi_port].nil?
|
52
52
|
|
53
53
|
# additional platform details
|
54
|
-
release = Gem.loaded_specs[
|
54
|
+
release = Gem.loaded_specs["azure_mgmt_resources"].version
|
55
55
|
@platform_details = { release: "azure_mgmt_resources-v#{release}" }
|
56
56
|
|
57
57
|
connect
|
58
58
|
end
|
59
59
|
|
60
60
|
def platform
|
61
|
-
force_platform!(
|
61
|
+
force_platform!("azure", @platform_details)
|
62
62
|
end
|
63
63
|
|
64
64
|
def azure_client(klass = ::Azure::Resources::Profiles::Latest::Mgmt::Client, opts = {})
|
@@ -85,13 +85,13 @@ module Train::Transports
|
|
85
85
|
def connect
|
86
86
|
if msi_auth?
|
87
87
|
# this needs set for azure cloud to authenticate
|
88
|
-
ENV[
|
88
|
+
ENV["MSI_VM"] = "true"
|
89
89
|
provider = ::MsRestAzure::MSITokenProvider.new(@options[:msi_port])
|
90
90
|
else
|
91
91
|
provider = ::MsRestAzure::ApplicationTokenProvider.new(
|
92
92
|
@options[:tenant_id],
|
93
93
|
@options[:client_id],
|
94
|
-
@options[:client_secret]
|
94
|
+
@options[:client_secret]
|
95
95
|
)
|
96
96
|
end
|
97
97
|
|
@@ -166,7 +166,7 @@ module Train::Transports
|
|
166
166
|
def port_open?(port, seconds = 3)
|
167
167
|
Timeout.timeout(seconds) do
|
168
168
|
begin
|
169
|
-
TCPSocket.new(
|
169
|
+
TCPSocket.new("localhost", port).close
|
170
170
|
true
|
171
171
|
rescue SystemCallError
|
172
172
|
false
|
@@ -24,8 +24,8 @@ class Train::Transports::SSH
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def unique_identifier
|
27
|
-
result = run_command_via_connection(
|
28
|
-
result.stdout.split(
|
27
|
+
result = run_command_via_connection("show version | include Processor")
|
28
|
+
result.stdout.split(" ")[-1]
|
29
29
|
end
|
30
30
|
|
31
31
|
private
|
@@ -45,26 +45,26 @@ class Train::Transports::SSH
|
|
45
45
|
if @enable_password
|
46
46
|
# This verifies we are not in privileged exec mode before running the
|
47
47
|
# enable command. Otherwise, the password will be in history.
|
48
|
-
if run_command_via_connection(
|
48
|
+
if run_command_via_connection("show privilege").stdout.split[-1] != "15"
|
49
49
|
# Extra newlines to get back to prompt if incorrect password is used
|
50
50
|
run_command_via_connection("enable\n#{@enable_password}\n\n\n")
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
54
|
# Prevent `--MORE--` by removing terminal length limit
|
55
|
-
run_command_via_connection(
|
55
|
+
run_command_via_connection("terminal length 0")
|
56
56
|
|
57
57
|
@session
|
58
58
|
end
|
59
59
|
|
60
60
|
def run_command_via_connection(cmd, &_data_handler)
|
61
61
|
# Ensure buffer is empty before sending data
|
62
|
-
@buf =
|
62
|
+
@buf = ""
|
63
63
|
|
64
64
|
logger.debug("[SSH] Running `#{cmd}` on #{self}")
|
65
65
|
session.send_data(cmd + "\r\n")
|
66
66
|
|
67
|
-
logger.debug(
|
67
|
+
logger.debug("[SSH] waiting for prompt")
|
68
68
|
until @buf =~ @prompt
|
69
69
|
if @buf =~ /Bad (secrets|password)|Access denied/
|
70
70
|
raise BadEnablePassword
|
@@ -74,16 +74,16 @@ class Train::Transports::SSH
|
|
74
74
|
|
75
75
|
# Save the buffer and clear it for the next command
|
76
76
|
output = @buf.dup
|
77
|
-
@buf =
|
77
|
+
@buf = ""
|
78
78
|
|
79
79
|
format_result(format_output(output, cmd))
|
80
80
|
end
|
81
81
|
|
82
82
|
ERROR_MATCHERS = [
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
83
|
+
"Bad IP address",
|
84
|
+
"Incomplete command",
|
85
|
+
"Invalid input detected",
|
86
|
+
"Unrecognized host",
|
87
87
|
].freeze
|
88
88
|
|
89
89
|
# IOS commands do not have an exit code so we must compare the command
|
@@ -92,9 +92,9 @@ class Train::Transports::SSH
|
|
92
92
|
# result.
|
93
93
|
def format_result(result)
|
94
94
|
if ERROR_MATCHERS.none? { |e| result.include?(e) }
|
95
|
-
CommandResult.new(result,
|
95
|
+
CommandResult.new(result, "", 0)
|
96
96
|
else
|
97
|
-
CommandResult.new(
|
97
|
+
CommandResult.new("", result, 1)
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
@@ -107,10 +107,10 @@ class Train::Transports::SSH
|
|
107
107
|
trailing_line_endings = /(\r\n)+$/
|
108
108
|
|
109
109
|
output
|
110
|
-
.sub(leading_prompt,
|
111
|
-
.sub(command_string,
|
112
|
-
.gsub(trailing_prompt,
|
113
|
-
.gsub(trailing_line_endings,
|
110
|
+
.sub(leading_prompt, "")
|
111
|
+
.sub(command_string, "")
|
112
|
+
.gsub(trailing_prompt, "")
|
113
|
+
.gsub(trailing_line_endings, "")
|
114
114
|
end
|
115
115
|
|
116
116
|
# Create an SSH channel that writes to @buf when data is received
|
@@ -121,9 +121,9 @@ class Train::Transports::SSH
|
|
121
121
|
@buf += data
|
122
122
|
end
|
123
123
|
|
124
|
-
ch.send_channel_request(
|
125
|
-
raise
|
126
|
-
logger.debug(
|
124
|
+
ch.send_channel_request("shell") do |_, success|
|
125
|
+
raise "Failed to open SSH shell" unless success
|
126
|
+
logger.debug("[SSH] shell opened")
|
127
127
|
end
|
128
128
|
end
|
129
129
|
end
|