immunio 0.15.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (157) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +234 -0
  3. data/README.md +147 -0
  4. data/bin/immunio +5 -0
  5. data/lib/immunio.rb +29 -0
  6. data/lib/immunio/agent.rb +260 -0
  7. data/lib/immunio/authentication.rb +96 -0
  8. data/lib/immunio/blocked_app.rb +38 -0
  9. data/lib/immunio/channel.rb +432 -0
  10. data/lib/immunio/cli.rb +39 -0
  11. data/lib/immunio/context.rb +114 -0
  12. data/lib/immunio/errors.rb +43 -0
  13. data/lib/immunio/immunio_ca.crt +45 -0
  14. data/lib/immunio/logger.rb +87 -0
  15. data/lib/immunio/plugins/action_dispatch.rb +45 -0
  16. data/lib/immunio/plugins/action_view.rb +431 -0
  17. data/lib/immunio/plugins/active_record.rb +707 -0
  18. data/lib/immunio/plugins/active_record_relation.rb +370 -0
  19. data/lib/immunio/plugins/authlogic.rb +80 -0
  20. data/lib/immunio/plugins/csrf.rb +24 -0
  21. data/lib/immunio/plugins/devise.rb +40 -0
  22. data/lib/immunio/plugins/environment_reporter.rb +69 -0
  23. data/lib/immunio/plugins/eval.rb +51 -0
  24. data/lib/immunio/plugins/exception_handler.rb +55 -0
  25. data/lib/immunio/plugins/gems_tracker.rb +5 -0
  26. data/lib/immunio/plugins/haml.rb +36 -0
  27. data/lib/immunio/plugins/http_finisher.rb +50 -0
  28. data/lib/immunio/plugins/http_tracker.rb +203 -0
  29. data/lib/immunio/plugins/io.rb +96 -0
  30. data/lib/immunio/plugins/redirect.rb +42 -0
  31. data/lib/immunio/plugins/warden.rb +66 -0
  32. data/lib/immunio/processor.rb +234 -0
  33. data/lib/immunio/rails.rb +26 -0
  34. data/lib/immunio/request.rb +139 -0
  35. data/lib/immunio/rufus_lua_ext/ref.rb +27 -0
  36. data/lib/immunio/rufus_lua_ext/state.rb +157 -0
  37. data/lib/immunio/rufus_lua_ext/table.rb +137 -0
  38. data/lib/immunio/rufus_lua_ext/utils.rb +13 -0
  39. data/lib/immunio/version.rb +5 -0
  40. data/lib/immunio/vm.rb +291 -0
  41. data/lua-hooks/ext/all.c +78 -0
  42. data/lua-hooks/ext/bitop/README +22 -0
  43. data/lua-hooks/ext/bitop/bit.c +189 -0
  44. data/lua-hooks/ext/extconf.rb +38 -0
  45. data/lua-hooks/ext/libinjection/COPYING +37 -0
  46. data/lua-hooks/ext/libinjection/libinjection.h +65 -0
  47. data/lua-hooks/ext/libinjection/libinjection_html5.c +847 -0
  48. data/lua-hooks/ext/libinjection/libinjection_html5.h +54 -0
  49. data/lua-hooks/ext/libinjection/libinjection_sqli.c +2301 -0
  50. data/lua-hooks/ext/libinjection/libinjection_sqli.h +295 -0
  51. data/lua-hooks/ext/libinjection/libinjection_sqli_data.h +9349 -0
  52. data/lua-hooks/ext/libinjection/libinjection_xss.c +531 -0
  53. data/lua-hooks/ext/libinjection/libinjection_xss.h +21 -0
  54. data/lua-hooks/ext/libinjection/lualib.c +109 -0
  55. data/lua-hooks/ext/lpeg/HISTORY +90 -0
  56. data/lua-hooks/ext/lpeg/lpcap.c +537 -0
  57. data/lua-hooks/ext/lpeg/lpcap.h +43 -0
  58. data/lua-hooks/ext/lpeg/lpcode.c +986 -0
  59. data/lua-hooks/ext/lpeg/lpcode.h +34 -0
  60. data/lua-hooks/ext/lpeg/lpeg-128.gif +0 -0
  61. data/lua-hooks/ext/lpeg/lpeg.html +1429 -0
  62. data/lua-hooks/ext/lpeg/lpprint.c +244 -0
  63. data/lua-hooks/ext/lpeg/lpprint.h +35 -0
  64. data/lua-hooks/ext/lpeg/lptree.c +1238 -0
  65. data/lua-hooks/ext/lpeg/lptree.h +77 -0
  66. data/lua-hooks/ext/lpeg/lptypes.h +149 -0
  67. data/lua-hooks/ext/lpeg/lpvm.c +355 -0
  68. data/lua-hooks/ext/lpeg/lpvm.h +58 -0
  69. data/lua-hooks/ext/lpeg/makefile +55 -0
  70. data/lua-hooks/ext/lpeg/re.html +498 -0
  71. data/lua-hooks/ext/lpeg/test.lua +1409 -0
  72. data/lua-hooks/ext/lua-cmsgpack/CMakeLists.txt +45 -0
  73. data/lua-hooks/ext/lua-cmsgpack/README.md +115 -0
  74. data/lua-hooks/ext/lua-cmsgpack/lua_cmsgpack.c +957 -0
  75. data/lua-hooks/ext/lua-cmsgpack/test.lua +570 -0
  76. data/lua-hooks/ext/lua-snapshot/LICENSE +7 -0
  77. data/lua-hooks/ext/lua-snapshot/Makefile +12 -0
  78. data/lua-hooks/ext/lua-snapshot/README.md +18 -0
  79. data/lua-hooks/ext/lua-snapshot/dump.lua +15 -0
  80. data/lua-hooks/ext/lua-snapshot/snapshot.c +455 -0
  81. data/lua-hooks/ext/lua/COPYRIGHT +34 -0
  82. data/lua-hooks/ext/lua/lapi.c +1087 -0
  83. data/lua-hooks/ext/lua/lapi.h +16 -0
  84. data/lua-hooks/ext/lua/lauxlib.c +652 -0
  85. data/lua-hooks/ext/lua/lauxlib.h +174 -0
  86. data/lua-hooks/ext/lua/lbaselib.c +659 -0
  87. data/lua-hooks/ext/lua/lcode.c +831 -0
  88. data/lua-hooks/ext/lua/lcode.h +76 -0
  89. data/lua-hooks/ext/lua/ldblib.c +398 -0
  90. data/lua-hooks/ext/lua/ldebug.c +638 -0
  91. data/lua-hooks/ext/lua/ldebug.h +33 -0
  92. data/lua-hooks/ext/lua/ldo.c +519 -0
  93. data/lua-hooks/ext/lua/ldo.h +57 -0
  94. data/lua-hooks/ext/lua/ldump.c +164 -0
  95. data/lua-hooks/ext/lua/lfunc.c +174 -0
  96. data/lua-hooks/ext/lua/lfunc.h +34 -0
  97. data/lua-hooks/ext/lua/lgc.c +710 -0
  98. data/lua-hooks/ext/lua/lgc.h +110 -0
  99. data/lua-hooks/ext/lua/linit.c +38 -0
  100. data/lua-hooks/ext/lua/liolib.c +556 -0
  101. data/lua-hooks/ext/lua/llex.c +463 -0
  102. data/lua-hooks/ext/lua/llex.h +81 -0
  103. data/lua-hooks/ext/lua/llimits.h +128 -0
  104. data/lua-hooks/ext/lua/lmathlib.c +263 -0
  105. data/lua-hooks/ext/lua/lmem.c +86 -0
  106. data/lua-hooks/ext/lua/lmem.h +49 -0
  107. data/lua-hooks/ext/lua/loadlib.c +705 -0
  108. data/lua-hooks/ext/lua/loadlib_rel.c +760 -0
  109. data/lua-hooks/ext/lua/lobject.c +214 -0
  110. data/lua-hooks/ext/lua/lobject.h +381 -0
  111. data/lua-hooks/ext/lua/lopcodes.c +102 -0
  112. data/lua-hooks/ext/lua/lopcodes.h +268 -0
  113. data/lua-hooks/ext/lua/loslib.c +243 -0
  114. data/lua-hooks/ext/lua/lparser.c +1339 -0
  115. data/lua-hooks/ext/lua/lparser.h +82 -0
  116. data/lua-hooks/ext/lua/lstate.c +214 -0
  117. data/lua-hooks/ext/lua/lstate.h +169 -0
  118. data/lua-hooks/ext/lua/lstring.c +111 -0
  119. data/lua-hooks/ext/lua/lstring.h +31 -0
  120. data/lua-hooks/ext/lua/lstrlib.c +871 -0
  121. data/lua-hooks/ext/lua/ltable.c +588 -0
  122. data/lua-hooks/ext/lua/ltable.h +40 -0
  123. data/lua-hooks/ext/lua/ltablib.c +287 -0
  124. data/lua-hooks/ext/lua/ltm.c +75 -0
  125. data/lua-hooks/ext/lua/ltm.h +54 -0
  126. data/lua-hooks/ext/lua/lua.c +392 -0
  127. data/lua-hooks/ext/lua/lua.def +131 -0
  128. data/lua-hooks/ext/lua/lua.h +388 -0
  129. data/lua-hooks/ext/lua/lua.rc +28 -0
  130. data/lua-hooks/ext/lua/lua_dll.rc +26 -0
  131. data/lua-hooks/ext/lua/luac.c +200 -0
  132. data/lua-hooks/ext/lua/luac.rc +1 -0
  133. data/lua-hooks/ext/lua/luaconf.h +763 -0
  134. data/lua-hooks/ext/lua/luaconf.h.in +724 -0
  135. data/lua-hooks/ext/lua/luaconf.h.orig +763 -0
  136. data/lua-hooks/ext/lua/lualib.h +53 -0
  137. data/lua-hooks/ext/lua/lundump.c +227 -0
  138. data/lua-hooks/ext/lua/lundump.h +36 -0
  139. data/lua-hooks/ext/lua/lvm.c +767 -0
  140. data/lua-hooks/ext/lua/lvm.h +36 -0
  141. data/lua-hooks/ext/lua/lzio.c +82 -0
  142. data/lua-hooks/ext/lua/lzio.h +67 -0
  143. data/lua-hooks/ext/lua/print.c +227 -0
  144. data/lua-hooks/ext/luautf8/README.md +152 -0
  145. data/lua-hooks/ext/luautf8/lutf8lib.c +1274 -0
  146. data/lua-hooks/ext/luautf8/unidata.h +3064 -0
  147. data/lua-hooks/lib/boot.lua +254 -0
  148. data/lua-hooks/lib/encode.lua +4 -0
  149. data/lua-hooks/lib/lexers/LICENSE +21 -0
  150. data/lua-hooks/lib/lexers/bash.lua +134 -0
  151. data/lua-hooks/lib/lexers/bash_dqstr.lua +62 -0
  152. data/lua-hooks/lib/lexers/css.lua +216 -0
  153. data/lua-hooks/lib/lexers/html.lua +106 -0
  154. data/lua-hooks/lib/lexers/javascript.lua +68 -0
  155. data/lua-hooks/lib/lexers/lexer.lua +1575 -0
  156. data/lua-hooks/lib/lexers/markers.lua +33 -0
  157. metadata +308 -0
