contrast-agent 6.9.0 → 6.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/ext/build_funchook.rb +1 -1
  4. data/lib/contrast/agent/assess/policy/propagator/split.rb +1 -4
  5. data/lib/contrast/agent/assess/rule/response/body_rule.rb +1 -1
  6. data/lib/contrast/agent/middleware.rb +5 -3
  7. data/lib/contrast/agent/patching/policy/method_policy_extend.rb +6 -2
  8. data/lib/contrast/agent/patching/policy/trigger_node.rb +1 -1
  9. data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +76 -83
  10. data/lib/contrast/agent/protect/input_analyzer/worth_watching_analyzer.rb +40 -35
  11. data/lib/contrast/agent/protect/policy/applies_command_injection_rule.rb +2 -0
  12. data/lib/contrast/agent/protect/policy/applies_no_sqli_rule.rb +6 -3
  13. data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +5 -2
  14. data/lib/contrast/agent/protect/policy/applies_sqli_rule.rb +3 -0
  15. data/lib/contrast/agent/protect/policy/rule_applicator.rb +12 -0
  16. data/lib/contrast/agent/protect/rule/base.rb +19 -5
  17. data/lib/contrast/agent/protect/rule/base_service.rb +7 -2
  18. data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker_input_classification.rb +1 -1
  19. data/lib/contrast/agent/protect/rule/bot_blocker.rb +8 -0
  20. data/lib/contrast/agent/protect/rule/cmdi/cmdi_backdoors.rb +1 -1
  21. data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +9 -1
  22. data/lib/contrast/agent/protect/rule/cmdi/cmdi_chained_command.rb +1 -1
  23. data/lib/contrast/agent/protect/rule/cmdi/cmdi_dangerous_path.rb +1 -1
  24. data/lib/contrast/agent/protect/rule/deserialization.rb +2 -2
  25. data/lib/contrast/agent/protect/rule/no_sqli.rb +24 -2
  26. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_input_classification.rb +1 -1
  27. data/lib/contrast/agent/protect/rule/path_traversal.rb +8 -0
  28. data/lib/contrast/agent/protect/rule/sqli/postgres_sql_scanner.rb +0 -1
  29. data/lib/contrast/agent/protect/rule/sqli/sqli_input_classification.rb +0 -1
  30. data/lib/contrast/agent/protect/rule/sqli.rb +6 -10
  31. data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload_input_classification.rb +6 -2
  32. data/lib/contrast/agent/protect/rule/unsafe_file_upload.rb +20 -0
  33. data/lib/contrast/agent/protect/rule/xss/reflected_xss_input_classification.rb +1 -1
  34. data/lib/contrast/agent/protect/rule/xss.rb +8 -0
  35. data/lib/contrast/agent/protect/rule/xxe.rb +2 -2
  36. data/lib/contrast/agent/protect/rule.rb +0 -3
  37. data/lib/contrast/agent/reporting/attack_result/user_input.rb +0 -1
  38. data/lib/contrast/agent/reporting/details/details.rb +0 -1
  39. data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +12 -0
  40. data/lib/contrast/agent/reporting/report.rb +1 -0
  41. data/lib/contrast/agent/reporting/reporter.rb +12 -15
  42. data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +4 -5
  43. data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +13 -1
  44. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_activity.rb +20 -5
  45. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample.rb +0 -1
  46. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +5 -0
  47. data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +10 -1
  48. data/lib/contrast/agent/reporting/reporting_events/application_inventory.rb +2 -1
  49. data/lib/contrast/agent/reporting/reporting_events/application_reporting_event.rb +10 -0
  50. data/lib/contrast/agent/reporting/reporting_events/application_settings.rb +40 -0
  51. data/lib/contrast/agent/reporting/reporting_events/discovered_route.rb +9 -5
  52. data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +8 -5
  53. data/lib/contrast/agent/reporting/reporting_utilities/endpoints.rb +7 -7
  54. data/lib/contrast/agent/reporting/reporting_utilities/ng_response_extractor.rb +137 -0
  55. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +12 -4
  56. data/lib/contrast/agent/reporting/reporting_utilities/response_extractor.rb +100 -107
  57. data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +5 -4
  58. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +101 -67
  59. data/lib/contrast/agent/reporting/reporting_workers/application_server_worker.rb +46 -0
  60. data/lib/contrast/agent/reporting/reporting_workers/reporter_heartbeat.rb +51 -0
  61. data/lib/contrast/agent/reporting/reporting_workers/reporting_workers.rb +14 -0
  62. data/lib/contrast/agent/reporting/reporting_workers/server_settings_worker.rb +46 -0
  63. data/lib/contrast/agent/reporting/settings/assess.rb +14 -1
  64. data/lib/contrast/agent/reporting/settings/assess_rule.rb +18 -0
  65. data/lib/contrast/agent/reporting/settings/helpers.rb +4 -2
  66. data/lib/contrast/agent/reporting/settings/protect.rb +17 -12
  67. data/lib/contrast/agent/reporting/settings/protect_rule.rb +18 -0
  68. data/lib/contrast/agent/reporting/settings/protect_server_feature.rb +1 -1
  69. data/lib/contrast/agent/reporting/settings/sensitive_data_masking.rb +1 -1
  70. data/lib/contrast/agent/reporting/settings/virtual_patch.rb +56 -0
  71. data/lib/contrast/agent/reporting/settings/virtual_patch_condition.rb +47 -0
  72. data/lib/contrast/agent/request_context_extend.rb +20 -0
  73. data/lib/contrast/agent/telemetry/base.rb +13 -15
  74. data/lib/contrast/agent/telemetry/events/exceptions/obfuscate.rb +108 -103
  75. data/lib/contrast/agent/telemetry/events/startup_metrics_event.rb +1 -1
  76. data/lib/contrast/agent/thread_watcher.rb +16 -10
  77. data/lib/contrast/agent/version.rb +1 -1
  78. data/lib/contrast/agent.rb +12 -0
  79. data/lib/contrast/agent_lib/api/init.rb +1 -7
  80. data/lib/contrast/agent_lib/api/input_tracing.rb +2 -4
  81. data/lib/contrast/agent_lib/interface.rb +1 -16
  82. data/lib/contrast/agent_lib/interface_base.rb +52 -39
  83. data/lib/contrast/agent_lib/return_types/eval_result.rb +2 -2
  84. data/lib/contrast/components/assess.rb +26 -4
  85. data/lib/contrast/components/config.rb +1 -1
  86. data/lib/contrast/components/polling.rb +4 -1
  87. data/lib/contrast/components/settings.rb +46 -3
  88. data/lib/contrast/config/config.rb +2 -2
  89. data/lib/contrast/config/protect_rule_configuration.rb +1 -1
  90. data/lib/contrast/config/protect_rules_configuration.rb +1 -1
  91. data/lib/contrast/extension/assess/array.rb +3 -3
  92. data/lib/contrast/extension/assess/regexp.rb +2 -2
  93. data/lib/contrast/framework/rack/patch/session_cookie.rb +2 -1
  94. data/lib/contrast/logger/aliased_logging.rb +48 -15
  95. data/lib/contrast/utils/duck_utils.rb +18 -0
  96. data/lib/contrast/utils/heap_dump_util.rb +1 -1
  97. data/lib/contrast/utils/input_classification_base.rb +21 -4
  98. data/lib/contrast/utils/log_utils.rb +1 -1
  99. data/lib/contrast/utils/middleware_utils.rb +1 -1
  100. data/lib/contrast/utils/patching/policy/patch_utils.rb +2 -2
  101. data/lib/contrast/utils/routes_sent.rb +6 -2
  102. data/lib/contrast/utils/telemetry.rb +2 -2
  103. data/lib/contrast/utils/telemetry_client.rb +1 -1
  104. data/resources/protect/policy.json +8 -0
  105. data/ruby-agent.gemspec +6 -6
  106. metadata +40 -30
  107. data/lib/contrast/agent/protect/rule/http_method_tampering/http_method_tampering_input_classification.rb +0 -96
  108. data/lib/contrast/agent/protect/rule/http_method_tampering.rb +0 -83
  109. data/lib/contrast/agent/reporting/details/http_method_tempering_details.rb +0 -27
  110. data/lib/contrast/agent/reporting/reporter_heartbeat.rb +0 -47
  111. data/lib/contrast/agent/reporting/server_settings_worker.rb +0 -44
  112. data/lib/contrast/agent_lib/api/method_tempering.rb +0 -29
