Class: LogStash::Filters::Phpipam

Inherits:
Base
  • Object
show all
Defined in:
lib/logstash/filters/phpipam.rb

Overview

A Logstash filter that looks up an IP-address, and returns results from phpIPAM

Instance Method Summary collapse

Instance Method Details

#cache_data(data) ⇒ void

This method returns an undefined value.

Caches data (if possible)

Parameters:

  • data:

    the data to cache



254
255
256
257
258
259
260
261
262
263
264
# File 'lib/logstash/filters/phpipam.rb', line 254

def cache_data(data)
  data = data.to_json

  File.open(@cache_path, 'a') do |file|
    file.write(data + "\n")
    @logger.debug? && @logger.debug('Cached data', data: data)
  rescue StandardError
    @logger.debug? && @logger.debug('Cache file is not writable, skipping caching of data', data: data, cache_file: @cache_path)
    break
  end
end

#filter(event) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/logstash/filters/phpipam.rb', line 50

def filter(event)
  ip = event.get(@source)

  return if ip.nil?

  return unless valid_ip?(ip, event)

  # Get data from cache or phpIPAM if not in cache
  event_data = search_cache(ip) if @cache
  event_data = phpipam_data(ip, event) unless event_data.is_a?(Hash)

  return if !event_data['error'].nil? && event_data['error']

  # Set the data to the target path
  event.set(@target, event_data)

  # filter_matched should go in the last line of our successful code
  filter_matched(event)
end

#nil_or_empty?(value) ⇒ bool

Checks whether the value is nil or empty

Parameters:

  • value:

    a value to check

Returns:

  • (bool)


150
151
152
# File 'lib/logstash/filters/phpipam.rb', line 150

def nil_or_empty?(value)
  value.nil? || value.empty?
end

#normalize_target(target) ⇒ string

make sure @target is in the format [field name] if defined, i.e. not empty and surrounded by brakets

Parameters:

  • target:

    the target to normalize

Returns:

  • (string)


74
75
76
77
# File 'lib/logstash/filters/phpipam.rb', line 74

def normalize_target(target)
  target = "[#{target}]" if target !~ %r{^\[[^\[\]]+\]$}
  target
end

#okay?(value) ⇒ bool

Checks if the value is defined and not nil or error

Parameters:

  • value:

    a value to check

Returns:

  • (bool)


157
158
159
# File 'lib/logstash/filters/phpipam.rb', line 157

def okay?(value)
  !defined?(value).nil? && !value.nil? && value['error'].nil?
end

#phpipam_data(ip, event) ⇒ hash

Queries phpIPAM and formats the data

Parameters:

  • ip:

    an IP-address to query

Returns:

  • (hash)


164
165
166
167
168
169
170
171
172
173
174
175
176
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/logstash/filters/phpipam.rb', line 164

def phpipam_data(ip, event)
  # Fetch base data needed from phpIPAM
  ip_data = send_rest_request('GET', "api/#{@app_id}/addresses/search/#{ip}/")

  # If the IP wasn't found, return and do nuthin'
  if !ip_data['error'].nil? && ip_data['error']
    event.tag('_phpipam_ip_not_found')
    return { 'error' => true }
  end

  subnet_data = send_rest_request('GET', "api/#{@app_id}/subnets/#{ip_data['subnetId']}/") unless nil_or_empty?(ip_data['subnetId'])
  vlan_data   = send_rest_request('GET', "api/#{@app_id}/vlans/#{subnet_data['vlanId']}/") unless nil_or_empty?(subnet_data['vlanId'])

  device_data   = send_rest_request('GET', "api/#{@app_id}/tools/devices/#{ip_data['deviceId']}/") unless ip_data['deviceId'] == '0' || nil_or_empty?(ip_data['deviceId'])
  location_data = send_rest_request('GET', "api/#{@app_id}/tools/locations/#{ip_data['location']}/") unless ip_data['location'] == '0' || nil_or_empty?(ip_data['location'])

  # Base hash to format data in
  base = {
    'ip' => {},
  }

  # IP information
  base['ip']['id']          = ip_data['id'].to_i
  base['ip']['address']     = ip_data['ip']
  base['ip']['description'] = ip_data['description'] unless nil_or_empty?(ip_data['description'])
  base['ip']['hostname']    = ip_data['hostname'] unless nil_or_empty?(ip_data['hostname'])
  base['ip']['mac']         = ip_data['mac'] unless nil_or_empty?(ip_data['mac'])
  base['ip']['note']        = ip_data['note'] unless nil_or_empty?(ip_data['note'])
  base['ip']['owner']       = ip_data['owner'] unless nil_or_empty?(ip_data['owner'])

  # Subnet information
  if okay?(subnet_data)
    base['subnet']               = {}
    base['subnet']['id']         = ip_data['subnetId'].to_i
    base['subnet']['section_id'] = subnet_data['sectionId'].to_i
    base['subnet']['bitmask']    = subnet_data['calculation']['Subnet bitmask'].to_i
    base['subnet']['wildcard']   = subnet_data['calculation']['Subnet wildcard']
    base['subnet']['netmask']    = subnet_data['calculation']['Subnet netmask']
    base['subnet']['network']    = subnet_data['calculation']['Network']
  end

  # VLAN information
  if okay?(vlan_data)
    base['vlan']                = {}
    base['vlan']['id']          = subnet_data['vlanId'].to_i
    base['vlan']['number']      = vlan_data['number'].to_i unless nil_or_empty?(vlan_data['number'])
    base['vlan']['name']        = vlan_data['name'] unless nil_or_empty?(vlan_data['name'])
    base['vlan']['description'] = vlan_data['description'] unless nil_or_empty?(vlan_data['description'])
  end

  # Device information
  if okay?(device_data)
    type = send_rest_request('GET', "api/#{@app_id}/tools/device_types/#{device_data['type']}/")

    base['device']                = {}
    base['device']['id']          = ip_data['deviceId'].to_i
    base['device']['name']        = device_data['hostname'] unless nil_or_empty?(device_data['hostname'])
    base['device']['description'] = device_data['description'] unless nil_or_empty?(device_data['description'])
    base['device']['type']        = type['tname'] unless nil_or_empty?(type['tname'])
  end

  # If the IP doesn't have the location directly, try to get it from the device or subnet
  unless okay?(location_data)
    location_data = if okay?(device_data) && device_data['location'] != '0' && !nil_or_empty?(device_data['location'])
                      send_rest_request('GET', "api/#{@app_id}/tools/locations/#{device_data['location']}/")
                    elsif okay?(subnet_data) && subnet_data['location'] != '0' && !nil_or_empty?(subnet_data['location'])
                      send_rest_request('GET', "api/#{@app_id}/tools/locations/#{subnet_data['location']}/")
                    end
  end

  # Location information
  if okay?(location_data)
    base['location']                = {}
    base['location']['id']          = location_data['id'].to_i
    base['location']['address']     = location_data['address'] unless nil_or_empty?(location_data['address'])
    base['location']['name']        = location_data['name'] unless nil_or_empty?(location_data['name'])
    base['location']['description'] = location_data['description'] unless nil_or_empty?(location_data['description'])
    base['location']['location']    = { 'lat' => location_data['lat'].to_f, 'lon' => location_data['long'].to_f } unless nil_or_empty?(location_data['lat'])
  end

  # Cache it for future needs
  cache_data(base) if @cache

  # all your base are belong to us
  base
