contrast-agent 6.8.0 → 6.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/lib/contrast/agent/assess/policy/trigger_method.rb +1 -1
  3. data/lib/contrast/agent/assess/property/evented.rb +11 -11
  4. data/lib/contrast/agent/assess.rb +0 -1
  5. data/lib/contrast/agent/excluder.rb +1 -1
  6. data/lib/contrast/agent/middleware.rb +8 -2
  7. data/lib/contrast/agent/protect/input_analyzer/worth_watching_analyzer.rb +116 -0
  8. data/lib/contrast/agent/protect/rule/base.rb +2 -2
  9. data/lib/contrast/agent/protect/rule/bot_blocker.rb +1 -1
  10. data/lib/contrast/agent/protect/rule/cmd_injection.rb +8 -7
  11. data/lib/contrast/agent/protect/rule/cmdi/cmdi_chained_command.rb +0 -5
  12. data/lib/contrast/agent/protect/rule/cmdi/cmdi_dangerous_path.rb +0 -5
  13. data/lib/contrast/agent/protect/rule/path_traversal.rb +4 -3
  14. data/lib/contrast/agent/protect/rule/sqli.rb +4 -3
  15. data/lib/contrast/agent/protect/rule/xss.rb +1 -0
  16. data/lib/contrast/agent/reporting/attack_result/rasp_rule_sample.rb +1 -1
  17. data/lib/contrast/agent/reporting/report.rb +1 -0
  18. data/lib/contrast/agent/reporting/reporter.rb +34 -0
  19. data/lib/contrast/agent/reporting/reporter_heartbeat.rb +3 -9
  20. data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +1 -1
  21. data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +12 -7
  22. data/lib/contrast/agent/reporting/reporting_events/application_inventory_activity.rb +6 -1
  23. data/lib/contrast/agent/reporting/reporting_events/finding.rb +2 -2
  24. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +239 -93
  25. data/lib/contrast/agent/reporting/reporting_events/finding_event_signature.rb +10 -23
  26. data/lib/contrast/agent/reporting/reporting_events/finding_event_source.rb +10 -9
  27. data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +12 -0
  28. data/lib/contrast/agent/reporting/reporting_events/server_reporting_event.rb +8 -0
  29. data/lib/contrast/agent/reporting/reporting_events/server_settings.rb +40 -0
  30. data/lib/contrast/agent/reporting/reporting_utilities/endpoints.rb +6 -0
  31. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +43 -1
  32. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +8 -4
  33. data/lib/contrast/agent/reporting/reporting_utilities/response_extractor.rb +58 -4
  34. data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +4 -3
  35. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +76 -16
  36. data/lib/contrast/agent/reporting/server_settings_worker.rb +44 -0
  37. data/lib/contrast/agent/reporting/settings/assess_server_feature.rb +14 -2
  38. data/lib/contrast/agent/reporting/settings/helpers.rb +7 -0
  39. data/lib/contrast/agent/reporting/settings/protect_server_feature.rb +39 -2
  40. data/lib/contrast/agent/reporting/settings/rule_definition.rb +3 -0
  41. data/lib/contrast/agent/reporting/settings/security_logger.rb +77 -0
  42. data/lib/contrast/agent/reporting/settings/server_features.rb +9 -0
  43. data/lib/contrast/agent/reporting/settings/syslog.rb +34 -5
  44. data/lib/contrast/agent/request.rb +1 -0
  45. data/lib/contrast/agent/request_handler.rb +5 -10
  46. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_event.rb +1 -1
  47. data/lib/contrast/agent/thread_watcher.rb +35 -1
  48. data/lib/contrast/agent/version.rb +1 -1
  49. data/lib/contrast/agent.rb +6 -0
  50. data/lib/contrast/api/communication/connection_status.rb +15 -0
  51. data/lib/contrast/components/agent.rb +34 -0
  52. data/lib/contrast/components/api.rb +23 -0
  53. data/lib/contrast/components/app_context.rb +23 -3
  54. data/lib/contrast/components/assess.rb +34 -4
  55. data/lib/contrast/components/assess_rules.rb +18 -0
  56. data/lib/contrast/components/base.rb +40 -0
  57. data/lib/contrast/components/config/sources.rb +95 -0
  58. data/lib/contrast/components/config.rb +18 -1
  59. data/lib/contrast/components/heap_dump.rb +10 -0
  60. data/lib/contrast/components/inventory.rb +15 -2
  61. data/lib/contrast/components/logger.rb +18 -0
  62. data/lib/contrast/components/polling.rb +36 -0
  63. data/lib/contrast/components/protect.rb +48 -1
  64. data/lib/contrast/components/ruby_component.rb +15 -0
  65. data/lib/contrast/components/sampling.rb +70 -13
  66. data/lib/contrast/components/security_logger.rb +13 -0
  67. data/lib/contrast/components/settings.rb +74 -7
  68. data/lib/contrast/config/certification_configuration.rb +14 -0
  69. data/lib/contrast/config/config.rb +46 -0
  70. data/lib/contrast/config/diagnostics.rb +114 -0
  71. data/lib/contrast/config/diagnostics_tools.rb +98 -0
  72. data/lib/contrast/config/effective_config.rb +65 -0
  73. data/lib/contrast/config/effective_config_value.rb +32 -0
  74. data/lib/contrast/config/exception_configuration.rb +12 -0
  75. data/lib/contrast/config/protect_rule_configuration.rb +1 -1
  76. data/lib/contrast/config/protect_rules_configuration.rb +8 -7
  77. data/lib/contrast/config/request_audit_configuration.rb +13 -0
  78. data/lib/contrast/config/server_configuration.rb +41 -2
  79. data/lib/contrast/configuration.rb +28 -2
  80. data/lib/contrast/extension/assess/erb.rb +1 -1
  81. data/lib/contrast/utils/assess/event_limit_utils.rb +31 -9
  82. data/lib/contrast/utils/assess/trigger_method_utils.rb +5 -4
  83. data/lib/contrast/utils/hash_digest.rb +2 -2
  84. data/lib/contrast/utils/input_classification_base.rb +1 -2
  85. data/lib/contrast/utils/reporting/application_activity_batch_utils.rb +81 -0
  86. data/lib/contrast/utils/routes_sent.rb +60 -0
  87. data/lib/contrast/utils/telemetry_client.rb +1 -2
  88. data/lib/contrast/utils/timer.rb +16 -0
  89. data/lib/contrast.rb +3 -1
  90. data/ruby-agent.gemspec +5 -1
  91. metadata +29 -20
  92. data/lib/contrast/agent/assess/contrast_event.rb +0 -157
  93. data/lib/contrast/agent/assess/events/event_factory.rb +0 -34
  94. data/lib/contrast/agent/assess/events/source_event.rb +0 -46
  95. data/lib/contrast/agent/reporting/reporting_events/server_activity.rb +0 -36
