contrast-agent 6.0.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. checksums.yaml +4 -4
  2. data/ext/cs__assess_regexp/cs__assess_regexp.c +15 -2
  3. data/ext/cs__assess_regexp/cs__assess_regexp.h +2 -0
  4. data/ext/cs__assess_string/cs__assess_string.c +8 -0
  5. data/ext/cs__assess_test/cs__assess_test.h +9 -0
  6. data/ext/cs__assess_test/cs__assess_tests.c +22 -0
  7. data/ext/cs__assess_test/extconf.rb +5 -0
  8. data/ext/cs__common/cs__common.c +101 -0
  9. data/ext/cs__common/cs__common.h +29 -5
  10. data/ext/cs__contrast_patch/cs__contrast_patch.c +1 -1
  11. data/ext/cs__tests/cs__tests.c +12 -0
  12. data/ext/cs__tests/cs__tests.h +3 -0
  13. data/ext/cs__tests/extconf.rb +5 -0
  14. data/lib/contrast/agent/assess/contrast_object.rb +16 -16
  15. data/lib/contrast/agent/assess/events/source_event.rb +17 -19
  16. data/lib/contrast/agent/assess/policy/policy_scanner.rb +2 -16
  17. data/lib/contrast/agent/assess/policy/propagator/split.rb +15 -19
  18. data/lib/contrast/agent/assess/policy/trigger_method.rb +3 -11
  19. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +7 -2
  20. data/lib/contrast/agent/assess/rule/response/base_rule.rb +11 -3
  21. data/lib/contrast/agent/assess/rule/response/cache_control_header_rule.rb +60 -36
  22. data/lib/contrast/agent/at_exit_hook.rb +1 -1
  23. data/lib/contrast/agent/inventory/database_config.rb +10 -3
  24. data/lib/contrast/agent/middleware.rb +3 -3
  25. data/lib/contrast/agent/patching/policy/after_load_patch.rb +0 -2
  26. data/lib/contrast/agent/patching/policy/patch.rb +13 -12
  27. data/lib/contrast/agent/patching/policy/patcher.rb +1 -1
  28. data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +6 -2
  29. data/lib/contrast/agent/reporting/masker/masker.rb +8 -11
  30. data/lib/contrast/agent/reporting/masker/masker_utils.rb +8 -4
  31. data/lib/contrast/agent/reporting/reporter.rb +11 -16
  32. data/lib/contrast/agent/reporting/reporter_heartbeat.rb +49 -0
  33. data/lib/contrast/agent/reporting/reporting_events/agent_startup.rb +6 -2
  34. data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +53 -0
  35. data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +48 -0
  36. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_activity.rb +64 -0
  37. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample.rb +70 -0
  38. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +57 -0
  39. data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +56 -0
  40. data/lib/contrast/agent/reporting/reporting_events/application_inventory.rb +5 -1
  41. data/lib/contrast/agent/reporting/reporting_events/application_inventory_activity.rb +58 -0
  42. data/lib/contrast/agent/reporting/reporting_events/application_reporting_event.rb +27 -0
  43. data/lib/contrast/agent/reporting/reporting_events/application_startup.rb +20 -10
  44. data/lib/contrast/agent/reporting/reporting_events/application_update.rb +7 -12
  45. data/lib/contrast/agent/reporting/reporting_events/finding.rb +9 -3
  46. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +2 -4
  47. data/lib/contrast/agent/reporting/reporting_events/finding_event_object.rb +3 -3
  48. data/lib/contrast/agent/reporting/reporting_events/observed_library_usage.rb +6 -2
  49. data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +7 -3
  50. data/lib/contrast/agent/reporting/reporting_events/poll.rb +6 -2
  51. data/lib/contrast/agent/reporting/reporting_events/preflight.rb +10 -8
  52. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +6 -10
  53. data/lib/contrast/agent/reporting/reporting_events/server_activity.rb +12 -20
  54. data/lib/contrast/agent/reporting/reporting_events/server_reporting_event.rb +27 -0
  55. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +17 -27
  56. data/lib/contrast/agent/reporting/reporting_utilities/build_preflight.rb +38 -0
  57. data/lib/contrast/agent/reporting/reporting_utilities/dtm_message.rb +8 -0
  58. data/lib/contrast/agent/reporting/reporting_utilities/endpoints.rb +6 -0
  59. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +9 -4
  60. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +54 -67
  61. data/lib/contrast/agent/reporting/reporting_utilities/response.rb +17 -7
  62. data/lib/contrast/agent/reporting/reporting_utilities/response_extractor.rb +8 -5
  63. data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +10 -10
  64. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +32 -17
  65. data/lib/contrast/agent/reporting/settings/protect.rb +1 -1
  66. data/lib/contrast/agent/reporting/settings/protect_server_feature.rb +1 -1
  67. data/lib/contrast/agent/request.rb +3 -3
  68. data/lib/contrast/agent/request_context_extend.rb +1 -1
  69. data/lib/contrast/agent/request_handler.rb +3 -3
  70. data/lib/contrast/agent/response.rb +2 -0
  71. data/lib/contrast/agent/service_heartbeat.rb +6 -48
  72. data/lib/contrast/agent/static_analysis.rb +1 -1
  73. data/lib/contrast/agent/telemetry/base.rb +151 -0
  74. data/lib/contrast/agent/telemetry/events/event.rb +35 -0
  75. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_base.rb +44 -36
  76. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_event.rb +29 -21
  77. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_message.rb +91 -73
  78. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_message_exception.rb +62 -44
  79. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_stack_frame.rb +50 -33
  80. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exceptions.rb +20 -0
  81. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exceptions_report.rb +32 -0
  82. data/lib/contrast/agent/telemetry/events/metric_event.rb +28 -0
  83. data/lib/contrast/agent/telemetry/events/startup_metrics_event.rb +123 -0
  84. data/lib/contrast/agent/thread_watcher.rb +52 -68
  85. data/lib/contrast/agent/version.rb +1 -1
  86. data/lib/contrast/agent/worker_thread.rb +8 -0
  87. data/lib/contrast/agent.rb +1 -3
  88. data/lib/contrast/api/communication/messaging_queue.rb +28 -11
  89. data/lib/contrast/api/communication/response_processor.rb +7 -10
  90. data/lib/contrast/api/communication/speedracer.rb +1 -1
  91. data/lib/contrast/api/decorators/activity.rb +33 -0
  92. data/lib/contrast/api/decorators/http_request.rb +1 -1
  93. data/lib/contrast/components/config.rb +13 -22
  94. data/lib/contrast/components/contrast_service.rb +9 -0
  95. data/lib/contrast/components/settings.rb +10 -0
  96. data/lib/contrast/config/agent_configuration.rb +21 -11
  97. data/lib/contrast/config/api_configuration.rb +12 -8
  98. data/lib/contrast/config/api_proxy_configuration.rb +7 -3
  99. data/lib/contrast/config/application_configuration.rb +15 -11
  100. data/lib/contrast/config/assess_configuration.rb +13 -9
  101. data/lib/contrast/config/assess_rules_configuration.rb +5 -1
  102. data/lib/contrast/config/base_configuration.rb +3 -35
  103. data/lib/contrast/config/certification_configuration.rb +9 -5
  104. data/lib/contrast/config/exception_configuration.rb +10 -7
  105. data/lib/contrast/config/heap_dump_configuration.rb +13 -9
  106. data/lib/contrast/config/inventory_configuration.rb +9 -6
  107. data/lib/contrast/config/logger_configuration.rb +9 -6
  108. data/lib/contrast/config/protect_configuration.rb +9 -6
  109. data/lib/contrast/config/protect_rule_configuration.rb +12 -8
  110. data/lib/contrast/config/protect_rules_configuration.rb +18 -17
  111. data/lib/contrast/config/request_audit_configuration.rb +10 -7
  112. data/lib/contrast/config/root_configuration.rb +28 -11
  113. data/lib/contrast/config/ruby_configuration.rb +14 -11
  114. data/lib/contrast/config/sampling_configuration.rb +11 -8
  115. data/lib/contrast/config/server_configuration.rb +13 -9
  116. data/lib/contrast/config/service_configuration.rb +14 -11
  117. data/lib/contrast/configuration.rb +19 -10
  118. data/lib/contrast/framework/rails/patch/support.rb +13 -45
  119. data/lib/contrast/logger/aliased_logging.rb +87 -0
  120. data/lib/contrast/logger/application.rb +0 -4
  121. data/lib/contrast/tasks/config.rb +22 -13
  122. data/lib/contrast/utils/class_util.rb +2 -6
  123. data/lib/contrast/utils/invalid_configuration_util.rb +1 -1
  124. data/lib/contrast/utils/log_utils.rb +2 -0
  125. data/lib/contrast/utils/middleware_utils.rb +1 -1
  126. data/lib/contrast/utils/object_share.rb +1 -1
  127. data/lib/contrast/utils/telemetry.rb +20 -2
  128. data/lib/contrast/utils/telemetry_client.rb +22 -10
  129. data/lib/contrast/utils/telemetry_hash.rb +41 -0
  130. data/lib/contrast/utils/telemetry_identifier.rb +16 -1
  131. data/lib/contrast.rb +9 -0
  132. data/ruby-agent.gemspec +1 -1
  133. data/service_executables/VERSION +1 -1
  134. data/service_executables/linux/contrast-service +0 -0
  135. data/service_executables/mac/contrast-service +0 -0
  136. metadata +39 -16
  137. data/lib/contrast/agent/telemetry/events/metric_telemetry_event.rb +0 -26
  138. data/lib/contrast/agent/telemetry/events/startup_metrics_telemetry_event.rb +0 -121
  139. data/lib/contrast/agent/telemetry/events/telemetry_event.rb +0 -33
  140. data/lib/contrast/agent/telemetry/telemetry.rb +0 -150
  141. data/lib/contrast/utils/exclude_key.rb +0 -20
