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.
- checksums.yaml +4 -4
- data/lib/contrast/agent/assess/policy/trigger_method.rb +1 -1
- data/lib/contrast/agent/assess/property/evented.rb +11 -11
- data/lib/contrast/agent/assess.rb +0 -1
- data/lib/contrast/agent/excluder.rb +1 -1
- data/lib/contrast/agent/middleware.rb +8 -2
- data/lib/contrast/agent/protect/input_analyzer/worth_watching_analyzer.rb +116 -0
- data/lib/contrast/agent/protect/rule/base.rb +2 -2
- data/lib/contrast/agent/protect/rule/bot_blocker.rb +1 -1
- data/lib/contrast/agent/protect/rule/cmd_injection.rb +8 -7
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_chained_command.rb +0 -5
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_dangerous_path.rb +0 -5
- data/lib/contrast/agent/protect/rule/path_traversal.rb +4 -3
- data/lib/contrast/agent/protect/rule/sqli.rb +4 -3
- data/lib/contrast/agent/protect/rule/xss.rb +1 -0
- data/lib/contrast/agent/reporting/attack_result/rasp_rule_sample.rb +1 -1
- data/lib/contrast/agent/reporting/report.rb +1 -0
- data/lib/contrast/agent/reporting/reporter.rb +34 -0
- data/lib/contrast/agent/reporting/reporter_heartbeat.rb +3 -9
- data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +12 -7
- data/lib/contrast/agent/reporting/reporting_events/application_inventory_activity.rb +6 -1
- data/lib/contrast/agent/reporting/reporting_events/finding.rb +2 -2
- data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +239 -93
- data/lib/contrast/agent/reporting/reporting_events/finding_event_signature.rb +10 -23
- data/lib/contrast/agent/reporting/reporting_events/finding_event_source.rb +10 -9
- data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +12 -0
- data/lib/contrast/agent/reporting/reporting_events/server_reporting_event.rb +8 -0
- data/lib/contrast/agent/reporting/reporting_events/server_settings.rb +40 -0
- data/lib/contrast/agent/reporting/reporting_utilities/endpoints.rb +6 -0
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +43 -1
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +8 -4
- data/lib/contrast/agent/reporting/reporting_utilities/response_extractor.rb +58 -4
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +4 -3
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +76 -16
- data/lib/contrast/agent/reporting/server_settings_worker.rb +44 -0
- data/lib/contrast/agent/reporting/settings/assess_server_feature.rb +14 -2
- data/lib/contrast/agent/reporting/settings/helpers.rb +7 -0
- data/lib/contrast/agent/reporting/settings/protect_server_feature.rb +39 -2
- data/lib/contrast/agent/reporting/settings/rule_definition.rb +3 -0
- data/lib/contrast/agent/reporting/settings/security_logger.rb +77 -0
- data/lib/contrast/agent/reporting/settings/server_features.rb +9 -0
- data/lib/contrast/agent/reporting/settings/syslog.rb +34 -5
- data/lib/contrast/agent/request.rb +1 -0
- data/lib/contrast/agent/request_handler.rb +5 -10
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_event.rb +1 -1
- data/lib/contrast/agent/thread_watcher.rb +35 -1
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/agent.rb +6 -0
- data/lib/contrast/api/communication/connection_status.rb +15 -0
- data/lib/contrast/components/agent.rb +34 -0
- data/lib/contrast/components/api.rb +23 -0
- data/lib/contrast/components/app_context.rb +23 -3
- data/lib/contrast/components/assess.rb +34 -4
- data/lib/contrast/components/assess_rules.rb +18 -0
- data/lib/contrast/components/base.rb +40 -0
- data/lib/contrast/components/config/sources.rb +95 -0
- data/lib/contrast/components/config.rb +18 -1
- data/lib/contrast/components/heap_dump.rb +10 -0
- data/lib/contrast/components/inventory.rb +15 -2
- data/lib/contrast/components/logger.rb +18 -0
- data/lib/contrast/components/polling.rb +36 -0
- data/lib/contrast/components/protect.rb +48 -1
- data/lib/contrast/components/ruby_component.rb +15 -0
- data/lib/contrast/components/sampling.rb +70 -13
- data/lib/contrast/components/security_logger.rb +13 -0
- data/lib/contrast/components/settings.rb +74 -7
- data/lib/contrast/config/certification_configuration.rb +14 -0
- data/lib/contrast/config/config.rb +46 -0
- data/lib/contrast/config/diagnostics.rb +114 -0
- data/lib/contrast/config/diagnostics_tools.rb +98 -0
- data/lib/contrast/config/effective_config.rb +65 -0
- data/lib/contrast/config/effective_config_value.rb +32 -0
- data/lib/contrast/config/exception_configuration.rb +12 -0
- data/lib/contrast/config/protect_rule_configuration.rb +1 -1
- data/lib/contrast/config/protect_rules_configuration.rb +8 -7
- data/lib/contrast/config/request_audit_configuration.rb +13 -0
- data/lib/contrast/config/server_configuration.rb +41 -2
- data/lib/contrast/configuration.rb +28 -2
- data/lib/contrast/extension/assess/erb.rb +1 -1
- data/lib/contrast/utils/assess/event_limit_utils.rb +31 -9
- data/lib/contrast/utils/assess/trigger_method_utils.rb +5 -4
- data/lib/contrast/utils/hash_digest.rb +2 -2
- data/lib/contrast/utils/input_classification_base.rb +1 -2
- data/lib/contrast/utils/reporting/application_activity_batch_utils.rb +81 -0
- data/lib/contrast/utils/routes_sent.rb +60 -0
- data/lib/contrast/utils/telemetry_client.rb +1 -2
- data/lib/contrast/utils/timer.rb +16 -0
- data/lib/contrast.rb +3 -1
- data/ruby-agent.gemspec +5 -1
- metadata +29 -20
- data/lib/contrast/agent/assess/contrast_event.rb +0 -157
- data/lib/contrast/agent/assess/events/event_factory.rb +0 -34
- data/lib/contrast/agent/assess/events/source_event.rb +0 -46
- data/lib/contrast/agent/reporting/reporting_events/server_activity.rb +0 -36
@@ -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
|
-
|
55
|
-
|
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('
|
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
|
-
|
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
|
-
#
|
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
|
-
# @
|
216
|
-
|
217
|
-
|
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 =
|
234
|
-
|
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
|
-
|
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
|
-
|
271
|
-
|
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
|
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
|