@@ -70,6 +70,12 @@ module Contrast
70
70
  end
71
71
  end
72
72
 
73
+ def server_settings
74
+ with_rescue do
75
+ "#{ server_endpoint }/settings".cs__freeze
76
+ end
77
+ end
78
+
73
79
  # @return [String,nil]
74
80
  def trace_observed
75
81
  with_rescue do
@@ -10,6 +10,8 @@ require 'contrast/agent/reporting/reporting_utilities/response_handler'
10
10
  require 'contrast/agent/reporting/reporting_utilities/reporter_client_utils'
11
11
  require 'contrast/agent/reporting/reporting_utilities/endpoints'
12
12
  require 'contrast/agent/reporting/reporting_utilities/headers'
13
+ require 'contrast/agent/reporting/reporting_events/server_settings'
14
+ require 'contrast/config/diagnostics'
13
15
 
14
16
  module Contrast
15
17
  module Agent
@@ -21,8 +23,10 @@ module Contrast
21
23
 
22
24
  include Contrast::Agent::Reporting::Endpoints
23
25
  include Contrast::Agent::Reporting::ReporterClientUtils
26
+ include Contrast::Agent::Reporting::ResponseHandlerUtils
24
27
  include Contrast::Components::Logger::InstanceMethods
25
28
  SERVICE_NAME = 'Reporter'
29
+ REPORT_CONFIG_WHEN = %w[200 304].cs__freeze
26
30
  def initialize
27
31
  @headers = Contrast::Agent::Reporting::Headers.new
28
32
  super()
@@ -61,13 +65,47 @@ module Contrast
61
65
  request = build_request(event)
62
66
  response = connection.request(request)