@@ -36,50 +36,64 @@ module Contrast
36
36
  # @param response [Contrast::Agent::Response] the response of the application
37
37
  # @return [Hash, nil] the evidence required to prove the violation of the rule
38
38
  def violated? response
39
- has_header, evidence = process_header(response)
40
- return evidence unless evidence.nil?
39
+ return unless header?(response) && meta_tag?(response)
41
40
 
42
- has_tag, evidence = process_body(response)
43
- return evidence unless evidence.nil?
44
- return {} if !has_header && !has_tag
41
+ header_evidence = header_evidence(response)
42
+ return if header_evidence.nil?
45
43
 
46
- nil
44
+ tag_evidence = tag_evidence(response)
45
+ return if tag_evidence.nil?
46
+
47
+ { DATA => [header_evidence, tag_evidence] }
47
48
  end
48
49
 
49
- # Process Header value to determine if it violates rule
50
+ # Is a cache-control header available?
50
51
  # @param response [Contrast::Agent::Response] the response of the application
51
- # @return [Array[Boolean, Hash]] whether the header exists and the evidence hash or nil
52
- def process_header response
53
- # Rails 7 adds support for the cache_control header directly in the
54
- # rack response, we should use that value
55
- if framework_supported?
56
- cache_control = response.rack_response.cache_control
57
- has_header = !cache_control.blank?
58
- not_valid = has_header && !((cache_control[:no_cache]) || (cache_control[:no_store]))
59
- # evidence requires header value string, pull directly instead of rebuilding from hash
60
- return has_header, evidence(HEADER_TYPE, NAME, cache_control_to_s(cache_control)) if not_valid
61
- else
62
- # This rule is violated if the header is not there is there,
63
- # but the value is not 'no-store' or 'no-cache'
64
- cache_control = get_header_value(response)
65
- has_header = !!cache_control
66
- not_valid = has_header && !valid_header?(cache_control)
67
- return has_header, evidence(HEADER_TYPE, NAME, cache_control) if not_valid
52
+ # @return [Boolean]
53
+ def header? response
54
+ cache_control = cache_control_from(response)
55
+ framework_supported? ? !cache_control.blank? : !!cache_control
56
+ end
57
+
58
+ # Is a cache-control meta tag available?
59
+ # @param response [Contrast::Agent::Response] the response of the application
60
+ # @return [Boolean]
61
+ def meta_tag? response
62
+ return false if meta_tags(response).empty?
63
+
64
+ meta_tags(response).each do |tag|
65
+ return true if meta_cache_tag? tag[HTML_PROP]
68
66
  end