@@ -1,6 +1,9 @@
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/settings/assess_rule'
5
+ require 'contrast/agent/reporting/settings/protect_rule'
6
+
4
7
  module Contrast
5
8
  module Agent
6
9
  module Reporting
@@ -8,46 +11,9 @@ module Contrast
8
11
  module ResponseExtractor
9
12
  private
10
13
 
11
- # @param response_data [Hash]
12
- # @param res [Contrast::Agent::Reporting::Response]
13
- def extract_assess response_data, res
14
- assessments = response_data[:settings][:assessment]
15
- return unless assessments
16
-
17
- res.application_settings.assess.disabled_rules = assessments[:disabledRules]
18
- res.application_settings.assess.session_id = assessments[:session_id]
19
- end
20
-
21
- # @param response_data [Hash]
22
- # @param res [Contrast::Agent::Reporting::Response]
23
- def extract_protect response_data, res
24
- protect = response_data[:settings][:defend]
25
- return unless protect
26
-
27
- res.application_settings.protect.protection_rules = protect[:protectionRules]
28
- res.application_settings.protect.virtual_patches = protect[:virtualPatches]
29
- end
30
-
31
- # @param response_data [Hash]
32
- # @param res [Contrast::Agent::Reporting::Response]
33
- def extract_exclusions response_data, res
34
- exclusions = response_data[:settings][:exceptions]
35
- return unless exclusions
36
-
37
- res.application_settings.exclusions.code_exclusions = exclusions[:codeExceptions]
38
- res.application_settings.exclusions.input_exclusions = exclusions[:inputExceptions]
39
- res.application_settings.exclusions.url_exclusions = exclusions[:urlExceptions]
40
- end
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
- #
45
- # @param response_data [Hash]
46
- # @param res [Contrast::Agent::Reporting::Response]
47
- def extract_reactions response_data, res
48
- res.reactions = response_data[:settings][:reactions] if response_data[:settings]
49
- res.reactions = response_data[:reactions] if response_data[:features]
50
- end
14
+ ##########################################
15
+ # Server Settings Parsing #
16
+ ##########################################
51
17
 
