Class: Boris::Target

Inherits:
Object
  • Object
show all
Includes:
Lumberjack
Defined in:
lib/boris/target.rb

Overview

Target is the basic class from which you can control the underlying framework for communicating with the device you wish to scan. A Target will allow you to provide options via Options, detect which profiler to use, connect to, and eventually scan your target device, returning a large amount of data.

Instance Attribute Summary collapse

Attributes included from Lumberjack

#logger

Instance Method Summary collapse

Methods included from Lumberjack

#debug, #error, #fatal, #info, #warn

Constructor Details

#initialize(host, options = {}) ⇒ Target

Create the target by passing in a mandatory hostname or IP address, and optional options hash.

When a block is passed, the Boris::Target object itself is returned, and the connection will be automatically disconnected at the end of the block (if it exists).

require 'boris'

target = Boris::Target.new('192.168.1.1', :auto_scrub_data=>false)

Parameters:

  • host (String)

    hostname or IP address

  • options (Hash) (defaults to: {})

    an optional list of options. See Options for a list of all possible options.



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/boris/target.rb', line 48

def initialize(host, options={})
  @logger = Boris.logger

  @host = host

  options ||= {}
  @options = Options.new(options)
  @connector = Connector.new(@host)
  @connection_failures = []
  @unavailable_connection_types = []

  if block_given?
    yield self
    disconnect if @connector.connected?
  end
end

Instance Attribute Details

#connection_failuresObject (readonly)

Returns the value of attribute connection_failures.



27
28
29
# File 'lib/boris/target.rb', line 27

def connection_failures
  @connection_failures
end

#connectorObject

Returns the value of attribute connector.



31
32
33
# File 'lib/boris/target.rb', line 31

def connector
  @connector
end

#hostObject (readonly)

Returns the value of attribute host.



28
29
30
# File 'lib/boris/target.rb', line 28

def host
  @host
end

#optionsObject

Returns the value of attribute options.



33
34
35
# File 'lib/boris/target.rb', line 33

def options
  @options
end

#profilerObject

Returns the value of attribute profiler.



32
33
34
# File 'lib/boris/target.rb', line 32

def profiler
  @profiler
end

#unavailable_connection_typesObject (readonly)

Returns the value of attribute unavailable_connection_types.



29
30
31
# File 'lib/boris/target.rb', line 29

def unavailable_connection_types
  @unavailable_connection_types
end

Instance Method Details

#[](category) ⇒ Array, Hash

Convience method for returning data already collected (internally looks at the Profiler object of Target).

target.get(:hardware)

target[:hardware]          #=> {:cpu_architecture=>64, :cpu_core_count=>2...}

# same thing as:

target.profiler.hardware   #=> {:cpu_architecture=>64, :cpu_core_count=>2...}

Parameters:

  • category (Hash)

    name

Returns:

  • (Array, Hash)

    scanned data elements for provided category



78
79
80
# File 'lib/boris/target.rb', line 78

def [](category)
  @profiler.instance_variable_get("@#{category}".to_sym)
end

#connectBoolean

Connects to the target using the credentials supplied via the connection type as specified by the credential. This method will smartly bypass any further connection attempts if it is determined that the connection will likely never work (for example, if you try to connect via WMI to a Linux host (which will fail), any further attempts to connect to that host via WMI will be ignored).

Returns:

  • (Boolean)

    returns true if the connection attempt succeeded

Raises:



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/boris/target.rb', line 91

def connect
  if @connector.connected?
    raise ConnectionAlreadyActive, 'a connect attempt has been made when active connection already exists'
  elsif @options[:credentials].empty?
    raise InvalidOption, 'no credentials specified'
  end
  
  debug 'preparing to connect'

  @options[:credentials].each do |cred|
    if @connector.connected?
      debug 'active connection established, will not try any more credentials'
      break
    end

    debug "using credential (#{cred[:user]})"

    cred[:connection_types].each do |conn_type|
      if @connector.connected?
        debug 'active connection established, will not try any more connection types'
        break
      end

      case conn_type
      when :snmp
        @connector = SNMPConnector.new(@host, cred, @options)
        @connector.establish_connection
        # we won't add snmp to the @unavailable_connection_types array, as it

        # could respond later with another community string

      when :ssh
        if @unavailable_connection_types.include?(:ssh)
          debug 'connection attempt bypassed (ssh connection unavailable)'
        else
          @connector = SSHConnector.new(@host, cred, @options)
          @connector.establish_connection

          if @connector.reconnectable == false
            @unavailable_connection_types << :ssh
          end
        end
      when :wmi
        if @unavailable_connection_types.include?(:wmi)
          debug 'connection attempt bypassed (wmi connection unavailable)'
        else
          @connector = WMIConnector.new(@host, cred)
          @connector.establish_connection
        
          if @connector.reconnectable == false
            @unavailable_connection_types << :wmi
          end
        end
      end

      info "connection established via #{conn_type}" if @connector.connected?
    end

    @connection_failures = @connection_failures.concat(@connector.failure_messages)
  end

  @connection_failures.uniq!

  info 'all connection attempts failed' if !@connector.connected?

  return @connector.connected? ? true : false