69
- [has_header, nil]
67
+
68
+ false
70
69
  end
71
70
 
72
- # Process Body to determine cache control exists as meta tag and if it violates rule
71
+ def meta_tags response
72
+ return @_meta_tags if defined? @meta_tags
73
+
74
+ @_meta_tags = html_elements(response.body&.split(HEAD_TAG)&.last, META_START_STR)
75
+ end
76
+
77
+ # Process Header value to determine if it violates rule
78
+ # @param response [Contrast::Agent::Response] the response of the application
79
+ # @return [Hash, nil] the evidence hash or nil
80
+ def header_evidence response
81
+ cache_control = cache_control_from(response)
82
+ value = framework_supported? ? cache_control : cache_control_to_s(cache_control)
83
+ # If header is valid, then this portion of the rule isn't violated.
84
+ return if valid_header?(value)
85
+
86
+ # evidence requires header value string, pull directly instead of rebuilding from hash
87
+ evidence(HEADER_TYPE, NAME, value)
88
+ end
89
+
90
+ # Process Body to determine if cache control meta tag violates rule
73
91
  # @param response [Contrast::Agent::Response] the response of the application
74
- # @return [Array[Boolean, Hash]] whether the meta tags exists and the evidence hash or nil
75
- def process_body response
76
- body = response.body
77
- # check if the meta tag is include it
78
- meta_tags = html_elements(body&.split(HEAD_TAG)&.last, META_START_STR)
79
- meta_tags.each do |tag|
80
- return true, evidence(META_TYPE, PRAGMA, tag[HTML_PROP]) if meta_cache_tag? tag[HTML_PROP]
92
+ # @return [Hash, nil] the evidence hash or nil
93
+ def tag_evidence response
94
+ meta_tags(response).each do |tag|
95
+ return evidence(META_TYPE, PRAGMA, tag[HTML_PROP]) if meta_cache_tag? tag[HTML_PROP]
81
96
  end
