contrast-agent 6.1.2 → 6.2.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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/lib/contrast/agent/at_exit_hook.rb +2 -1
  3. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +9 -5
  4. data/lib/contrast/agent/protect/rule/xss.rb +4 -0
  5. data/lib/contrast/agent/reporting/reporter.rb +2 -11
  6. data/lib/contrast/agent/reporting/reporting_events/application_inventory.rb +3 -18
  7. data/lib/contrast/agent/reporting/reporting_events/discovered_route.rb +75 -15
  8. data/lib/contrast/agent/reporting/reporting_events/finding.rb +2 -2
  9. data/lib/contrast/agent/reporting/reporting_events/library_usage_observation.rb +5 -19
  10. data/lib/contrast/agent/reporting/reporting_events/observed_library_usage.rb +6 -22
  11. data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +1 -1
  12. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +2 -3
  13. data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +1 -3
  14. data/lib/contrast/agent/reporting/reporting_events/route_coverage.rb +9 -0
  15. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +1 -2
  16. data/lib/contrast/agent/reporting/reporting_utilities/dtm_message.rb +0 -10
  17. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +0 -1
  18. data/lib/contrast/agent/reporting/reporting_utilities/response.rb +60 -2
  19. data/lib/contrast/agent/reporting/reporting_utilities/response_extractor.rb +32 -10
  20. data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +1 -1
  21. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +58 -26
  22. data/lib/contrast/agent/reporting/settings/application_settings.rb +8 -23
  23. data/lib/contrast/agent/reporting/settings/assess_server_feature.rb +27 -33
  24. data/lib/contrast/agent/reporting/settings/bot_blocker.rb +68 -0
  25. data/lib/contrast/agent/reporting/settings/code_exclusion.rb +27 -0
  26. data/lib/contrast/agent/reporting/settings/exclusion_base.rb +33 -0
  27. data/lib/contrast/agent/reporting/settings/exclusions.rb +39 -57
  28. data/lib/contrast/agent/reporting/settings/helpers.rb +56 -0
  29. data/lib/contrast/agent/reporting/settings/input_exclusion.rb +37 -0
  30. data/lib/contrast/agent/reporting/settings/ip_filter.rb +35 -0
  31. data/lib/contrast/agent/reporting/settings/keyword.rb +74 -0
  32. data/lib/contrast/agent/reporting/settings/log_enhancer.rb +65 -0
  33. data/lib/contrast/agent/reporting/settings/protect.rb +4 -2
  34. data/lib/contrast/agent/reporting/settings/protect_server_feature.rb +62 -115
  35. data/lib/contrast/agent/reporting/settings/reaction.rb +11 -2
  36. data/lib/contrast/agent/reporting/settings/rule_definition.rb +63 -0
  37. data/lib/contrast/agent/reporting/settings/sampling.rb +10 -0
  38. data/lib/contrast/agent/reporting/settings/sanitizer.rb +38 -0
  39. data/lib/contrast/agent/reporting/settings/sensitive_data_masking.rb +9 -1
  40. data/lib/contrast/agent/reporting/settings/sensitive_data_masking_rule.rb +7 -0
  41. data/lib/contrast/agent/reporting/settings/server_features.rb +8 -0
  42. data/lib/contrast/agent/reporting/settings/syslog.rb +176 -0
  43. data/lib/contrast/agent/reporting/settings/url_exclusion.rb +42 -0
  44. data/lib/contrast/agent/reporting/settings/validator.rb +17 -0
  45. data/lib/contrast/agent/request_context.rb +4 -0
  46. data/lib/contrast/agent/request_handler.rb +8 -4
  47. data/lib/contrast/agent/static_analysis.rb +4 -8
  48. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exceptions_report.rb +1 -1
  49. data/lib/contrast/agent/thread_watcher.rb +4 -5
  50. data/lib/contrast/agent/version.rb +1 -1
  51. data/lib/contrast/agent.rb +1 -3
  52. data/lib/contrast/api/decorators/application_update.rb +0 -8
  53. data/lib/contrast/api/decorators.rb +0 -1
  54. data/lib/contrast/framework/base_support.rb +5 -4
  55. data/lib/contrast/framework/grape/support.rb +6 -6
  56. data/lib/contrast/framework/manager.rb +2 -4
  57. data/lib/contrast/framework/manager_extend.rb +1 -0
  58. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +2 -1
  59. data/lib/contrast/framework/rails/support.rb +9 -2
  60. data/lib/contrast/framework/sinatra/support.rb +3 -2
  61. data/lib/contrast/logger/aliased_logging.rb +31 -26
  62. data/lib/contrast/utils/response_utils.rb +14 -1
  63. data/lib/contrast/utils/telemetry.rb +9 -0
  64. data/lib/contrast/utils/telemetry_hash.rb +36 -12
  65. data/lib/contrast/utils/telemetry_identifier.rb +8 -0
  66. data/lib/contrast/utils/thread_tracker.rb +26 -9
  67. data/lib/contrast/utils/timer.rb +6 -1
  68. data/lib/contrast.rb +1 -3
  69. metadata +26 -14
  70. data/lib/contrast/api/decorators/library_usage_update.rb +0 -31
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c901ed882ebff8176fe2f3794907e29b03cc2903b61260f138d93b2ef02a465c
4
- data.tar.gz: 72e4f01ccf5a57bbd5afa0cac58c51cddbe26183691d9495f563dfd9fb37e7e1
3
+ metadata.gz: b285299c51379c206ff8c7a64426ef5207594391d59c1ce284bae3a899d6d56f
4
+ data.tar.gz: f7847b7523600ebad30ba380cd338d36f6ed2e8136e86d2fdb71f4c7953f530a
5
5
  SHA512:
