boris 1.0.0.beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. data/LICENSE.md +9 -0
  2. data/README.md +94 -0
  3. data/boris.gemspec +28 -0
  4. data/doc/Array.html +437 -0
  5. data/doc/Boris.html +230 -0
  6. data/doc/Boris/ConnectionAlreadyActive.html +123 -0
  7. data/doc/Boris/ConnectionFailed.html +127 -0
  8. data/doc/Boris/Connector.html +794 -0
  9. data/doc/Boris/InvalidCredentials.html +131 -0
  10. data/doc/Boris/InvalidOption.html +123 -0
  11. data/doc/Boris/InvalidTargetName.html +123 -0
  12. data/doc/Boris/Lumberjack.html +466 -0
  13. data/doc/Boris/MissingCredentials.html +123 -0
  14. data/doc/Boris/NoActiveConnection.html +123 -0
  15. data/doc/Boris/NoProfileDetected.html +123 -0
  16. data/doc/Boris/Options.html +783 -0
  17. data/doc/Boris/Profiles.html +117 -0
  18. data/doc/Boris/Profiles/Linux.html +1151 -0
  19. data/doc/Boris/Profiles/RedHat.html +875 -0
  20. data/doc/Boris/Profiles/Solaris.html +1230 -0
  21. data/doc/Boris/Profiles/Structure.html +2050 -0
  22. data/doc/Boris/Profiles/UNIX.html +893 -0
  23. data/doc/Boris/Profiles/Windows.html +1846 -0
  24. data/doc/Boris/Profiles/Windows/Windows2003.html +304 -0
  25. data/doc/Boris/Profiles/Windows/Windows2008.html +379 -0
  26. data/doc/Boris/Profiles/Windows/Windows2012.html +304 -0
  27. data/doc/Boris/SNMPConnector.html +512 -0
  28. data/doc/Boris/SSHConnector.html +633 -0
  29. data/doc/Boris/Target.html +2002 -0
  30. data/doc/Boris/WMIConnector.html +1134 -0
  31. data/doc/BorisLogger.html +217 -0
  32. data/doc/Hash.html +195 -0
  33. data/doc/String.html +1246 -0
  34. data/doc/_index.html +420 -0
  35. data/doc/class_list.html +53 -0
  36. data/doc/css/common.css +1 -0
  37. data/doc/css/full_list.css +57 -0
  38. data/doc/css/style.css +328 -0
  39. data/doc/file.README.html +183 -0
  40. data/doc/file_list.html +55 -0
  41. data/doc/frames.html +28 -0
  42. data/doc/index.html +183 -0
  43. data/doc/js/app.js +214 -0
  44. data/doc/js/full_list.js +173 -0
  45. data/doc/js/jquery.js +4 -0
  46. data/doc/method_list.html +1468 -0
  47. data/doc/top-level-namespace.html +126 -0
  48. data/lib/boris.rb +30 -0
  49. data/lib/boris/connectors.rb +47 -0
  50. data/lib/boris/connectors/snmp.rb +56 -0
  51. data/lib/boris/connectors/ssh.rb +110 -0
  52. data/lib/boris/connectors/wmi.rb +186 -0
  53. data/lib/boris/errors.rb +17 -0
  54. data/lib/boris/helpers/array.rb +63 -0
  55. data/lib/boris/helpers/constants.rb +20 -0
  56. data/lib/boris/helpers/hash.rb +8 -0
  57. data/lib/boris/helpers/scrubber.rb +51 -0
  58. data/lib/boris/helpers/string.rb +130 -0
  59. data/lib/boris/lumberjack.rb +47 -0
  60. data/lib/boris/options.rb +86 -0
  61. data/lib/boris/profiles/linux/redhat.rb +77 -0
  62. data/lib/boris/profiles/linux_core.rb +216 -0
  63. data/lib/boris/profiles/unix/solaris.rb +307 -0
  64. data/lib/boris/profiles/unix_core.rb +85 -0
  65. data/lib/boris/profiles/windows/windows2003.rb +15 -0
  66. data/lib/boris/profiles/windows/windows2008.rb +23 -0
  67. data/lib/boris/profiles/windows/windows2012.rb +15 -0
  68. data/lib/boris/profiles/windows_core.rb +530 -0
  69. data/lib/boris/structure.rb +167 -0
  70. data/lib/boris/target.rb +340 -0
  71. data/test/connector_tests/test_snmp.rb +35 -0
  72. data/test/connector_tests/test_ssh.rb +51 -0
  73. data/test/connector_tests/test_wmi.rb +129 -0
  74. data/test/helper_tests/test_array.rb +25 -0
  75. data/test/helper_tests/test_hash.rb +10 -0
  76. data/test/helper_tests/test_string.rb +136 -0
  77. data/test/profile_tests/test_core_skeleton +107 -0
  78. data/test/profile_tests/test_linux_core.rb +331 -0
  79. data/test/profile_tests/test_redhat.rb +134 -0
  80. data/test/profile_tests/test_solaris.rb +523 -0
  81. data/test/profile_tests/test_unix_core.rb +117 -0
  82. data/test/profile_tests/test_windows.rb +536 -0
  83. data/test/setup_tests.rb +14 -0
  84. data/test/test_all.rb +8 -0
  85. data/test/test_options.rb +44 -0
  86. data/test/test_structure.rb +136 -0
  87. data/test/test_target.rb +146 -0
  88. metadata +241 -0