82
- [!meta_tags.empty?, nil]
83
97
  end
84
98
 
85
99
  def potential_elements section, element_start
@@ -121,13 +135,23 @@ module Contrast
121
135
  end
122
136
 
123
137
  # This method accepts the violation and transforms it to the proper hash
124
- # before return in as violation
138
+ # before returning a violation
125
139
  #
126
140
  # @param type [String] String of Header or META of the type
127
141
  # @param name [String] String of either cache-control or pragma
128
142
  # @param value [String] String of the violated value
129
143
  def evidence type, name, value
130
- { DATA => { type: type, name: name, value: value }.to_s }
144
+ { type: type, name: name, value: value }.to_json
145
+ end
146
+
147
+ def cache_control_from response
148
+ # Rails 7 adds support for the cache_control header directly in the
149
+ # rack response, we should use that value
150
+ if framework_supported? && response.rack_response.cs__is_a?(Rack::Response)
151
+ response.rack_response.cache_control
152
+ else
153
+ get_header_value(response)
154
+ end
131
155
  end
132
156
 
133
157
  # Rebuilds the String value of the Cache-Control Header
@@ -31,7 +31,7 @@ module Contrast
31
31
  context = Contrast::Agent::REQUEST_TRACKER.current
32
32
  return unless context
33
33
 
34
- Contrast::Agent.messaging_queue.send_event_immediately(context.activity)
34
+ Contrast::Agent.messaging_queue&.send_event_immediately(context.activity)
35
35
  end
36
36
  end
37
37
  end
@@ -44,20 +44,27 @@ module Contrast
44
44
  end
45
45
  end
46
46
  rescue StandardError => e
47
- logger.error('Unable to append db config', e)
47
+ logger.warn('Unable to append db config', e)
48
48
  nil
49
49
  end
50
50
 
51
51
  private
52
52
 
53
53
  # We capture the active record configuration used by this application, as reported by
54
- # ActiveRecord::Base.connection_config, so that we can record it once and report it as needed.
54
+ # ActiveRecord::Base.connection_db_config, so that we can record it once and report it as needed.
55
55
  #
56
56
  # @return [Hash]
57
57
  def active_record_config
58
58
  return @_active_record_config if instance_variable_defined?(:@_active_record_config)
59
59
 
60
- @_active_record_config = ActiveRecord::Base.connection_config rescue nil # rubocop:disable Style/RescueModifier
60
+ @_active_record_config = if ActiveRecord::Base.cs__respond_to?(:connection_db_config)
61
+ ActiveRecord::Base.connection_db_config
62
+ else
63
+ # TODO: RUBY-99999 - Remove when Rails 6.0 is not supported
64
+ ActiveRecord::Base.connection_config
65
+ end
66
+ rescue StandardError
67
+ nil
61
68
  end
62
69
 
63
70
  # The classes we instrument in order to determine which, if any, database(s) an application connects to take
@@ -13,7 +13,7 @@ require 'contrast/utils/heap_dump_util'
13
13
  require 'contrast/utils/telemetry'
14
14
  require 'contrast/agent/request_handler'
15
15
  require 'contrast/agent/static_analysis'
16
- require 'contrast/agent/telemetry/events/startup_metrics_telemetry_event'
16
+ require 'contrast/agent/telemetry/events/startup_metrics_event'
17
17
  require 'contrast/utils/middleware_utils'
18
18
 
19
19
  require 'contrast/utils/timer'
@@ -78,9 +78,9 @@ module Contrast
78
78
  Contrast::Agent.thread_watcher.ensure_running?
79
79
  end
80
80
 
81
- if Contrast::Agent::Telemetry.enabled?
81
+ if Contrast::Agent::Telemetry::Base.enabled?
82
82
  logger.debug_with_time('middleware: sending startup metrics telemetry event') do
83
- event = Contrast::Agent::StartupMetricsTelemetryEvent.new
83
+ event = Contrast::Agent::Telemetry::StartupMetricsEvent.new
84
84
  Contrast::Agent.thread_watcher.telemetry_queue.send_event(event)
85
85
  end
86
86
  end
@@ -59,8 +59,6 @@ module Contrast
59
59
  end
60
60
 
61
61
  def instrument!