end

#registerObject

Raises:

  • (LogStash::ConfigurationError)


40
41
42
43
44
45
46
47
48
# File 'lib/logstash/filters/phpipam.rb', line 40

def register
  # Validate auth
  raise LogStash::ConfigurationError, 'Authentication was enabled, but no user/pass found' if @auth && (@username.empty? || @password.empty?)

  # Get a session token
  @token = send_rest_request('POST', "api/#{@app_id}/user/")['token'] if @auth

  @target = normalize_target(@target)
end

#search_cache(ip) ⇒ hash/bool

Seaches the cache file for the IP. Returns a hash if the IP was found, else false

Parameters:

  • ip:

    The IP-address to search for

Returns:

  • (hash/bool)


270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/logstash/filters/phpipam.rb', line 270

def search_cache(ip)
  @logger.debug? && @logger.debug('Searching cache...', ip: ip)

  return false unless File.exist?(@cache_path)

  File.foreach(@cache_path) do |line|
    line = JSON.parse(line)
    return line if line['ip']['address'] == ip
  end

  false
end

#send_rest_request(method, url_path) ⇒ hash

Sends a GET method REST request.

Parameters:

  • method:

    which HTTP method to use (POST, GET)

  • url_path:

    path to connect to

Returns:

  • (hash)

Raises:

  • (LogStash::ConfigurationError)


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
# File 'lib/logstash/filters/phpipam.rb', line 100

def send_rest_request(method, url_path)
  @logger.debug? && @logger.debug('Sending request', host: @host, path: url_path)

  url = URI("#{@host}/#{url_path}")

  http             = Net::HTTP.new(url.host, url.port)
  http.use_ssl     = url.scheme == 'https'
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE

  request = case method
            when 'POST' then Net::HTTP::Post.new(url)
            when 'GET' then Net::HTTP::Get.new(url)
            end

  request['accept']        = 'application/json'
  request['content-type']  = 'application/json'
  request['phpipam-token'] = @token unless @token.nil?
  request.basic_auth(@username, @password) if @token.nil? && @auth

  begin
    response = http.request(request)
  rescue StandardError
    raise LogStash::ConfigurationError, I18n.t(
      'logstash.runner.configuration.invalid_plugin_register',
      plugin: 'filter',
      type:   'phpipam',
      error:  'Could not connect to configured host',
    )
  end

  # Parse the body
  rsp = JSON.parse(response.body)

  # Raise an error if not a code 200 is returned
  raise LogStash::ConfigurationError, "#{rsp['code']}:#{rsp['message']}" if rsp['code'] != 200

  # Return error if no data field is present, else return the data
  rsp = if rsp['data'].nil?
          { 'error' => true }
        else
          rsp['data'].is_a?(Array) ? rsp['data'][0] : rsp['data']
        end

  @logger.debug? && @logger.debug('Got response', body: response.body, data: rsp)
  rsp
end

#valid_ip?(ip, event) ⇒ bool

Validates a IP-address

Parameters:

  • ip:

    an IP-address

  • event:

    The Logstash event variable

Returns:

  • (bool)


83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/logstash/filters/phpipam.rb', line 83

def valid_ip?(ip, event)
  IPAddr.new(ip)

  @logger.debug? && @logger.debug('Valid IP', ip: ip)

  # Return true. Rescue would take over if a non-valid IP was parsed
  true
rescue StandardError
  @logger.debug? && @logger.debug('NOT a valid IP', ip: ip)
  event.tag('_phpipam_invalid_ip')
  false
end