@@ -0,0 +1,96 @@
1
+ module Immunio
2
+ # API for triggering the Immunio authentication hooks.
3
+ module Authentication
4
+ # Each method below takes an options hash argument. The options hash may
5
+ # contain any of the following information:
6
+ #
7
+ # * user_id: String or Number
8
+ # * username: String
9
+ # * email: String
10
+ # * user_record: ActiveRecord object for the user
11
+ # * reason: String (for failures)
12
+
13
+ # Call after a successful login.
14
+ def login(options_ro={})
15
+ plugin, options = parse_opts(options_ro)
16
+ return unless plugin
17
+ Immunio.run_hook! plugin, "authenticate", options.merge({is_valid: true})
18
+ Immunio.run_hook! plugin, "framework_login", options
19
+ end
20
+
21
+ # Call after an unsuccessful login.
22
+ def failed_login(options_ro={})
23
+ plugin, options = parse_opts(options_ro)
24
+ return unless plugin
25
+ Immunio.run_hook! plugin, "authenticate", options.merge({is_valid: false})
26
+ end
27
+
28
+ # Call after a user logs out.
29
+ def logout(options_ro={})
30
+ plugin, options = parse_opts(options_ro)
31
+ return unless plugin
32
+ Immunio.run_hook! plugin, "framework_logout", options
33
+ end
34
+
35
+ # Call after the current user is changed.
36
+ def set_user(options_ro={})
37
+ plugin, options = parse_opts(options_ro)
38
+ return unless plugin
39
+ Immunio.run_hook! plugin, "framework_user", options
40
+ end
41
+
42
+ # Call after a successful password reset has been requested for a user.
43
+ def password_reset(options_ro={})
44
+ plugin, options = parse_opts(options_ro)
45
+ return unless plugin
46
+ options[:is_valid] = true
47
+ Immunio.run_hook! plugin, "framework_password_reset", options
48
+ end
49
+
50
+ # Call after a failed password reset has been requested.
51
+ def failed_password_reset(options_ro={})
52
+ plugin, options = parse_opts(options_ro)
53
+ return unless plugin
54
+ options[:is_valid] = false
55
+ Immunio.run_hook! plugin, "framework_password_reset", options
56
+ end
57
+
58
+ private
59
+ def parse_opts(options_ro)
60
+ unless options_ro.is_a? Hash
61
+ Immunio.logger.warn "Passed a non-hash options object into an authentication method: #{options_ro.inspect}"
62
+ return
63
+ end
64
+
65
+ Immunio.logger.debug {"Authentication API called with options #{options_ro}"}
66
+
67
+ options = options_ro.clone
68
+ plugin = options.delete(:plugin) { "api" }
69
+ options[:user_id] = options[:user_id].to_s if options[:user_id]
70
+
71
+ user_record = options.delete :user_record
72
+ if user_record
73
+ # Prefer user info passed in over those parsed from user_record
74
+ record_info = parse_record(user_record)
75
+ options = record_info.merge(options)
76
+ end
77
+
78
+ return plugin, options
79
+ end
80
+
81
+ def parse_record(record)
82
+ info = {}
83
+
84
+ info[:user_id] = record.id.to_s if record.respond_to?(:id)
85
+ info[:email] = record.email.to_s if record.respond_to?(:email)
86
+
87
+ attribute = [:username, :login, :name].detect { |attr| record.respond_to?(attr) }
88
+ info[:username] = record.send(attribute).to_s if attribute
89
+
90
+ info
91
+ end
92
+ end
93
+
94
+ # Make all module methods accessible from `Immunio` module. Eg.: `Immunio.login`
95
+ extend Authentication
96
+ end
@@ -0,0 +1,38 @@
1
+ module Immunio
2
+ # Rack app called when a request is blocked.
3
+ # Change this to customize the response sent on blocked requests.
4
+ # Defaults to a plain text 403 response.
5
+ mattr_accessor :blocked_app
6
+ mattr_accessor :override_response
7
+
8
+ self.blocked_app = ->(_env) do
9
+ [
10
+ 403,
11
+ { 'Content-Type' => 'text/plain' },
12
+ ["Request blocked"]
13
+ ]
14
+ end
15
+
16
+ self.override_response = ->(_env, override) do
17
+ [
18
+ override.status,
19
+ list_to_headers(override.headers.to_a),
20
+ [override.body]
21
+ ]
22
+ end
23
+
24
+ def self.list_to_headers(list)
25
+ new_headers = {}
26
+ list.each do |name, value|
27
+ # If this header is already in `new_headers`, append to the
28
+ # existing value with a linefeed separator.
29
+ if new_headers.has_key?(name)
30
+ new_headers[name] += ("\n" + value)
31
+ else
32
+ new_headers[name] = value
33
+ end
34
+ end
35
+ new_headers
36
+ end
37
+
38
+ end
@@ -0,0 +1,432 @@
1
+ require "thread"
2
+ require "faraday"
3
+ require "faraday_middleware"
4
+ require "msgpack"
5
+ require "base64"
6
+
7
+ require_relative "errors"
8
+ require_relative "logger"
9
+ require_relative "version"
10
+
11
+ module Immunio
12
+ # Communication channel with the Immunio webservice.
13
+ class Channel
14
+ DIGEST = OpenSSL::Digest.new('sha1')
15
+
16
+ attr_reader :success_count, :error_count, :message_queue
17
+ attr_reader :rejected_message_count
18
+
19
+ def initialize(config)
20
+ @config = config
21
+
22
+ @agent_uuid = nil
23
+
24
+ # Messages waiting to be sent.
25
+ @message_queue = Queue.new
26
+ # Messages that were sent but failed. Need to be resent.
27
+ @send_buffer = []
28
+ @send_buffer_bytes = 0
29
+ @last_report_time = 0
30
+
31
+ # A large message we may have popped from the queue but couldn't fit
32
+ # in the current report
33
+ @next_message = nil
34
+
35
+ @send_seq = 0
36
+ @dropped_message_count = 0
37
+ @rejected_message_count = 0
38
+ @success_count = 0
39
+ @error_count = 0
40
+ @quick_connect = true
41
+
42
+ @started = false
43
+ @ready = false
44
+
45
+ @callbacks = []
46
+
47
+ # Anything looking to add to the messages sent to the server:
48
+ @senders = []
49
+ end
50
+
51
+ def ready?
52
+ @ready
53
+ end
54
+
55
+ def set_ready
56
+ @ready = true
57
+ end
58
+
59
+ def started?
60
+ @started
61
+ end
62
+
63
+ def messages_count
64
+ @message_queue.size
65
+ end
66
+
67
+ def start
68
+ return if @started
69
+
70
+ @started = true
71
+ @thread = Thread.new { run }
72
+ end
73
+
74
+ # Stop and wait for the last messages to be sent.
75
+ def stop
76
+ return unless @started
77
+
78
+ Immunio.logger.debug "Stopping channel"
79
+
80
+ @started = false
81
+ @ready = false
82
+
83
+ if @thread
84
+ @thread.kill
85
+ @thread.join
86
+ end
87
+ end
88
+
89
+ def send_message(message)
90
+ send_encoded_message message.to_msgpack
91
+ end
92
+
93
+ def send_encoded_message(message)
94
+ if @message_queue.size > @config.max_send_queue_size
95
+ Immunio.logger.warn "Dropping message for agent manager due to queue overflow (#{@message_queue.size} > #{@config.max_send_queue_size})"
96
+ Immunio.logger.debug "Dropped message: (#{message})"
97
+ # No room for this message on the queue. Discard.
98
+ @dropped_message_count += 1
99
+ return
100
+ end
101
+
102
+ Immunio.logger.debug {"Sending message to backend: #{MessagePack.unpack(message)}"}
103
+
104
+ @message_queue << message
105
+ end
106
+
107
+ def on_message(&block)
108
+ @callbacks << block
109
+ end
110
+
111
+ def on_sending(&block)
112
+ @senders << block
113
+ end
114
+
115
+ # Wait until we receive a message from the agentmanager.
116
+ # This is used primarily for internal testing to wait until all the hooks
117
+ # are loaded.
118
+ def wait_until_ready!
119
+ return if @ready
120
+
121
+ if @config.ready_timeout.to_i <= 0
122
+ return
123
+ end
124
+
125
+ Immunio.logger.debug "Channel waiting #{@config.ready_timeout.to_i} seconds until ready..."
126
+ Timeout.timeout @config.ready_timeout.to_i do
127
+ # Wait until we get a response from the agentmanager
128
+ sleep 0.1 until ready?
129
+ Immunio.logger.debug "Channel ready!"
130
+ end
131
+ end
132
+
133
+ protected
134
+ def setup_connection(faraday)
135
+ # Override to modify Faraday options & middlewares
136
+ faraday.adapter Faraday.default_adapter
137
+ end
138
+
139
+ private
140
+ # Core method running in a thread
141
+ def run
142
+ Immunio.logger.debug "Starting channel on thread #{Thread.current.object_id}"
143
+ # Create an empty cert_store to prevent Faraday from using the system default OpenSSL store.
144
+ cert_store = OpenSSL::X509::Store.new
145
+ # Setup the connection for making requests to the server.
146
+ @connection = Faraday::Connection.new(@config.hello_url, ssl: { ca_file: "#{Immunio::DIR}/immunio/immunio_ca.crt", cert_store: cert_store },
147
+ request: { timeout: @config.http_timeout }) do |faraday|
148
+ faraday.request :url_encoded
149
+
150
+ # Provide a hook for additional Faraday config.
151
+ setup_connection faraday
152
+ end
153
+
154
+ # Get the polling URL from the server.
155
+ hello
156
+
157
+ # Start sending messages
158
+ while @started
159
+ poll
160
+ @success_count += 1
161
+ @error_count = 0
162
+ end
163
+
164
+ rescue StandardError => e
165
+ # Retry forever on error
166
+ @error_count += 1
167
+
168
+ log_error(e)
169
+
170
+ @success_count = 0
171
+ @quick_connect = true
172
+
173
+ exponential_backoff
174
+
175
+ retry
176
+ end
177
+
178
+ def log_error(e)
179
+ if @error_count == 1
180
+ Immunio.logger.warn "Connection failed after #{@success_count} successes: #{e} (#{e.class})"
181
+ else
182
+ Immunio.logger.warn "Connection failure [#{@error_count}]: #{e} (#{e.class})"
183
+ end
184
+ Immunio.logger.debug e.backtrace.join("\n")
185
+ end
186
+
187
+ def exponential_backoff()
188
+ # Exponential backoff
189
+ delay_ms = @config.initial_delay_ms * (2 ** (@error_count - 1))
190
+ # Cap at max_delay_ms
191
+ delay_ms = [delay_ms, @config.max_delay_ms].min
192
+
193
+ # choose a random delay less than the computed delay.
194
+ # The randomness avoids a herd effect from transient failures.
195
+ delay_ms *= rand
196
+ delay_ms = delay_ms.round
197
+
198
+ Immunio.logger.info "Delaying #{delay_ms} ms before next request"
199
+ sleep delay_ms / 1000.0
200
+ end
201
+
202
+ def notify(message)
203
+ message = message.symbolize_keys!
204
+ @callbacks.each { |callback| callback.call(message) }
205
+ end
206
+
207
+ def raw_log(raw)
208
+ raw.encode 'utf-8', invalid: :replace, undef: :replace
209
+ end
210
+
211
+ # Send initial hello request.
212
+ def hello
213
+ response = @connection.get do |req|
214
+ req.url "/"
215
+
216
+ req.params['name'] = AGENT_TYPE
217
+ req.params['version'] = VERSION
218
+ req.params['key'] = @config[:key]
219
+
220
+ req.headers['Accept'] = 'application/x-msgpack'
221
+
222
+ Immunio.logger.trace {"Sending hello request to agent manager (#{req})"}
223
+ end
224
+
225
+ if response.status != 200 then
226
+ raise Error, "Bad response from Immunio server: #{response.status} #{raw_log response.body}"
227
+ end
228
+
229
+ Immunio.logger.trace {"Received hello response from agent manager (status: #{response.status}, body: #{raw_log response.body})"}
230
+
231
+ body = MessagePack.unpack(response.body)
232
+
233
+ @polling_url = body["url"]
234
+
235
+ if @polling_url.blank? then
236
+ raise Error, "No URL in HELLO response: #{response.body}"
237
+ end
238
+
239
+ Immunio.logger.info "Agent connected to #{@config.hello_url}"
240
+ end
241
+
242
+ # Execute a block for at max a given time to match `@config.max_report_interval`.
243
+ def with_report_timeout
244
+ time_since_last_report = Time.now.to_i - @last_report_time
245
+
246
+ # Interval expired already
247
+ return if time_since_last_report >= @config.max_report_interval
248
+
249
+ # Compute time to wait for new messages
250
+ timeout = @config.max_report_interval - time_since_last_report
251
+
252
+ Timeout.timeout timeout do
253
+ yield
254
+ end
255
+
256
+ rescue Timeout::Error # rubocop:disable Lint/HandleExceptions
257
+ # Timeout expired
258
+ end
259
+
260
+ def send_buffer_has_room(extra_bytes)
261
+ used_bytes = @send_buffer_bytes + extra_bytes + @next_message.bytesize
262
+ return @send_buffer.size < @config.max_report_size && used_bytes < @config.max_report_bytes
263
+ end
264
+
265
+ def add_to_send_buffer(message)
266
+ @send_buffer_bytes += message.bytesize
267
+ @send_buffer << message
268
+ end
269
+
270
+ # Fill send_buffer with messages to send
271
+ def collect_messages(used_bytes)
272
+ # if we had a message leftover from last send, make sure its not
273
+ # so big that it would fill the whole buffer
274
+ if @next_message != nil
275
+ if send_buffer_has_room used_bytes
276
+ add_to_send_buffer @next_message
277
+ else
278
+ Immunio.logger.warn "Dropped message over max byte send size, next message size #{@next_message.bytesize}"
279
+ Immunio.logger.debug "Dropped next message used: #{used_bytes} over max byte: #{@next_message}"
280
+ @dropped_message_count += 1
281
+ end
282
+ @next_message = nil
283
+ end
284
+
285
+ # Empty the queue as much as possible.
286
+ while !@message_queue.empty?
287
+ @next_message = @message_queue.pop
288
+ if !send_buffer_has_room used_bytes
289
+ break
290
+ end
291
+
292
+ add_to_send_buffer @next_message
293
+ @next_message = nil
294
+ end
295
+
296
+ # Return immediately if we've got the minimum number of messages to
297
+ # report or we're still waiting for an agent.ready message,
298
+ # or if we're at the maximum buffer size
299
+ return @send_buffer if @send_buffer.size >= @config.min_report_size || @next_message != nil
300
+
301
+ # Wait for messages from the queue until we have enough or get a timeout.
302
+ with_report_timeout do
303
+ while @send_buffer.size < @config.min_report_size
304
+ # If there are no messages in the queue, this will block until one arrives.
305
+ @next_message = @message_queue.pop
306
+ if !send_buffer_has_room used_bytes
307
+ break
308
+ end
309
+
310
+ add_to_send_buffer @next_message
311
+ @next_message = nil
312
+ end
313
+ end
314
+
315
+ @send_buffer
316
+ end
317
+
318
+ def gzip(s)
319
+ gzip_io = StringIO.new
320
+ begin
321
+ gzip = Zlib::GzipWriter.new(gzip_io)
322
+ gzip.write s
323
+ ret = gzip_io.string
324
+ ensure
325
+ gzip.close
326
+ end
327
+ ret
328
+ end
329
+
330
+ # Poll the server sending queued messages at the same time.
331
+ def poll
332
+ # Prep data
333
+ body = {
334
+ send_seq: @send_seq,
335
+ dropped_message_count: @dropped_message_count,
336
+ rejected_message_count: @rejected_message_count,
337
+ name: AGENT_TYPE,
338
+ version: VERSION,
339
+ vm_version: VM_VERSION,
340
+ }
341
+
342
+ body[:agent_uuid] = @agent_uuid if @agent_uuid
343
+
344
+ # Add any values that senders would like to send:
345
+ @senders.each { |callback|
346
+ body.merge! callback.call()
347
+ }
348
+
349
+ # Encode the body of the request using msgpack streaming API.
350
+ # See http://ruby.msgpack.org/MessagePack/Packer.html#write_map_header-instance_method for other methods.
351
+ packer = MessagePack::Packer.new
352
+
353
+ # Pack the `body` hash.
354
+ packer.write_map_header body.size + 1 # body's + 'msgs'
355
+ body.each_pair { |key, value| packer.write(key).write(value) }
356
+
357
+ packer.write 'msgs' # The key
358
+ if not @quick_connect
359
+ # Append the prepacked messages.
360
+ collect_messages packer.size
361
+ packer.write_array_header @send_buffer.size
362
+ @send_buffer.each { |message| packer.buffer << message }
363
+ else
364
+ packer.write_array_header 0
365
+ @quick_connect = false
366
+ end
367
+
368
+ encoded_body = packer.to_s
369
+
370
+ # Send the request
371
+ response = @connection.post do |req|
372
+ req.url @polling_url
373
+
374
+ req.params['name'] = AGENT_TYPE
375
+ req.params['version'] = VERSION
376
+ req.params['key'] = @config[:key]
377
+ req.params['sig'] = OpenSSL::HMAC.hexdigest(DIGEST, @config.secret, encoded_body)
378
+
379
+ req.headers['Content-Type'] = 'application/x-msgpack'
380
+ req.headers['Content-Encoding'] = 'gzip'
381
+ req.headers['Accept'] = 'application/x-msgpack'
382
+
383
+ req.body = gzip(encoded_body)
384
+
385
+ Immunio.logger.trace {"Sending request to agent manager (data: #{MessagePack.unpack(encoded_body)}, request: #{req})"}
386
+ end
387
+
388
+ if response.status >= 400 and response.status < 500 then
389
+ # 4XX response codes should NOT be retried. Discard the report.
390
+ @rejected_message_count += @send_buffer.size
391
+ Immunio.logger.trace "Rejecting #{@send_buffer.size} messages"
392
+ @send_buffer = []
393
+ @send_buffer_bytes = 0
394
+
395
+ Immunio.logger.trace {"Received response from agent manager (status: #{response.status}, raw body: #{raw_log response.body})"}
396
+ raise Error, "Bad response from Immunio server: #{response.status} #{response.body}"
397
+ end
398
+
399
+ if response.status >= 500 then
400
+ # 5XX response codes are treated like errors.
401
+ Immunio.logger.trace {"Received response from agent manager (status: #{response.status}, raw body: #{raw_log response.body})"}
402
+ raise Error, "Bad response from Immunio server: #{response.status} #{response.body}"
403
+ end
404
+
405
+ body = MessagePack.unpack(response.body)
406
+
407
+ Immunio.logger.trace {"Received response from agent manager (status: #{response.status}, body: #{body}, raw body: #{raw_log response.body})"}
408
+
409
+ # Update local data from response
410
+ new_agent_uuid = body["agent_uuid"]
411
+ if new_agent_uuid
412
+ Immunio.logger.info "Agent UUID: #{new_agent_uuid}" if new_agent_uuid != @agent_uuid
413
+ @agent_uuid = new_agent_uuid
414
+ end
415
+ @send_seq += @send_buffer.size
416
+
417
+ # If messages were delivered successfully, clear send buffer.
418
+ if response.success?
419
+ @send_buffer = []
420
+ @send_buffer_bytes = 0
421
+ end
422
+
423
+ @last_report_time = Time.now.to_i
424
+
425
+ received_messages = body["msgs"]
426
+ if received_messages
427
+ received_messages.each { |message| notify message }
428
+ end
429
+
430
+ end
431
+ end
432
+ end