62
- return if instrumenting_module == :'Contrast::Framework::Rails::Rewrite' && RAILS_VER >= '2.6.0'
63
-
64
62
  require instrumentation_file_path
65
63
 
66
64
  if instrumenting_module
@@ -114,16 +114,15 @@ module Contrast
114
114
  # (equivalent to :alias, where `module = module.singleton class`)
115
115
  # (this is a.k.a. "class-method patch")
116
116
  # :prepend -> prepend instance method of module
117
- # [prepending singleton is easily supported too, just not implemented yet.]
117
+ # :prepending singleton -> prepend singleton method of module
118
118
  # @return [Symbol] new alias for the underlying method (presumably, so the patched method can call it)
119
119
  def register_c_patch target_module_name, unbound_method, impl = :alias_instance
120
120
  # These could be set as AfterLoadPatches.
121
121
  method_name = unbound_method.name.to_sym # rubocop:disable Security/Module/Name -- ruby built in attribute.
122
- underlying_method_name = build_unbound_method_name(method_name).to_sym
122
+ underlying_method_name = underlying_method_name(method_name, impl)
123
123
 
124
124
  target_module = Module.cs__const_get(target_module_name)
125
- target_module = target_module.cs__singleton_class if %i[prepend_singleton prepend].include? impl
126
- target_module = target_module.cs__singleton_class if %i[alias_singleton prepend].include? impl
125
+ target_module = target_module.cs__singleton_class if %i[prepend_singleton alias_singleton].include? impl
127
126
 
128
127
  visibility = if target_module.private_instance_methods(false).include?(method_name)
129
128
  :private
@@ -161,7 +160,7 @@ module Contrast
161
160
  # @param visibility [Symbol] method visibility
162
161
  def reflect_implementation impl, target_module, unbound_method, visibility
163
162
  method_name = unbound_method.name.to_sym # rubocop:disable Security/Module/Name -- ruby built in attribute.
164
- underlying_method_name = build_unbound_method_name(method_name).to_sym
163
+ underlying_method_name = underlying_method_name(method_name, impl)
165
164
 
166
165
  case impl
167
166
  when :alias_instance, :alias_singleton
@@ -174,14 +173,10 @@ module Contrast
174
173
  end
175
174
  target_module.send(visibility, method_name) # e.g., module.private(:my_method)
176
175
  when :prepend_instance, :prepend_singleton
176
+ prepending_module = Module.new
177
+ prepending_module.send(:define_method, method_name, unbound_method.bind(target_module))
178
+ prepending_module.send(visibility, method_name)
177
179
 
178
- unless target_module.instance_methods(false).include? underlying_method_name
179
-
180
- prepending_module = Module.new
181
- prepending_module.send(:define_method, method_name, unbound_method.bind(target_module))
182
- prepending_module.send(visibility, method_name)
183
-
184
- end
185
180
  # This prepends to the singleton class (it patches a class method)
186
181
  target_module.prepend prepending_module
187
182
  # rubocop:enable Performance/Kernel/DefineMethod
@@ -207,6 +202,12 @@ module Contrast
207
202
 
208
203
  !ASSESS&.enabled?
209
204
  end
205
+
206
+ def underlying_method_name method_name, impl
207
+ return method_name.to_sym if %i[prepend_instance prepend_singleton].include? impl
208
+
209
+ build_unbound_method_name(method_name).to_sym
210
+ end
210
211
  end
211
212
  end
212
213
  end
@@ -207,7 +207,7 @@ module Contrast
207
207
  def patch_into_instance_methods module_data, module_policy
208
208
  mod = module_data.mod
209
209
  methods = all_instance_methods(mod, private: true)
210
- methods.delete(:initialize) if mod.to_s.starts_with?('RSpec') && mod.to_s.include?('Matchers')
210
+ methods.delete(:initialize) if mod.to_s.start_with?('RSpec') && mod.to_s.include?('Matchers')
211
211
  patch_into_methods(mod, methods, module_policy, true)
212
212
  end
213
213
 
@@ -8,6 +8,7 @@ require 'contrast/agent/protect/rule/no_sqli/no_sqli_input_classification'
8
8
  require 'contrast/agent/protect/rule/sqli/sqli_input_classification'
9
9
  require 'contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload_input_classification'
10
10
  require 'contrast/agent/protect/rule/unsafe_file_upload'
11
+ require 'contrast/components/logger'
11
12
  require 'contrast/utils/object_share'
12
13
  require 'contrast/agent/protect/rule/cmdi/cmdi_input_classification'
13
14
  require 'contrast/agent/protect/rule/http_method_tampering/http_method_tampering_input_classification'