63
67
  audit&.audit_event(event, response) if ::Contrast::API.request_audit_enable
64
- process_settings_response(response)
68
+ process_settings_response(response, event)
69
+ record_status(response, event)
70
+ record_configuration(response, event)
65
71
  process_preflight_response(event, response, connection)
66
72
  response
67
73
  rescue StandardError => e
68
74
  handle_error(event, e)
69
75
  end
70
76
 
77
+ # This is going to populate the config status value in the diagnostics json
78
+ #
79
+ # @param response [Contrast::Agent::Reporting::Response, nil]
80
+ # @param event [Contrast::Agent::Reporting::ReportingEvent]
81
+ def record_status response, event
82
+ return unless response
83
+ return unless event&.cs__class == Contrast::Agent::Reporting::AgentStartup ||
84
+ event&.cs__class == Contrast::Agent::Reporting::ApplicationStartup
85
+
86
+ diagnostics.config.determine_config_status(response)
87
+ nil
88
+ end
89
+
90
+ # Write effective config to file:
91
+ # If we are here the create and server messages are sent and the code received is
92
+ # 200 or 304. In case of 304 there will be no new settings and we can write current ones.
93
+ # This is done on every settings request.
94
+ #
95
+ # @param response [Contrast::Agent::Reporting::Response, nil]
96
+ # @param event [Contrast::Agent::Reporting::ReportingEvent]
97
+ def record_configuration response, event
98
+ return unless response
99
+ return unless REPORT_CONFIG_WHEN.include?(response_handler.last_response_code)
100
+ return unless event&.cs__class == Contrast::Agent::Reporting::ServerSettings ||
101
+ event&.cs__class == Contrast::Agent::Reporting::AgentStartup ||
102
+ event&.cs__class == Contrast::Agent::Reporting::ApplicationStartup
103
+
104
+ logger.info('[Reporter Diagnostics] last response code:', response_code: response_handler.last_response_code)
105
+ diagnostics.write_to_file
106
+ status.server_response_success!
107
+ end
108
+
71
109
  def status
72
110
  @_status ||= Contrast::Api::Communication::ConnectionStatus.new
73
111
  end
@@ -76,6 +114,10 @@ module Contrast
76
114
  @_response_handler ||= Contrast::Agent::Reporting::ResponseHandler.new
77
115
  end
78
116
 
117
+ def diagnostics
118
+ @_diagnostics ||= Contrast::Agent::DiagnosticsConfig::Diagnostics.new(Contrast::LOGGER.path)
119
+ end
120
+
79
121
  def sleep?
80
122
  response_handler.sleep?
81
123
  end
@@ -84,10 +84,12 @@ module Contrast
84
84
 
85
85
  # Handles response processing and sets status
86
86
  #
87
+ # @param event [Contrast::Agent::Reporting::ReportingEvent] The event sent to TeamServer.
87
88
  # @param response [Net::HTTP::Response]
88
- def process_settings_response response
89
- response_handler.process(response)
90
- status.success!
89
+ def process_settings_response response, event
90
+ res = response_handler.process(response, event)
91
+ status.success! if res
92
+ res
91
93
  end
92
94
 
93
95
  # Given a response from preflght, when the finding hash is desired, then send the finding to which it pertains.
@@ -100,7 +102,7 @@ module Contrast
100
102
  # @param connection [Net::HTTP] open connection
101
103
  def process_preflight_response event, response, connection
102
104
  return unless event.cs__is_a?(Contrast::Agent::Reporting::Preflight)
103
- return unless response && connection
105
+ return unless response&.body && connection
104
106
 
105
107
  findings_to_return = response.body.split(',').delete_if { |el| el.include?('*') }
106
108
  findings_to_return.each do |index|
@@ -125,6 +127,8 @@ module Contrast
125
127
  request = case event.event_method
126
128
  when :PUT
127
129
  Net::HTTP::Put.new(event.event_endpoint)
130
+ when :GET
131
+ Net::HTTP::Get.new(event.event_endpoint)
128
132
  else # :POST
129
133
  Net::HTTP::Post.new(event.event_endpoint)
130
134
  end
@@ -49,13 +49,20 @@ module Contrast
49
49
  res.reactions = response_data[:reactions] if response_data[:features]
50
50
  end
51
51
 
