Class: LogStash::Filters::RealIp

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

Overview

Evaluate an HTTP request’s client address like Apache httpd’s mod_remoteip or Nginx’s realip module.

For an event like this…

source,ruby

"remote_addr" => "10.1.1.1"
"x_fwd_for" => ["1.2.3.4", "10.2.2.2"]

…an example configuration looks like so:

source,ruby

filter {

real_ip {
  remote_address_field => "remote_addr"
  x_forwarded_for_field => "x_fwd_for"
  trusted_networks => [
    "10.0.0.0/8",
    "192.168.0.0/16"
  ]
}

}

This will evaluate the real client IP, writing it to a new field “realip”. For above example event that would be “1.2.3.4”

Often web servers don’t provide the value of the X-Forwarded-For header as an array. For ease of use the real_ip plugin provides capabilities to parse such a comma-separated string. To enable this feature, use the ‘x_forwarded_for_is_string` option.

For an event like this…

source,ruby

"remote_addr" => "10.1.1.1"
"x_fwd_for" => "1.2.3.4, 10.2.2.2"

…an example configuration looks like so:

source,ruby

filter {

real_ip {
  remote_address_field => "remote_addr"
  x_forwarded_for_field => "x_fwd_for"
  x_forwarded_for_is_string => true
  trusted_networks => [
    "10.0.0.0/8",
    "192.168.0.0/16"
  ]
}

}

In case the plugin fails to evaluate the real client IP, it will add a tag to the event, by default ‘_real_ip_lookup_failure`. The plugin will fail if one of below it true:

  • The ‘remote_address` field is absent.

  • The ‘remote_address` field doesn’t contain an IP address.

  • The filter is configured using ‘x_forwarded_for_is_string = true`, but the

‘x_forwarded_for` field isn’t a string.

  • The ‘x_forwarded_for` field contains anything other that IP addresses.

Evaluation behavior ====

The plugin checks whether the ‘remote_address_field` is trusted, if not, it will be written to `target_field`, and evaluation ends.

Otherwise each IP in the ‘x_forwarded_for_field` is checked, from right to left until an untrusted IP is encountered, which will be written to `target_field` and evaluation ends at that point.

In case ‘remote_address_field` and all IPs in `x_forwarded_for_field` are trusted, the left-most IP of the `x_forwarded_for_field` is written to `target_field`.

Instance Method Summary collapse

Instance Method Details

#filter(event) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
163
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
250
251
252
253
254
255
256
257
258
# File 'lib/logstash/filters/real_ip.rb', line 152

def filter(event)
  remote_addr = event.get(@remote_address_field)
  fwdfor = event.get(@x_forwarded_for_field)

  # check for presence of remote_address_field
  if remote_addr == nil
    @logger.warn("remote_address_field missing from event", :event => event)
    @tags_on_failure.each {|tag| event.tag(tag)}
    return
  end

  # check for presence of x_forwarded_for_field
  if fwdfor == nil
    @logger.debug? and @logger.debug("x_forwarded_for_field missing from event", :event => event)
    event.set(@target_field, remote_addr)
    filter_matched(event)
    return
  end


  begin
    ip = IPAddr.new(remote_addr)
  rescue ArgumentError => e
    @logger.warn("Invalid IP address in remote_addr field", :address => remote_addr, :event => event)
    @tags_on_failure.each {|tag| event.tag(tag)}
    return
  end

  # If remote_addr isn't trusted, we don't even have to look at the X-Forwarded-For header
  if match(ip) == false
    @logger.debug? and @logger.debug("remote_addr isn't trusted. evaluating to remote_addr", :address => remote_addr)
    event.set(@target_field, remote_addr)
    filter_matched(event)
    return
  end

  if @x_forwarded_for_is_string
    if not fwdfor.kind_of?(String)
      @logger.warn("x_forwarded_for_field isn't of type string", :event => event)
      @tags_on_failure.each {|tag| event.tag(tag)}
      return
    end

    fwdfor = fwdfor.gsub(/[ +]/, '').split(/,/)
  else
    # If there's only one IP in the X-Forwarded-For header, it's a string instead
    # of an Array.
    if not fwdfor.kind_of?(Array)
      @logger.debug? and @logger.debug("creating array from single string value xfwdfor", :xfwdfor => fwdfor)
      fwdfor = [fwdfor]
    end
  end

  # in case x_forwarded_for is empty
  if fwdfor.length == 0
    event.set(@target_field, remote_addr)
    filter_matched(event)
    return
  end

  # In case X-Forwarded-For header is set, but zero-length string
  if fwdfor.length == 1 and fwdfor[0].length < 1
    @logger.debug? and @logger.debug("xfwdfor header was present but empty, evaluate to remote_addr", :address => remote_addr)
    event.set(@target_field, remote_addr)
    filter_matched(event)
    return
  end

  found = false
  fatal = false
  target = []
  # check each IP in x_forwarded_for_field from last to first
  (fwdfor.length - 1).downto(0) do |i|
    begin
      ip = IPAddr.new(fwdfor[i])
    rescue ArgumentError => e
      @logger.warn("Invalid IP address", :address => fwdfor[i], :event => event)
      if not found
        @tags_on_failure.each {|tag| event.tag(tag)}
        fatal = true
      end
      @tags_in_invalid_ip.each {|tag| event.tag(tag)}
      next
    end

    target.unshift(ip.to_s()) if @need_all

    # return on the first non-match against our trusted networks
    if found == false and fatal == false and match(ip) == false
      event.set(@target_field, fwdfor[i])
      filter_matched(event)
      return if not @need_all
      found = true
    end
  end

  event.set(@x_forwarded_for_target, target) if @need_all

  if found == false and fatal == false
    # in case remote_addr and all x_forwarded_for IPs are trusted, use the
    # left-most IP from x_forwarded_for
    event.set(@target_field, fwdfor[0])
    filter_matched(event)
    return
  end

end

#registerObject



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/logstash/filters/real_ip.rb', line 115

def register
  if @remote_address_field.length < 1
    raise LogStash::ConfigurationError, I18n.t(
      "logstash.agent.configuration.invalid_plugin_register",
      :plugin => "filter",
      :type => "real_ip",
      :error => "The configuration option 'remote_address_field' must be a non-zero length string"
    )
  end

  if @x_forwarded_for_field.length < 1
    raise LogStash::ConfigurationError, I18n.t(
      "logstash.agent.configuration.invalid_plugin_register",
      :plugin => "filter",
      :type => "real_ip",
      :error => "The configuration option 'x_forwarded_for_field' must be a non-zero length string"
    )
  end

  @trusted_networks.map! {|e| IPAddr.new(e)}
  @need_all = x_forwarded_for_target.length > 0
end