@@ -59,7 +60,7 @@ module Contrast
59
60
  # @param request [Contrast::Agent::Request] current request context.
60
61
  # @return input_analysis [Contrast::Agent::Reporting::InputAnalysis, nil]
61
62
  def analyse request
62
- return unless Contrast::SETTINGS.protect_state.enabled
63
+ return unless Contrast::PROTECT.enabled?
63
64
  return if request.nil?
64
65
 
65
66
  inputs = extract_input request
@@ -86,8 +87,11 @@ module Contrast
86
87
  next if value.nil? || value.empty?
87
88
 
88
89
  PROTECT_RULES.each do |_key, rule|
90
+ protect_rule = Contrast::PROTECT.rule(rule[:rule_name])
91
+ logger.debug("Rule #{ rule[:rule_name] } not recognised in Protect rules") if protect_rule.nil?
92
+
89
93
  # check if rule is enabled
90
- next unless Contrast::PROTECT.rule(rule[:rule_name]).enabled?
94
+ next unless protect_rule&.enabled?
91
95
 
92
96
  # method tampering doesn't take value
93
97
  if rule[:rule_name] == Contrast::Agent::Protect::Rule::HttpMethodTampering::NAME
@@ -33,21 +33,18 @@ module Contrast
33
33
  # @param [Contrast::Api::Dtm::Activity]
34
34
  def mask activity
35
35
  return unless Contrast::Agent::Reporter.enabled?
36
- return unless activity || activity.http_request || activity.results
36
+ return unless activity
37
37
 
38
38
  logger.debug('Searching for sensitive data',
39
39
  activity: activity.__id__,
40
40
  request: activity.http_request&.uuid)
41
- mask_body activity
42
- mask_query_string activity
43
- mask_request_params activity
44
- mask_request_cookies activity
45
- mask_request_headers activity
46
- rescue StandardError => e
47
- logger.debug('Could not mask activity!',
48
- activity: activity.__id__,
49
- request: activity.http_request&.uuid,
50
- error_msg: e.message)
41
+ mask_body(activity)
42
+ mask_query_string(activity)
43
+ mask_request_params(activity)
44
+ mask_request_cookies(activity)
45
+ mask_request_headers(activity)
46
+ rescue StandardError => _e
47
+ logger.debug('Could not mask activity!', activity: activity.__id__, request: activity.http_request&.uuid)
51
48
  end
52
49
 
53
50
  private
@@ -14,10 +14,14 @@ module Contrast
14
14
  # @param field_hash [Protobuf::Field::FieldHash] hash to be masked
15
15
  # @param results [Array<Contrast::Api::Dtm::AttackResults>]
16
16
  # results to match against.
17
+ # @return [Hash]
17
18
  def mask_field_hash field_hash, results
19
+ return {} unless field_hash&.any?
20
+
18
21
  hash = {}
19
- masked = EMPTY_STRING
20
- field_hash.any? do |entry|
22
+ # Because this is the start of a built string, we have to be sure that it is not frozen.
23
+ masked = +''
24
+ field_hash.each do |entry|
21
25
  # Protobuf::Field::FieldHash produces array, with the key as first param and value as second.
22
26
  new_value = entry[1].delete(SEMICOLON).split(SPACE)
23
27
  new_value.each do |value|
@@ -29,7 +33,7 @@ module Contrast
29
33
  mask_with_dictionary results, hash
30
34
 
31
35
  # Restore to original form.
32
- hash.each { |k, v| masked += "#{ k + EQUALS }#{ v + SEMICOLON + SPACE }" }
36
+ hash.each { |k, v| masked += "#{ k }=#{ v }; " }
33
37
  masked.rstrip!
34
38
  field_hash[entry[0]] = masked
35
39
  end
@@ -48,7 +52,7 @@ module Contrast
48
52
  hash = URI.decode_www_form(query).to_h
49
53
  mask_with_dictionary results, hash
50
54
  # Restore to string form.
51
- hash.each { |k, v| masked += "#{ k + EQUALS }#{ v }#{ AMPERSAND }" }
55
+ hash.each { |k, v| masked += "#{ k }=#{ v }&" }
52
56
  query = masked
53
57
  query.chomp!(masked[-1])
54
58
  end
@@ -31,10 +31,6 @@ module Contrast
31
31
  @_connection ||= client.initialize_connection
32
32
  end
33
33
 
34
- def audit
35
- @_audit ||= Contrast::Agent::Reporting::Audit.new
36
- end
37
-
38
34
  def attempt_to_start?
39
35
  unless cs__class.enabled?
40
36
  logger.warn('Reporter service is disabled!')
@@ -65,7 +61,7 @@ module Contrast
65
61
  # Suspend the Reporter and try sending the event after the timeout.