6
- metadata.gz: 1d60653e61e95443c45bb43caac325b3c72449ddea435096e2d8e49c0a0851ff9ae6486fc16a17f65f663c2f069c58f6157f4f7ef8745d0b082d4fe7c5c0b8b6
7
- data.tar.gz: e1e5dd1a542009d153fa6e77f86d889d8bb852d85d66c84400a583b63536be06a70423c4f05e547280f06368851a43720ac941efedf3aeb17a314ff3c9f61a14
6
+ metadata.gz: 3edc5557919437f5c7f76b741ed4133263bd975baebb6c81d8ac416713571c6ef871e9ab4d3b1ddf34aa0ff548242e71081443bc5c3cd04c2f4bae35b030e866
7
+ data.tar.gz: 21c33b917d5f9c0f3b8070bde64b42c8d800dd73c952b0d45830b9d13eb6135ba086c9abf43b3003168f5c3902753cf1a480444bbfca506f00d7bf51b5039d03
@@ -31,11 +31,12 @@ module Contrast
31
31
  context = Contrast::Agent::REQUEST_TRACKER.current
32
32
  return unless context
33
33
 
34
+ Contrast::Agent.reporter.send_event_immediately(context.observed_library_usage)
35
+
34
36
  if Contrast::Agent::Reporter.enabled?
35
37
  [
36
38
  context.new_observed_route,
37
39
  Contrast::Agent::Reporting::DtmMessage.dtm_to_event(context.server_activity),
38
- Contrast::Agent::Reporting::DtmMessage.dtm_to_event(context.activity.library_usages),
39
40
  Contrast::Agent::Reporting::DtmMessage.dtm_to_event(context.activity)
40
41
  ].each do |event|
41
42
  Contrast::Agent.reporter&.send_event_immediately(event)
@@ -1,6 +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/library_usage_observation'
4
5
  require 'contrast/agent/inventory/dependencies'
5
6
  require 'contrast/components/logger'
6
7
  require 'contrast/utils/object_share'
@@ -73,10 +74,11 @@ module Contrast
73
74
  #
74
75
  # TODO: RUBY-1355
75
76
  # TODO: RUBY-1438 -- change to just use EventMessage
76
- # @param activity [Contrast::Api::Dtm::Activity] the message to which to append the usage data
77
- def generate_library_usage activity
77
+ # @param observed_library_usage [Contrast::Agent::Reporting::ObservedLibraryUsage] the message to
78
+ # which to append the usage data
79
+ def generate_library_usage observed_library_usage
78
80
  return unless enabled?
79
- return unless activity
81
+ return unless observed_library_usage
80
82
 
81
83
  # Disconnect gemdigest_cache and replace it with an empty one; synch so new libs cannot be added between the
82
84
  # assignment and the replace
@@ -86,8 +88,10 @@ module Contrast
86
88
  hold
87
89
  end
88
90
  gem_spec_digest_to_files.each_pair do |digest, files|
89
- usage = Contrast::Api::Dtm::LibraryUsageUpdate.build(digest, files)
90
- activity.library_usages[usage.hash_code] = usage
91
+ usage = Contrast::Agent::Reporting::LibraryUsageObservation.new(digest, files)
92
+ next if usage.names.empty?
93
+
94
+ observed_library_usage.observations << usage
91
95
  end