52
+ # This method is universal and used for both ng endpoing of feature settings and the new
53
+ # Server settings endpoint. Used in both build_feature_settings and build_server_settings.
54
+ # Passing the ng_ flag determines the use of the endpoint and response used because some of
55
+ # the response fields are with different names or do not exist: [enabled vs enable]
56
+ #
52
57
  # @param response_data [Hash]
53
58
  # @param res [Contrast::Agent::Reporting::Response]
54
- def extract_assess_server_features response_data, res
55
- assess = response_data[:features][:assessment]
59
+ # @param ng_ [Boolean]
60
+ def extract_assess_settings response_data, res, ng_: true
61
+ assess = ng_ ? response_data[:features][:assessment] : response_data[:assess]
56
62
  return unless assess
57
63
 
58
- res.server_features.assess.enabled = assess[:enabled]
64
+ res.server_features.assess.enabled = ng_ ? assess[:enabled] : assess[:enable]
65
+ res.server_features.assess.report_stacktraces = assess[:report_stacktraces] unless ng_
59
66
  res.server_features.assess.sampling = assess[:sampling]
60
67
  res.server_features.assess.sanitizers = assess[:sanitizers]
61
68
  res.server_features.assess.validators = assess[:validators]
@@ -78,7 +85,7 @@ module Contrast
78
85
  def extract_syslog response_data, res
79
86
  return unless (syslog = response_data[:features][:defend][:syslog])
80
87
 
81
- res.server_features.protect.syslog.assign_array(syslog)
88
+ res.server_features.protect.syslog.assign_array(syslog, ng_: true)
82
89
  end
83
90
 
84
91
  # @param response_data [Hash]
@@ -116,6 +123,53 @@ module Contrast
116
123
  res.server_features.log_level = log_level
117
124
  res.server_features.log_file = response_data[:logFile] if response_data[:logFile]
118
125
  end
126
+
127
+ # This method is used with ServerSettings as we expect to have data for
128
+ # all the loggers - security_logger, logger, syslog.
129
+ #
130
+ # @param response_data [Hash]
131
+ # @param res [Contrast::Agent::Reporting::Response]
132
+ def extract_loggers response_data, res
133
+ logger = response_data[:logger]
134
+ security_logger = response_data[:security_logger]
135
+
136
+ if logger
137
+ res.server_features.log_level = logger[:level]
138
+ res.server_features.log_file = logger[:path]
139
+ end
140
+ return unless security_logger
141
+
142
+ log_level = security_logger[:level]
143
+ log_file = security_logger[:path]
144
+ res.server_features.security_logger.log_level = log_level
145
+ res.server_features.security_logger.log_file = log_file
146
+ res.server_features.security_logger.not_blank!
147
+ res.server_features.security_logger.syslog.assign_array(response_data[:security_logger][:syslog], ng_: false)
148
+ end
149
+
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
+ # @param protect [Hash] response data
163
+ # @param res [Contrast::Agent::Reporting::Response]
164
+ def update_protect_rules protect, res
165
+ return unless (rules = protect[:rules])
166
+
167
+ res.server_features.protect.ip_allowlist = rules[:ip_allowlist]
168
+ res.server_features.protect.ip_denylist = rules[:ip_denylist]
169
+ res.server_features.protect.bot_blocker.enable = rules[:bot_blocker][:enable]
170
+ res.server_features.protect.bot_blocker.bots = rules[:bot_blocker][:bots]
171
+ res.server_features.protect.rules_to_definition_list(rules)
172
+ end
119
173
  end
120
174
  end
121
175
  end
@@ -20,13 +20,14 @@ module Contrast
20
20
  # Process the response from TS
21
21
  #
22
22
  # @param response [Net::HTTP::Response, nil]
23
+ # @param event [Contrast::Agent::Reporting::ReportingEvent] The event sent to TeamServer.
23
24
  # @return response [Net::HTTP::Response, nil]
24
- def process response
25
+ def process response, event
25
26
  logger.debug('Reporter Received a response')
26
27
  return unless analyze_response?(response)
27
28
 
28
29
  # Handle the response body and obtain server_features or app_settings
29
- report_response = convert_response(response)
30
+ report_response = convert_response(response, event)
30
31
  return unless report_response
31
32
 
32
33
  # Update Server Features and Application Settings to provide current agent settings