66
62
  # The timeout is either default 15 min or received via TS response.
67
63
  #
68
- # @param event [Contrast::Agent::Reporting::FindingEvent] Freshly pop-ed event.
64
+ # @param event [Contrast::Agent::Reporting::ReportingEvent] Freshly pop-ed event.
69
65
  def handle_resend event
70
66
  sleep(client.timeout) if client.sleep?
71
67
  # Retry once than discard the event. This is trigger on too many events of
@@ -75,6 +71,7 @@ module Contrast
75
71
  client.wake_up
76
72
  end
77
73
 
74
+ # @param event [Contrast::Agent::Reporting::ReportingEvent]
78
75
  def send_event event
79
76
  if ::Contrast::AGENT.disabled?
80
77
  logger.warn('Attempted to queue event with Agent disabled', caller: caller, event: event)
@@ -84,23 +81,21 @@ module Contrast
84
81
  return unless cs__class.enabled?
85
82
 
86
83
  logger.debug('Enqueued event for sending', event_type: event.cs__class)
87
- audit&.audit_event(event) if ::Contrast::API.request_audit_enable?
88
84
  queue << event
89
85
  end
90
86
 
91
87
  # Use this to bypass the messaging queue and leave response processing to the caller
88
+ #
89
+ # @param event [Contrast::Agent::Reporting::ReportingEvent]
90
+ # @return [Net::HTTPResponse, nil]
92
91
  def send_event_immediately event
93
92
  if ::Contrast::AGENT.disabled?
94
93
  logger.warn('Reporter attempted to send event immediately with Agent disabled', caller: caller, event: event)
95
94
  return
96
95
  end
97
- response = client.send_event(event, connection, true)
98
- return unless response
99
-
100
- client.handle_response(event, response, connection)
101
- audit&.audit_event(event, response) if ::Contrast::API.request_audit_enable?
96
+ client.send_event(event, connection, send_immediately: true)
102
97
  rescue StandardError => e
103
- logger.error('Could not send message to service from Reporter queue.', e)
98
+ logger.error('Could not send message to TeamServer from Reporter queue.', e)
104
99
  end
105
100
 
106
101
  def delete_queue!
@@ -122,12 +117,12 @@ module Contrast
122
117
  @_queue ||= Queue.new
123
118
  end
124
119
 
120
+ # @param event [Contrast::Agent::Reporting::ReportingEvent]
125
121
  def process_event event
126
- response = client.send_event(event, connection)
127
- audit&.audit_event(event, response) if ::Contrast::API.request_audit_enable?
128
- handle_resend event
122
+ client.send_event(event, connection)
123
+ handle_resend(event) if client.mode.status == client.mode.resending
129
124
  rescue StandardError => e
130
- logger.error('Could not send message to service from Reporter queue.', e)
125
+ logger.error('Could not send message to TeamServer from Reporter queue.', e)
131
126
  end
132
127
  end
133
128
  end
@@ -0,0 +1,49 @@
1
+ # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/agent/worker_thread'
5
+ require 'contrast/agent/reporting/report'
6
+ require 'contrast/components/logger'
7
+ require 'contrast/agent/reporting/reporting_events/agent_startup'
8
+
9
+ module Contrast
10
+ module Agent
11
+ # The ReporterHeartbeat will make sure that the process remains marked alive by TeamServer and that we periodically
12
+ # reach out to get the latest settings for this application.
13
+ class ReporterHeartbeat < WorkerThread
14
+ include Contrast::Components::Logger::InstanceMethods
15
+
16
+ # TeamServer will mark an application offline after 5 minutes. Sending this every one should be more than enough
17
+ # to satisfy our goals.
18
+ REFRESH_INTERVAL_SEC = 60
19
+
20
+ # check if we can report to TS
21
+ #
22
+ # @return[Boolean] true if bypass is enabled, or false if bypass disabled
23
+ def enabled?
24
+ @_enabled = Contrast::CONTRAST_SERVICE.use_agent_communication? if @_enabled.nil?
25
+ @_enabled
26
+ end
27
+
28
+ def connection
29
+ @_connection ||= client.initialize_connection
30
+ end
31
+
32
+ def start_thread!
33
+ return if running?
34
+
35
+ @_thread = Contrast::Agent::Thread.new do
36
+ logger.info('Starting heartbeat thread.')
37
+ loop do
38
+ Contrast::Agent.reporter&.send_event(poll_message)
39
+ sleep(REFRESH_INTERVAL_SEC)
40
+ end
41
+ end
42
+ end
43
+
44
+ def poll_message
45
+ @_poll_message ||= Contrast::Agent::Reporting::Poll.new
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,7 +1,7 @@
1
1
  # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'contrast/agent/reporting/reporting_events/reporting_event'