end

#connected?Boolean

Checks on the status of the connection.

Returns:

  • (Boolean)

    returns true if the connection to the target is active



160
161
162
# File 'lib/boris/target.rb', line 160

def connected?
  @connector.connected?
end

#detect_profilerModule

Cycles through all of the profilers as specified in Options for this target. Each profiler includes a method for determining whether the output of a certain command will properly fit the target. Once a suitable profiler is determined, it is then loaded up, which provides Boris the instructions on how to proceed.

Returns:

  • (Module)

    returns the Class of a suitable profiler, else it will throw an error

Raises:

See Also:



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/boris/target.rb', line 177

def detect_profiler
  raise InvalidOption, 'no profilers loaded' if @options[:profilers].empty? || @options[:profilers].nil?
  raise NoActiveConnection, 'no active connection' if @connector.connected? == false

  @options[:profilers].each do |profiler|
    break if @profiler || @connector.failure_messages.any?

    if profiler.connection_type == @connector.class
      debug "testing profiler: #{profiler}"

      if profiler.matches_target?(@connector)
        @profiler = profiler

        debug "suitable profiler found (#{profiler})"

        @profiler = @profiler.new(@connector)
        
        debug "profiler set to #{profiler}"
      end
    end
  end

  raise NoProfilerDetected, 'no suitable profiler found' if !@profiler

  @profiler
end

#disconnectBoolean

Gracefully disconnects from the target (if a connection exists).

Returns:

  • (Boolean)

    returns true if the connection disconnected successfully



207
208
209
# File 'lib/boris/target.rb', line 207

def disconnect
  @connector.disconnect if @connector.connected?
end

#force_profiler_to(profiler) ⇒ Object

Allows us to force the use of a profiler. This can be used instead of #detect_profiler.

Parameters:

  • profiler

    the module of the profiler we wish to set the target to use

See Also:



214
215
216
217
# File 'lib/boris/target.rb', line 214

def force_profiler_to(profiler)
  @profiler = profiler.new(@connector)
  debug "profiler successfully forced to #{profiler}"
end

#get(category) ⇒ Array, Hash

Convience method for collecting data from a Target, where the user can pass in a symbol for the category that should be collected.

target.get(:hardware)          #=> {:cpu_architecture=>64, :cpu_core_count=>2...}

# same thing as:

target.profiler.get_hardware   #=> {:cpu_architecture=>64, :cpu_core_count=>2...}

Parameters:

  • category (Hash)

    name

Returns:

  • (Array, Hash)

    scanned data elements for provided category



230
231
232
233
# File 'lib/boris/target.rb', line 230

def get(category)
  @profiler.send("get_#{category.to_s}")
  self[category]
end

#retrieve_allObject

Note:

Running the full gamut of data collection methods may take some time, and connections over WMI usually take longer than their SSH counterparts. Typically, a Linux server scan can be completed in just a minute or two, whereas a Windows host will be completed in 2-3 minutes (there are many other factors, of course).

Calls all data-collecting methods. Probably will be used in most cases after a connection has been established to the host. Methods that will be called include:

  • get_file_systems (Array)

  • get_hardware (Hash)

  • get_hosted_shares (Array)

  • get_installed_applications (Array)

  • get_local_user_groups (Array)

  • get_installed_patches (Array)

  • get_installed_services (Array)

  • get_network_id (Hash)

  • get_network_interfaces (Array)

  • get_operating_system (Hash)

target.retrieve_all
target[:file_systems].size #=> 2
target[:installed_applications].first #=> {:application_name=>'Adobe Reader'...}

This method will also scrub the data after retrieving all of the items (unless the

:auto_scrub option is set to false).

Raises:

See Also:

  • Profilers::Structure a complete list of the data scructure


260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/boris/target.rb', line 260

def retrieve_all
  raise NoActiveConnection, 'no active connection' if @connector.connected? == false

  debug 'retrieving all configuration items'

  Structure::CATEGORIES.each do |category|
    debug "calling #get_#{category.to_s}"
    @profiler.send("get_#{category.to_s}")
  end
  
  debug 'all items retrieved successfully'

  @profiler.scrub_data! if @options[:auto_scrub_data]
end

#to_json(print_type = :standard) ⇒ JSON

Parses the target’s scanned data into JSON format for portability.

target.get(:network_id)
json_string = target.to_json #=> "...{\"domain\":\"mydomain.com\",\"hostname\":\"SERVER01\"}"...

Parameters:

  • print_type (Hash) (defaults to: :standard)

    a symbol value for json output options. Only option supported now is :pretty_print to determine whether the data should be returned in json format with proper indentation.

Returns:

  • (JSON)

    a JSON object containing retrieved data from the target



284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/boris/target.rb', line 284

def to_json(print_type=:standard)
  json = {}

  Structure::CATEGORIES.each do |category|
    json[category.to_sym] = @profiler.instance_variable_get("@#{category}".to_sym)
  end

  generated_json = print_type == :pretty_print ? JSON.pretty_generate(json) : JSON.generate(json)

  debug "json generated successfully"

  generated_json
end