tcell_agent 1.0.0 → 1.1.0

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/Readme.txt +7 -0
  3. data/bin/tcell_agent +6 -2
  4. data/lib/tcell_agent.rb +0 -3
  5. data/lib/tcell_agent/agent/event_processor.rb +1 -4
  6. data/lib/tcell_agent/agent/policy_manager.rb +5 -8
  7. data/lib/tcell_agent/agent/policy_types.rb +1 -7
  8. data/lib/tcell_agent/agent/static_agent.rb +2 -2
  9. data/lib/tcell_agent/api.rb +7 -9
  10. data/lib/tcell_agent/configuration.rb +42 -6
  11. data/lib/tcell_agent/policies/rust_policies.rb +33 -8
  12. data/lib/tcell_agent/rails/js_agent_insert.rb +17 -18
  13. data/lib/tcell_agent/rails/middleware/headers_middleware.rb +18 -59
  14. data/lib/tcell_agent/rails/tcell_body_proxy.rb +10 -6
  15. data/lib/tcell_agent/rust/libtcellagent-0.19.5.dylib +0 -0
  16. data/lib/tcell_agent/rust/{libtcellagent-0.11.1.so → libtcellagent-0.19.5.so} +0 -0
  17. data/lib/tcell_agent/rust/tcellagent-0.19.5.dll +0 -0
  18. data/lib/tcell_agent/rust/whisperer.rb +165 -39
  19. data/lib/tcell_agent/sensor_events/patches.rb +2 -0
  20. data/lib/tcell_agent/sinatra.rb +17 -14
  21. data/lib/tcell_agent/version.rb +1 -1
  22. data/spec/lib/tcell_agent/agent/policy_manager_spec.rb +17 -0
  23. data/spec/lib/tcell_agent/api/api_spec.rb +10 -7
  24. data/spec/lib/tcell_agent/cmdi_spec.rb +91 -80
  25. data/spec/lib/tcell_agent/instrumentation_spec.rb +20 -0
  26. data/spec/lib/tcell_agent/patches_spec.rb +33 -15
  27. data/spec/lib/tcell_agent/policies/appsensor_policy_spec.rb +150 -99
  28. data/spec/lib/tcell_agent/policies/command_injection_policy_spec.rb +13 -1
  29. data/spec/lib/tcell_agent/policies/patches_policy_spec.rb +12 -0
  30. data/spec/lib/tcell_agent/rails/middleware/global_middleware_spec.rb +2 -39
  31. data/spec/lib/tcell_agent/rails/middleware/tcell_body_proxy_spec.rb +6 -2
  32. data/spec/lib/tcell_agent/rails_spec.rb +0 -31
  33. data/spec/lib/tcell_agent/rust/whisperer_spec.rb +234 -120
  34. data/tcell_agent.gemspec +1 -1
  35. metadata +21 -40
  36. data/lib/tcell_agent/policies/clickjacking_policy.rb +0 -114
  37. data/lib/tcell_agent/policies/content_security_policy.rb +0 -166
  38. data/lib/tcell_agent/policies/secure_headers_policy.rb +0 -67
  39. data/lib/tcell_agent/rust/libtcellagent-0.11.1.dylib +0 -0
  40. data/lib/tcell_agent/rust/tcellagent-0.11.1.dll +0 -0
  41. data/spec/apps/rails-3.2/config/tcell_agent.config +0 -15
  42. data/spec/apps/rails-3.2/log/development.log +0 -0
  43. data/spec/apps/rails-3.2/log/test.log +0 -12
  44. data/spec/apps/rails-4.1/log/test.log +0 -0
  45. data/spec/lib/tcell_agent/policies/clickjacking_policy_spec.rb +0 -71
  46. data/spec/lib/tcell_agent/policies/content_security_policy_spec.rb +0 -130
  47. data/spec/lib/tcell_agent/policies/secure_headers_policy_spec.rb +0 -67
  48. data/spec/lib/tcell_agent_spec.rb +0 -22
@@ -44,7 +44,7 @@ module TCellAgent
44
44
  return to_enum(:each) unless block_given?
45
45
 