92
96
  rescue StandardError => e
93
97
  logger.error('Unable to generate library usage.', e)
@@ -16,6 +16,10 @@ module Contrast
16
16
  # @param attack_sample [Contrast::Api::Dtm::RaspRuleSample]
17
17
  # @return [Hash] the details for this specific rule
18
18
  def extract_details attack_sample
19
+ # TODO: RUBY-1702 - figure out why xss isn't populated when reported; probably something to do w/
20
+ # suspicious
21
+ return Contrast::Utils::ObjectShare::EMPTY_HASH unless attack_sample&.xss
22
+
19
23
  {
20
24
  input: attack_sample.xss.input,
21
25
  matches: attack_sample.xss.matches.map do |match|
@@ -31,16 +31,6 @@ module Contrast
31
31
  @_connection ||= client.initialize_connection
32
32
  end
33
33
 
34
- def attempt_to_start?
35
- unless cs__class.enabled?
36
- logger.warn('Reporter service is disabled!')
37
- return false
38
- end
39
-
40
- logger.debug('Attempting to start Reporter thread') unless running?
41
- true
42
- end
43
-
44
34
  def start_thread!
45
35
  return if running?
46
36
 
@@ -54,6 +44,8 @@ module Contrast
54
44
  next unless client && connection
55
45
 
56
46
  process_event(queue.pop)
47
+ rescue StandardError => e
48
+ logger.debug('Reporter thread could not process because of:', error: e)
57
49
  end
58
50
  end
59
51
  end
@@ -78,7 +70,6 @@ module Contrast
78
70
  return
79
71
  end
80
72
  return unless event
81
- return unless cs__class.enabled?
82
73
 
83
74
  logger.debug('Enqueued event for sending', event_type: event.cs__class)
84
75
  queue << event
@@ -17,19 +17,12 @@ module Contrast
17
17
  # discovered during first request processing.
18
18
  attr_reader :routes
19
19
 
20
- class << self
21
- def convert app_update_dtm
22
- app_inventory = new
23
- app_inventory.attach_data(app_update_dtm)
24
- app_inventory
25
- end
26
- end
27
-
28
20
  def initialize
29
- @routes = []
30
21
  @event_type = :application_inventory
31
22
  @event_method = :POST
32
23
  @event_endpoint = Contrast::Agent::Reporting::Endpoints.application_inventory
24
+ # The API spec limits us to 500 routes, so we'll enforce that here to not hold on to superfulous data.
25
+ @routes = Contrast::Agent.framework_manager.find_route_discovery_data.take(500)
33
26
  super
34
27
  end
35
28
 
@@ -39,18 +32,10 @@ module Contrast
39
32
 
40
33
  def to_controlled_hash
41
34
  {
42
- session_id: @agent_session_id_value,
35
+ session_id: ::Contrast::ASSESS.session_id,
43
36
  routes: routes.map(&:to_controlled_hash)
44
37
  }
45
38
  end
46
-
47
- # @param inventory_dtm [Contrast::Api::Dtm::ApplicationUpdate]
48
- # @return [Contrast::Agent::Reporting::ApplicationInventory]
49
- def attach_data inventory_dtm
50
- inventory_dtm.routes.each do |route|
51
- @routes << Contrast::Agent::Reporting::DiscoveredRoute.convert(route)
52
- end
53
- end
54
39
  end
55
40
  end
56
41
  end
@@ -17,15 +17,84 @@ module Contrast
17
17
  # recorded.
18
18
  class DiscoveredRoute < Contrast::Agent::Reporting::ObservedRoute
19
19
  class << self
20
- # @param dtm [Contrast::Api::Dtm::RouteCoverage]
20
+ # @param obj [Regexp, Object]
21
+ # @return [String]
22
+ def source_or_string obj
23
+ if obj.cs__is_a?(Regexp)
24
+ obj.source
25
+ elsif obj.cs__respond_to?(:safe_string)
26
+ obj.safe_string
27
+ else
28
+ obj.to_s
29
+ end
30
+ end
31
+
32
+ # Convert ActionDispatch::Journey::Route to Contrast::Agent::Reporting::DiscoveredRoute
33
+ #
34
+ # @param journey_obj [ActionDispatch::Journey::Route] a rails route
35
+ # @param url [String, nil] use url from string instead of journey object.
36
+ # @return [Contrast::Agent::Reporting::DiscoveredRoute]
37
+ def from_action_dispatch_journey journey_obj, url = nil
38
+ msg = new
39
+ msg.signature = "#{ journey_obj.defaults[:controller] }##{ journey_obj.defaults[:action] }"
40
+
41
+ verb = source_or_string(journey_obj.verb)
42
+ msg.verb = Contrast::Utils::StringUtils.force_utf8(verb)
43
+
44
+ url ||= source_or_string(journey_obj.path.spec)
45
+ msg.url = Contrast::Utils::StringUtils.force_utf8(url)
46
+ msg
47
+ end
48
+
49
+ # Convert Grape route data to discovered route.
50
+ #
51
+ # @param controller [::Grape::API] the route's final controller.
52
+ # @param method [String] GET, PUT, POST, etc...
53
+ # @param url [String, nil] use url from string instead matched pattern.
54
+ # @param pattern [String, Grape::Router::Route] the pattern that was matched in routing.
21
55
  # @return [Contrast::Agent::Reporting::DiscoveredRoute]
22
- def convert dtm
23
- discovered_route = new
24
- discovered_route.attach_data(dtm)
25
- discovered_route
56
+ def from_grape_controller controller, method, pattern, url = nil
57
+ if pattern.cs__is_a?(Grape::Router::Route)
58
+ safe_pattern = pattern.pattern&.path&.to_s
59
+ safe_url = source_or_string(url || safe_pattern)
60
+ else
61
+ safe_pattern = source_or_string(pattern)
62
+ safe_url = source_or_string(url || pattern)
63
+ end
64
+
65
+ msg = new
66
+ msg.signature = "#{ controller }##{ method } #{ safe_pattern }"
67
+ msg.verb = Contrast::Utils::StringUtils.force_utf8(method)
68
+ msg.url = Contrast::Utils::StringUtils.force_utf8(safe_url)
69
+ msg
70
+ end
71
+
72
+ # Convert Sinatra route data to discovered route.
73
+ #
74
+ # @param controller [::Sinatra::Base] the route's final controller.
75
+ # @param method [String] GET, PUT, POST, etc...
76
+ # @param pattern [::Mustermann::Sinatra] the pattern that was matched in routing.
77
+ # @param url [String, nil] use url from string instead matched pattern.
78
+ # @return [Contrast::Agent::Reporting::DiscoveredRoute]
79
+ def from_sinatra_route controller, method, pattern, url = nil
80
+ safe_pattern = source_or_string(pattern)
81
+ safe_url = source_or_string(url || pattern)
82
+
83
+ msg = new
84
+ msg.signature = "#{ controller }##{ method } #{ safe_pattern }"
85
+ msg.verb = Contrast::Utils::StringUtils.force_utf8(method)
86
+ msg.url = Contrast::Utils::StringUtils.force_utf8(safe_url)
87
+ msg
26
88
  end
27
89
  end
28
90
 
91
+ # @return [String] the controller, method, and pattern of this route
92
+ attr_accessor :signature
93
+ # @return [String] the url (or url pattern) used to access this route
94
+ attr_accessor :url
95
+ # @return [String, nil] the HTTP verb used to access this route or nil for any verb
96
+ attr_accessor :verb
97
+
29
98
  def initialize
30
99
  super
31
100
  @event_type = :discovered_route
@@ -34,18 +103,9 @@ module Contrast
34
103
  @url = Contrast::Utils::ObjectShare::EMPTY_STRING
35
104
  end
36
105
 
37
- # @param dtm[Contrast::Api::Dtm::RouteCoverage]
38
- def attach_data dtm
39
- @signature = dtm.route
40
- @verb = dtm.verb
41
- @url = dtm.url
42
- end
43
-
44
106
  def to_controlled_hash
45
107
  validate
46
- dr_hash = { session_id: @agent_session_id_value, signature: @signature, verb: @verb, url: @url }
47
- dr_hash.delete(:verb) unless @verb
48
- dr_hash
108
+ { session_id: ::Contrast::ASSESS.session_id, signature: @signature, verb: @verb, url: @url }.compact
49
109
  end
50
110
 
51
111
  def validate
@@ -135,7 +135,7 @@ module Contrast
135
135
  created: created,
136
136
  hash: hash_code.to_s,
137
137
  ruleId: rule_id,
138
- session_id: @agent_session_id_value.to_s,
138
+ session_id: ::Contrast::ASSESS.session_id,
139
139
  version: 4
140
140
  }