@@ -87,7 +88,7 @@ module Contrast
87
88
  when ERROR_CODES[:too_many_requests]
88
89
  handle_response_errors(response, RETRY_AFTER_MSG, mode.resending)
89
90
  else
90
- logger.error('Response Error code could not be processed')
91
+ logger.error('Unable to execute agent post_call')
91
92
  end
92
93
  end
93
94
 
@@ -32,6 +32,17 @@ module Contrast
32
32
  UNPROCESSABLE_ENTITY_MSG = 'Reporter received Unprocessable Entity response. Disabling permanently.'
33
33
  RETRY_AFTER_MSG = "There are too many requests of this type being sent by this Agent. #{ SUSPEND_MSG }"
34
34
 
35
+ def last_response_code
36
+ @_last_response_code ||= ''
37
+ end
38
+
39
+ # String format of the Header:<day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT
40
+ #
41
+ # @return [String]
42
+ def last_server_modified
43
+ @_last_server_modified
44
+ end
45
+
35
46
  private
36
47
 
37
48
  # check if response code is valid before analyze it
@@ -79,6 +90,11 @@ module Contrast
79
90
  # in the standard format per RFC 2616
80
91
  # used for in observed routes message.
81
92
  return false unless response && (response_code = response&.code)
93
+
94
+ # We still need to check the response code even if we are not analyzing it, since the 304 code does not
95
+ # contain settings to be extracted but we still need to know for the diagnostics. Do not move this bellow
96
+ # the ANALYZE_WHEN check.
97
+ @_last_response_code = response_code
82
98
  return true if ANALYZE_WHEN.include?(response_code)
83
99
 
84
100
  handle_error(response) if ERROR_CODES.value?(response_code)
@@ -110,7 +126,7 @@ module Contrast
110
126
 
111
127
  stop_reporting(message, application: Contrast::APP_CONTEXT.name, error_message: error_message) # rubocop:disable Security/Module/Name
112
128
  rescue StandardError => e
113
- logger.debug('Could not handle Response error information', error: e)
129
+ logger.debug('Could not handle Response error information', error: e, backtrace: e.backtrace)
114
130
  end
115
131
 
116
132
  # Extract what we've received.
@@ -127,6 +143,19 @@ module Contrast
127
143
  [ready_after.to_i, error_message, auth_error]
128
144
  end
129
145
 
146
+ # Extract Last-Modified header from ServerSettings response.
147
+ # The new GET server settings endpoint have different payload.
148
+ # Extract the last modify headers with last update form TS.
149
+ #
150
+ # @param response [Net::HTTP::Response, nil]
151
+ # @return last_modified[integer, nil] Time since last server update
152
+ def extract_response_last_modified response
153
+ return unless response.cs__is_a?(Net::HTTPResponse)
154
+
155
+ header = response['Last-Modified'] if response&.to_hash&.keys&.map(&:downcase)&.include?('last-modified')
156
+ @_last_server_modified = header if header
157
+ end
158
+
130
159
  # Cease reporting about this application
131
160
  #
132
161
  # @param message [String] Message to log
@@ -194,27 +223,32 @@ module Contrast
194
223
  # Converts response from Net to Reporting Response object
195
224
  #
196
225
  # @param response [Net::HTTP::Response, nil]
226
+ # @param event [Contrast::Agent::Reporting::ReportingEvent] The event sent to TeamServer.
197
227
  # @return response [Contrast::Agent::Reporting::Response]
198
- def convert_response response
228
+ def convert_response response, event
199
229
  response_body = response&.body
200
230
  return unless response_body
201
231
 
202
232
  response_data = JSON.parse(response_body, symbolize_names: true)
203
233
  return unless response_data.cs__is_a?(Hash)
204
234
 
205
- populate_response(response_data)
235
+ extract_response_last_modified(response)
236
+ populate_response(response_data, event)
206
237
  rescue StandardError => e
207
238
  logger.error('Unable to convert response', e)
208
239
  nil
209
240
  end
210
241
 
211
- # Extracts the data from the response and coverts it to
212
- # Contrast::Agent::Reporting::Response.
242
+ # Extracts the data from the response and coverts it to Contrast::Agent::Reporting::Response.
243
+ # The response is being checked for it's type and settings received so the extractor methods
244
+ # are invoked accordingly to the response type.
213
245
  #
