tcell_agent 2.2.1 → 2.5.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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +2 -2
  3. data/bin/tcell_agent +6 -11
  4. data/lib/tcell_agent/agent.rb +18 -13
  5. data/lib/tcell_agent/config_initializer.rb +2 -5
  6. data/lib/tcell_agent/configuration.rb +6 -6
  7. data/lib/tcell_agent/hooks/login_fraud.rb +1 -1
  8. data/lib/tcell_agent/instrumentation/cmdi.rb +32 -0
  9. data/lib/tcell_agent/instrumentation/lfi.rb +55 -9
  10. data/lib/tcell_agent/instrumentation/monkey_patches/ruby_2/file.rb +21 -0
  11. data/lib/tcell_agent/instrumentation/monkey_patches/ruby_2/io.rb +75 -0
  12. data/lib/tcell_agent/instrumentation/monkey_patches/ruby_2/kernel.rb +80 -0
  13. data/lib/tcell_agent/instrumentation/monkey_patches/ruby_3/file.rb +21 -0
  14. data/lib/tcell_agent/instrumentation/monkey_patches/ruby_3/io.rb +75 -0
  15. data/lib/tcell_agent/instrumentation/monkey_patches/ruby_3/kernel.rb +80 -0
  16. data/lib/tcell_agent/instrumentation.rb +14 -6
  17. data/lib/tcell_agent/logger.rb +2 -2
  18. data/lib/tcell_agent/policies/dataloss_policy.rb +15 -8
  19. data/lib/tcell_agent/policies/headers_policy.rb +2 -2
  20. data/lib/tcell_agent/policies/patches_policy.rb +8 -4
  21. data/lib/tcell_agent/policies/policies_manager.rb +1 -0
  22. data/lib/tcell_agent/policies/policy_polling.rb +4 -3
  23. data/lib/tcell_agent/rails/auth/doorkeeper.rb +1 -0
  24. data/lib/tcell_agent/rails/better_ip.rb +7 -19
  25. data/lib/tcell_agent/rails/dlp/process_request.rb +5 -0
  26. data/lib/tcell_agent/rails/dlp.rb +48 -48
  27. data/lib/tcell_agent/rails/dlp_handler.rb +9 -10
  28. data/lib/tcell_agent/rails/js_agent_insert.rb +2 -3
  29. data/lib/tcell_agent/rails/middleware/context_middleware.rb +2 -1
  30. data/lib/tcell_agent/rails/middleware/global_middleware.rb +1 -5
  31. data/lib/tcell_agent/rails/middleware/headers_middleware.rb +1 -0
  32. data/lib/tcell_agent/rails/routes/grape.rb +2 -1
  33. data/lib/tcell_agent/rails/settings_reporter.rb +3 -6
  34. data/lib/tcell_agent/rails/tcell_body_proxy.rb +4 -6
  35. data/lib/tcell_agent/routes/table.rb +3 -0
  36. data/lib/tcell_agent/rust/agent_config.rb +20 -2
  37. data/lib/tcell_agent/rust/{libtcellagent-5.0.2.so → libtcellagent-alpine.so} +0 -0
  38. data/lib/tcell_agent/rust/{tcellagent-5.0.2.dll → libtcellagent-x64.dll} +0 -0
  39. data/lib/tcell_agent/rust/{libtcellagent-5.0.2.dylib → libtcellagent.dylib} +0 -0
  40. data/lib/tcell_agent/rust/{libtcellagent-alpine-5.0.2.so → libtcellagent.so} +0 -0
  41. data/lib/tcell_agent/rust/native_agent.rb +51 -59
  42. data/lib/tcell_agent/rust/native_library.rb +7 -10
  43. data/lib/tcell_agent/sensor_events/server_agent.rb +3 -100
  44. data/lib/tcell_agent/sensor_events/util/sanitizer_utilities.rb +1 -0
  45. data/lib/tcell_agent/servers/puma.rb +25 -8
  46. data/lib/tcell_agent/servers/rack_puma_handler.rb +13 -3
  47. data/lib/tcell_agent/servers/webrick.rb +13 -3
  48. data/lib/tcell_agent/settings_reporter.rb +0 -14
  49. data/lib/tcell_agent/sinatra.rb +1 -0
  50. data/lib/tcell_agent/tcell_context.rb +15 -6
  51. data/lib/tcell_agent/utils/headers.rb +0 -1
  52. data/lib/tcell_agent/utils/strings.rb +2 -2
  53. data/lib/tcell_agent/version.rb +1 -1
  54. data/spec/cruby_spec_helper.rb +26 -0
  55. data/spec/lib/tcell_agent/instrument_servers_spec.rb +1 -1
  56. data/spec/lib/tcell_agent/instrumentation/cmdi/io_cmdi_spec.rb +2 -2
  57. data/spec/lib/tcell_agent/instrumentation/lfi/file_lfi_spec.rb +211 -272
  58. data/spec/lib/tcell_agent/instrumentation/lfi/io_lfi_spec.rb +207 -223
  59. data/spec/lib/tcell_agent/instrumentation/lfi/kernel_lfi_spec.rb +89 -70
  60. data/spec/lib/tcell_agent/instrumentation/lfi_spec.rb +73 -0
  61. data/spec/lib/tcell_agent/patches_spec.rb +2 -1
  62. data/spec/lib/tcell_agent/policies/clickjacking_policy_spec.rb +1 -2
  63. data/spec/lib/tcell_agent/policies/content_security_policy_spec.rb +5 -6
  64. data/spec/lib/tcell_agent/policies/patches_policy_spec.rb +21 -2
  65. data/spec/lib/tcell_agent/policies/policies_manager_spec.rb +1 -1
  66. data/spec/lib/tcell_agent/policies/secure_headers_policy_spec.rb +13 -8
  67. data/spec/lib/tcell_agent/rails/better_ip_spec.rb +9 -11
  68. data/spec/lib/tcell_agent/rails/csrf_exception_spec.rb +6 -6
  69. data/spec/lib/tcell_agent/rails/dlp_spec.rb +1 -0
  70. data/spec/lib/tcell_agent/rails/js_agent_insert_spec.rb +10 -2
  71. data/spec/lib/tcell_agent/rails/middleware/tcell_body_proxy_spec.rb +2 -1
  72. data/spec/lib/tcell_agent/rails/routes/route_id_spec.rb +4 -4
  73. data/spec/lib/tcell_agent/settings_reporter_spec.rb +2 -16
  74. data/spec/lib/tcell_agent/tcell_context_spec.rb +6 -5
  75. data/spec/spec_helper.rb +3 -1
  76. data/spec/support/builders.rb +2 -1
  77. data/spec/support/server_mocks/puma_mock.rb +4 -0
  78. data/spec/support/shared_spec.rb +29 -0
  79. data/tcell_agent.gemspec +14 -14
  80. metadata +23 -19
  81. data/Rakefile +0 -18
  82. data/lib/tcell_agent/instrumentation/monkey_patches/file.rb +0 -25
  83. data/lib/tcell_agent/instrumentation/monkey_patches/io.rb +0 -131
  84. data/lib/tcell_agent/instrumentation/monkey_patches/kernel.rb +0 -102