141
141
  hsh[:events] = events.map(&:to_controlled_hash) if event_based?
@@ -152,7 +152,7 @@ module Contrast
152
152
  # @raise [ArgumentError]
153
153
  def validate
154
154
  raise(ArgumentError, "#{ self } did not have a proper rule. Unable to continue.") unless @rule_id
155
- unless @agent_session_id_value
155
+ unless ::Contrast::ASSESS.session_id
156
156
  raise(ArgumentError, "#{ self } did not have a proper session id. Unable to continue.")
157
157
  end
158
158
  if event_based? && events.empty?
@@ -11,25 +11,11 @@ module Contrast
11
11
  # @param [Array<String>] List of file paths that have been loaded out of or executed by the library
12
12
  attr_reader :names
13
13
 
14
- class << self
15
- # Convert a DTM for SpeedRacer to an Event for TeamServer.
16
- #
17
- # @param usage_dtm [Contrast::Api::Dtm::LibraryUsageUpdate]
18
- # @return [Contrast::Agent::Reporting::LibraryUsageObservation]
19
- def convert usage_dtm
20
- observation = new
21
- observation.attach_data(usage_dtm)
22
- observation
23
- end
24
- end
25
-
26
- def initialize
27
- @names = []
28
- end
29
-
30
- def attach_data usage_dtm
31
- @id = usage_dtm.hash_code
32
- @names = usage_dtm.class_names.keys
14
+ # @param id [String] Sha256Sum of library as identified by the agent
15
+ # @param class_names [Array<String>] List of file paths that have been loaded out of or executed by the library
16
+ def initialize id, class_names
17
+ @id = id
18
+ @names = class_names
33
19
  end
