logjam_agent 0.18.0 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 375304657bb752c670704f325f55054bf811d9c5
4
- data.tar.gz: 09ab3cf2c0037d3c2f0986d9d926b342f543e7ac
3
+ metadata.gz: 758acadd75c92e8f90ec5048473e2f5790d708bb
4
+ data.tar.gz: b63d83423a36353e614eca9e0ac25716b498f327
5
5
  SHA512:
6
- metadata.gz: cc70919214e1d998562108257dbb824235c71fc8a0e90c914207968cac7559f07f250205fa081757201639a8192fd4a44041b940cade6b29b1d71c857de66db4
7
- data.tar.gz: f952178602f923e69cd98a9963e9a95bd4e01ed1575b520a60e0bad92e68ba97e5b3418421e171f601c1844c9dca71fde22a6cd3955e5a9fad39c1329513ba06
6
+ metadata.gz: aced1491d337a12c47163792ec3f0f2bc413f2fd358536fe405c39d33afe22a0417a9388160384ae091006b7b77a2c498d2db3801f8062c5885f12cffb34f0f7
7
+ data.tar.gz: 2833c3916bc3538c4a8d6cf49bc8d17b1711b407489e2bc7fe1204026a3b3f5ddca5155a15fb2e377eb6c1b54582d99b2efbd02fc2f34186524e14991e03165f
@@ -0,0 +1,181 @@
1
+ # As the name suggests, still is a copy of the rails/actionpack remote_ip middleware
2
+ # It's duplicated here, in case it's not already available (e.g. in older rails versions),
3
+ # because we don't want to add actionpack as dependecy.
4
+ #
5
+ # see https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/remote_ip.rb
6
+
7
+ require 'ipaddr'
8
+
9
+ module LogjamAgent
10
+ module ActionDispatch
11
+ # This middleware calculates the IP address of the remote client that is
12
+ # making the request. It does this by checking various headers that could
13
+ # contain the address, and then picking the last-set address that is not
14
+ # on the list of trusted IPs. This follows the precedent set by e.g.
15
+ # {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453],
16
+ # with {reasoning explained at length}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection]
17
+ # by @gingerlime. A more detailed explanation of the algorithm is given
18
+ # at GetIp#calculate_ip.
19
+ #
20
+ # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
21
+ # requires. Some Rack servers simply drop preceding headers, and only report
22
+ # the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
23
+ # If you are behind multiple proxy servers (like NGINX to HAProxy to Unicorn)
24
+ # then you should test your Rack server to make sure your data is good.
25
+ #
26
+ # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING.
27
+ # This middleware assumes that there is at least one proxy sitting around
28
+ # and setting headers with the client's remote IP address. If you don't use
29
+ # a proxy, because you are hosted on e.g. Heroku without SSL, any client can
30
+ # claim to have any IP address by setting the X-Forwarded-For header. If you
31
+ # care about that, then you need to explicitly drop or ignore those headers
32
+ # sometime before this middleware runs.
33
+ class RemoteIp
34
+ class IpSpoofAttackError < StandardError; end
35
+
36
+ # The default trusted IPs list simply includes IP addresses that are
37
+ # guaranteed by the IP specification to be private addresses. Those will
38
+ # not be the ultimate client IP in production, and so are discarded. See
39
+ # http://en.wikipedia.org/wiki/Private_network for details.
40
+ TRUSTED_PROXIES = [
41
+ "127.0.0.1", # localhost IPv4
42
+ "::1", # localhost IPv6
43
+ "fc00::/7", # private IPv6 range fc00::/7
44
+ "10.0.0.0/8", # private IPv4 range 10.x.x.x
45
+ "172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
46
+ "192.168.0.0/16", # private IPv4 range 192.168.x.x
47
+ ].map { |proxy| IPAddr.new(proxy) }
48
+
49
+ attr_reader :check_ip, :proxies
50
+
51
+ # Create a new +RemoteIp+ middleware instance.
52
+ #
53
+ # The +check_ip_spoofing+ option is on by default. When on, an exception
54
+ # is raised if it looks like the client is trying to lie about its own IP
55
+ # address. It makes sense to turn off this check on sites aimed at non-IP
56
+ # clients (like WAP devices), or behind proxies that set headers in an
57
+ # incorrect or confusing way (like AWS ELB).
58
+ #
59
+ # The +custom_proxies+ argument can take an Array of string, IPAddr, or
60
+ # Regexp objects which will be used instead of +TRUSTED_PROXIES+. If a
61
+ # single string, IPAddr, or Regexp object is provided, it will be used in
62
+ # addition to +TRUSTED_PROXIES+. Any proxy setup will put the value you
63
+ # want in the middle (or at the beginning) of the X-Forwarded-For list,
64
+ # with your proxy servers after it. If your proxies aren't removed, pass
65
+ # them in via the +custom_proxies+ parameter. That way, the middleware will
66
+ # ignore those IP addresses, and return the one that you want.
67
+ def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
68
+ @app = app
69
+ @check_ip = check_ip_spoofing
70
+ @proxies = if custom_proxies.blank?
71
+ TRUSTED_PROXIES
72
+ elsif custom_proxies.respond_to?(:any?)
73
+ custom_proxies
74
+ else
75
+ Array(custom_proxies) + TRUSTED_PROXIES
76
+ end
77
+ end
78
+
79
+ # Since the IP address may not be needed, we store the object here
80
+ # without calculating the IP to keep from slowing down the majority of
81
+ # requests. For those requests that do need to know the IP, the
82
+ # GetIp#calculate_ip method will calculate the memoized client IP address.
83
+ def call(env)
84
+ env["action_dispatch.remote_ip"] = GetIp.new(env, self)
85
+ @app.call(env)
86
+ end
87
+
88
+ # The GetIp class exists as a way to defer processing of the request data
89
+ # into an actual IP address. If the ActionDispatch::Request#remote_ip method
90
+ # is called, this class will calculate the value and then memoize it.
91
+ class GetIp
92
+ def initialize(env, middleware)
93
+ @env = env
94
+ @check_ip = middleware.check_ip
95
+ @proxies = middleware.proxies
96
+ end
97
+
98
+ # Sort through the various IP address headers, looking for the IP most
99
+ # likely to be the address of the actual remote client making this
100
+ # request.
101
+ #
102
+ # REMOTE_ADDR will be correct if the request is made directly against the
103
+ # Ruby process, on e.g. Heroku. When the request is proxied by another
104
+ # server like HAProxy or NGINX, the IP address that made the original
105
+ # request will be put in an X-Forwarded-For header. If there are multiple
106
+ # proxies, that header may contain a list of IPs. Other proxy services
107
+ # set the Client-Ip header instead, so we check that too.
108
+ #
109
+ # As discussed in {this post about Rails IP Spoofing}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
110
+ # while the first IP in the list is likely to be the "originating" IP,
111
+ # it could also have been set by the client maliciously.
112
+ #
113
+ # In order to find the first address that is (probably) accurate, we
114
+ # take the list of IPs, remove known and trusted proxies, and then take
115
+ # the last address left, which was presumably set by one of those proxies.
116
+ def calculate_ip
117
+ # Set by the Rack web server, this is a single value.
118
+ remote_addr = ips_from('REMOTE_ADDR').last
119
+
120
+ # Could be a CSV list and/or repeated headers that were concatenated.
121
+ client_ips = ips_from('HTTP_CLIENT_IP').reverse
122
+ forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR').reverse
123
+
124
+ # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set.
125
+ # If they are both set, it means that this request passed through two
126
+ # proxies with incompatible IP header conventions, and there is no way
127
+ # for us to determine which header is the right one after the fact.
128
+ # Since we have no idea, we give up and explode.
129
+ should_check_ip = @check_ip && client_ips.last && forwarded_ips.last
130
+ if should_check_ip && !forwarded_ips.include?(client_ips.last)
131
+ # We don't know which came from the proxy, and which from the user
132
+ raise IpSpoofAttackError, "IP spoofing attack?! " +
133
+ "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect} " +
134
+ "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
135
+ end
136
+
137
+ # We assume these things about the IP headers:
138
+ #
139
+ # - X-Forwarded-For will be a list of IPs, one per proxy, or blank
140
+ # - Client-Ip is propagated from the outermost proxy, or is blank
141
+ # - REMOTE_ADDR will be the IP that made the request to Rack
142
+ ips = [forwarded_ips, client_ips, remote_addr].flatten.compact
143
+
144
+ # If every single IP option is in the trusted list, just return REMOTE_ADDR
145
+ filter_proxies(ips).first || remote_addr
146
+ end
147
+
148
+ # Memoizes the value returned by #calculate_ip and returns it for
149
+ # ActionDispatch::Request to use.
150
+ def to_s
151
+ @ip ||= calculate_ip
152
+ end
153
+
154
+ protected
155
+
156
+ def ips_from(header)
157
+ # Split the comma-separated list into an array of strings
158
+ ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
159
+ ips.select do |ip|
160
+ begin
161
+ # Only return IPs that are valid according to the IPAddr#new method
162
+ range = IPAddr.new(ip).to_range
163
+ # we want to make sure nobody is sneaking a netmask in
164
+ range.begin == range.end
165
+ rescue ArgumentError
166
+ nil
167
+ end
168
+ end
169
+ end
170
+
171
+ def filter_proxies(ips)
172
+ ips.reject do |ip|
173
+ @proxies.any? { |proxy| proxy === ip }
174
+ end
175
+ end
176
+
177
+ end
178
+
179
+ end
180
+ end
181
+ end
@@ -15,11 +15,6 @@ module LogjamAgent
15
15
  ensure