52
18
  # This method is universal and used for both ng endpoing of feature settings and the new
53
19
  # Server settings endpoint. Used in both build_feature_settings and build_server_settings.
@@ -56,13 +22,12 @@ module Contrast
56
22
  #
57
23
  # @param response_data [Hash]
58
24
  # @param res [Contrast::Agent::Reporting::Response]
59
- # @param ng_ [Boolean]
60
- def extract_assess_settings response_data, res, ng_: true
61
- assess = ng_ ? response_data[:features][:assessment] : response_data[:assess]
25
+ def extract_assess_server_settings response_data, res
26
+ assess = response_data[:assess]
62
27
  return unless assess
63
28
 
64
- res.server_features.assess.enabled = ng_ ? assess[:enabled] : assess[:enable]
65
- res.server_features.assess.report_stacktraces = assess[:report_stacktraces] unless ng_
29
+ res.server_features.assess.enabled = assess[:enable]
30
+ res.server_features.assess.report_stacktraces = assess[:report_stacktraces]
66
31
  res.server_features.assess.sampling = assess[:sampling]
67
32
  res.server_features.assess.sanitizers = assess[:sanitizers]
68
33
  res.server_features.assess.validators = assess[:validators]
@@ -70,58 +35,14 @@ module Contrast
70
35
 
71
36
  # @param response_data [Hash]
72
37
  # @param res [Contrast::Agent::Reporting::Response]
73
- def extract_protect_server_features response_data, res
74
- protect = response_data[:features][:defend]
75
- return unless protect
76
-
77
- res.server_features.protect.enabled = protect[:enabled]
78
- res.server_features.protect.bot_blocker.enable = protect[:'bot-blocker']
79
- res.server_features.protect.bot_blocker.bots = protect[:botBlockers]
80
- extract_syslog(response_data, res)
81
- end
82
-
83
- # @param response_data [Hash]
84
- # @param res [Contrast::Agent::Reporting::Response]
85
- def extract_syslog response_data, res
86
- return unless (syslog = response_data[:features][:defend][:syslog])
87
-
88
- res.server_features.protect.syslog.assign_array(syslog, ng_: true)
89
- end
90
-
91
- # @param response_data [Hash]
92
- # @param res [Contrast::Agent::Reporting::Response]
93
- def extract_protect_lists response_data, res
94
- protect = response_data[:features][:defend]
38
+ def extract_protect_server_settings response_data, res
39
+ protect = response_data[:protect]
95
40
  return unless protect
96
41
 
97
- res.server_features.protect.ip_allowlist = protect[:ipAllowlist]
98
- res.server_features.protect.ip_denylist = protect[:ipDenylist]
99
- res.server_features.protect.log_enhancers = protect[:logEnhancers]
100
- res.server_features.protect.rule_definition_list = protect[:ruleDefinitionList]
101
- end
102
-
103
- # Here we extract the rules and state for the sensitive data masking policy
104
- # received from TS.
105
- #
106
- # @param response_data [Hash]
107
- # @param res [Contrast::Agent::Reporting::Response]
108
- def extract_sensitive_data_policy response_data, res
109
- return unless (sensitive_data = response_data[:settings][:sensitive_data_masking_policy])
110
-
111
- res.application_settings.sensitive_data_masking.mask_http_body = sensitive_data[:mask_http_body]
112
- res.application_settings.sensitive_data_masking.mask_attack_vector = sensitive_data[:mask_attack_vector]
113
- res.application_settings.sensitive_data_masking.build_rules_form_settings(sensitive_data[:rules])
114
- end
115
-
116
- # Here we extract the log settings received from TS.
117
- #
118
- # @param response_data [Hash]
119
- # @param res [Contrast::Agent::Reporting::Response]
120
- def extract_log_settings response_data, res
121
- return unless (log_level = response_data[:logLevel])
122
-
123
- res.server_features.log_level = log_level
124
- res.server_features.log_file = response_data[:logFile] if response_data[:logFile]
42
+ res.server_features.protect.enabled = protect[:enable]
43
+ res.server_features.protect.observability = protect[:observability]
44
+ res.server_features.protect.log_enhancers = protect[:log_enhancers]
45
+ update_protect_rules(protect, res)
125
46
  end