214
246
  # @param response_data[Hash]
215
- # @return response [Contrast::Agent::Reporting::Response]
216
- def populate_response response_data
217
- return unless (success, messages = extract_success(response_data))
247
+ # @param event [Contrast::Agent::Reporting::ReportingEvent] The event sent to TeamServer.
248
+ # this is used to check the expected response type for this event.
249
+ # @return response [Contrast::Agent::Reporting::Response, nil]
250
+ def populate_response response_data, event
251
+ return unless (success, messages = extract_success(response_data, event))
218
252
 
219
253
  # check if response contains application settings or Feature settings
220
254
  if response_data[:settings]
@@ -226,17 +260,23 @@ module Contrast
226
260
  logger.trace('Agent: Received updated application settings', raw: response_data, processed: app_settings)
227
261
  app_settings
228
262
  else
229
- # the response contains FeatureSettings
263
+ # the response contains FeatureSettings. The ng endpoint data feature
230
264
  response = Contrast::Agent::Reporting::Response.build_server_response
231
265
  response.success = success
232
266
  response.messages = messages
233
- server_features = build_feature_settings(response_data, response)
234
- logger.trace('Agent: Received updated application settings', raw: response_data, processed: server_features)
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)
235
274
  server_features
236
275
  end
237
- response
238
276
  end
239
277
 
278
+ # This method is used with the ng endpoint.
279
+ #
240
280
  # @param response_data [Hash]
241
281
  # @return res [Contrast::Agent::Reporting::Response]
242
282
  def build_application_settings response_data, response
@@ -248,11 +288,13 @@ module Contrast
248
288
  response
249
289
  end
250
290
 
291
+ # This method is used with the ng startup endpoint.
292
+ #
251
293
  # @param response_data [Hash]
252
294
  # @return res [Contrast::Agent::Reporting::Response]
253
295
  def build_feature_settings response_data, response
254
296
  extract_reactions(response_data, response)
255
- extract_assess_server_features(response_data, response)
297
+ extract_assess_settings(response_data, response)
256
298
  extract_protect_server_features(response_data, response)
257
299
  extract_protect_lists(response_data, response)
258
300
  extract_log_settings(response_data, response)
@@ -260,15 +302,33 @@ module Contrast
260
302
  response
261
303
  end
262
304
 
305
+ # This method is used with the server settings endpoint.
306
+ #
307
+ # @param response_data [Hash]
308
+ # @return res [Contrast::Agent::Reporting::Response]
309
+ def build_server_settings response_data, response
310
+ extract_loggers(response_data, response)
311
+ extract_protect_server_settings(response_data, response)
312
+ extract_assess_settings(response_data, response, ng_: false)
313
+ response.server_features.telemetry = response_data[:telemetry][:enable]
314
+ response
315
+ end
316
+
263
317
  # This method with check the success and messages field of the response.
264
318
  # If the success is false, then it will return nil and log the error.
265
319
  #
266
320
  # @param response_data [Hash]
267
321
  # @return [Array, nil] Returns the success status or nil if request
268
322
  # was not processed by TS.
269
- def extract_success response_data
270
- success = response_data[:success]
271
- messages = response_data[:messages]
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
272
332
  return [success, messages] if success
273
333
 
274
334
  logger.error('Unable to connect to Contrast UI') if messages.nil?
@@ -0,0 +1,44 @@
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
+ # The ServerSettingsWorker will send request on interval, to make sure the Agent gets the settings it
10
+ # need to operate, from TS. This Thead should be started after the AgentStartup is complete.
11
+ class ServerSettingsWorker < WorkerThread
12
+ RESEND_INTERVAL_MS = 60_000.cs__freeze
13
+
14
+ def start_thread!
15
+ return if running?
16
+
17
+ @_thread = Contrast::Agent::Thread.new do
18
+ logger.info('Starting Server Settings Worker thread.', sending_interval: server_settings_resend_ms)
19
+ loop do
20
+ logger.info('Fetching Settings', sending_interval: server_settings_resend_ms)
21
+ Contrast::Agent.reporter&.send_event(settings_message)
22
+ sleep(server_settings_resend_ms / 1000)
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ # Polling messages for this thread. Including Server settings:
30
+ #
31
+ # @return [Contrast::Agent::Reporting::ReportingEvent]
32
+ def settings_message
33
+ @_settings_message ||= Contrast::Agent::Reporting::ServerSettings.new
34
+ end
35
+
36
+ # Get the value from settings or use the default one.
37
+ #
38
+ # @return resend_ms [Integer] time to resend the message
39
+ def server_settings_resend_ms
40
+ @_server_settings_resend_ms ||= Contrast::AGENT.polling.server_settings_ms&.to_i || RESEND_INTERVAL_MS
41
+ end
42
+ end
43
+ end
44
+ end
@@ -15,6 +15,7 @@ module Contrast
15
15
  # Application level settings for the Assess featureset.
