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.
- 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
|