@@ -6,22 +6,20 @@ module TCellAgent
6
6
  require 'ffi'
7
7
  extend FFI::Library
8
8
 
9
- VERSION = '5.0.2'.freeze
10
- prefix = 'lib'
11
9
  extension = '.so'
12
10
  variant = ''
13
11
  if /cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
12
+ variant = '-x64'
14
13
  extension = '.dll'
15
- prefix = ''
16
14
  elsif /darwin/ =~ RUBY_PLATFORM
17
15
  extension = '.dylib'
18
16
  elsif /musl/ =~ RUBY_PLATFORM
19
- variant = 'alpine-'
17
+ variant = '-alpine'
20
18
  end
21
19
 
22
20
  begin
23
21
  ffi_lib File.join(File.dirname(__FILE__),
24
- "#{prefix}tcellagent-#{variant}#{VERSION}#{extension}")
22
+ "libtcellagent#{variant}#{extension}")
25
23
 
26
24
  # All the rust library calls have the following response api:
27
25
  #
@@ -36,6 +34,7 @@ module TCellAgent
36
34
  attach_function :poll_new_policies, %i[pointer pointer size_t], :int
37
35
  attach_function :appfirewall_apply, %i[pointer pointer size_t pointer size_t], :int
38
36
  attach_function :patches_apply, %i[pointer pointer size_t pointer size_t], :int