16
16
  # Used for the FeatureSet TS response
17
17
  class AssessServerFeature
18
+ REPORT_STACKTRACES = %w[ALL SOME NONE].cs__freeze
18
19
  # Indicate if the assess feature set is enabled for this server or not.
19
20
  #
20
21
  # @return enabled [Boolean]
@@ -27,7 +28,17 @@ module Contrast
27
28
  # @param enabled [Boolean]
28
29
  # @return enabled [Boolean]
29
30
  def enabled= enabled
30
- @_enabled = enabled if !!enabled == enabled
31
+ @_enabled = enabled
32
+ end
33
+
34
+ # @return [String] ALL, SOME, NONE
35
+ def report_stacktraces
36
+ @_report_stacktraces
37
+ end
38
+
39
+ # @return [String] ALL, SOME, NONE
40
+ def report_stacktraces= level
41
+ @_report_stacktraces = level if REPORT_STACKTRACES.include?(level)
31
42
  end
32
43
 
33
44
  # Used to control the sampling feature in the agent.
@@ -91,9 +102,10 @@ module Contrast
91
102
  {
92
103
  enabled: enabled?,
93
104
  sampling: sampling.to_controlled_hash,
105
+ report_stacktraces: report_stacktraces, # used with ServerSettings only
94
106
  sanitizers: sanitizers.map(&:to_controlled_hash),
95
107
  validators: validators.map(&:to_controlled_hash)
96
- }
108
+ }.compact
97
109
  end
98
110
  end
99
111
  end
@@ -48,6 +48,13 @@ module Contrast
48
48
  result.slice!(idx + 1)
49
49
  result.index('_') ? no_more_underscore(result).to_sym : result.to_sym
50
50
  end
51
+
52
+ # rule_id => rule-id
53
+ #
54
+ # @param id [String]
55
+ def to_rule_id id
56
+ id.to_s.tr('_', '-')
57
+ end
51
58
  end
52
59
  end
53
60
  end
@@ -17,6 +17,10 @@ module Contrast
17
17
  # Application level settings for the Protect featureset.
18
18
  # Used for the FeatureSet TS response
19
19
  class ProtectServerFeature
20
+ PROTECT_RULES_KEYS = %i[
21
+ cmd_injection method_tampering nosql_injection path_traversal redos reflected_xss sql_injection
22
+ ssrf unsafe_file_upload untrusted_deserialization xxe
23
+ ].cs__freeze
20
24
  # Indicate if the protect feature set is enabled for this server or not.
21
25
  #
22
26
  # @return enabled [Boolean]
@@ -32,6 +36,21 @@ module Contrast
32
36
  @_enabled = enabled
33
37
  end
34
38
 
39
+ # When false, the agent should not track observations.
40
+ # when true, the agent should track observed usage of protect URLs
41
+ # { enable: true }
42
+ #
43
+ # @return observability [Boolean]
44
+ def observability
45
+ @_observability
46
+ end
47
+
48
+ # @param enable [Boolean]
49
+ # @return observability [Boolean]
50
+ def observability= enable
51
+ @_observability = enable
52
+ end
53
+
35
54
  # Indicate if the bot protection feature set is enabled for this server or not.
36
55
  #
37
56
  # @return bot_blocker [Contrast::Agent::Reporting::Settings::BotBlocker]
@@ -149,6 +168,23 @@ module Contrast
149
168
  list)
150
169
  end
151
170
 