16
16
  headers = result[1]
17
17
  headers["X-Logjam-Request-Id"] = request.id
18
- # this cookie is intended for JS code, which can't read it from response headers.
19
- # it's superior to returning it with the html body, because that makes caching hard.
20
- if set_request_id_cookie?(request, env)
21
- ::Rack::Utils.set_cookie_header!(headers, "X-Logjam-Request-Id", {:value => request.id, :path => "/"})
22
- end
23
18
  unless (request_action = request.fields[:action]).blank?
24
19
  headers["X-Logjam-Request-Action"] = request_action
25
20
  end
@@ -42,9 +37,5 @@ module LogjamAgent
42
37
  def finish_request(env)
43
38
  LogjamAgent.finish_request(env["time_bandits.metrics"])
44
39
  end
45
-
46
- def set_request_id_cookie?(request, env)
47
- !(request.ignored? || env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest")
48
- end
49
40
  end
50
41
  end
@@ -77,7 +77,7 @@ module LogjamAgent
77
77
 
78
78
  logjam_request.start_time = start_time
79
79
  logjam_fields = logjam_request.fields
80
- ip = LogjamAgent.ip_obfuscator(request.ip)
80
+ ip = LogjamAgent.ip_obfuscator(env["action_dispatch.remote_ip"])
81
81
  logjam_fields.merge!(:ip => ip, :host => @hostname)
82
82
  logjam_fields.merge!(extract_request_info(request))
83
83
 
@@ -48,6 +48,14 @@ module LogjamAgent
48
48
  app.config.middleware.swap("TimeBandits::Rack::Logger", "LogjamAgent::Rack::Logger")
49
49
  app.config.middleware.insert_before("LogjamAgent::Rack::Logger", "LogjamAgent::Middleware")
50
50
 
51
+ if defined?(::ActionDispatch::RemoteIp)
52
+ app.config.middleware.delete ::ActionDispatch::RemoteIp
53
+ app.config.middleware.insert_before LogjamAgent::Middleware, ::ActionDispatch::RemoteIp, app.config.action_dispatch.ip_spoofing_check, app.config.action_dispatch.trusted_proxies
54
+ else
55
+ require 'logjam_agent/actionpack/lib/action_dispatch/middleware/remote_ip'
56
+ app.config.middleware.insert_before LogjamAgent::Middleware, LogjamAgent::ActionDispatch::RemoteIp
57
+ end
58
+
51
59
  # install a default error handler for forwarding errors
52
60
  log_dir = File.dirname(logjam_log_path(app))
53
61
  begin
@@ -79,38 +87,6 @@ module LogjamAgent
79
87
  end
80
88
  end
81
89
  end
82
-
83
- # patch rack so that the ip method returns the same result as rails remote_ip (modulo exceptions)
84
- app.config.after_initialize do
85
- if app.config.action_dispatch.trusted_proxies
86
- trusted_proxies = /^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|^::1$|^fd[0-9a-f]{2}:.+|^localhost$/i
87
- trusted_proxies = Regexp.union(trusted_proxies, app.config.action_dispatch.trusted_proxies)
88
- ::Rack::Request.class_eval <<-"EVA"
89
- def trusted_proxy?(ip)
90
- ip =~ #{trusted_proxies.inspect}
91
- end
92
- EVA
93
- end
94
-
95
- ::Rack::Request.class_eval <<-EVA
96
- def ip
97
- remote_addrs = @env['REMOTE_ADDR'] ? @env['REMOTE_ADDR'].split(/[,\s]+/) : []
98
- remote_addrs.reject! { |addr| trusted_proxy?(addr) }
99
-
100
- return remote_addrs.first if remote_addrs.any?
101
-
102
- forwarded_ips = @env['HTTP_X_FORWARDED_FOR'] ? @env['HTTP_X_FORWARDED_FOR'].strip.split(/[,\s]+/) : []
103
-
104
- if client_ip = @env['HTTP_TRUE_CLIENT_IP'] || @env['HTTP_CLIENT_IP']
105
- # If forwarded_ips doesn't include the client_ip, it might be an
106
- # ip spoofing attempt, so we ignore HTTP_CLIENT_IP
107
- return client_ip if forwarded_ips.include?(client_ip)
108
- end
109
-
110
- return forwarded_ips.reject { |ip| trusted_proxy?(ip) }.last || @env["REMOTE_ADDR"]
111
- end
112
- EVA
113
- end
114
90
  end
115
91
 
116
92
  # avoid garbled tempfile information in the logs
@@ -1,3 +1,3 @@
1
1
  module LogjamAgent
2
- VERSION = "0.18.0"
2
+ VERSION = "0.19.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logjam_agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.0
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Kaes
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-25 00:00:00.000000000 Z
11
+ date: 2016-04-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -120,6 +120,7 @@ files:
120
120
  - README.md
121
121
  - Rakefile
122
122
  - lib/logjam_agent.rb
123
+ - lib/logjam_agent/actionpack/lib/action_dispatch/middleware/remote_ip.rb
123
124
  - lib/logjam_agent/amqp_forwarder.rb
124
125
  - lib/logjam_agent/buffered_logger.rb
125
126
  - lib/logjam_agent/forwarders.rb