46
46
  @body.each { |body_chunk|
47
- process_body!(body_chunk)
47
+ body_chunk = process_body(body_chunk)
48
48
 
49
49
  yield body_chunk
50
50
  }
@@ -58,7 +58,7 @@ module TCellAgent
58
58
  @body.__send__(method_name, *args, &block)
59
59
  end
60
60
 
61
- def process_body!(body)
61
+ def process_body(body)
62
62
  TCellAgent::Instrumentation.safe_block("Processing tcell body proxy body") do
63
63
  chunked_response_match = nil
64
64
  if body.class.name == "String"
@@ -68,23 +68,27 @@ module TCellAgent
68
68
  end
69
69
  end
70
70
 
71
+ new_body = body
71
72
  if body.class.name == "ActionView::OutputBuffer" ||
72
73
  (body.class.name == "String" && !chunked_response_match)
73
- @content_length += body.bytesize
74
-
75
74
  if @process_js_and_dlp
76
75
  if @js_agent_insertion_proc
77
- if @js_agent_insertion_proc.call(@script_insert, body)
76
+ new_body = @js_agent_insertion_proc.call(@script_insert, body)
77
+ if new_body != body
78
78
  # js agent was successfully inserted so no need to keep
79
79
  # calling this proc
80
80
  @js_agent_insertion_proc = nil
81
81
  end
82
82
  end
83
83
  if @dlp_cleaner_proc
84
- @dlp_cleaner_proc.call(@tcell_context, body)
84
+ @dlp_cleaner_proc.call(@tcell_context, new_body)
85
85
  end
86
86
  end
87
+
88
+ @content_length += new_body.bytesize
87
89
  end
90
+
91
+ new_body
88
92
  end
89
93
  end
90
94
  end
@@ -9,7 +9,7 @@ module TCellAgent
9
9
  module Wrapper
10
10
  extend FFI::Library
11
11
 
12
- VERSION = "0.11.1"
12
+ VERSION = "0.19.5"
13
13
  prefix = "lib"
14
14
  extension = ".so"
15
15
  if /cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
@@ -22,35 +22,21 @@ module TCellAgent
22
22
  begin
23
23
  ffi_lib File.join(File.dirname(__FILE__), "#{prefix}tcellagent-#{VERSION}#{extension}")
24
24
 
25
+ # All the rust library calls have the following response api:
26
+ #
25
27
  # result [int]: 0+ length of buffer_out answer
26
28
  # -1 general error
27
29
  # -2 buffer_out is not big enough for response
28
30
  # -3 buffer_out is null
29
- attach_function :create_agent, [:pointer, :size_t, :pointer, :size_t], :int
30
31
 
31
- # result [int]: 0+ length of buffer_out answer
32
- # -1 general error
33
- # -2 buffer_out is not big enough for response
34
- # -3 buffer_out is null
32
+ attach_function :create_agent, [:pointer, :size_t, :pointer, :size_t], :int
33
+ attach_function :free_agent, [:pointer], :int
35
34
  attach_function :update_policies, [:pointer, :pointer, :size_t, :pointer, :size_t], :int
36
-
37
- # result [int]: 0+ length of buffer_out answer
38
- # -1 general error
39
- # -2 buffer_out is not big enough for response
40
- # -3 buffer_out is null
41
35
  attach_function :appfirewall_apply, [:pointer, :pointer, :size_t, :pointer, :size_t], :int
42
-
43
- # result [int]: 0+ length of buffer_out answer
44
- # -1 general error
45
- # -2 buffer_out is not big enough for response
46
- # -3 buffer_out is null
47
36
  attach_function :patches_apply, [:pointer, :pointer, :size_t, :pointer, :size_t], :int
48
-
49
- # result [int]: 0+ length of buffer_out answer
50
- # -1 general error
51
- # -2 buffer_out is not big enough for response
52
- # -3 buffer_out is null
53
37
  attach_function :cmdi_apply, [:pointer, :pointer, :size_t, :pointer, :size_t], :int
38
+ attach_function :get_headers, [:pointer, :pointer, :size_t, :pointer, :size_t], :int
39
+ attach_function :get_js_agent_script_tag, [:pointer, :pointer, :size_t, :pointer, :size_t], :int
54
40
 
