train 3.2.14 → 3.2.20

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. metadata +29 -149
  3. data/LICENSE +0 -201
  4. data/lib/train.rb +0 -193
  5. data/lib/train/errors.rb +0 -44
  6. data/lib/train/extras.rb +0 -11
  7. data/lib/train/extras/command_wrapper.rb +0 -201
  8. data/lib/train/extras/stat.rb +0 -136
  9. data/lib/train/file.rb +0 -212
  10. data/lib/train/file/local.rb +0 -82
  11. data/lib/train/file/local/unix.rb +0 -96
  12. data/lib/train/file/local/windows.rb +0 -68
  13. data/lib/train/file/remote.rb +0 -40
  14. data/lib/train/file/remote/aix.rb +0 -29
  15. data/lib/train/file/remote/linux.rb +0 -21
  16. data/lib/train/file/remote/qnx.rb +0 -41
  17. data/lib/train/file/remote/unix.rb +0 -110
  18. data/lib/train/file/remote/windows.rb +0 -110
  19. data/lib/train/globals.rb +0 -5
  20. data/lib/train/options.rb +0 -81
  21. data/lib/train/platforms.rb +0 -102
  22. data/lib/train/platforms/common.rb +0 -34
  23. data/lib/train/platforms/detect.rb +0 -12
  24. data/lib/train/platforms/detect/helpers/os_common.rb +0 -160
  25. data/lib/train/platforms/detect/helpers/os_linux.rb +0 -80
  26. data/lib/train/platforms/detect/helpers/os_windows.rb +0 -142
  27. data/lib/train/platforms/detect/scanner.rb +0 -85
  28. data/lib/train/platforms/detect/specifications/api.rb +0 -20
  29. data/lib/train/platforms/detect/specifications/os.rb +0 -629
  30. data/lib/train/platforms/detect/uuid.rb +0 -32
  31. data/lib/train/platforms/family.rb +0 -31
  32. data/lib/train/platforms/platform.rb +0 -109
  33. data/lib/train/plugin_test_helper.rb +0 -51
  34. data/lib/train/plugins.rb +0 -40
  35. data/lib/train/plugins/base_connection.rb +0 -198
  36. data/lib/train/plugins/transport.rb +0 -49
  37. data/lib/train/transports/cisco_ios_connection.rb +0 -133
  38. data/lib/train/transports/local.rb +0 -240
  39. data/lib/train/transports/mock.rb +0 -183
  40. data/lib/train/transports/ssh.rb +0 -271
  41. data/lib/train/transports/ssh_connection.rb +0 -342
  42. data/lib/train/version.rb +0 -7