126
47
 
127
48
  # This method is used with ServerSettings as we expect to have data for
@@ -147,18 +68,6 @@ module Contrast
147
68
  res.server_features.security_logger.syslog.assign_array(response_data[:security_logger][:syslog], ng_: false)
148
69
  end
149
70
 
150
- # @param response_data [Hash]
151
- # @param res [Contrast::Agent::Reporting::Response]
152
- def extract_protect_server_settings response_data, res
153
- protect = response_data[:protect]
154
- return unless protect
155
-
156
- res.server_features.protect.enabled = protect[:enable]
157
- res.server_features.protect.observability = protect[:observability]
158
- res.server_features.protect.log_enhancers = protect[:log_enhancers]
159
- update_protect_rules(protect, res)
160
- end
161
-
162
71
  # @param protect [Hash] response data
163
72
  # @param res [Contrast::Agent::Reporting::Response]
164
73
  def update_protect_rules protect, res
@@ -170,6 +79,90 @@ module Contrast
170
79
  res.server_features.protect.bot_blocker.bots = rules[:bot_blocker][:bots]
171
80
  res.server_features.protect.rules_to_definition_list(rules)
172
81
  end
82
+
83
+ ##########################################
84
+ # Application Settings Parsing #
85
+ ##########################################
86
+
87
+ # @param response_data [Hash]
88
+ # @param res [Contrast::Agent::Reporting::Response]
89
+ def extract_assess_application_settings response_data, res
90
+ assess = response_data[:assess]
91
+ return unless assess
92
+
93
+ assess.each_pair do |rule_id, value|
94
+ rule_setting =
95
+ Contrast::Agent::Reporting::Settings::AssessRule.new.tap { |setting| setting.enable = value[:enable] }
96
+ res.application_settings.assess.rule_settings[rule_id.to_s] = rule_setting
97
+ end
98
+
99
+ # This endpoint can never give us session_id, so we dont need to set it here
100
+ # res.application_settings.assess.session_id
101
+ end
102
+
103
+ # @param response_data [Hash]
104
+ # @param res [Contrast::Agent::Reporting::Response]
105
+ def extract_protect_application_settings response_data, res
106
+ protect = response_data[:protect]
107
+ return unless protect
108
+
109
+ rules = protect[:rules]
110
+ rules&.each_pair do |rule_id, value|
111
+ rule_setting =
112
+ Contrast::Agent::Reporting::Settings::ProtectRule.new.tap { |setting| setting.mode = value[:mode] }
113
+ res.application_settings.protect.rule_settings[rule_id.to_s] = rule_setting
114
+ end
115
+ protect[:'virtual-patches']&.each do |patch_json|
116
+ res.application_settings.protect.virtual_patches <<
117
+ Contrast::Agent::Reporting::Settings::VirtualPatch.new(patch_json)
118
+ end
119
+ end
120
+
121
+ # @param response_data [Hash]
122
+ # @param res [Contrast::Agent::Reporting::Response]
123
+ def extract_exclusions response_data, res
124
+ exclusions = response_data[:exclusions]
125
+ return unless exclusions
126
+
127
+ res.application_settings.exclusions.code_exclusions = exclusions[:code]
128
+ res.application_settings.exclusions.url_exclusions = exclusions[:url]
129
+ extract_input_exclusions(response_data[:exclusions], res)
130
+ end
131
+
132
+ # Input exclusions between NG and non-NG are different, so need to be cast separately.
133
+ #
134
+ # @param exclusions [Hash]
135
+ # @param res [Contrast::Agent::Reporting::Response]
136
+ def extract_input_exclusions exclusions, res
137
+ res.application_settings.exclusions.input_exclusions = []
138
+ exclusions[:input].each do |exclusion_details|
139
+ input_exclusion = Contrast::Agent::Reporting::Settings::InputExclusion.new
140
+ input_exclusion.name = exclusion_details[:name]
141
+ input_exclusion.modes = exclusion_details[:modes]
142
+ input_exclusion.assess_rules = exclusion_details[:assess_rules]
143
+ input_exclusion.protect_rules = exclusion_details[:protect_rules]
144
+ input_exclusion.input_name = exclusion_details[:name]
145
+ input_exclusion.input_type = exclusion_details[:type]
146
+ input_exclusion.urls = exclusion_details[:urls]
147
+ res.application_settings.exclusions.input_exclusions << exclusion
148
+ end
149
+ end
150
+
151
+ # @param response_data [Hash]
152
+ # @param res [Contrast::Agent::Reporting::Response]
153
+ def extract_sensitive_data_policy response_data, res
154
+ return unless (sensitive_data = response_data[:sensitive_data_masking_policy])
155
+
156
+ res.application_settings.sensitive_data_masking.mask_http_body = sensitive_data[:mask_http_body]
157
+ res.application_settings.sensitive_data_masking.mask_attack_vector = sensitive_data[:mask_attack_vector]
158
+ res.application_settings.sensitive_data_masking.build_rules_form_settings(sensitive_data[:rules])
159
+ end
160
+
161
+ # @param response_data [Hash]
162
+ # @param res [Contrast::Agent::Reporting::Response]
163
+ def extract_reactions response_data, res
164
+ res.reactions = response_data[:reactions]
165
+ end
173
166
  end