171
+ # Transforms ServerSettings hash rules to definition_list
172
+ #
173
+ # @param rules [Hash]
174
+ def rules_to_definition_list rules
175
+ return unless rules&.cs__is_a?(Hash)
176
+
177
+ definition_list = []
178
+ rules.slice(*PROTECT_RULES_KEYS).each_pair do |key, rule|
179
+ new_entry = Contrast::Agent::Reporting::Settings::RuleDefinition.new
180
+ new_entry.name = Contrast::Agent::Reporting::Settings::Helpers.to_rule_id(key)
181
+ new_entry.patterns = rule[:patterns]
182
+ new_entry.keywords = rule[:keywords]
183
+ definition_list << new_entry
184
+ end
185
+ @_rule_definition_list = definition_list
186
+ end
187
+
152
188
  # Controls for the syslogging feature in the agent.
153
189
  #
154
190
  # @return syslog [Contrast::Agent::Reporting::Settings::Syslog]
@@ -175,13 +211,14 @@ module Contrast
175
211
  {
176
212
  botBlockers: bot_blocker.bots.map(&:to_controlled_hash),
177
213
  enabled: enabled?,
214
+ observability: observability, # used with ServerSettings only
178
215
  logEnhancers: log_enhancers.map(&:to_controlled_hash),
179
216
  ipDenylist: ip_denylist.map(&:to_controlled_hash),
180
217
  ipAllowlist: ip_allowlist.map(&:to_controlled_hash),
181
- syslog: syslog.to_controlled_hash,
218
+ syslog: syslog.settings_blank? ? nil : syslog.to_controlled_hash, # used with ServerSettings only
182
219
  ruleDefinitionList: rule_definition_list.map(&:to_controlled_hash),
183
220
  'bot-blocker': bot_blocker.to_controlled_hash
184
- }
221
+ }.compact
185
222
  end
186
223
  end
187
224
  end
@@ -12,6 +12,9 @@ module Contrast
12
12
  class RuleDefinition
13
13
  ATTRIBUTES = %i[name keywords patterns].cs__freeze
14
14
 
15
+ # For the ServerSettings this name is not used instead it is used as key to the keywords and patterns
16
+ # arrays. It is used in the agent startup message.
17
+ #
15
18
  # @return name [String] Name of the rule
16
19
  attr_accessor :name
17
20
 
@@ -0,0 +1,77 @@
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/reporting/settings/syslog'
5
+
6
+ module Contrast
7
+ module Agent
8
+ module Reporting
9
+ module Settings
10
+ # this class will hold the security logger settings.
11
+ class SecurityLogger
12
+ def initialize
13
+ @blank = true
14
+ end
15
+
16
+ # check to see if object is being used
17
+ #
18
+ # @return [Boolean]
19
+ def settings_blank?
20
+ @blank
21
+ end
22
+
23
+ # Set the state of settings
24
+ #
25
+ # @return [Boolean]
26
+ def not_blank!
27
+ @blank = false
28
+ end
29
+
30
+ # The level at which the agent should log. Overridden by agent.logger.level
31
+ # if set in a local configuration
32
+ #
33
+ # @return log_level [String] [ ERROR, WARN, INFO, DEBUG, TRACE ]
34
+ def log_level
35
+ @_log_level ||= Contrast::Utils::ObjectShare::EMPTY_STRING
36
+ end
37
+
38
+ # set the log level
39
+ #
40
+ # @param log_level [String]
41
+ # @return log_level [String] [ ERROR, WARN, INFO, DEBUG, TRACE ]
42
+ def log_level= log_level
43
+ @_log_level = log_level if log_level.is_a?(String)
44
+ end
45
+
46
+ # Where to log the agent's log file, if set by the user. Overridden by agent.logger.path
47
+ # if set in a local configuration.
48
+ #
49
+ # @return log_file [String] path
50
+ def log_file
51
+ @_log_file ||= Contrast::Utils::ObjectShare::EMPTY_STRING
52
+ end
53
+
54
+ # Set the log file
55
+ #
56
+ # @param log_file [String] path
57
+ # @return log_file [String] path
58
+ def log_file= log_file
59
+ @_log_file = log_file if log_file.is_a?(String)
60
+ end
61
+
62
+ def syslog
63
+ @_syslog ||= Contrast::Agent::Reporting::Settings::Syslog.new
64
+ end
65
+
66
+ def to_controlled_hash
67
+ {
68
+ level: log_level,
69
+ path: log_file,
70
+ syslog: syslog.to_controlled_hash
71
+ }
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end