@@ -1,49 +0,0 @@
1
- # encoding: utf-8
2
- #
3
- # Author:: Dominik Richter (<dominik.richter@gmail.com>)
4
- # Author:: Christoph Hartmann (<chris@lollyrock.com>)
5
-
6
- require "logger"
7
- require_relative "../errors"
8
- require_relative "../extras"
9
- require_relative "../options"
10
-
11
- class Train::Plugins
12
- class Transport
13
- include Train::Extras
14
- Train::Options.attach(self)
15
-
16
- require_relative "base_connection"
17
-
18
- # Initialize a new Transport object
19
- #
20
- # @param [Hash] config = nil the configuration for this transport
21
- # @return [Transport] the transport object
22
- def initialize(options = {})
23
- @options = merge_options({}, options || {})
24
- @logger = @options[:logger] || Logger.new($stdout, level: :fatal)
25
- end
26
-
27
- # Create a connection to the target. Options may be provided
28
- # for additional configuration.
29
- #
30
- # @param [Hash] _options = nil provide optional configuration params
31
- # @return [Connection] the connection for this configuration
32
- def connection(_options = nil)
33
- raise Train::ClientError, "#{self.class} does not implement #connection()"
34
- end
35
-
36
- # Register the inheriting class with as a train plugin using the
37
- # provided name.
38
- #
39
- # @param [String] name of the plugin, by which it will be found
40
- def self.name(name)
41
- Train::Plugins.registry[name] = self
42
- end
43
-
44
- private
45
-
46
- # @return [Logger] logger for reporting information
47
- attr_reader :logger
48
- end
49
- end
@@ -1,133 +0,0 @@
1
- # encoding: utf-8
2
-
3
- class Train::Transports::SSH
4
- class CiscoIOSConnection < BaseConnection
5
- class BadEnablePassword < Train::TransportError; end
6
-
7
- def initialize(options)
8
- super(options)
9
-
10
- # Extract options to avoid passing them in to `Net::SSH.start` later
11
- @host = options.delete(:host)
12
- @user = options.delete(:user)
13
- @port = options.delete(:port)
14
- @enable_password = options.delete(:enable_password)
15
-
16
- # Use all options left that are not `nil` for `Net::SSH.start` later
17
- @ssh_options = options.reject { |_key, value| value.nil? }
18
-
19
- @prompt = /^\S+[>#]\r\n.*$/
20
- end
21
-
22
- def uri
23
- "ssh://#{@user}@#{@host}:#{@port}"
24
- end
25
-
26
- def unique_identifier
27
- result = run_command_via_connection("show version | include Processor")
28
- result.stdout.split(" ")[-1]
29
- end
30
-
31
- private
32
-
33
- def establish_connection
34
- logger.debug("[SSH] opening connection to #{self}")
35
-
36
- Net::SSH.start(@host, @user, @ssh_options)
37
- end
38
-
39
- def session
40
- return @session unless @session.nil?
41
-
42
- @session = open_channel(establish_connection)
43
-
44
- # Escalate privilege to enable mode if password is given
45
- if @enable_password
46
- # This verifies we are not in privileged exec mode before running the
47
- # enable command. Otherwise, the password will be in history.
48
- if run_command_via_connection("show privilege").stdout.split[-1] != "15"
49
- # Extra newlines to get back to prompt if incorrect password is used
50
- run_command_via_connection("enable\n#{@enable_password}\n\n\n")
51
- end
52
- end
53
-
54
- # Prevent `--MORE--` by removing terminal length limit
55
- run_command_via_connection("terminal length 0")
56
-
57
- @session
58
- end
59
-
60
- def run_command_via_connection(cmd, &_data_handler)
61
- # Ensure buffer is empty before sending data
62
- @buf = ""
63
-
64
- logger.debug("[SSH] Running `#{cmd}` on #{self}")
65
- session.send_data(cmd + "\r\n")
66
-
67
- logger.debug("[SSH] waiting for prompt")
68
- until @buf =~ @prompt
69
- if @buf =~ /Bad (secrets|password)|Access denied/
70
- raise BadEnablePassword
71
- end
72
-
73
- session.connection.process(0)
74
- end
75
-
76
- # Save the buffer and clear it for the next command
77
- output = @buf.dup
78
- @buf = ""
79
-
80
- format_result(format_output(output, cmd))
81
- end
82
-
83
- ERROR_MATCHERS = [
84
- "Bad IP address",
85
- "Incomplete command",
86
- "Invalid input detected",
87
- "Unrecognized host",
88
- ].freeze
89
-
90
- # IOS commands do not have an exit code so we must compare the command
91
- # output with partial segments of known errors. Then, we return a
92
- # `CommandResult` with arguments in the correct position based on the
93
- # result.
94
- def format_result(result)
95
- if ERROR_MATCHERS.none? { |e| result.include?(e) }
96
- CommandResult.new(result, "", 0)
97
- else
98
- CommandResult.new("", result, 1)
99
- end
100
- end
101
-
102
- # The buffer (@buf) contains all data sent/received on the SSH channel so
103
- # we need to format the data to match what we would expect from Train
104
- def format_output(output, cmd)
105
- leading_prompt = /(\r\n|^)\S+[>#]/
106
- command_string = /#{Regexp.quote(cmd)}\r\n/
107
- trailing_prompt = /\S+[>#](\r\n|$)/
108
- trailing_line_endings = /(\r\n)+$/
109
-
110
- output
111
- .sub(leading_prompt, "")
112
- .sub(command_string, "")
113
- .gsub(trailing_prompt, "")
114
- .gsub(trailing_line_endings, "")
115
- end
116
-
117
- # Create an SSH channel that writes to @buf when data is received
118
- def open_channel(ssh)
119
- logger.debug("[SSH] opening SSH channel to #{self}")
120
- ssh.open_channel do |ch|
121
- ch.on_data do |_, data|
122
- @buf += data
123
- end
124
-
125
- ch.send_channel_request("shell") do |_, success|
126
- raise "Failed to open SSH shell" unless success
127
-
128
- logger.debug("[SSH] shell opened")
129
- end
130
- end
131
- end
132
- end
133
- end
@@ -1,240 +0,0 @@
1
- # encoding: utf-8
2
- #
3
- # author: Dominik Richter
4
- # author: Christoph Hartmann
5
-
6
- require_relative "../plugins"
7
- require_relative "../errors"
8
- require "mixlib/shellout"
9
-
10
- module Train::Transports
11
- class Local < Train.plugin(1)
12
- name "local"
13
-
14
- class PipeError < Train::TransportError; end
15
-
16
- def connection(_ = nil)
17
- @connection ||= Connection.new(@options)
18
- end
19
-
20
- class Connection < BaseConnection
21
- def initialize(options)
22
- super(options)
23
-
24
- @runner = if options[:command_runner]
25
- force_runner(options[:command_runner])
26
- else
27
- select_runner(options)
28
- end
29
- end
30
-
31
- def login_command
32
- nil # none, open your shell
33
- end
34
-
35
- def uri
36
- "local://"
37
- end
38
-
39
- private
40
-
41
- def select_runner(options)
42
- if os.windows?
43
- # Force a 64 bit poweshell if needed
44
- if RUBY_PLATFORM == "i386-mingw32" && os.arch == "x86_64"
45
- powershell_cmd = "#{ENV["SystemRoot"]}\\sysnative\\WindowsPowerShell\\v1.0\\powershell.exe"
46
- else
47
- powershell_cmd = "powershell"
48
- end
49
-
50
- # Attempt to use a named pipe but fallback to ShellOut if that fails
51
- begin
52
- WindowsPipeRunner.new(powershell_cmd)
53
- rescue PipeError
54
- WindowsShellRunner.new(powershell_cmd)
55
- end
56
- else
57
- GenericRunner.new(self, options)
58
- end
59
- end
60
-
61
- def force_runner(command_runner)
62
- case command_runner
63
- when :generic
64
- GenericRunner.new(self, options)
65
- when :windows_pipe
66
- WindowsPipeRunner.new
67
- when :windows_shell
68
- WindowsShellRunner.new
69
- else
70
- raise "Runner type `#{command_runner}` not supported"
71
- end
72
- end
73
-
74
- def run_command_via_connection(cmd, &_data_handler)
75
- # Use the runner if it is available
76
- return @runner.run_command(cmd) if defined?(@runner)
77
-
78
- # If we don't have a runner, such as at the beginning of setting up the
79
- # transport and performing the first few steps of OS detection, fall
80
- # back to shelling out.
81
- res = Mixlib::ShellOut.new(cmd)
82
- res.run_command
83
- Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
84
- rescue Errno::ENOENT => _
85
- CommandResult.new("", "", 1)
86
- end
87
-
88
- def file_via_connection(path)
89
- if os.windows?
90
- Train::File::Local::Windows.new(self, path)
91
- else
92
- Train::File::Local::Unix.new(self, path)
93
- end
94
- end
95
-
96
- class GenericRunner
97
- include_options Train::Extras::CommandWrapper
98
-
99
- def initialize(connection, options)
100
- @cmd_wrapper = Local::CommandWrapper.load(connection, options)
101
- end
102
-
103
- def run_command(cmd)
104
- if defined?(@cmd_wrapper) && !@cmd_wrapper.nil?
105
- cmd = @cmd_wrapper.run(cmd)
106
- end
107
-
108
- res = Mixlib::ShellOut.new(cmd)
109
- res.run_command
110
- Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
111
- end
112
- end
113
-
114
- class WindowsShellRunner
115
- require "json"
116
- require "base64"
117
-
118
- def initialize(powershell_cmd = "powershell")
119
- @powershell_cmd = powershell_cmd
120
- end
121
-
122
- def run_command(script)
123
- # Prevent progress stream from leaking into stderr
124
- script = "$ProgressPreference='SilentlyContinue';" + script
125
-
126
- # Encode script so PowerShell can use it
127
- script = script.encode("UTF-16LE", "UTF-8")
128
- base64_script = Base64.strict_encode64(script)
129
-
130
- cmd = "#{@powershell_cmd} -NoProfile -EncodedCommand #{base64_script}"
131
-
132
- res = Mixlib::ShellOut.new(cmd)
133
- res.run_command
134
- Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
135
- end
136
- end
137
-
138
- class WindowsPipeRunner
139
- require "json"
140
- require "base64"
141
- require "securerandom"
142
-
143
- def initialize(powershell_cmd = "powershell")
144
- @powershell_cmd = powershell_cmd
145
- @pipe = acquire_pipe
146
- raise PipeError if @pipe.nil?
147
- end
148
-
149
- # @param cmd The command to execute
150
- # @return Local::ComandResult with stdout, stderr and exitstatus
151
- # Note that exitstatus ($?) in PowerShell is boolean, but we use a numeric exit code.
152
- # A command that succeeds without setting an exit code will have exitstatus 0
153
- # A command that exits with an exit code will have that value as exitstatus
154
- # A command that fails (e.g. throws exception) before setting an exit code will have exitstatus 1
155
- def run_command(cmd)
156
- script = "$ProgressPreference='SilentlyContinue';" + cmd
157
- encoded_script = Base64.strict_encode64(script)
158
- @pipe.puts(encoded_script)
159
- @pipe.flush
160
- res = OpenStruct.new(JSON.parse(Base64.decode64(@pipe.readline)))
161
- Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
162
- end
163
-
164
- private
165
-
166
- def acquire_pipe
167
- pipe_name = "inspec_#{SecureRandom.hex}"
168
-
169
- start_pipe_server(pipe_name)
170
-
171
- pipe = nil
172
-
173
- # PowerShell needs time to create pipe.
174
- 100.times do
175
- begin
176
- pipe = open("//./pipe/#{pipe_name}", "r+")
177
- break
178
- rescue
179
- sleep 0.1
180
- end
181
- end
182
-
183
- pipe
184
- end
185
-
186
- def start_pipe_server(pipe_name)
187
- require "win32/process"
188
-
189
- script = <<-EOF
190
- $ErrorActionPreference = 'Stop'
191
-
192
- $pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream('#{pipe_name}')
193
- $pipeReader = New-Object System.IO.StreamReader($pipeServer)
194
- $pipeWriter = New-Object System.IO.StreamWriter($pipeServer)
195
-
196
- $pipeServer.WaitForConnection()
197
-
198
- # Create loop to receive and process user commands/scripts
199
- $clientConnected = $true
200
- while($clientConnected) {
201
- $input = $pipeReader.ReadLine()
202
- $command = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($input))
203
-
204
- # Execute user command/script and convert result to JSON
205
- $scriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock($command)
206
- try {
207
- $stdout = & $scriptBlock | Out-String
208
- $exit_code = $LastExitCode
209
- if ($exit_code -eq $null)
210
- {
211
- $exit_code = 0
212
- }
213
- $result = @{ 'stdout' = $stdout ; 'stderr' = ''; 'exitstatus' = $exit_code }
214
- } catch {
215
- $stderr = $_ | Out-String
216
- $exit_code = $LastExitCode
217
- $result = @{ 'stdout' = ''; 'stderr' = $stderr; 'exitstatus' = $exit_code }
218
- }
219
- $resultJSON = $result | ConvertTo-JSON
220
-
221
- # Encode JSON in Base64 and write to pipe
222
- $encodedResult = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($resultJSON))
223
- $pipeWriter.WriteLine($encodedResult)
224
- $pipeWriter.Flush()
225
- }
226
- EOF
227
-
228
- utf8_script = script.encode("UTF-16LE", "UTF-8")
229
- base64_script = Base64.strict_encode64(utf8_script)
230
- cmd = "#{@powershell_cmd} -NoProfile -ExecutionPolicy bypass -NonInteractive -EncodedCommand #{base64_script}"
231
-
232
- server_pid = Process.create(command_line: cmd).process_id
233
-
234
- # Ensure process is killed when the Train process exits
235
- at_exit { Process.kill("KILL", server_pid) }
236
- end
237
- end
238
- end
239
- end
240
- end
@@ -1,183 +0,0 @@
1
- require_relative "../plugins"
2
- require "digest"
3
-
4
- module Train::Transports
5
- class Mock < Train.plugin(1)
6
- name "mock"
7
-
8
- def initialize(conf = nil)
9
- @conf = conf || {}
10
- trace_calls if @conf[:trace]
11
- end
12
-
13
- def connection
14
- @connection ||= Connection.new(@conf)
15
- end
16
-
17
- def to_s
18
- "Mock Transport"
19
- end
20
-
21
- private
22
-
23
- def trace_calls
24
- interface_methods = {
25
- "Train::Transports::Mock" =>
26
- Train::Transports::Mock.instance_methods(false),
27
- "Train::Transports::Mock::Connection" =>
28
- Connection.instance_methods(false),
29
- "Train::Transports::Mock::Connection::File" =>
30
- Connection::FileCommon.instance_methods(false),
31
- "Train::Transports::Mock::Connection::OS" =>
32
- Train::Platform.instance_methods(false),
33
- }
34
-
35
- # rubocop:disable Metrics/ParameterLists
36
- # rubocop:disable Security/Eval
37
- set_trace_func(proc { |event, _file, _line, id, binding, classname|
38
- unless classname.to_s.start_with?("Train::Transports::Mock") &&
39
- (event == "call") &&
40
- interface_methods[classname.to_s].include?(id)
41
- next
42
- end
43
-
44
- # kindly borrowed from the wonderful simple-tracer by matugm
45
- arg_names = eval(
46
- "method(__method__).parameters.map { |arg| arg[1].to_s }",
47
- binding
48
- )
49
- args = eval("#{arg_names}.map { |arg| eval(arg) }", binding).join(", ")
50
- prefix = "-" * (classname.to_s.count(":") - 2) + "> "
51
- puts("#{prefix}#{id} #{args}")
52
- })
53
- # rubocop:enable all
54
- end
55
- end
56
- end
57
-
58
- class Train::Transports::Mock
59
- class Connection < BaseConnection
60
- attr_reader :options
61
-
62
- def initialize(conf = nil)
63
- super(conf)
64
- mock_os
65
- enable_cache(:file)
66
- enable_cache(:command)
67
- end
68
-
69
- def uri
70
- "mock://"
71
- end
72
-
73
- DEFAULTS = {
74
- name: "mock",
75
- family: "mock",
76
- release: "unknown",
77
- arch: "unknown",
78
- }.freeze
79
-
80
- def mock_os(value = {})
81
- value = DEFAULTS
82
- .merge(value || {})
83
- .transform_values { |v| v || "unknown" }
84
-
85
- platform = Train::Platforms.name(value[:name])
86
- platform.find_family_hierarchy
87
- platform.platform = value
88
- platform.add_platform_methods
89
- @platform = platform
90
- end
91
-
92
- def commands=(commands)
93
- @cache[:command] = commands
94
- end
95
-
96
- def commands
97
- @cache[:command]
98
- end
99
-
100
- def files=(files)
101
- @cache[:file] = files
102
- end
103
-
104
- def files
105
- @cache[:file]
106
- end
107
-
108
- def mock_command(cmd, stdout = nil, stderr = nil, exit_status = 0)
109
- @cache[:command][cmd] = Command.new(stdout || "", stderr || "", exit_status)
110
- end
111
-
112
- def command_not_found(cmd)
113
- if @options[:verbose]
114
- $stderr.puts("Command not mocked:")
115
- $stderr.puts(" " + cmd.to_s.split("\n").join("\n "))
116
- $stderr.puts(" SHA: " + Digest::SHA256.hexdigest(cmd.to_s))
117
- end
118
- # return a non-zero exit code
119
- mock_command(cmd, nil, nil, 1)
120
- end
121
-
122
- def file_not_found(path)
123
- $stderr.puts("File not mocked: " + path.to_s) if @options[:verbose]
124
- File.new(self, path)
125
- end
126
-
127
- def to_s
128
- "Mock Connection"
129
- end
130
-
131
- private
132
-
133
- def run_command_via_connection(cmd, &_data_handler)
134
- @cache[:command][Digest::SHA256.hexdigest cmd.to_s] ||
135
- command_not_found(cmd)
136
- end
137
-
138
- def file_via_connection(path)
139
- file_not_found(path)
140
- end
141
- end
142
- end
143
-
144
- class Train::Transports::Mock::Connection
145
- Command = Struct.new(:stdout, :stderr, :exit_status)
146
- end
147
-
148
- class Train::Transports::Mock::Connection
149
- class File < Train::File
150
- def self.from_json(json)
151
- res = new(json["backend"],
152
- json["path"],
153
- json["follow_symlink"])
154
- res.type = json["type"]
155
- Train::File::DATA_FIELDS.each do |f|
156
- m = (f.tr("?", "") + "=").to_sym
157
- res.method(m).call(json[f])
158
- end
159
- res
160
- end
161
-
162
- Train::File::DATA_FIELDS.each do |m|
163
- attr_accessor m.tr("?", "").to_sym
164
- next unless m.include?("?")
165
-
166
- define_method m.to_sym do
167
- method(m.tr("?", "").to_sym).call
168
- end
169
- end
170
- attr_accessor :type
171
-
172
- def initialize(backend, path, follow_symlink = true)
173
- super(backend, path, follow_symlink)
174
- @type = :unknown
175
- @exist = false
176
- end
177
-
178
- def mounted
179
- @mounted ||=
180
- @backend.run_command("mount | grep -- ' on #{@path}'")
181
- end
182
- end
183
- end