174
167
  end
175
168
  end
@@ -14,6 +14,7 @@ module Contrast
14
14
  class ResponseHandler
15
15
  include Contrast::Components::Logger::InstanceMethods
16
16
  include Contrast::Agent::Reporting::ResponseHandlerUtils
17
+
17
18
  # 15 min
18
19
  TIMEOUT = 900.cs__freeze
19
20
 
@@ -23,7 +24,7 @@ module Contrast
23
24
  # @param event [Contrast::Agent::Reporting::ReportingEvent] The event sent to TeamServer.
24
25
  # @return response [Net::HTTP::Response, nil]
25
26
  def process response, event
26
- logger.debug('Reporter Received a response')
27
+ logger.debug('[Reporter] Received a response')
27
28
  return unless analyze_response?(response)
28
29
 
29
30
  # Handle the response body and obtain server_features or app_settings
@@ -100,7 +101,7 @@ module Contrast
100
101
  # @param error_message [String, nil] Error message if any received.
101
102
  def suspend_reporting message, timeout, error_message
102
103
  @_timeout = timeout || Contrast::Agent::Reporting::ResponseHandler::TIMEOUT
103
- log_debug_msg(message, timeout: @_timeout, error_message: error_message || 'none')
104
+ log_error_msg(message, timeout: @_timeout, error_message: error_message || 'none')
104
105
  @_sleep = true
105
106
  end
106
107
 
@@ -108,8 +109,8 @@ module Contrast
108
109
  #
109
110
  # @param message [String] Message to log
110
111
  # @param info_hash [Hash] information about the context to log.
111
- def log_debug_msg message, info_hash
112
- logger.debug(message, info_hash)
112
+ def log_error_msg message, info_hash
113
+ logger.error(message, info_hash)
113
114
  end
114
115
  end
115
116
  end
@@ -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_utilities/ng_response_extractor'
4
5
  require 'contrast/agent/reporting/reporting_utilities/response_extractor'
5
6
  require 'contrast/agent/disable_reaction'
6
7
 
@@ -9,6 +10,7 @@ module Contrast
9
10
  module Reporting
10
11
  # This module holds utilities required by the reporting service response handler
11
12
  module ResponseHandlerUtils
13
+ include Contrast::Agent::Reporting::NgResponseExtractor
12
14
  include Contrast::Agent::Reporting::ResponseExtractor
13
15
 
14
16
  ANALYZE_WHEN = %w[200 204].cs__freeze
@@ -20,14 +22,14 @@ module Contrast
20
22
  unprocessable_entity: '422',
21
23
  too_many_requests: '429'
22
24
  }.cs__freeze
23
- APP_NON_EXISTENT_MSG = 'Application does not exist! Either it has not been created or has '\
24
- 'been deleted or archived. '\
25
+ APP_NON_EXISTENT_MSG = 'Application does not exist! Either it has not been created or has ' \
26
+ 'been deleted or archived. ' \
25
27
  'Disabling permanently.'
26
28
  SUSPEND_MSG = 'Reporter is temporarily suspended.'
27
29
  UNSUCCESSFULLY_RECEIVED_MSG = 'The Reporter was unable to send message.'
28
- FORBIDDEN_MSG = 'Access was forbidden for current Report because the request authentication '\
30
+ FORBIDDEN_MSG = 'Access was forbidden for current Report because the request authentication ' \
29
31
  'information was not provided'
30
- FORBIDDEN_NO_ACTION_MSG = 'Report access was forbidden because the supplied credentials failed '\
32
+ FORBIDDEN_NO_ACTION_MSG = 'Report access was forbidden because the supplied credentials failed ' \
31
33
  'to authenticate the Agent'