55
41
  def self.common_lib_available?
56
42
  true
@@ -74,7 +60,13 @@ module TCellAgent
74
60
  )
75
61
  else
76
62
  begin
77
- return JSON.parse(result.get_string(0, result_size))
63
+ response = JSON.parse(result.get_string(0, result_size))
64
+ if response['error']
65
+ Logger.error(function_called + ' returned an error: ' + response['error'])
66
+ response = {}
67
+ end
68
+
69
+ return response
78
70
  rescue JSON::ParserError
79
71
  # don't log the actual error since it might contain payload information
80
72
  TCellAgent.logger.error(
@@ -88,15 +80,42 @@ module TCellAgent
88
80
 
89
81
  def self.create_agent()
90
82
  if TCellAgent::Rust::Wrapper.common_lib_available?
83
+ allow_payloads = !!TCellAgent.configuration.allow_payloads
91
84
  agent_config = {
85
+ "application" => {
86
+ "app_id" => TCellAgent.configuration.app_id,
87
+ "api_key" => TCellAgent.configuration.api_key,
88
+ "tcell_api_url" => "",
89
+ "tcell_input_url" => "",
90
+ "allow_payloads" => allow_payloads,
91
+ "js_agent_api_base_url" => TCellAgent.configuration.js_agent_api_base_url,
92
+ "js_agent_url" => TCellAgent.configuration.js_agent_url
93
+ },
92
94
  "appfirewall" => {
93
95
  "enable_body_xxe_inspection" => false,
94
96
  "enable_body_json_inspection" => false,
95
- "allow_send_payloads" => TCellAgent.configuration.allow_payloads,
97
+ "allow_send_payloads" => allow_payloads,
96
98
  "allow_log_payloads" => true
97
- }
99
+ },
100
+ "policy_versions" => {
101
+ "patches" => 1,
102
+ "login" => 1,
103
+ "appsensor" => 2,
104
+ "regex" => 1,
105
+ "csp-headers" => 1,
106
+ "http-redirect" => 1,
107
+ "clickjacking" => 1,
108
+ "secure-headers" => 1,
109
+ "canaries" => 1,
110
+ "dlp" => 1,
111
+ "cmdi" => 1,
112
+ "jsagentinjection" => 1
113
+ },
114
+ "max_header_size" => TCellAgent.configuration.max_csp_header_bytes || (1024 * 1024)
98
115
  }
99
- config_pointer = FFI::MemoryPointer.from_string(JSON.dump(agent_config))
116
+ config_pointer = FFI::MemoryPointer.from_string(
117
+ JSON.dump(agent_config)
118
+ )
100
119
 
101
120
  buf = FFI::MemoryPointer.new(:uint8, 1024 * 8)
102
121
  # config_pointer.size - 1: strips null terminator
@@ -109,16 +128,31 @@ module TCellAgent
109
128
  return {}
110
129
  end
111
130
 
131
+ def self.free_agent(agent_ptr)
132
+ if TCellAgent::Rust::Wrapper.common_lib_available? &&
133
+ agent_ptr
134
+ TCellAgent::Rust::Wrapper.free_agent(
135
+ FFI::Pointer.new(agent_ptr)
136
+ )
137
+ end
138
+ end
139
+
112
140
  def self.update_policies(agent_ptr, policies)
113
141
  if TCellAgent::Rust::Wrapper.common_lib_available? &&
114
142
  agent_ptr &&
115
143
  TCellAgent::Utils::Strings.present?(policies)
116
- policies_pointer = FFI::MemoryPointer.from_string(JSON.dump(policies))
144
+ policies_pointer = FFI::MemoryPointer.from_string(
145
+ JSON.dump(policies)
146
+ )
117
147
 
118
148
  buf = FFI::MemoryPointer.new(:uint8, 1024 * 8)
119
149
  # policies_pointer.size - 1: strips null terminator
120
150
  result_size = TCellAgent::Rust::Wrapper.update_policies(
121
- FFI::Pointer.new(agent_ptr), policies_pointer, policies_pointer.size - 1, buf, buf.size
151
+ FFI::Pointer.new(agent_ptr),
152
+ policies_pointer,
153
+ policies_pointer.size - 1,
154
+ buf,
155
+ buf.size
122
156
  )