34
20
 
35
21
  def to_controlled_hash
@@ -9,23 +9,11 @@ module Contrast
9
9
  module Reporting
10
10
  # List of libraries that have been observed to have something loaded or executed.
11
11
  #
12
- # @attr_reader observations - Array[Contrast::Agent::Reporting::LibraryUsageObservation]
13
- # - Hash of LibraryUsageObservations
14
12
  class ObservedLibraryUsage < Contrast::Agent::Reporting::ApplicationReportingEvent
13
+ # @attr_reader observations - Array[Contrast::Agent::Reporting::LibraryUsageObservation]
14
+ # - Hash of LibraryUsageObservations
15
15
  attr_reader :observations
16
16
 
17
- class << self
18
- # Convert a Hash of LibraryUsageUpdate DTMs for SpeedRacer to an Event for TeamServer.
19
- #
20
- # @param usages_dtm_hash Hash[Contrast::Api::Dtm::LibraryUsageUpdate]
21
- # @return [Contrast::Agent::Reporting::ObservedLibraryUsage]
22
- def convert usages_dtm_hash
23
- report = new
24
- report.attach_data(usages_dtm_hash)
25
- report
26
- end
27
- end
28
-
29
17
  def initialize
30
18
  @event_endpoint = Contrast::Agent::Reporting::Endpoints.library_usage
31
19
  @observations = []
@@ -41,17 +29,13 @@ module Contrast
41
29
  { observations: @observations.map(&:to_controlled_hash) }
42
30
  end
43
31
 
44
- def attach_data usages_dtm_hash
45
- usages_dtm_hash.each do |_key, value|
46
- next unless value.class_names.any?
47
-
48
- @observations << Contrast::Agent::Reporting::LibraryUsageObservation.convert(value)
49
- end
50
- end
51
-
52
32
  def validate
53
33
  raise(ArgumentError, "#{ self } did not have observations. Unable to continue.") if observations.empty?
54
34
  end
35
+
36
+ def clear
37
+ @observations = []
38
+ end
55
39
  end
56
40
  end
57
41
  end
@@ -47,7 +47,7 @@ module Contrast
47
47
  def to_controlled_hash
48
48
  validate