37
+ attach_function :suspicious_quick_check_apply, %i[pointer pointer size_t], :int
39
38
  attach_function :cmdi_apply, %i[pointer pointer size_t pointer size_t], :int
40
39
  attach_function :get_headers, %i[pointer pointer size_t pointer size_t], :int
41
40
  attach_function :get_js_agent_script_tag, %i[pointer pointer size_t pointer size_t], :int
@@ -48,17 +47,15 @@ module TCellAgent
48
47
  attach_function :log_message, %i[pointer pointer size_t pointer size_t], :int
49
48
 
50
49
  attach_function :update_policies, %i[pointer pointer size_t pointer size_t], :int
51
- attach_function :test_event_sender, %i[pointer size_t pointer size_t], :int
52
- attach_function :test_policies, %i[pointer size_t pointer size_t], :int
53
50
  attach_function :test_agent, %i[pointer size_t pointer size_t], :int
54
51
 
55
52
  def self.common_lib_available?
56
53
  true
57
54
  end
58
- rescue LoadError => load_error
55
+ rescue LoadError => e
59
56
  logger = TCellAgent::ModuleLogger.new(TCellAgent::RubyLogger.new, name)
60
- logger.error("Failed loading agent library. #{load_error.message}")
61
- logger.exception(load_error)
57
+ logger.error("Failed loading agent library. #{e.message}")
58
+ logger.exception(e)
62
59
 
63
60
  def self.common_lib_available? # rubocop:disable Lint/DuplicateMethods
64
61
  false
@@ -7,59 +7,6 @@ require 'etc'
7
7
 
8
8
  module TCellAgent
9
9
  module SensorEvents
10
- class ServerAgentDetailsSensorEvent < TCellSensorEvent
11
- include TCellAgent::ModuleLoggerAccess
12
-
13
- def initialize
14
- super('server_agent_details')
15
- @flush = true
16
- @ensure = true
17
-
18
- self['user'] = 'unknown'
19
- self['group'] = 'unknown'
20
-
21
- begin
22
- login = Etc.getlogin
23
- if login
24
- self['user'] = login
25
- begin
26
- info = Etc.getpwnam(login)
27
- self['group'] = info.gid.to_s
28
- rescue StandardError => te
29
- module_logger.debug("Could not get group id: #{te.message}")
30
- module_logger.exception(te)
31
- end
32
- end
33
- rescue StandardError => to
34
- module_logger.debug("Could not get user & group: #{to.message}")
35
- module_logger.exception(te)
36
- end
37
-
38
- module_logger.debug("User #{self['user']}")
39
- module_logger.debug("Group #{self['group']}")
40
- end
41
- end
42
-
43
- class ServerAgentDetailsLanguageEvent < TCellSensorEvent
44
- def initialize(language, language_version)
45
- super('server_agent_details')
46
- @flush = true
47
- @ensure = true
48
- self['language'] = language
49
- self['language_version'] = language_version
50
- end
51
- end
52
-
53
- class ServerAgentAppFrameworkEvent < TCellSensorEvent
54
- def initialize(framework_name, framework_version)
55
- super('server_agent_details')
56
- @flush = true
57
- @ensure = true
58
- self['app_framework'] = framework_name
59
- self['app_framework_version'] = framework_version
60
- end
61
- end
62
-
63
10
  class ServerAgentPackagesSensorEvent < TCellSensorEvent
64
11
  include TCellAgent::ModuleLoggerAccess
65
12
 
@@ -75,59 +22,15 @@ module TCellAgent
75
22
  packages.push(package)
76
23
  module_logger.debug("Adding packages #{x.name}")
77
24
  end
78
- rescue StandardError => te
79
- module_logger.error("Exception adding package: #{te.message}")
80
- module_logger.exception(te)
25
+ rescue StandardError => e
26
+ module_logger.error("Exception adding package: #{e.message}")
27
+ module_logger.exception(e)
81
28
  end