123
157
  return self.convert_result("update_policies", result_size, buf)
124
158
  end
@@ -130,13 +164,21 @@ module TCellAgent
130
164
  if TCellAgent::Rust::Wrapper.common_lib_available? &&
131
165
  agent_ptr &&
132
166
  appsensor_meta
133
- request_response_json = TCellAgent::Rust::Models.create_request_response(appsensor_meta)
134
- request_response_pointer = FFI::MemoryPointer.from_string(JSON.dump(request_response_json))
167
+ request_response_json = TCellAgent::Rust::Models.create_request_response(
168
+ appsensor_meta
169
+ )
170
+ request_response_pointer = FFI::MemoryPointer.from_string(
171
+ JSON.dump(request_response_json)
172
+ )
135
173
 
136
- buf = FFI::MemoryPointer.new(:uint8, 1024 * 8)
174
+ buf = FFI::MemoryPointer.new(:uint8, 1024 * 32)
137
175
  # request_response_pointer.size - 1: strips null terminator
138
176
  result_size = TCellAgent::Rust::Wrapper.appfirewall_apply(
139
- FFI::Pointer.new(agent_ptr), request_response_pointer, request_response_pointer.size - 1, buf, buf.size
177
+ FFI::Pointer.new(agent_ptr),
178
+ request_response_pointer,
179
+ request_response_pointer.size - 1,
180
+ buf,
181
+ buf.size
140
182
  )
141
183
  return self.convert_result("apply_appfirewall", result_size, buf)
142
184
  end
@@ -148,13 +190,21 @@ module TCellAgent
148
190
  if TCellAgent::Rust::Wrapper.common_lib_available? &&
149
191
  agent_ptr &&
150
192
  appsensor_meta
151
- patches_request_json = TCellAgent::Rust::Models.create_patches_request(appsensor_meta)
152
- patches_request_pointer = FFI::MemoryPointer.from_string(JSON.dump(patches_request_json))
193
+ patches_request_json = TCellAgent::Rust::Models.create_patches_request(
194
+ appsensor_meta
195
+ )
196
+ patches_request_pointer = FFI::MemoryPointer.from_string(
197
+ JSON.dump(patches_request_json)
198
+ )
153
199
 
154
- buf = FFI::MemoryPointer.new(:uint8, 1024 * 8)
200
+ buf = FFI::MemoryPointer.new(:uint8, 1024 * 32)
155
201
  # patches_request_pointer.size - 1: strips null terminator
156
202
  result_size = TCellAgent::Rust::Wrapper.patches_apply(
157
- FFI::Pointer.new(agent_ptr), patches_request_pointer, patches_request_pointer.size - 1, buf, buf.size
203
+ FFI::Pointer.new(agent_ptr),
204
+ patches_request_pointer,
205
+ patches_request_pointer.size - 1,
206
+ buf,
207
+ buf.size
158
208
  )
159
209
  return self.convert_result("apply_patches", result_size, buf)
160
210
  end
@@ -162,22 +212,98 @@ module TCellAgent
162
212
  return {}
163
213
  end
164
214
 
165
- def self.apply_cmdi(agent_ptr, command)
215
+ def self.apply_cmdi(agent_ptr, command, tcell_context)
216
+
166
217
  if TCellAgent::Rust::Wrapper.common_lib_available? &&
167
218
  agent_ptr &&
168
219
  TCellAgent::Utils::Strings.present?(command)
169
- command_pointer = FFI::MemoryPointer.from_string(command)
220
+ method = tcell_context && tcell_context.request_method
221
+ path = tcell_context && tcell_context.path
222
+ command_info = {
223
+ "command" => command,
224
+ "method" => method,
225
+ "path" => path
226
+ }
227
+ command_pointer = FFI::MemoryPointer.from_string(
228
+ JSON.dump(command_info)
229
+ )
170
230
 
171
- buf = FFI::MemoryPointer.new(:uint8, 1024 * 8)
231
+ buf = FFI::MemoryPointer.new(:uint8, 1024 * 32)
172
232
  # patches_request_pointer.size - 1: strips null terminator
