boris 1.0.0.beta.1

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