49
49
  rc_hash = {
50
- session_id: @agent_session_id_value,
50
+ session_id: ::Contrast::ASSESS.session_id,
51
51
  sources: @sources.map(&:to_controlled_hash),
52
52
  signature: @signature,
53
53
  verb: @verb,
@@ -30,7 +30,6 @@ module Contrast
30
30
  @app_name = ::Contrast::APP_CONTEXT.app_name
31
31
  @app_version = ::Contrast::APP_CONTEXT.app_version
32
32
  @routes = []
33
- @agent_session_id_value = ::Contrast::ASSESS.session_id
34
33
  end
35
34
 
36
35
  # Convert the instance variables on the class, and other information, into the identifiers required for
@@ -48,7 +47,7 @@ module Contrast
48
47
  data: '',
49
48
  key: 0,
50
49
  routes: @routes,
51
- session_id: @agent_session_id_value
50
+ session_id: ::Contrast::ASSESS.session_id
52
51
  }
53
52
  end
54
53
 
@@ -60,7 +59,7 @@ module Contrast
60
59
  unless @app_language
61
60
  raise(ArgumentError, "#{ cs__class } did not have a proper application language. Unable to continue.")
62
61
  end
63
- unless @agent_session_id_value
62
+ unless ::Contrast::ASSESS.session_id
64
63
  raise(ArgumentError, "#{ cs__class } did not have a proper session id. Unable to continue.")
65
64
  end
66
65
 
@@ -20,9 +20,7 @@ module Contrast
20
20
  attr_reader :event_method
21
21
 
22
22
  def initialize
23
- @agent_session_id_value = ::Contrast::ASSESS.session_id
24
- @event_endpoint ||= nil
25
- @event_method ||= :POST
23
+ @event_method ||= :POST # rubocop:disable Lint/DisjunctiveAssignmentInConstructor
26
24
  end
27
25
 
28
26
  # Some reports require specific additional headers to be used. To that end, we'll attach them here, letting
@@ -32,6 +32,15 @@ module Contrast
32
32
  @count = 0
33
33
  end
34
34
 
35
+ # Parse the given controller and route from a Rack based application framework in order to create an instance
36
+ # of this class
37
+ #
38
+ # @param final_controller [Grape::API, Sinatra::Base] the controller responsible for the definition of the
39
+ # entrypoint of the route actively being executed
40
+ # @param method [String] the HTTP request method of the route actively being executed
41
+ # @param route_pattern [Grape::Router::Route, Mustermann::Sinatra] the pattern to which the url maps
42
+ # @param url [String] the literal url of the route actively being executed
43
+ # @return [Contrast::Agent::Reporting::RouteCoverage]
35
44
  def attach_rack_based_data final_controller, method, route_pattern, url = nil
36
45
  if route_pattern.cs__is_a?(Grape::Router::Route)
37
46
  safe_pattern = route_pattern.pattern&.path&.to_s
@@ -16,7 +16,7 @@ module Contrast
16
16
  attr_reader :path_for_requests, :path_for_responses
17
17
 
18
18
  def initialize
19
- generate_paths if enabled? && Contrast::CONTRAST_SERVICE.use_agent_communication?
19
+ generate_paths if enabled?
20
20
  end
21
21
 
22
22
  # This method will be handling the auditing of the requests and responses we send to SpeedRacer. If the audit
@@ -45,7 +45,6 @@ module Contrast
45
45
  # @param data[String] String representation if the logged data
46
46
  def log_data type, file_name, data = nil
47
47
  return unless enabled?
48
- return unless Contrast::CONTRAST_SERVICE.use_agent_communication?
49
48
 
50
49
  logger.debug('logging to file', file_name: file_name) # TODO: RUBY-99999 DO NOT COMMIT THIS
51
50
  write_to_file(type, file_name, data)
@@ -21,15 +21,6 @@ module Contrast
21
21
  dtm.cs__is_a?(Contrast::Api::Dtm::ServerActivity)
22
22
  end
23
23
 
24
- # Checks if the message is a Hash of Contrast::Api::Dtm::LibraryUsageUpdate class
25
- #
26
- # @param message [Protobuf::Field::FieldHash<String,::Contrast::Api::Dtm::LibraryUsageUpdate>]
27
- # @return [Boolean]
28
- def library_usage? message
29
- message.cs__is_a?(Protobuf::Field::FieldHash) &&
30
- message.values[0].cs__is_a?(Contrast::Api::Dtm::LibraryUsageUpdate)
31
- end
32
-
33
24
  # Checks if the message is of Contrast::Api::Dtm::ApplicationUpdate class
34
25
  #
35
26
  # @param dtm [Contrast::Api::Dtm::ApplicationUpdate,Object]
@@ -61,7 +52,6 @@ module Contrast
61
52
  return Contrast::Agent::Reporting::ServerActivity.new if server_activity?(dtm)
62
53
 
63
54
  # For the others, we convert them.