173
233
  result_size = TCellAgent::Rust::Wrapper.cmdi_apply(
174
- FFI::Pointer.new(agent_ptr), command_pointer, command_pointer.size - 1, buf, buf.size
234
+ FFI::Pointer.new(agent_ptr),
235
+ command_pointer,
236
+ command_pointer.size - 1,
237
+ buf,
238
+ buf.size
175
239
  )
176
240
  return self.convert_result("apply_cmdi", result_size, buf)
177
241
  end
178
242
 
179
243
  return {}
180
244
  end
245
+
246
+ def self.get_headers(agent_ptr, tcell_context)
247
+ if TCellAgent::Rust::Wrapper.common_lib_available? &&
248
+ agent_ptr &&
249
+ tcell_context
250
+ method = tcell_context.request_method
251
+ path = tcell_context.path
252
+ route_id = tcell_context.route_id
253
+ session_id = tcell_context.session_id
254
+ headers_request = {
255
+ method: method,
256
+ path: path,
257
+ route_id: route_id && route_id.to_s,
258
+ session_id: session_id && session_id.to_s
259
+ }
260
+ headers_request_pointer = FFI::MemoryPointer.from_string(
261
+ JSON.dump(headers_request)
262
+ )
263
+
264
+ buf = FFI::MemoryPointer.new(:uint8, 1024 * 16)
265
+ # patches_request_pointer.size - 1: strips null terminator
266
+ result_size = TCellAgent::Rust::Wrapper.get_headers(
267
+ FFI::Pointer.new(agent_ptr),
268
+ headers_request_pointer,
269
+ headers_request_pointer.size - 1,
270
+ buf,
271
+ buf.size
272
+ )
273
+ return self.convert_result("get_headers", result_size, buf)
274
+ end
275
+
276
+ return {}
277
+ end
278
+
279
+ def self.get_js_agent_script_tag(agent_ptr, tcell_context)
280
+ if TCellAgent::Rust::Wrapper.common_lib_available? &&
281
+ agent_ptr &&
282
+ tcell_context
283
+ method = tcell_context.request_method
284
+ path = tcell_context.path
285
+ jsagent_request = {
286
+ method: method,
287
+ path: path
288
+ }
289
+ jsagent_request_pointer = FFI::MemoryPointer.from_string(
290
+ JSON.dump(jsagent_request)
291
+ )
292
+
293
+ buf = FFI::MemoryPointer.new(:uint8, 1024 * 8)
294
+ # patches_request_pointer.size - 1: strips null terminator
295
+ result_size = TCellAgent::Rust::Wrapper.get_js_agent_script_tag(
296
+ FFI::Pointer.new(agent_ptr),
297
+ jsagent_request_pointer,
298
+ jsagent_request_pointer.size - 1,
299
+ buf,
300
+ buf.size
301
+ )
302
+ return self.convert_result("get_js_agent_script_tag", result_size, buf)
303
+ end
304
+
305
+ return {}
306
+ end
181
307
  end
182
308
  end
183
309
  end
@@ -14,6 +14,8 @@ module TCellAgent
14
14
  self["m"] = appsensor_meta.method if appsensor_meta.method
15
15
  self["remote_addr"] = appsensor_meta.remote_address if appsensor_meta.remote_address
16
16
  self["uri"] = TCellAgent::SensorEvents::Util.strip_uri_values(appsensor_meta.location) if appsensor_meta.location
17
+ self["regex_pid"] = rust_response["regex_pid"] if rust_response["regex_pid"]
18
+ self["payload"] = rust_response["payload"] if rust_response["payload"]
17
19
  end
18
20
  end
19
21
 
@@ -12,23 +12,26 @@ module TCellAgent
12
12
  alias_method :original_finish, :finish
13
13
  def finish
14
14
  status, headers, response = original_finish
15
- TCellAgent::Instrumentation.safe_block("Setting CSP Headers") {
16
- content_security_policy = TCellAgent.policy(TCellAgent::PolicyTypes::CSP)
17
- if content_security_policy
18
- content_security_policy.each_header_pair do |header_name, header_value|
19
- headers[header_name] = header_value
20
- end
21
- end
22
- }
23
15
 