82
29
  end
83
30
  self['packages'] = packages
84
31
  end
85
32
  end
86
33
 
87
- class AppFramework < TCellSensorEvent
88
- def initialize(name, version)
89
- super('appserver_framework')
90
- @flush = false
91
- @ensure = true
92
- self['n'] = name
93
- self['v'] = version
94
- end
95
- end
96
-
97
- class AppAuthFramework < TCellSensorEvent
98
- def initialize(name, version)
99
- super('appserver_auth_framework')
100
- @flush = false
101
- @ensure = true
102
- self['n'] = name
103
- self['v'] = version
104
- end
105
- end
106
-
107
- class AppFrameworkSetting < TCellSensorEvent
108
- def initialize(framework_name, setting, value)
109
- super('appserver_framework_setting')
110
- @flush = false
111
- @ensure = true
112
- self['framework'] = framework_name
113
- self['s'] = setting
114
- self['v'] = value
115
- end
116
- end
117
-
118
- class AppCookie < TCellSensorEvent
119
- def initialize(name, value, secure, http_only, session)
120
- super('appserver_framework_setting')
121
- @flush = false
122
- @ensure = true
123
- self['n'] = name
124
- self['v'] = value
125
- self['http_only'] = http_only
126
- self['secure'] = secure
127
- self['session'] = session
128
- end
129
- end
130
-
131
34
  class AppRoutesSensorEvent < TCellSensorEvent
132
35
  def initialize(uri, method, route_id, params = nil, destination = nil)
133
36
  super('appserver_routes')
@@ -19,6 +19,7 @@ module TCellAgent
19
19
  params = CGI.parse(query)
20
20
  params.each do |param_name, param_values|
21
21
  next if param_values.nil? || param_values.empty?
22
+
22
23
  params[param_name] = ['']
23
24
  end
24
25
  params.map { |k, v| "#{k}=#{v.join(',')}" }.join('&')
@@ -1,6 +1,6 @@
1
1
  if defined?(Puma.cli_config)
2
2
  if Puma.cli_config.options[:preload_app]
3
- if Puma.cli_config.options[:workers] == 0 # rubocop:disable Style/NumericPredicate
3
+ if Puma.cli_config.options[:workers] == 0
4
4
  # Puma is running in single mode, so run both the initial instrumentation and
5
5
  # start the agent
6
6
  Puma::Runner.class_eval do
@@ -15,24 +15,41 @@ if defined?(Puma.cli_config)
15
15
  else
16
16
  Puma::Server.class_eval do
17
17
  alias_method :tcell_original_run, :run
18
- def run(background = true)
19
- TCellAgent.thread_agent.start('Puma Cluster Mode (Worker)')
20
18
 
21
- tcell_original_run(background)
19
+ if defined?(Gem::Version) &&
20
+ defined?(Puma::Const::PUMA_VERSION) &&
21
+ (Gem::Version.new(Puma::Const::PUMA_VERSION) < Gem::Version.new('5.1.0'))
22
+ def run(background = true)
23
+ TCellAgent.thread_agent.start('Puma')
24
+ original_run(background, options)
25
+ end
26
+ else
27
+ def run(background = true, thread_name: 'server')
28
+ TCellAgent.thread_agent.start('Puma')
29
+ original_run(background, :thread_name => thread_name)
30
+ end
22
31
  end
23
32
  end
24
33
  end
25
-
26
34
  else
27
35
  # this ensures instrumentation runs for preload_app = false.
28
36
  # Instrumentation will run for each worker but there's
29
37
  # nothing we can do about that (Unicorn's preload_app behaves the same way)
30
38
  Puma::Server.class_eval do
31
39
  alias_method :tcell_original_run, :run
32
- def run(background = true)
33
- TCellAgent.thread_agent.start('Puma Cluster Mode (Worker)')
34
40
 