4
+ require 'contrast/agent/reporting/reporting_events/server_reporting_event'
5
5
  require 'contrast/config'
6
6
 
7
7
  module Contrast
@@ -9,7 +9,7 @@ module Contrast
9
9
  module Reporting
10
10
  # AgentStartup Event which sends the agent data to TeamServer on the startup of a server or process,
11
11
  # used to create a new Server entity there.
12
- class AgentStartup < Contrast::Agent::Reporting::ReportingEvent
12
+ class AgentStartup < Contrast::Agent::Reporting::ServerReportingEvent
13
13
  def initialize
14
14
  @event_method = :PUT
15
15
  @event_endpoint = Contrast::Agent::Reporting::Endpoints::NG_ENDPOINTS[:agent_startup]
@@ -17,6 +17,10 @@ module Contrast
17
17
  super
18
18
  end
19
19
 
20
+ def file_name
21
+ 'agent-startup'
22
+ end
23
+
20
24
  def to_controlled_hash
21
25
  {
22
26
  environment: ::Contrast::CONFIG.root.server.environment,
@@ -0,0 +1,53 @@
1
+ # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/components/logger'
5
+ require 'contrast/agent/reporting/reporting_events/application_reporting_event'
6
+ require 'contrast/agent/reporting/reporting_events/application_defend_activity'
7
+ require 'contrast/agent/reporting/reporting_events/application_inventory_activity'
8
+
9
+ module Contrast
10
+ module Agent
11
+ module Reporting
12
+ # This is the new ApplicationActivity class which will include all the needed information for the new reporting
13
+ # system to report
14
+ class ApplicationActivity < Contrast::Agent::Reporting::ApplicationReportingEvent
15
+ class << self
16
+ # @param app_activity_dtm [Contrast::Api::Dtm::Activity]
17
+ # @return [Contrast::Agent::Reporting::ApplicationActivity]
18
+ def convert app_activity_dtm
19
+ app_activity = new
20
+ app_activity.attach_data app_activity_dtm
21
+ app_activity
22
+ end
23
+ end
24
+
25
+ def initialize
26
+ @defend = []
27
+ @inventory = []
28
+ @event_type = :application_activity
29
+ @event_endpoint = Contrast::Agent::Reporting::Endpoints.application_activity
30
+ super
31
+ end
32
+
33
+ def file_name
34
+ 'activity-application'
35
+ end
36
+
37
+ def to_controlled_hash
38
+ {
39
+ lastUpdate: since_last_update,
40
+ defend: @defend.map(&:to_controlled_hash),
41
+ inventory: @inventory.map(&:to_controlled_hash)
42
+ }
43
+ end
44
+
45
+ # @param activity_dtm [Contrast::Api::Dtm::ApplicationActivity]
46
+ def attach_data activity_dtm
47
+ @defend << Contrast::Agent::Reporting::ApplicationDefendActivity.convert(activity_dtm)
48
+ @inventory << Contrast::Agent::Reporting::ApplicationInventoryActivity.convert(activity_dtm)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,48 @@
1
+ # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/components/logger'
5
+ require 'contrast/agent/reporting/reporting_events/application_defend_attacker_activity'
6
+
7
+ module Contrast
8
+ module Agent
9
+ module Reporting
10
+ # This is the new ApplicationDefendActivity class which includes information about the defense of the application
11
+ # which was discovered during exercise of the application during this activity period.
12
+ class ApplicationDefendActivity
13
+ # @return [Array<Contrast::Agent::Reporting::ApplicationDefendAttackerActivity>]
14
+ attr_reader :attackers
15
+
16
+ class << self
17
+ # @param activity_dtm [Contrast::Api::Dtm::ApplicationActivity]
18
+ # @return [Contrast::Agent::Reporting::ApplicationDefendActivity]
19
+ def convert activity_dtm
20
+ activity = new
21
+ activity.attach_data activity_dtm.results
22
+ activity
23
+ end
24
+ end
25
+
26
+ def initialize
27
+ @attackers = []
28
+ @event_type = :application_defend_activity
29
+ super
30
+ end
31
+
32
+ def to_controlled_hash
33
+ {
34
+ attackers: attackers.map(&:to_controlled_hash)
35
+ }
36
+ end
37
+
38
+ # @param attack_dtms [Contrast::Api::Dtm::AttackResult]
39
+ # @return [Contrast::Agent::Reporting::ApplicationDefendAttackerActivity]
40
+ def attach_data attack_dtms
41
+ attack_dtms.each do |attack|
42
+ @attackers << Contrast::Agent::Reporting::ApplicationDefendAttackerActivity.convert(attack)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end