24
- TCellAgent::Instrumentation.safe_block("Setting Secure Headers") {
25
- secure_headers_policy = TCellAgent.policy(TCellAgent::PolicyTypes::SecureHeaders)
26
- if secure_headers_policy
27
- secure_headers_policy.headers.each do | secure_header |
28
- headers[secure_header.name] = secure_header.value
16
+ TCellAgent::Instrumentation.safe_block("Setting Headers") do
17
+ rust_policies = TCellAgent.policy(TCellAgent::PolicyTypes::Rust)
18
+ if rust_policies
19
+ policy_headers = rust_policies.get_headers(
20
+ request.env[TCellAgent::Instrumentation::TCELL_ID]
21
+ )
22
+ policy_headers.each do |header_info|
23
+ header_name = header_info['name']
24
+ header_value = header_info['value']
25
+ existing_header_value = headers[header_name]
26
+ if existing_header_value
27
+ headers[header_name] = "#{existing_header_value}, #{header_value}"
28
+ else
29
+ headers[header_name] = header_value
30
+ end
29
31
  end
32
+ response = [status, headers, active_response]
30
33
  end
31
- }
34
+ end
32
35
 
33
36
  [status, headers, response]
34
37
  end
@@ -1,5 +1,5 @@
1
1
  # See the file "LICENSE" for the full license governing this code.
2
2
 
3
3
  module TCellAgent
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.0"
5
5
  end
@@ -203,6 +203,23 @@ module TCellAgent
203
203
  context 'with two processes' do
204
204
  context 'while one process is writing to the cached file' do
205
205
  before(:each) do
206
+ configuration = double(
207
+ 'configuration',
208
+ {
209
+ 'app_id' => 'app_id',
210
+ 'api_key' => 'api_key',
211
+ 'allow_payloads' => true,
212
+ 'js_agent_api_base_url' => 'http://api.tcell.com/',
213
+ 'js_agent_url' => 'https://jsagent.tcell.io/tcellagent.min.js',
214
+ 'max_csp_header_bytes' => nil,
215
+ 'event_time_limit_seconds' => 15,
216
+ 'event_batch_size_limit' => 50,
217
+ 'preload_policy_filename' => nil,
218
+ 'cache_filename_with_app_id' => '/tcellagent_src/tcell/cache/tcell_agent.cache',
219
+ 'agent_home_owner' => nil
220
+ }
221
+ )
222
+ expect(TCellAgent).to receive(:configuration).and_return(configuration).at_least(:once)
206
223
  TCellAgent.thread_agent.cache(
207
224
  'http-redirect',
208
225
  {
@@ -17,19 +17,22 @@ module TCellAgent
17
17
  ' \'none\'" ,"report-uri":"http://localhost:3000/csp/cab5e750e66d614bd46fd07a7078db1e74b4f427b2a135b2c96eca684a642707"}]}}}'
18
18
  end
19
19
  uri_template =
20
- Addressable::Template.new 'https://api.tcell.io/api/v1/app/{app}/update'
21
- stub_request(:any, uri_template)
22
- .to_return(lambda { |request|
20
+ Addressable::Template.new 'https://api.tcell.io/agents/api/v1/apps/test-appid/policies/latest?type=patches:v1'
21
+
22
+ stub_request(:any, uri_template).to_return(
23
+ lambda { |request|
23
24
  {
24
- :body => checkreq(request), :status => 200,
25
- :headers => { 'Content-Tyoe' => 'application/json' }
25
+ :body => checkreq(request),
26
+ :status => 200,
27
+ :headers => { 'Content-Type' => 'application/json' }
26
28
  }
27
- })
29
+ }
30
+ )
28
31
 
29
32
  result = tapi.poll_api
30
33
  TCellAgent.configuration.app_id = nil
31
34
  TCellAgent.configuration.api_key = nil
32
- expect(result['csp-headers']['app_id']).to eq('testapp-Becwu')
35
+ expect(result['result']['csp-headers']['app_id']).to eq('testapp-Becwu')
33
36
  end
34
37
  end
35
38
  end