35
- tcell_original_run(background)
41
+ if defined?(Gem::Version) &&
42
+ defined?(Puma::Const::PUMA_VERSION) &&
43
+ (Gem::Version.new(Puma::Const::PUMA_VERSION) < Gem::Version.new('5.1.0'))
44
+ def run(background = true)
45
+ TCellAgent.thread_agent.start('Puma')
46
+ tcell_original_run(background)
47
+ end
48
+ else
49
+ def run(background = true, thread_name: 'server')
50
+ TCellAgent.thread_agent.start('Puma')
51
+ original_run(background, :thread_name => thread_name)
52
+ end
36
53
  end
37
54
  end
38
55
  end
@@ -9,10 +9,20 @@ Rack::Handler::Puma.class_eval do
9
9
  if defined?(Puma::Server) && !Puma::Server.instance_methods.include?(:tcell_original_run)
10
10
  Puma::Server.class_eval do
11
11
  alias_method :tcell_original_run, :run
12
- def run(background = true)
13
- TCellAgent.thread_agent.start('Puma')
14
12
 
15
- tcell_original_run(background)
13
+ if defined?(Gem::Version) &&
14
+ defined?(Puma::Const::PUMA_VERSION) &&
15
+ (Gem::Version.new(Puma::Const::PUMA_VERSION) >= Gem::Version.new('5.1.0'))
16
+ def run(background = true, thread_name: 'server')
17
+ TCellAgent.thread_agent.start('Puma')
18
+ original_run(background, :thread_name => thread_name)
19
+ end
20
+ else
21
+ def run(background = true)
22
+ TCellAgent.thread_agent.start('Puma')
23
+
24
+ tcell_original_run(background)
25
+ end
16
26
  end
17
27
  end
18
28
  end
@@ -1,9 +1,19 @@
1
1
  Rack::Handler::WEBrick.class_eval do
2
2
  class << self
3
3
  alias_method :original_run, :run
4
- def run(app, options = {})
5
- TCellAgent.thread_agent.start('WEBrick')
6
- original_run(app, options)
4
+
5
+ if defined?(Gem::Version) &&
6
+ defined?(Rack.release) &&
7
+ Gem::Version.new(Rack.release) < Gem::Version.new('2.2.0')
8
+ def run(app, options = {})
9
+ TCellAgent.thread_agent.start('WEBrick')
10
+ original_run(app, options)
11
+ end
12
+ else
13
+ def run(app, **options)
14
+ TCellAgent.thread_agent.start('WEBrick')
15
+ original_run(app, **options)
16
+ end
7
17
  end
8
18
  end
9
19
  end
@@ -7,25 +7,11 @@ require 'thread'
7
7
  module TCellAgent
8
8
  def self.report_settings
9
9
  Thread.new do
10
- TCellAgent::Instrumentation.safe_block('Instrumenting Agent Details') do
11
- event = TCellAgent::SensorEvents::ServerAgentDetailsSensorEvent.new
12
- TCellAgent.send_event(event)
13
- end
14
-
15
10
  TCellAgent::Instrumentation.safe_block('Instrumenting Server Packages') do
16
11
  event = TCellAgent::SensorEvents::ServerAgentPackagesSensorEvent.new
17
12
  TCellAgent.send_event(event)
18
13
  end
19
14
 
20
- TCellAgent::Instrumentation.safe_block('Instrumenting Language Info') do
21
- TCellAgent.send_event(
22
- TCellAgent::SensorEvents::ServerAgentDetailsLanguageEvent.new(
23
- 'Ruby',
24
- RUBY_VERSION
25
- )
26
- )
27
- end
28
-
29
15
  TCellAgent::Instrumentation.safe_block('Instrumenting Native Lib Status') do
30
16
  require 'tcell_agent/rust/native_agent'
31
17
 
@@ -16,6 +16,7 @@ module TCellAgent
16
16
  TCellAgent::Instrumentation.safe_block('Setting Headers') do
17
17
  headers_policy = TCellAgent.policy(TCellAgent::PolicyTypes::HEADERS)
18
18
  policy_headers = headers_policy.get_headers(
19
+ headers['Content-Type'],
19
20
  request.env[TCellAgent::Instrumentation::TCELL_ID]
20
21
  )
21
22
  policy_headers.each do |header_info|
@@ -43,7 +43,8 @@ module TCellAgent
43
43
  tcell_context.session_id,
44
44
  tcell_context.user_id,
45
45
  tcell_context.transaction_id,