64
- return Contrast::Agent::Reporting::ObservedLibraryUsage.convert(dtm) if library_usage?(dtm)
65
55
  return Contrast::Agent::Reporting::ApplicationUpdate.convert(dtm) if application_update?(dtm)
66
56
  return Contrast::Agent::Reporting::Finding.convert(dtm) if finding?(dtm)
67
57
  return Contrast::Agent::Reporting::ApplicationActivity.convert(dtm) if activity?(dtm)
@@ -56,7 +56,6 @@ module Contrast
56
56
  # @param send_immediately [Boolean] flag for the logger
57
57
  # @return response [Net::HTTP::Response, nil] response from TS if no response
58
58
  def send_event event, connection, send_immediately: false
59
- return unless Contrast::Agent::Reporter.enabled?
60
59
  return unless connection
61
60
 
62
61
  log_send_event(event) if send_immediately
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'contrast/agent/reporting/settings/application_settings'
5
5
  require 'contrast/agent/reporting/settings/server_features'
6
+ require 'contrast/agent/reporting/settings/reaction'
6
7
 
7
8
  module Contrast
8
9
  module Agent
@@ -21,19 +22,76 @@ module Contrast
21
22
  # @return [Contrast::Agent::Reporting::Settings::FeatureSettings, nil]
22
23
  attr_accessor :server_features
23
24
 
25
+ # Success boolean message value
26
+ #
27
+ # @return [Boolean]
28
+ attr_accessor :success
29
+
30
+ # Message with reasons for success or fail.
31
+ #
32
+ # @return [Array<String>] Messages received from TS.
33
+ attr_accessor :messages
34
+
24
35
  class << self
25
- def application_response
36
+ # All of the settings from TeamServer that apply at the application level.
37
+ #
38
+ # @return response [Contrast::Agent::Reporting::Response]
39
+ def build_application_response
26
40
  res = new
27
41
  res.application_settings = Contrast::Agent::Reporting::Settings::ApplicationSettings.new
28
42
  res
29
43
  end
30
44
 
31
- def server_response
45
+ # All of the settings from TeamServer that apply at the server level.
46
+ #
47
+ # @return response [Contrast::Agent::Reporting::Response]
48
+ def build_server_response
32
49
  res = new
33
50
  res.server_features = Contrast::Agent::Reporting::Settings::ServerFeatures.new
34
51
  res
35
52
  end
36
53
  end
54
+
55
+ # Reaction the agent should take based on a state in TS.
56
+ # This is moved one level up because the responses we
57
+ # receive for feature and settings from TS have different
58
+ # place to store these reactions:
59
+ #
60
+ # body.reactions vs body.settings.reactions
61
+ #
62
+ # @return [Array<Contrast::Agent::Reporting::Settings::Reaction>]
63
+ def reactions
64
+ @_reactions ||= []
65
+ end
66
+
67
+ # Set the reaction
68
+ #
69
+ # @param reaction_array [Array<Reaction>] {
70
+ # level [String] The level at which the agent should log this reaction.
71
+ # [ERROR, WARN, INFO, DEBUG, TRACE]
72
+ # message [String] A message to log when receiving this reaction.
73
+ # operation [String] What to do in response to this reaction.[NOOP, DISABLE] }
74
+ # @return [Array<Contrast::Agent::Reporting::Settings::Reaction>]
75
+ def reactions= reaction_array
76
+ return unless reaction_array.is_a?(Array)
77
+
78
+ reaction_array.each do |r|
79
+ reactions << Contrast::Agent::Reporting::Settings::Reaction.new(r[:level], r[:operation], r[:message])
80
+ end
81
+ end
82
+
83
+ # This method is used only for testing with golden files.
84
+ def to_controlled_hash
85
+ {
86
+ success: success,
87
+ messages: messages,
88
+ features: server_features.nil? ? nil : server_features.to_controlled_hash,
89
+ settings: application_settings.nil? ? nil : application_settings.to_controlled_hash,
90
+ logLevel: server_features&.log_level,
91
+ logFile: server_features&.log_file,
92
+ reactions: server_features.nil? ? nil : reactions.map(&:to_controlled_hash)
93
+ }.compact
94
+ end
37
95
  end
38
96
  end
39
97
  end
@@ -24,7 +24,6 @@ module Contrast
24
24
  protect = response_data[:settings][:defend]
25
25
  return unless protect
26
26
 