32
34
  UNPROCESSABLE_ENTITY_MSG = 'Reporter received Unprocessable Entity response. Disabling permanently.'
33
35
  RETRY_AFTER_MSG = "There are too many requests of this type being sent by this Agent. #{ SUSPEND_MSG }"
@@ -115,7 +117,7 @@ module Contrast
115
117
  ready_after, error_message, auth_error = extract_response_info(response)
116
118
  # log, suspend, disable:
117
119
  if mode == @_mode.running
118
- log_debug_msg(message,
120
+ log_error_msg(message,
119
121
  response: response.__id__,
120
122
  request: Contrast::Agent::REQUEST_TRACKER.current&.request&.type,
121
123
  error_message: error_message || 'none',
@@ -151,9 +153,9 @@ module Contrast
151
153
  # @return last_modified[integer, nil] Time since last server update
152
154
  def extract_response_last_modified response
153
155
  return unless response.cs__is_a?(Net::HTTPResponse)
156
+ return unless (header = response['last-modified'])
154
157
 
155
- header = response['Last-Modified'] if response&.to_hash&.keys&.map(&:downcase)&.include?('last-modified')
156
- @_last_server_modified = header if header
158
+ @_last_server_modified = header
157
159
  end
158
160
 
159
161
  # Cease reporting about this application
@@ -162,7 +164,7 @@ module Contrast
162
164
  # @param info_hash [Hash] information about the context to log.
163
165
  def stop_reporting message, info_hash
164
166
  Contrast::Agent.reporter&.stop!
165
- log_debug_msg(message, info_hash)
167
+ log_error_msg(message, info_hash)
166
168
  ::Contrast::AGENT.disable!
167
169
  end
168
170
 
@@ -220,7 +222,16 @@ module Contrast
220
222
  end
221
223
  end
222
224
 
223
- # Converts response from Net to Reporting Response object
225
+ # Converts response from Net to Reporting Response object. Unfortunately, there are four types of responses
226
+ # that TeamServer can send back to us. The FeatureSet for Servers, which come from Agent Startup and Server
227
+ # Settings, and the SettingsState for Applications, which come from Application Startup and Application
228
+ # Settings.
229
+ #
230
+ # The Startup messages come from NG and have the nested structure w/ success, message, and features/settings.
231
+ # The Settings messages come from v1 and have the flat structure.
232
+ # Neither have uniform keys, for instance assessment in startup vs assess in settings.
233
+ #
234
+ # This method works to extract away these differences.
224
235
  #
225
236
  # @param response [Net::HTTP::Response, nil]
226
237
  # @param event [Contrast::Agent::Reporting::ReportingEvent] The event sent to TeamServer.
@@ -248,70 +259,95 @@ module Contrast
248
259
  # this is used to check the expected response type for this event.
249
260
  # @return response [Contrast::Agent::Reporting::Response, nil]
250
261
  def populate_response response_data, event
251
- return unless (success, messages = extract_success(response_data, event))
252
-
253
- # check if response contains application settings or Feature settings
254
- if response_data[:settings]
255
- # the response contains ApplicationSettings
256
- response = Contrast::Agent::Reporting::Response.build_application_response
257
- response.success = success
258
- response.messages = messages
259
- app_settings = build_application_settings(response_data, response)
260
- logger.trace('Agent: Received updated application settings', raw: response_data, processed: app_settings)
261
- app_settings
262
+ # Responses fall into one of two types - those for Servers or those for Applications
263
+ response = case event
264
+ # Server feature based events
265
+ when Contrast::Agent::Reporting::AgentStartup, Contrast::Agent::Reporting::ServerSettings
266
+ Contrast::Agent::Reporting::Response.build_server_response
267
+ # Application settings based events
268
+ when Contrast::Agent::Reporting::ApplicationStartup,
269
+ Contrast::Agent::Reporting::ApplicationSettings
270
+
271
+ Contrast::Agent::Reporting::Response.build_application_response
272
+ end
273
+ return unless response
274
+
275
+ return unless (success, messages = extract_success(response_data))
276
+
277
+ response.success = success
278
+ response.messages = messages
279
+ # Features & Settings have to be parsed from the response based on the event type sent
280
+ case event
281
+ when Contrast::Agent::Reporting::AgentStartup
282
+ extract_agent_startup(response_data, response)
283
+ when Contrast::Agent::Reporting::ApplicationStartup
284
+ extract_application_startup(response_data, response)
285
+ when Contrast::Agent::Reporting::ServerSettings
286
+ extract_server_settings(response_data, response)
287
+ when Contrast::Agent::Reporting::ApplicationSettings
288
+ extract_application_settings(response_data, response)
262
289
  else
263
- # the response contains FeatureSettings. The ng endpoint data feature
264
- response = Contrast::Agent::Reporting::Response.build_server_response
265
- response.success = success
266
- response.messages = messages
267
- server_features = if event.cs__is_a?(Contrast::Agent::Reporting::ServerSettings)
268
- # do the new extraction.
269
- build_server_settings(response_data, response)
270
- else
271
- build_feature_settings(response_data, response)
272
- end
273
- logger.trace('Agent: Received updated feature settings', raw: response_data, processed: server_features)
274
- server_features
290
+ return
275
291
  end
292
+ logger.trace('Agent: Received updated features or settings',
293
+ event: event.cs__class,
294
+ raw: response_data,
295
+ processed: response)
296
+ response
276
297
  end
277
298
 
278
299
  # This method is used with the ng endpoint.
279
300
  #
280
- # @param response_data [Hash]
281
- # @return res [Contrast::Agent::Reporting::Response]
282
- def build_application_settings response_data, response
283
- extract_assess(response_data, response)
284
- extract_protect(response_data, response)
285
- extract_exclusions(response_data, response)
286
- extract_reactions(response_data, response)
287
- extract_sensitive_data_policy(response_data, response)
288
- response
301
+ # @param response_data [Hash] JSON of the response body from a Contrast::Agent::Reporting::ApplicationStartup
302
+ # event
303
+ # @param response [Contrast::Agent::Reporting::Response] the object to populate with the body data
304
+ def extract_application_startup response_data, response
305
+ return unless response_data[:settings]
306
+
307
+ ng_extract_assess(response_data, response)
308
+ ng_extract_protect(response_data, response)
309
+ ng_extract_exclusions(response_data, response)
310
+ ng_extract_reactions(response_data, response)
311
+ ng_extract_sensitive_data_policy(response_data, response)
289
312
  end
290
313
 
291
314
  # This method is used with the ng startup endpoint.
292
315
  #
293
- # @param response_data [Hash]
294
- # @return res [Contrast::Agent::Reporting::Response]
295
- def build_feature_settings response_data, response
296
- extract_reactions(response_data, response)
297
- extract_assess_settings(response_data, response)
298
- extract_protect_server_features(response_data, response)
299
- extract_protect_lists(response_data, response)
300
- extract_log_settings(response_data, response)
316
+ # @param response_data [Hash] JSON of the response body from a Contrast::Agent::Reporting::AgentStartup event
317
+ # @param response [Contrast::Agent::Reporting::Response] the object to populate with the body data
318
+ def extract_agent_startup response_data, response
319
+ ng_extract_log_settings(response_data, response)
301
320
  response.server_features.telemetry = response_data[:telemetry]
302
- response
321
+ return unless response_data[:features]
322
+
323
+ ng_extract_reactions(response_data, response)
324
+ ng_extract_assess_features(response_data, response)
325
+ ng_extract_protect_features(response_data, response)
326
+ ng_extract_protect_lists(response_data, response)
303
327
  end
304
328
 
305
329
  # This method is used with the server settings endpoint.
306
330
  #
307
- # @param response_data [Hash]
308
- # @return res [Contrast::Agent::Reporting::Response]
309
- def build_server_settings response_data, response
331
+ # @param response_data [Hash] JSON of the response body from a Contrast::Agent::Reporting::ServerSettings event
332
+ # @param response [Contrast::Agent::Reporting::Response] the object to populate with the body data
333
+ def extract_server_settings response_data, response
310
334
  extract_loggers(response_data, response)
311
335
  extract_protect_server_settings(response_data, response)
312
- extract_assess_settings(response_data, response, ng_: false)
336
+ extract_assess_server_settings(response_data, response)
313
337
  response.server_features.telemetry = response_data[:telemetry][:enable]
314
- response
338
+ end
339
+
340
+ # This method is used with the ng startup endpoint.
341
+ #
342
+ # @param response_data [Hash] JSON of the response body from a Contrast::Agent::Reporting::ApplicationSettings
343
+ # event
344
+ # @param response [Contrast::Agent::Reporting::Response] the object to populate with the body data
345
+ def extract_application_settings response_data, response
346
+ extract_assess_application_settings(response_data, response)
347
+ extract_protect_application_settings(response_data, response)
348
+ extract_exclusions(response_data, response)
349
+ extract_sensitive_data_policy(response_data, response)
350
+ extract_reactions(response_data, response)
315
351
  end
316
352
 
317
353
  # This method with check the success and messages field of the response.
@@ -320,20 +356,18 @@ module Contrast
320
356
  # @param response_data [Hash]
321
357
  # @return [Array, nil] Returns the success status or nil if request
322
358
  # was not processed by TS.
323
- def extract_success response_data, event
324
- if event.cs__is_a?(Contrast::Agent::Reporting::ServerSettings)
325
- # we don't those in the body but we can count on the response code.
326
- success = @_last_response_code == '200' ? true : nil
327
- messages = @_last_response_code == '200' ? ['success'] : nil
328
- else
329
- success = response_data[:success]
330
- messages = response_data[:messages]
331
- end
359
+ def extract_success response_data
360
+ # If we are here we have receive 200 or 204 response code. We'll try and
361
+ # extract the success and messages received,but not all of the responses
362
+ # we receive will have success field or messages. All of the new non-ng
363
+ # endpoints won't have messages or success. The way we'll be sure that
364
+ # a response is successful is by checking the response code.
365
+ #
366
+ # To extract response we need only 200 response code.
367
+ success = @_last_response_code == '200'
368
+ messages = response_data[:messages] || []
332
369
  return [success, messages] if success
333
370
 
334
- logger.error('Unable to connect to Contrast UI') if messages.nil?
335
- logger.error('Failure on Contrast UI processing request', reasons: messages.join(', ')) if messages
336
-
337
371
  nil
338
372
  end
339
373
  end
@@ -0,0 +1,46 @@
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
+
7
+ module Contrast
8
+ module Agent
9
+ module ReportingWorkers
10
+ # The ApplicationServerWorker will send request on interval, to make sure the Agent gets the settings it
11
+ # need to operate, from TS. This Thead should be started after the ApplicationStartup is complete.
12
+ class ApplicationServerWorker < WorkerThread
13
+ RESEND_INTERVAL_MS = 30_000.cs__freeze
14
+
15
+ def start_thread!
16
+ return if running?
17
+
18
+ @_thread = Contrast::Agent::Thread.new do
19
+ logger.info('[ApplicationSettingsWorker] Starting thread.', sending_interval: application_server_ms)
20
+ loop do
21
+ logger.info('[ApplicationSettingsWorker] Fetching Settings', sending_interval: application_server_ms)
22
+ Contrast::Agent.reporter&.send_event(application_settings_message)
23
+ sleep(application_server_ms / 1000)
24
+ end
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ # This method will generate the reporting event for the application settings
31
+ #
32
+ # @return [Contrast::Agent::Reporting::ReportingEvent]
33
+ def application_settings_message
34
+ @_application_settings_message ||= Contrast::Agent::Reporting::ApplicationSettings.new
35
+ end
36
+
37
+ # Get the value from settings or use the default one.
38
+ #
39
+ # @return resend_ms [Integer] time to resend the message
40
+ def application_server_ms
41
+ @_application_server_ms ||= Contrast::AGENT.polling.app_settings_ms&.to_i || RESEND_INTERVAL_MS
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,51 @@
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/agent/inventory/dependency_usage_analysis'
7
+ require 'contrast/agent/reporting/reporting_events/poll'
8
+
9
+ module Contrast
10
+ module Agent
11
+ module ReportingWorkers
12
+ # The ReporterHeartbeat will make sure that the process remains marked alive by TeamServer and that we
13
+ # periodically reach out to get the latest settings for this application. It also sends out those
14
+ # messages which do not need to be associated directly with a request,
15
+ # such as Server Activity and Library Observation.
16
+ class ReporterHeartbeat < WorkerThread
17
+ # TeamServer will mark an application offline after 5 minutes. Sending this every one should be more than enough
18
+ # to satisfy our goals.
19
+ REFRESH_INTERVAL_SEC = 60
20
+
21
+ def start_thread!
22
+ return if running?
23
+
24
+ @_thread = Contrast::Agent::Thread.new do
25
+ logger.info('[Heartbeat] Starting thread.')
26
+ loop do
27
+ polling_events.each do |event|
28
+ Contrast::Agent.reporter&.send_event(event)
29
+ end
30
+ clean_properties
31
+ sleep(REFRESH_INTERVAL_SEC)
32
+ end
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def poll_message
39
+ @_poll_message ||= Contrast::Agent::Reporting::Poll.new
40
+ end
41
+
42
+ # Those events which should be sent periodically, rather than on event or request.
43
+ #
44
+ # @return [Array<Contrast::Agent::Reporting::ReportingEvent>]
45
+ def polling_events
46
+ [Contrast::Agent::Inventory::DependencyUsageAnalysis.instance.generate_library_usage, poll_message].compact
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,14 @@
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
+ module Contrast
5
+ module Agent
6
+ # This module will include all the worker that we have, which are not part of the regular thread reporters
7
+ module ReportingWorkers
8
+ end
9
+ end
10
+ end
11
+
12
+ require 'contrast/agent/reporting/reporting_workers/reporter_heartbeat'
13
+ require 'contrast/agent/reporting/reporting_workers/server_settings_worker'
14
+ require 'contrast/agent/reporting/reporting_workers/application_server_worker'