46
- tcell_context.uri
46
+ tcell_context.uri,
47
+ tcell_context.reverse_proxy_header_value
47
48
  )
48
49
  meta_data.path = tcell_context.path
49
50
 
@@ -70,7 +71,8 @@ module TCellAgent
70
71
  :response_headers,
71
72
  :csrf_exception_name,
72
73
  :sql_exceptions,
73
- :database_result_sizes
74
+ :database_result_sizes,
75
+ :reverse_proxy_header_value
74
76
 
75
77
  attr_reader :flattened_get_dict,
76
78
  :flattened_cookie_dict,
@@ -85,7 +87,8 @@ module TCellAgent
85
87
  session_id,
86
88
  user_id,
87
89
  transaction_id,
88
- location)
90
+ location,
91
+ reverse_proxy_header_value)
89
92
  @method = method
90
93
  @remote_address = remote_address
91
94
  @route_id = route_id
@@ -93,7 +96,7 @@ module TCellAgent
93
96
  @user_id = user_id
94
97
  @transaction_id = transaction_id
95
98
  @location = location
96
- @path = path
99
+ @reverse_proxy_header_value = reverse_proxy_header_value
97
100
 
98
101
  @flattened_get_dict = {}
99
102
  @flattened_cookie_dict = {}
@@ -148,6 +151,7 @@ module TCellAgent
148
151
  if !content_length.nil? && content_length > TCELL_MAX_BODY_LENGTH || request.content_type.nil?
149
152
  return nil
150
153
  end
154
+
151
155
  raw_post_data = nil
152
156
  # Positions strio to the beginning of input, resetting lineno to zero.
153
157
  # rails 4.1 seems to read the stringIO directly and so body.gets is empty
@@ -171,10 +175,15 @@ module TCellAgent
171
175
  self.get_dict = request.GET
172
176
  self.cookie_dict = request.cookies
173
177
 
174
- self.post_dict = if @content_type.start_with?('application/json', 'application/xml')
178
+ self.post_dict = if @content_type.start_with?('application/json', 'application/xml') ||
179
+ (@content_type.start_with?('multipart/form-data') && @request_content_bytes_len == 0)
175
180
  {}
176
181
  else
177
- request.POST
182
+ begin
183
+ request.POST
184
+ rescue EOFError
185
+ {}
186
+ end
178
187
  end
179
188
 
180
189
  self.headers_dict = request.env
@@ -1,4 +1,3 @@
1
-
2
1
  module TCellAgent
3
2
  module Utils
4
3
  module Headers
@@ -1,10 +1,10 @@
1
1
  module TCellAgent
2
2
  module Utils
3
3
  module Strings
4
- BLANK_RE = /\A[[:space:]]*\z/
4
+ BLANK_RE = /\A[[:space:]]*\z/.freeze
5
5
 
6
6
  def self.blank?(str)
7
- str.nil? || str.empty? || BLANK_RE === str # rubocop:disable Style/CaseEquality
7
+ str.nil? || str.empty? || BLANK_RE === str
8
8
  end
9
9
 
10
10
  def self.present?(str)
@@ -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 = '2.2.1'.freeze
4
+ VERSION = '2.5.0'.freeze
5
5
  end
@@ -0,0 +1,26 @@
1
+ require 'tcell_agent'
2
+
3
+ TCellAgent.configuration.enabled = true
4
+ TCellAgent.configuration.instrument = true
5
+ TCellAgent.configuration.enable_intercept_requests = true
6
+ TCellAgent.configuration.disabled_instrumentation = []
7
+ TCellAgent.thread_agent.instrument_built_ins
8
+
9
+ # This monkey patch turns off blocking for LFI/CMDI when run in CRuby specs/
10
+ # test suite. The original method also depends on a Rails installation,
11
+ # which CRuby does not have installed.
12
+ module TCellAgent
13
+ module Instrumentation
14
+ module Lfi
15
+ def self.block_file_access?(_path, _mode)
16
+ false
17
+ end
18
+ end
19
+ end
20
+
21
+ module Cmdi
22
+ def self.block_command?(_cmd)
23
+ false
24
+ end
25
+ end
26
+ end