27
- # TODO: RUBY-1636 should this be `:rules` or `:protectionRules`
28
27
  res.application_settings.protect.protection_rules = protect[:protectionRules]
29
28
  res.application_settings.protect.virtual_patches = protect[:virtualPatches]
30
29
  end
@@ -40,10 +39,14 @@ module Contrast
40
39
  res.application_settings.exclusions.url_exclusions = exclusions[:urlExceptions]
41
40
  end
42
41
 
42
+ # The responses we receive for feature and settings from TS have different
43
+ # place to store these reactions: body.reactions vs body.settings.reactions.
44
+ #
43
45
  # @param response_data [Hash]
44
46
  # @param res [Contrast::Agent::Reporting::Response]
45
47
  def extract_reactions response_data, res
46
- res.application_settings.reactions = response_data[:settings][:reactions]
48
+ res.reactions = response_data[:settings][:reactions] if response_data[:settings]
49
+ res.reactions = response_data[:reactions] if response_data[:features]
47
50
  end
48
51
 
49
52
  # @param response_data [Hash]
@@ -65,10 +68,17 @@ module Contrast
65
68
  return unless protect
66
69
 
67
70
  res.server_features.protect.enabled = protect[:enabled]
68
- res.server_features.protect.bot_blocker = protect[:'bot-blocker']
69
- # TODO: RUBY-1636 should this be `:rules` or `:protectionRules`
70
- # process the botBlockers field
71
- res.server_features.protect.syslog = protect[:syslog]
71
+ res.server_features.protect.bot_blocker.enable = protect[:'bot-blocker']
72
+ res.server_features.protect.bot_blocker.bots = protect[:botBlockers]
73
+ extract_syslog(response_data, res)
74
+ end
75
+
76
+ # @param response_data [Hash]
77
+ # @param res [Contrast::Agent::Reporting::Response]
78
+ def extract_syslog response_data, res
79
+ return unless (syslog = response_data[:features][:defend][:syslog])
80
+
81
+ res.server_features.protect.syslog.assign_array(syslog)
72
82
  end
73
83
 
74
84
  # @param response_data [Hash]
@@ -78,22 +88,34 @@ module Contrast
78
88
  return unless protect
79
89
 
80
90
  res.server_features.protect.ip_allowlist = protect[:ipAllowlist]
81
- res.server_features.protect.ip_denylist = protect[:ipDenyList]
82
- res.server_features.protect.log_enchancers = protect[:logEnhancers]
91
+ res.server_features.protect.ip_denylist = protect[:ipDenylist]
92
+ res.server_features.protect.log_enhancers = protect[:logEnhancers]
83
93
  res.server_features.protect.rule_definition_list = protect[:ruleDefinitionList]
84
94
  end
85
95
 
86
96
  # Here we extract the rules and state for the sensitive data masking policy
87
- # Received from TS.
97
+ # received from TS.
88
98
  #
89
99
  # @param response_data [Hash]
90
100
  # @param res [Contrast::Agent::Reporting::Response]
91
101
  def extract_sensitive_data_policy response_data, res
92
- sensitive_data = response_data[:settings][:sensitive_data_masking_policy]
102
+ return unless (sensitive_data = response_data[:settings][:sensitive_data_masking_policy])
103
+
93
104
  res.application_settings.sensitive_data_masking.mask_http_body = sensitive_data[:mask_http_body]
94
105
  res.application_settings.sensitive_data_masking.mask_attack_vector = sensitive_data[:mask_attack_vector]
95
106
  res.application_settings.sensitive_data_masking.build_rules_form_settings(sensitive_data[:rules])
96
107
  end
108
+
109
+ # Here we extract the log settings received from TS.
110
+ #
111
+ # @param response_data [Hash]
112
+ # @param res [Contrast::Agent::Reporting::Response]
113
+ def extract_log_settings response_data, res
114
+ return unless (log_level = response_data[:logLevel])
115
+
116
+ res.server_features.log_level = log_level
117
+ res.server_features.log_file = response_data[:logFile] if response_data[:logFile]
118
+ end
97
119
  end
98
120
  end
99
121
  end
@@ -90,7 +90,7 @@ module Contrast
90
90
  when ERROR_CODES[:too_many_requests]
91
91
  handle_response_errors(response, RETRY_AFTER_MSG, mode.resending)
92
92
  else
93
- logger.debug('Response Error code could not be processed')
93
+ logger.error('Response Error code could not be processed')
94
94
  end
95
95
  end
96
96