immunio 0.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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