contrast-agent 6.8.0 → 6.9.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 (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