@@ -0,0 +1,167 @@
1
+ module Boris; module Profiles
2
+ module Structure
3
+ include Lumberjack
4
+
5
+ attr_accessor :file_systems
6
+ attr_accessor :hardware
7
+ attr_accessor :hosted_shares
8
+ attr_accessor :installed_applications
9
+ attr_accessor :installed_patches
10
+ attr_accessor :installed_services
11
+ attr_accessor :local_user_groups
12
+ attr_accessor :network_id
13
+ attr_accessor :network_interfaces
14
+ attr_accessor :operating_system
15
+
16
+ def file_system_template
17
+ [
18
+ :capacity_mb,
19
+ :file_system,
20
+ :mount_point,
21
+ :san_storage,
22
+ :used_space_mb
23
+ ].to_nil_hash
24
+ end
25
+
26
+ def hosted_share_template
27
+ [
28
+ :name,
29
+ :path
30
+ ].to_nil_hash
31
+ end
32
+
33
+ def installed_application_template
34
+ [
35
+ :date_installed,
36
+ :install_location,
37
+ :license_key,
38
+ :name,
39
+ :vendor,
40
+ :version
41
+ ].to_nil_hash
42
+ end
43
+
44
+ def installed_patch_template
45
+ [
46
+ :date_installed,
47
+ :installed_by,
48
+ :patch_code
49
+ ].to_nil_hash
50
+ end
51
+
52
+ def installed_service_template
53
+ [
54
+ :name,
55
+ :install_location,
56
+ :start_mode
57
+ ].to_nil_hash
58
+ end
59
+
60
+ def local_user_groups_template
61
+ {
62
+ :group=>nil,
63
+ :members=>[]
64
+ }
65
+ end
66
+
67
+ def network_interface_template
68
+ [
69
+ :auto_negotiate,
70
+ :current_speed_mbps,
71
+ :duplex,
72
+ :fabric_name,
73
+ :is_uplink,
74
+ :mac_address,
75
+ :model,
76
+ :model_id,
77
+ :mtu,
78
+ :name,
79
+ :node_wwn,
80
+ :port_wwn,
81
+ :remote_mac_address,
82
+ :status,
83
+ :type,
84
+ :vendor,
85
+ :vendor_id,
86
+ :dns_servers=>[],
87
+ :ip_addresses=>[]
88
+ ].to_nil_hash
89
+ end
90
+
91
+ def get_file_systems
92
+ debug 'preparing to fetch file systems'
93
+ @file_systems = []
94
+ end
95
+
96
+ def get_hardware
97
+ debug 'preparing to fetch hardware'
98
+ @hardware = [
99
+ :cpu_architecture,
100
+ :cpu_core_count,
101
+ :cpu_model,
102
+ :cpu_physical_count,
103
+ :cpu_speed_mhz,
104
+ :cpu_vendor,
105
+ :firmware_version,
106
+ :model,
107
+ :memory_installed_mb,
108
+ :serial,
109
+ :vendor
110
+ ].to_nil_hash
111
+ end
112
+
113
+ def get_hosted_shares
114
+ debug 'preparing to fetch hosted shares'
115
+ @hosted_shares = []
116
+ end
117
+
118
+ def get_installed_applications
119
+ debug 'preparing to fetch installed applications'
120
+ @installed_applications = []
121
+ end
122
+
123
+ def get_installed_patches
124
+ debug 'preparing to fetch installed patches'
125
+ @installed_patches = []
126
+ end
127
+
128
+ def get_installed_services
129
+ debug 'preparing to fetch installed_services'
130
+ @installed_services = []
131
+ end
132
+
133
+ def get_local_user_groups
134
+ debug 'preparing to fetch users and groups'
135
+ @local_user_groups = []
136
+ end
137
+
138
+ def get_network_id
139
+ debug 'preparing to fetch network id'
140
+ @network_id = [
141
+ :domain,
142
+ :hostname
143
+ ].to_nil_hash
144
+ end
145
+
146
+ def get_network_interfaces
147
+ debug 'preparing to fetch network_interfaces'
148
+ @network_interfaces = []
149
+ end
150
+
151
+ def get_operating_system
152
+ debug 'preparing to fetch operating system'
153
+ @operating_system = [
154
+ :date_installed,
155
+ :kernel,
156
+ :license_key,
157
+ :name,
158
+ :service_pack,
159
+ :version,
160
+ :features=>[],
161
+ :roles=>[]
162
+ ].to_nil_hash
163
+ end
164
+
165
+ alias get_installed_daemons get_installed_services
166
+ end
167
+ end; end
@@ -0,0 +1,340 @@
1
+ require 'boris/connectors/snmp'
2
+ require 'boris/connectors/ssh'
3
+ require 'boris/connectors/wmi'
4
+
5
+ require 'boris/profiles/linux/redhat'
6
+ require 'boris/profiles/unix/solaris'
7
+
8
+ require 'boris/profiles/windows/windows2003'
9
+ require 'boris/profiles/windows/windows2008'
10
+ require 'boris/profiles/windows/windows2012'
11
+
12
+ module Boris
13
+ PORT_DEFAULTS = {:ssh=>22, :wmi=>135}
14
+ VALID_CONNECTION_TYPES = [:snmp, :ssh, :wmi]
15
+
16
+ # {Boris::Target} is the basic class from which you can control the underlying framework
17
+ # for communicating with the device you wish to scan. A Target will allow you to provide
18
+ # options via {Boris::Options}, detect which profile to use, connect to, and eventually
19
+ # scan your target device, returning a large amount of data.
20
+ class Target
21
+ include Lumberjack
22
+
23
+ attr_reader :host
24
+ attr_reader :target_profile
25
+ attr_reader :unavailable_connection_types
26
+
27
+ attr_accessor :connector
28
+ attr_accessor :options
29
+ attr_accessor :logger
30
+
31
+ # Create the target by passing in a mandatory hostname or IP address, and optional
32
+ # {Boris::Options options hash}.
33
+ #
34
+ # When a block is passed, the {Boris::Target} object itself is returned, and the connection
35
+ # will be automatically disconnected at the end of the block (if it exists).
36
+ #
37
+ # require 'boris'
38
+ #
39
+ # target = Boris::Target.new('192.168.1.1', :log_level=>:debug)
40
+ #
41
+ # @param [String] host hostname or IP address
42
+ # @param [Hash] options an optional list of options. See {Boris::Options} for a list of all
43
+ # possible options.
44
+ def initialize(host, options={})
45
+ @host = host
46
+
47
+ options ||= {}
48
+ @options = Options.new(options)
49
+
50
+ @logger = BorisLogger.new(STDERR)
51
+
52
+ if @options[:log_level]
53
+ @logger.level = case @options[:log_level]
54
+ when :debug then Logger::DEBUG
55
+ when :info then Logger::INFO
56
+ when :warn then Logger::WARN
57
+ when :error then Logger::ERROR
58
+ when :fatal then Logger::FATAL
59
+ else raise ArgumentError, "invalid logger level specified (#{@options[:log_level].inspect})"
60
+ end
61
+ end
62
+
63
+ @unavailable_connection_types = []
64
+
65
+ if block_given?
66
+ yield self
67
+ disconnect if @connector && @connector.connected?
68
+ else
69
+ self
70
+ end
71
+ end
72
+
73
+ # Connects to the target using the credentials supplied via the connection type as specified
74
+ # by the credential. This method will smartly bypass any further connection attempts if it
75
+ # is determined that the connection will likely never work (for example, if you try to
76
+ # connect via WMI to a Linux host (which will fail), any further attempts to connect to that
77
+ # host via WMI will be ignored).
78
+ # @raise [ConnectionAlreadyActive] when a connection is already active
79
+ # @raise [InvalidOption] if credentials are not specified in the target's options hash
80
+ # @return [Boolean] returns true if the connection attempt succeeded
81
+ def connect
82
+ if @connector && @connector.connected?
83
+ raise ConnectionAlreadyActive, 'a connect attempt has been made when active connection already exists'
84
+ elsif @options[:credentials].empty?
85
+ raise InvalidOption, 'no credentials specified'
86
+ end
87
+
88
+ debug 'preparing to connect'
89
+
90
+ @options[:credentials].each do |cred|
91
+ if @connector && @connector.connected?
92
+ debug 'active connection established, will not try any more credentials'
93
+ break
94
+ end
95
+
96
+ debug "using credential (#{cred[:user]})"
97
+
98
+ cred[:connection_types].each do |conn_type|
99
+ if @connector && @connector.connected?
100
+ debug 'active connection established, will not try any more connection types'
101
+ break
102
+ end
103
+
104
+ case conn_type
105
+ when :snmp
106
+ @connector = SNMPConnector.new(@host, cred, @options, @logger)
107
+ @connector.establish_connection
108
+ # we won't add snmp to the @unavailable_connection_types array, as it
109
+ # could respond later with another community string
110
+ when :ssh
111
+ if !@unavailable_connection_types.include?(:ssh)
112
+ @connector = SSHConnector.new(@host, cred, @options, @logger)
113
+ @connector.establish_connection
114
+
115
+ if @connector.reconnectable == false
116
+ @unavailable_connection_types << :ssh
117
+ end
118
+ end
119
+ when :wmi
120
+ if !@unavailable_connection_types.include?(:wmi)
121
+ @connector = WMIConnector.new(@host, cred, @options, @logger)
122
+ @connector.establish_connection
123
+
124
+ if @connector.reconnectable == false
125
+ @unavailable_connection_types << :wmi
126
+ end
127
+ end
128
+ end
129
+
130
+ info "connection established via #{conn_type}" if @connector.connected?
131
+ end
132
+ end
133
+
134
+ @connector = nil if @connector.connected? == false
135
+
136
+ return @connector ? true : false
137
+ end
138
+
139
+ # Checks on the status of the connection.
140
+ #
141
+ # @return [Boolean] returns true if the connection to the target is active
142
+ def connected?
143
+ (@connector && @connector.connected?) ? true : false
144
+ end
145
+
146
+ # Cycles through all of the profiles as specified in {Boris::Options} for this
147
+ # target. Each profile includes a method for determining whether the output of a
148
+ # certain command will properly fit the target. Once a suitable profile is
149
+ # determined, it is then loaded up, which provides {Boris} the instructions on
150
+ # how to proceed.
151
+ #
152
+ # @raise [InvalidOption] if no profiles are loaded prior to calling #detect_profile
153
+ # @raise [NoActiveConnection] if no active connection is available when calling
154
+ # #detect_profile
155
+ # @raise [NoProfileDetected] when no suitable profile was found
156
+ # @return [Module] returns the Module of a suitable profile, else it will throw
157
+ # an error
158
+ # @see #force_profile_to
159
+ def detect_profile
160
+ raise InvalidOption, 'no profiles loaded' if @options[:profiles].empty? || @options[:profiles].nil?
161
+ raise NoActiveConnection, 'no active connection' if (!@connector || @connector.connected? == false)
162
+
163
+ @target_profile = nil
164
+
165
+ @options[:profiles].each do |profile|
166
+ break if @target_profile
167
+
168
+ if profile.connection_type == @connector.class
169
+ debug "testing profile: #{profile}"
170
+
171
+ if profile.matches_target?(@connector)
172
+ @target_profile = profile
173
+
174
+ debug "suitable profile found (#{@target_profile})"
175
+
176
+ self.extend @target_profile
177
+
178
+ debug "profile set to #{@target_profile}"
179
+ end
180
+ end
181
+ end
182
+
183
+ raise NoProfileDetected, 'no suitable profile found' if !@target_profile
184
+
185
+ @target_profile
186
+ end
187
+
188
+ # Gracefully disconnects from the target (if a connection exists).
189
+ #
190
+ # @return [Boolean] returns true if the connection disconnected successfully
191
+ def disconnect
192
+ @connector.disconnect if @connector && @connector.connected?
193
+ end
194
+
195
+ # Allows us to force the use of a profile. This can be used instead of #detect_profile.
196
+ # @param profile the module of the profile we wish to set the target to use
197
+ # @see #detect_profile
198
+ def force_profile_to(profile)
199
+ self.extend profile
200
+ @target_profile = profile
201
+ debug "profile successfully forced to #{profile}"
202
+ end
203
+
204
+ # Calls all data-collecting methods. Probably will be used in most cases after a
205
+ # connection has been established to the host.
206
+ # @note Running the full gamut of data collection methods may take some time, and
207
+ # connections over WMI usually take longer than their SSH counterparts. Typically,
208
+ # a Linux server scan can be completed in around a minute, where a Windows host
209
+ # will be completed in 2-3 minutes (in a perfect world, of course).
210
+ # Methods that will be called include:
211
+ # * get_file_systems (Array)
212
+ # * get_hardware (Hash)
213
+ # * get_hosted_shares (Array)
214
+ # * get_installed_applications (Array)
215
+ # * get_local_user_groups (Array)
216
+ # * get_installed_patches (Array)
217
+ # * get_installed_services (Array)
218
+ # * get_network_id (Hash)
219
+ # * get_network_interfaces (Array)
220
+ # * get_operating_system (Hash)
221
+ #
222
+ # target.retrieve_all
223
+ # target.file_systems.size #=> 2
224
+ # target.installed_applications.first #=> {:application_name=>'Adobe Reader'...}
225
+ #
226
+ # @see Boris::Profiles::Structure Profiles::Structure: a complete list of the data scructure
227
+ # This method will also scrub the data after retrieving all of the items.
228
+ def retrieve_all
229
+ debug 'retrieving all configuration items'
230
+
231
+ get_file_systems
232
+ get_hardware
233
+ get_hosted_shares
234
+ get_installed_applications
235
+ get_local_user_groups
236
+ get_installed_patches
237
+ get_installed_services
238
+ get_network_id
239
+ get_network_interfaces
240
+ get_operating_system
241
+
242
+ debug 'all items retrieved successfully'
243
+
244
+ scrub_data! if @options[:auto_scrub_data]
245
+ end
246
+
247
+ # Attempts to suggest a connection method based on whether certain TCP ports
248
+ # on the target are responding (135 for WMI, 22 for SSH by default). Can be
249
+ # used to speed up the process of determining whether we should try to
250
+ # connect to our host using different methods, or bypass certain attempts
251
+ # entirely.
252
+ #
253
+ # target = Target.new('redhatserver01')
254
+ # target.suggested_connection_method #=> :ssh
255
+ #
256
+ # @return [Symbol] returns :wmi, :ssh, or nil
257
+ # @see tcp_port_responding?
258
+ def suggested_connection_method
259
+ connection_method = nil
260
+
261
+ debug 'detecting if wmi is available'
262
+ connection_method = :wmi if tcp_port_responding?(PORT_DEFAULTS[:wmi])
263
+ info 'wmi does not appear to be responding'
264
+
265
+ if connection_method.nil?
266
+ debug 'detecting if ssh is available'
267
+ connection_method = :ssh if tcp_port_responding?(PORT_DEFAULTS[:ssh])
268
+ info 'ssh does not appear to be responding'
269
+ end
270
+
271
+ info 'failed to detect connection method'if connection_method.nil?
272
+ connection_method
273
+ end
274
+
275
+ # Checks if the supplied TCP port is responding on the target. Useful for
276
+ # determining which connection type we should use instead of taking more
277
+ # time connecting to the target using different methods just to check if
278
+ # they succeed or not.
279
+ #
280
+ # target = Target.new('windowsserver01')
281
+ # target.tcp_port_responding?(22) #=> false
282
+ #
283
+ # @param port the TCP port number we wish to test
284
+ # @return [Boolean] returns true of the supplied port is responding
285
+ def tcp_port_responding?(port)
286
+ status = false
287
+
288
+ debug "checking if port #{port} is responding"
289
+
290
+ begin
291
+ conn = TCPSocket.new(@host, port)
292
+ info "port #{port} is responding"
293
+ conn.close
294
+ debug "connection to port closed"
295
+ status = true
296
+ rescue
297
+ info "port #{port} is not responding"
298
+ status = false
299
+ end
300
+
301
+ status
302
+ end
303
+
304
+ # Parses the target's scanned data into JSON format for portability.
305
+ #
306
+ # target.get_network_id
307
+ # json_string = target.to_json #=> "{\"domain\":\"mydomain.com\",\"hostname\":\"SERVER01\"}"...
308
+ #
309
+ # # The JSON string can later be parsed back into an object
310
+ # target_object = JSON.parse(json_string, :symbolize_names=>true)
311
+ # @param pretty a boolean value to determine whether the data should be
312
+ # returned in json format with proper indentation.
313
+ def to_json(pretty=false)
314
+ json = {}
315
+
316
+ data_vars = %w{
317
+ file_systems
318
+ hardware
319
+ hosted_shares
320
+ installed_applications
321
+ installed_patches
322
+ installed_services
323
+ local_user_groups
324
+ network_id
325
+ network_interfaces
326
+ operating_system
327
+ }
328
+
329
+ data_vars.each do |var|
330
+ json[var.to_sym] = self.instance_variable_get("@#{var}".to_sym)
331
+ end
332
+
333
+ generated_json = pretty ? JSON.pretty_generate(json) : JSON.generate(json)
334
+
335
+ debug "json generated successfully"
336
+
337
+ generated_json
338
+ end
339
+ end
340
+ end