contrast-agent 7.3.1 → 7.4.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/ext/cs__scope/cs__scope.c +76 -7
- data/ext/cs__scope/cs__scope.h +4 -0
- data/lib/contrast/agent/inventory/policy/datastores.rb +0 -3
- data/lib/contrast/agent/protect/rule/base.rb +5 -1
- data/lib/contrast/agent/protect/rule/cmdi/cmd_injection.rb +17 -5
- data/lib/contrast/agent/protect/rule/input_classification/base.rb +7 -2
- data/lib/contrast/agent/protect/rule/input_classification/encoding.rb +1 -1
- data/lib/contrast/agent/protect/rule/path_traversal/path_traversal.rb +8 -1
- data/lib/contrast/agent/protect/rule/sqli/sqli.rb +8 -1
- data/lib/contrast/agent/protect/state.rb +110 -0
- data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +4 -10
- data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +11 -12
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +6 -29
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +1 -2
- data/lib/contrast/agent/reporting/reporting_events/application_inventory_activity.rb +2 -2
- data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +2 -0
- data/lib/contrast/agent/reporting/reporting_events/finding.rb +1 -0
- data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +4 -0
- data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +4 -2
- data/lib/contrast/agent/reporting/reporting_events/observed_library_usage.rb +2 -0
- data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +9 -5
- data/lib/contrast/agent/reporting/reporting_events/reportable_hash.rb +30 -6
- data/lib/contrast/agent/reporting/reporting_utilities/ng_response_extractor.rb +15 -2
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/resend.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/response.rb +0 -2
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +4 -5
- data/lib/contrast/agent/reporting/settings/protect.rb +61 -18
- data/lib/contrast/agent/reporting/settings/sampling.rb +5 -4
- data/lib/contrast/agent/reporting/settings/server_features.rb +2 -0
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/components/agent.rb +3 -5
- data/lib/contrast/components/api.rb +3 -3
- data/lib/contrast/components/assess_rules.rb +1 -2
- data/lib/contrast/components/base.rb +1 -2
- data/lib/contrast/components/config/sources.rb +23 -0
- data/lib/contrast/components/logger.rb +19 -0
- data/lib/contrast/components/protect.rb +69 -15
- data/lib/contrast/components/sampling.rb +5 -12
- data/lib/contrast/components/security_logger.rb +17 -0
- data/lib/contrast/components/settings.rb +114 -70
- data/lib/contrast/config/certification_configuration.rb +1 -1
- data/lib/contrast/config/configuration_files.rb +0 -2
- data/lib/contrast/config/diagnostics/config.rb +3 -3
- data/lib/contrast/config/diagnostics/effective_config.rb +1 -1
- data/lib/contrast/config/diagnostics/environment_variables.rb +21 -11
- data/lib/contrast/config/diagnostics/monitor.rb +1 -1
- data/lib/contrast/config/diagnostics/singleton_tools.rb +170 -0
- data/lib/contrast/config/diagnostics/source_config_value.rb +14 -9
- data/lib/contrast/config/diagnostics/tools.rb +23 -84
- data/lib/contrast/config/request_audit_configuration.rb +1 -1
- data/lib/contrast/config/server_configuration.rb +3 -15
- data/lib/contrast/configuration.rb +5 -2
- data/lib/contrast/framework/manager.rb +4 -3
- data/lib/contrast/framework/manager_extend.rb +3 -1
- data/lib/contrast/framework/rack/support.rb +11 -2
- data/lib/contrast/utils/log_utils.rb +1 -1
- data/lib/contrast/utils/reporting/application_activity_batch_utils.rb +0 -3
- data/lib/contrast/utils/request_utils.rb +1 -1
- data/lib/contrast/utils/timer.rb +1 -1
- data/lib/contrast.rb +1 -1
- metadata +4 -2
@@ -111,7 +111,6 @@ module Contrast
|
|
111
111
|
include Contrast::Config::BaseConfiguration
|
112
112
|
|
113
113
|
CANON_NAME = 'assess.sampling'
|
114
|
-
NAME_PREFIX = "#{ CONTRAST }.#{ CANON_NAME }".cs__freeze
|
115
114
|
CONFIG_VALUES = %w[enable baseline request_frequency response_frequency window_ms].cs__freeze
|
116
115
|
|
117
116
|
# @return [Boolean, nil]
|
@@ -145,19 +144,13 @@ module Contrast
|
|
145
144
|
def to_effective_config effective_config
|
146
145
|
confirm_sources
|
147
146
|
|
148
|
-
add_single_effective_value(effective_config, 'enable', sampling_control[:enabled], canon_name
|
149
|
-
add_single_effective_value(effective_config, 'baseline', sampling_control[:baseline], canon_name
|
150
|
-
add_single_effective_value(effective_config, 'window_ms', sampling_control[:window], canon_name
|
147
|
+
add_single_effective_value(effective_config, 'enable', sampling_control[:enabled], canon_name)
|
148
|
+
add_single_effective_value(effective_config, 'baseline', sampling_control[:baseline], canon_name)
|
149
|
+
add_single_effective_value(effective_config, 'window_ms', sampling_control[:window], canon_name)
|
151
150
|
add_single_effective_value(effective_config,
|
152
|
-
'request_frequency',
|
153
|
-
sampling_control[:request_frequency],
|
154
|
-
canon_name,
|
155
|
-
NAME_PREFIX)
|
151
|
+
'request_frequency', sampling_control[:request_frequency], canon_name)
|
156
152
|
add_single_effective_value(effective_config,
|
157
|
-
'response_frequency',
|
158
|
-
sampling_control[:response_frequency],
|
159
|
-
canon_name,
|
160
|
-
NAME_PREFIX)
|
153
|
+
'response_frequency', sampling_control[:response_frequency], canon_name)
|
161
154
|
end
|
162
155
|
|
163
156
|
private
|
@@ -10,6 +10,7 @@ module Contrast
|
|
10
10
|
class Interface
|
11
11
|
include Contrast::Components::ComponentBase
|
12
12
|
|
13
|
+
DEFAULT_CEF_NAME = 'security.log'
|
13
14
|
CANON_NAME = 'agent.security_logger'
|
14
15
|
CONFIG_VALUES = %w[path level].cs__freeze
|
15
16
|
|
@@ -30,6 +31,22 @@ module Contrast
|
|
30
31
|
@path = hsh[:path]
|
31
32
|
@level = hsh[:level]
|
32
33
|
end
|
34
|
+
|
35
|
+
def to_effective_config effective_config
|
36
|
+
path_setting = nil
|
37
|
+
level_setting = nil
|
38
|
+
|
39
|
+
if defined?(Contrast::SETTINGS)
|
40
|
+
path_setting = Contrast::SETTINGS.agent_state.cef_logger_path
|
41
|
+
level_setting = Contrast::SETTINGS.agent_state.cef_logger_level
|
42
|
+
end
|
43
|
+
|
44
|
+
path_setting ||= DEFAULT_CEF_NAME
|
45
|
+
level_setting ||= Contrast::CONFIG.agent.logger.level
|
46
|
+
|
47
|
+
add_single_effective_value(effective_config, config_values[0], path || path_setting, CANON_NAME)
|
48
|
+
add_single_effective_value(effective_config, config_values[1], level || level_setting, CANON_NAME)
|
49
|
+
end
|
33
50
|
end
|
34
51
|
end
|
35
52
|
end
|
@@ -14,10 +14,12 @@ module Contrast
|
|
14
14
|
# directives (likely provided by TeamServer) about product operation.
|
15
15
|
# 'Settings' is not a generic term for 'configurable stuff'.
|
16
16
|
module Settings
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
AGENT_STATE_BASE = Struct.new(:logger_path, :logger_level, :cef_logger_path, :cef_logger_level).
|
18
|
+
new(nil, nil, nil, nil)
|
19
|
+
APPLICATION_STATE_BASE = Struct.new(:modes_by_id).new({})
|
20
|
+
PROTECT_STATE_BASE = Struct.new(:enabled).new(false)
|
21
|
+
ASSESS_STATE_BASE = Struct.new(:enabled, :sampling_settings, :disabled_assess_rules, :session_id).
|
22
|
+
new(false, nil, [], nil) do
|
21
23
|
def sampling_settings= new_val
|
22
24
|
@sampling_settings = new_val
|
23
25
|
Contrast::Utils::Assess::SamplingUtil.instance.update
|
@@ -32,6 +34,13 @@ module Contrast
|
|
32
34
|
|
33
35
|
# tainted_columns are database columns that receive unsanitized input.
|
34
36
|
attr_reader :tainted_columns # This can probably go into assess_state?
|
37
|
+
# Agent state. Used for extracting Agent level settings.
|
38
|
+
#
|
39
|
+
# logger_path[String] Path to the log file.
|
40
|
+
# logger_level[String] Log level for the logger.
|
41
|
+
# cef_logger_path[String] Path to the log file.
|
42
|
+
# cef_logger_level[String] Log level for the logger.
|
43
|
+
attr_reader :agent_state
|
35
44
|
# Current state for Assess.
|
36
45
|
# enabled [Boolean] Indicate if the assess feature set is enabled for this server or not.
|
37
46
|
#
|
@@ -87,26 +96,17 @@ module Contrast
|
|
87
96
|
end
|
88
97
|
|
89
98
|
# @param features_response [Contrast::Agent::Reporting::Response]
|
90
|
-
def update_from_server_features features_response
|
99
|
+
def update_from_server_features features_response
|
91
100
|
return unless (server_features = features_response&.server_features)
|
92
101
|
|
93
|
-
|
94
|
-
log_level = server_features.log_level
|
95
|
-
# Update logger:
|
96
|
-
Contrast::Logger::Log.instance.update(log_file, log_level) if log_file || log_level
|
97
|
-
# Update AgentLib Logger
|
98
|
-
update_agent_lib_log(log_level.to_s)
|
99
|
-
# Update CEFlogger:
|
100
|
-
unless server_features.security_logger.settings_blank?
|
101
|
-
cef_logger.build_logger(server_features.security_logger.log_level, server_features.security_logger.log_file)
|
102
|
-
end
|
102
|
+
update_loggers(server_features)
|
103
103
|
# TODO: RUBY-99999 Update Bot-Blocker from server settings - check enable value.
|
104
104
|
# For now all protection rules are rebuild on Application update. Bot blocker uses the default
|
105
105
|
# enable from the base rule, and update it's mode on app settings update.
|
106
106
|
# Here we receive also bots for that rule.
|
107
107
|
unless settings_empty?(server_features.protect.enabled?)
|
108
108
|
@protect_state.enabled = server_features.protect.enabled?
|
109
|
-
|
109
|
+
update_config_from_settings(%i[protect enable], server_features.protect.enabled?)
|
110
110
|
end
|
111
111
|
update_assess_server_features(server_features.assess)
|
112
112
|
@last_server_update_ms = Contrast::Utils::Timer.now_ms
|
@@ -118,6 +118,54 @@ module Contrast
|
|
118
118
|
logger.warn('The following error occurred from server update: ', e: e)
|
119
119
|
end
|
120
120
|
|
121
|
+
# Update Assess server features
|
122
|
+
#
|
123
|
+
# @param assess [Contrast::Agent::Reporting::Settings::AssessServerFeature]
|
124
|
+
def update_assess_server_features assess
|
125
|
+
return if settings_empty?(assess.enabled?)
|
126
|
+
|
127
|
+
@assess_state.enabled = assess.enabled?
|
128
|
+
update_config_from_settings(%i[assess enable], assess.enabled?)
|
129
|
+
@assess_state.sampling_settings = assess.sampling
|
130
|
+
|
131
|
+
samplings_path = Contrast::Components::Sampling::Interface::CANON_NAME.split('.').map(&:to_sym)
|
132
|
+
Contrast::Components::Sampling::Interface::CONFIG_VALUES.each do |field|
|
133
|
+
lookup_field = field == 'enable' ? :enabled : field.to_sym
|
134
|
+
update_config_from_settings(samplings_path + [field.to_sym], assess.sampling.send(lookup_field))
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Updates logging settings
|
139
|
+
# @param [Contrast::Agent::Reporting::Settings::ServerFeatures]
|
140
|
+
def update_loggers server_features
|
141
|
+
log_file = server_features.log_file
|
142
|
+
log_level = server_features.log_level
|
143
|
+
# Update logger:
|
144
|
+
Contrast::Logger::Log.instance.update(log_file, log_level) if log_file || log_level
|
145
|
+
unless settings_empty?(log_file)
|
146
|
+
update_config_from_settings(%i[agent logger path], log_file)
|
147
|
+
@agent_state.logger_path = log_file
|
148
|
+
end
|
149
|
+
unless settings_empty?(log_level)
|
150
|
+
update_config_from_settings(%i[agent logger level], log_level)
|
151
|
+
@agent_state.logger_level = log_level
|
152
|
+
end
|
153
|
+
# Update AgentLib Logger
|
154
|
+
update_agent_lib_log(log_level.to_s)
|
155
|
+
# Update CEFlogger:
|
156
|
+
return if server_features.security_logger.settings_blank?
|
157
|
+
|
158
|
+
cef_logger.build_logger(server_features.security_logger.log_level, server_features.security_logger.log_file)
|
159
|
+
unless settings_empty?(log_file)
|
160
|
+
update_config_from_settings(%i[agent security_logger path], log_file)
|
161
|
+
@agent_state.cef_logger_level = log_file
|
162
|
+
end
|
163
|
+
return unless settings_empty?(log_level)
|
164
|
+
|
165
|
+
update_config_from_settings(%i[agent security_logger level], log_level)
|
166
|
+
@agent_state.cef_logger_level = log_level
|
167
|
+
end
|
168
|
+
|
121
169
|
# Update AgentLib log level
|
122
170
|
def update_agent_lib_log new_log_level
|
123
171
|
agent_lib_log_level = Contrast::AgentLib::InterfaceBase::LOG_LEVEL[0] if new_log_level.empty?
|
@@ -135,42 +183,46 @@ module Contrast
|
|
135
183
|
Contrast::AGENT_LIB.change_log_options(true, agent_lib_log_level)
|
136
184
|
end
|
137
185
|
|
138
|
-
# Update Assess server features
|
139
|
-
#
|
140
|
-
# @param assess [Contrast::Agent::Reporting::Settings::AssessServerFeature]
|
141
|
-
def update_assess_server_features assess
|
142
|
-
return if settings_empty?(assess.enabled?)
|
143
|
-
|
144
|
-
@assess_state.enabled = assess.enabled?
|
145
|
-
store_in_config(%i[assess enable], assess.enabled?)
|
146
|
-
@assess_state.sampling_settings = assess.sampling
|
147
|
-
|
148
|
-
Contrast::Components::Sampling::Interface::CONFIG_VALUES.each do |field|
|
149
|
-
lookup_field = field == 'enable' ? :enabled : field.to_sym
|
150
|
-
store_in_config(Contrast::Components::Sampling::Interface::CANON_NAME.split('.') + [field.to_sym],
|
151
|
-
assess.sampling.send(lookup_field))
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
186
|
# @param settings_response [Contrast::Agent::Reporting::Response]
|
156
187
|
def update_from_application_settings settings_response
|
157
188
|
return unless (app_settings = settings_response&.application_settings)
|
158
189
|
|
159
|
-
|
160
|
-
|
161
|
-
app_settings.protect.virtual_patches = app_settings.protect.virtual_patches unless
|
162
|
-
settings_empty?(app_settings.protect.virtual_patches)
|
163
|
-
update_sensitive_data_policy(app_settings.sensitive_data_masking)
|
190
|
+
extract_protect_app_settings(app_settings)
|
191
|
+
update_matchers_and_sensitive_data(app_settings)
|
164
192
|
@assess_state.disabled_assess_rules = app_settings.assess.disabled_rules
|
193
|
+
update_config_from_settings(%i[assess rules disabled_rules], app_settings.assess.disabled_rules)
|
165
194
|
new_session_id = app_settings.assess.session_id
|
166
|
-
|
195
|
+
unless settings_empty?(new_session_id)
|
196
|
+
@assess_state.session_id = new_session_id
|
197
|
+
# TODO: RUBY-99999 Update the default values and effective config update from TS.
|
198
|
+
# Using the session_id from the settings response to update the config.
|
199
|
+
# The Effective Config sources values are fetched from the
|
200
|
+
# Contrast::CONFIG.config.loaded_config. Some values are displayed from
|
201
|
+
# their components, however not updated here. Using this may cause some
|
202
|
+
# specs to fails check the update of all values from TS.
|
203
|
+
# Contrast::CONFIG.application.session_id = new_session_id
|
204
|
+
update_config_from_settings(%i[application session_id], new_session_id)
|
205
|
+
end
|
167
206
|
@last_app_update_ms = Contrast::Utils::Timer.now_ms
|
168
207
|
@app_settings_last_httpdate = header_application_last_update
|
169
208
|
end
|
170
209
|
|
210
|
+
# @param app_settings [Contrast::Agent::Reporting::Settings::ApplicationSettings]
|
211
|
+
def update_matchers_and_sensitive_data app_settings
|
212
|
+
update_exclusion_matchers(app_settings.exclusions)
|
213
|
+
app_settings.protect.virtual_patches = app_settings.protect.virtual_patches unless
|
214
|
+
settings_empty?(app_settings.protect.virtual_patches)
|
215
|
+
update_sensitive_data_policy(app_settings.sensitive_data_masking)
|
216
|
+
end
|
217
|
+
|
171
218
|
# Wipe state to zero.
|
172
|
-
|
173
|
-
|
219
|
+
#
|
220
|
+
# @param purge [Boolean] If true, also purge the persistent states.
|
221
|
+
def reset_state purge: false
|
222
|
+
@agent_state = AGENT_STATE_BASE.dup
|
223
|
+
# Keep the protect state, since once set the rules depend ont it.
|
224
|
+
# The state will be update on first settings response from TS.
|
225
|
+
@protect_state = PROTECT_STATE_BASE.dup if purge || @protect_state.nil?
|
174
226
|
update_assess_state
|
175
227
|
@application_state = APPLICATION_STATE_BASE.dup
|
176
228
|
@tainted_columns = {}
|
@@ -193,24 +245,6 @@ module Contrast
|
|
193
245
|
@assess_state
|
194
246
|
end
|
195
247
|
|
196
|
-
def build_protect_rules
|
197
|
-
@protect_state.rules = {}
|
198
|
-
|
199
|
-
# Rules. They add themselves on initialize.
|
200
|
-
Contrast::Agent::Protect::Rule::BotBlocker.new
|
201
|
-
cmdi = Contrast::Agent::Protect::Rule::CmdInjection.new
|
202
|
-
cmdi.sub_rules
|
203
|
-
Contrast::Agent::Protect::Rule::Deserialization.new
|
204
|
-
Contrast::Agent::Protect::Rule::NoSqli.new
|
205
|
-
path = Contrast::Agent::Protect::Rule::PathTraversal.new
|
206
|
-
path.sub_rules
|
207
|
-
sqli = Contrast::Agent::Protect::Rule::Sqli.new
|
208
|
-
sqli.sub_rules
|
209
|
-
Contrast::Agent::Protect::Rule::UnsafeFileUpload.new
|
210
|
-
Contrast::Agent::Protect::Rule::Xss.new
|
211
|
-
Contrast::Agent::Protect::Rule::Xxe.new
|
212
|
-
end
|
213
|
-
|
214
248
|
# @param exclusions [Contrast::Agent::Reporting::Settings::Exclusions]
|
215
249
|
def update_exclusion_matchers exclusions
|
216
250
|
matchers = []
|
@@ -278,21 +312,31 @@ module Contrast
|
|
278
312
|
Contrast::Agent.reporter.client.response_handler.last_application_modified
|
279
313
|
end
|
280
314
|
|
315
|
+
# Extract the rules modes from protection_rules or rules_settings fields.
|
316
|
+
#
|
317
|
+
# @param app_settings [Contrast::Agent::Reporting::Settings::ApplicationSettings]
|
318
|
+
def extract_protect_app_settings app_settings
|
319
|
+
modes_by_id = app_settings.protect.protection_rules_to_settings_hash
|
320
|
+
modes_by_id = app_settings.protect.rules_settings_to_settings_hash if settings_empty?(modes_by_id)
|
321
|
+
# Preserve previous state if no new settings are extracted:
|
322
|
+
return if settings_empty?(modes_by_id)
|
323
|
+
|
324
|
+
@application_state.modes_by_id = modes_by_id
|
325
|
+
end
|
326
|
+
|
281
327
|
# Update the stored config values to ensure that we know about the correct values,
|
282
328
|
# and that the sources are correct for entries updated from the UI.
|
283
329
|
#
|
284
|
-
#
|
330
|
+
# NOTE: For each passed component path here, there should br implemented a check for the value from
|
331
|
+
# Config and Settings. For example if enable from CONFIG is nil, check the SETTINGS.
|
332
|
+
# This will keep the value not empty and be reflected in effective config reporting.
|
333
|
+
#
|
334
|
+
# @param path [String] the canonical name for the config entry (such as api.proxy.enable)
|
285
335
|
# @param value [String, Integer, Array, nil] the value for the configuration setting
|
286
|
-
def
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
level = level[segment]
|
291
|
-
end
|
292
|
-
return unless level.cs__is_a?(Hash)
|
293
|
-
|
294
|
-
level[parts[-1]] = value
|
295
|
-
Contrast::CONFIG.sources.set(parts.join('.'), Contrast::Components::Config::Sources::CONTRAST_UI)
|
336
|
+
def update_config_from_settings path, value
|
337
|
+
Contrast::Config::Diagnostics::Tools.update_config(path,
|
338
|
+
value,
|
339
|
+
Contrast::Components::Config::Sources::CONTRAST_UI)
|
296
340
|
end
|
297
341
|
end
|
298
342
|
end
|
@@ -40,7 +40,7 @@ module Contrast
|
|
40
40
|
#
|
41
41
|
# @param effective_config [Contrast::Config::Diagnostics::EffectiveConfig]
|
42
42
|
def to_effective_config effective_config
|
43
|
-
add_effective_config_values(effective_config, CONFIG_VALUES, CANON_NAME
|
43
|
+
add_effective_config_values(effective_config, CONFIG_VALUES, CANON_NAME)
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
@@ -52,9 +52,9 @@ module Contrast
|
|
52
52
|
effective_config: effective_config.to_controlled_hash,
|
53
53
|
user_configuration_file: user_configuration_file.to_controlled_hash,
|
54
54
|
environment_variable: Contrast::Config::Diagnostics::EnvironmentVariables.environment_settings(ENV).
|
55
|
-
map(&:
|
56
|
-
command_line: Contrast::Config::Diagnostics::CommandLine.command_line_settings.map(&:
|
57
|
-
contrast_ui: Contrast::Config::Diagnostics::ContrastUI.contrast_ui_settings.map(&:
|
55
|
+
map(&:to_source_hash),
|
56
|
+
command_line: Contrast::Config::Diagnostics::CommandLine.command_line_settings.map(&:to_source_hash),
|
57
|
+
contrast_ui: Contrast::Config::Diagnostics::ContrastUI.contrast_ui_settings.map(&:to_source_hash)
|
58
58
|
}.compact
|
59
59
|
end
|
60
60
|
end
|
@@ -14,36 +14,46 @@ module Contrast
|
|
14
14
|
NON_COMMON_ENV = %w[
|
15
15
|
CONTRAST_CONFIG_PATH CONTRAST_AGENT_TELEMETRY_OPTOUT CONTRAST_AGENT_TELEMETRY_TEST
|
16
16
|
].cs__freeze
|
17
|
+
MASKED_ENV = %w[CONTRAST__API__API_KEY CONTRAST__API__SERVICE_KEY].cs__freeze
|
17
18
|
|
18
19
|
# This method will fill the canonical name for each env var and will check for any uncommon ones.
|
19
20
|
#
|
20
21
|
# @param env [Hash]
|
21
22
|
# @return [Array] array of all the values needed to be written.
|
22
|
-
def environment_settings env
|
23
|
+
def environment_settings env # rubocop:disable Metrics/MethodLength
|
23
24
|
env_hash = env.select do |e|
|
24
25
|
e.to_s.start_with?(Contrast::Configuration::CONTRAST_ENV_MARKER) || NON_COMMON_ENV.include?(e.to_s)
|
25
26
|
end
|
26
27
|
environment_settings = []
|
27
|
-
env_hash.each do |key, value|
|
28
|
+
env_hash.each do |key, value| # rubocop:disable Metrics/BlockLength
|
28
29
|
efc_value = Contrast::Config::Diagnostics::EffectiveConfigValue.new.tap do |effective_value|
|
29
30
|
next unless value
|
30
31
|
|
31
32
|
effective_value.canonical_name = if NON_COMMON_ENV.include?(key)
|
32
33
|
key.gsub(Contrast::Utils::ObjectShare::UNDERSCORE,
|
33
|
-
Contrast::Utils::ObjectShare::PERIOD).downcase
|
34
|
+
Contrast::Utils::ObjectShare::PERIOD).downcase.
|
35
|
+
gsub(Contrast::Utils::ObjectShare::CONTRAST_DOT,
|
36
|
+
Contrast::Utils::ObjectShare::EMPTY_STRING)
|
34
37
|
else
|
35
38
|
key.gsub(Contrast::Utils::ObjectShare::DOUBLE_UNDERSCORE,
|
36
|
-
Contrast::Utils::ObjectShare::PERIOD).downcase
|
39
|
+
Contrast::Utils::ObjectShare::PERIOD).downcase.
|
40
|
+
gsub(Contrast::Utils::ObjectShare::CONTRAST_DOT,
|
41
|
+
Contrast::Utils::ObjectShare::EMPTY_STRING)
|
37
42
|
end
|
38
|
-
if
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
effective_value.value = Contrast::Config::Diagnostics::Tools.value_to_s(value)
|
43
|
+
effective_value.value = if MASKED_ENV.include?(key)
|
44
|
+
Contrast::Configuration::EFFECTIVE_REDACTED
|
45
|
+
else
|
46
|
+
Contrast::Config::Diagnostics::Tools.value_to_s(value)
|
47
|
+
end
|
44
48
|
effective_value.key = key
|
45
49
|
end
|
46
|
-
|
50
|
+
next unless efc_value
|
51
|
+
|
52
|
+
environment_settings << efc_value
|
53
|
+
Contrast::Config::Diagnostics::Tools.
|
54
|
+
update_config(efc_value.key,
|
55
|
+
efc_value.value,
|
56
|
+
Contrast::Components::Config::Sources::ENVIRONMENT_VARIABLE)
|
47
57
|
end
|
48
58
|
environment_settings
|
49
59
|
end
|
@@ -102,7 +102,7 @@ module Contrast
|
|
102
102
|
extract_settings
|
103
103
|
File.open(File.join(dir_name, FILE_NAME), WRITE) do |file|
|
104
104
|
file.truncate(0)
|
105
|
-
file.write(JSON.pretty_generate(to_controlled_hash, { space: Contrast::Utils::ObjectShare::
|
105
|
+
file.write(JSON.pretty_generate(to_controlled_hash, { space: Contrast::Utils::ObjectShare::SPACE }))
|
106
106
|
status = true if file
|
107
107
|
file.close
|
108
108
|
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Contrast
|
5
|
+
module Config
|
6
|
+
module Diagnostics
|
7
|
+
# Tools to help with the config diagnostics, called directly from the module.
|
8
|
+
module SingletonTools
|
9
|
+
API_CREDENTIALS = %w[api_key service_key].cs__freeze
|
10
|
+
CONTRAST_MARK = 'CONTRAST_'
|
11
|
+
|
12
|
+
# Creates new config instances for each read config entry from the flat generated configs.
|
13
|
+
#
|
14
|
+
# @param flats [Array] of flatten configs produced by #flatten_settings
|
15
|
+
# @param source [Boolean] flag to set the desired value class, it may be a effective or source value.
|
16
|
+
# @param cli [Boolean] flag to check if the value comes from cli.
|
17
|
+
# @return [Array<Contrast::Config::Diagnostics::SourceConfigValue>]
|
18
|
+
def to_config_values flats, source: false, cli: false
|
19
|
+
config_value_klass = if source
|
20
|
+
Contrast::Config::Diagnostics::SourceConfigValue
|
21
|
+
else
|
22
|
+
Contrast::Config::Diagnostics::EffectiveConfigValue
|
23
|
+
end
|
24
|
+
settings = []
|
25
|
+
flats.each do |entry|
|
26
|
+
entry.each do |key, value|
|
27
|
+
efc_value = config_value_klass.new.tap do |config_value|
|
28
|
+
config_value.canonical_name = key
|
29
|
+
if cli && key.to_s.include?(CONTRAST_MARK)
|
30
|
+
config_value.canonical_name = key.gsub(Contrast::Utils::ObjectShare::DOUBLE_UNDERSCORE,
|
31
|
+
Contrast::Utils::ObjectShare::PERIOD).downcase
|
32
|
+
end
|
33
|
+
config_value.key = key
|
34
|
+
config_value.value = if API_CREDENTIALS.include?(key.to_s)
|
35
|
+
Contrast::Configuration::EFFECTIVE_REDACTED
|
36
|
+
else
|
37
|
+
value_to_s(value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
next unless efc_value
|
41
|
+
|
42
|
+
settings << efc_value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
settings
|
46
|
+
end
|
47
|
+
|
48
|
+
# Flattens out the read settings from file, env or contrast ui.
|
49
|
+
# example: {"agent.polling.server_settings_ms"=>"50000"}
|
50
|
+
#
|
51
|
+
# If cli is set we avoid adding the path and additional '.' to the key.
|
52
|
+
#
|
53
|
+
# @param data [Hash, nil]
|
54
|
+
# @param path [String] where to look for settings.
|
55
|
+
# @param config [Hash] symbolized config to fetch keys from.
|
56
|
+
# @param cli [Boolean] does the config come from cli.
|
57
|
+
def flatten_settings data, path = [], config: Contrast::CONFIG.config.loaded_config, cli: false
|
58
|
+
return [] unless data
|
59
|
+
|
60
|
+
data.each_with_object([]) do |(k, v), entries|
|
61
|
+
if v.cs__is_a?(Hash)
|
62
|
+
entries.concat(flatten_settings(v, path.dup.append(k.to_sym)))
|
63
|
+
else
|
64
|
+
if API_CREDENTIALS.include?(k.to_s)
|
65
|
+
entries << { k.to_s => Contrast::Configuration::EFFECTIVE_REDACTED } if cli
|
66
|
+
entries << { "#{ path.join('.') }.#{ k }" => Contrast::Configuration::EFFECTIVE_REDACTED } unless cli
|
67
|
+
next
|
68
|
+
end
|
69
|
+
entries << { k.to_s => value_to_s(config.dig(*path, k)) } if cli
|
70
|
+
entries << { "#{ path.join('.') }.#{ k }" => value_to_s(config.dig(*path, k)) } unless cli
|
71
|
+
end
|
72
|
+
end.flatten # rubocop:disable Style/MethodCalledOnDoEndBlock
|
73
|
+
end
|
74
|
+
|
75
|
+
# Update the stored config values to ensure that we know about the correct values,
|
76
|
+
# and that the sources are correct for entries updated from the UI.
|
77
|
+
#
|
78
|
+
# @param parts [Array<Symbols>, String] the path to the setting in config
|
79
|
+
# Accepts Array: [:agent :enable] or String: 'agent.enable'
|
80
|
+
# @param value [String, Integer, Array, nil] the value for the configuration setting
|
81
|
+
# @param source_type [String] the source of the configuration setting
|
82
|
+
def update_config parts, value, source_type
|
83
|
+
parts_array, string = handle_parts_array(parts)
|
84
|
+
path = string ? parts : parts_array.join('.')
|
85
|
+
return unless parts_array
|
86
|
+
|
87
|
+
# Check to see whether the source has been overridden by local settings,
|
88
|
+
# Before updating from Contrast UI.
|
89
|
+
if source_type == Contrast::Components::Config::Sources::CONTRAST_UI &&
|
90
|
+
Contrast::CONFIG.sources.source_overridden?(path)
|
91
|
+
|
92
|
+
return
|
93
|
+
end
|
94
|
+
|
95
|
+
level = Contrast::CONFIG.config.loaded_config
|
96
|
+
parts_array[0...-1]&.each do |segment|
|
97
|
+
level[segment] ||= {}
|
98
|
+
level = level[segment]
|
99
|
+
end
|
100
|
+
return unless level.cs__is_a?(Hash)
|
101
|
+
|
102
|
+
level[parts_array[-1]] = value
|
103
|
+
Contrast::CONFIG.sources.set(path, source_type)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Recursively converts each value to string.
|
107
|
+
#
|
108
|
+
# @param value [Hash, nil]
|
109
|
+
def value_to_s value
|
110
|
+
case value
|
111
|
+
when String
|
112
|
+
if Contrast::Utils::DuckUtils.empty_duck?(value)
|
113
|
+
Contrast::Config::Diagnostics::SourceConfigValue::NULL
|
114
|
+
else
|
115
|
+
value
|
116
|
+
end
|
117
|
+
when Array
|
118
|
+
handle_array_to_s(value)
|
119
|
+
when Hash
|
120
|
+
handle_hash_to_s(value)
|
121
|
+
when TrueClass, FalseClass, Symbol, Integer
|
122
|
+
value.to_s
|
123
|
+
else
|
124
|
+
Contrast::Config::Diagnostics::SourceConfigValue::NULL
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
# Checks the type of path and converts it to array.
|
131
|
+
# If the path is string it splits it by '.' and converts each element to symbol.
|
132
|
+
#
|
133
|
+
# @param parts [Array<Symbols>, String] the path to the setting in config
|
134
|
+
# @return [Array<Symbols>, String]
|
135
|
+
def handle_parts_array parts
|
136
|
+
string = false
|
137
|
+
arr = if parts.cs__is_a?(String)
|
138
|
+
string = true
|
139
|
+
parts.split('.')&.map&.each(&:to_sym)
|
140
|
+
else
|
141
|
+
parts
|
142
|
+
end
|
143
|
+
[arr, string]
|
144
|
+
end
|
145
|
+
|
146
|
+
# @param hash [Hash]
|
147
|
+
# @return [Hash]
|
148
|
+
def handle_hash_to_s hash
|
149
|
+
hash&.each_with_object({}) do |(k, v), m| # rubocop:disable Style/HashTransformValues
|
150
|
+
m[k] = if v.cs__is_a?(Hash)
|
151
|
+
value_to_s(v)
|
152
|
+
elsif v.cs__is_a?(Array)
|
153
|
+
v.map(&:to_s)
|
154
|
+
else
|
155
|
+
v.to_s
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# @param array [Array]
|
161
|
+
# @return [String]
|
162
|
+
def handle_array_to_s array
|
163
|
+
return Contrast::Config::Diagnostics::SourceConfigValue::NULL if Contrast::Utils::DuckUtils.empty_duck?(array)
|
164
|
+
|
165
|
+
array.join(',')
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -1,11 +1,16 @@
|
|
1
1
|
# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require 'contrast/utils/object_share'
|
5
|
+
require 'contrast/utils/duck_utils'
|
6
|
+
|
4
7
|
module Contrast
|
5
8
|
module Config
|
6
9
|
module Diagnostics
|
7
10
|
# All config values from all sources, stored in a easy to write representation.
|
8
11
|
class SourceConfigValue
|
12
|
+
NULL = 'null'
|
13
|
+
|
9
14
|
# @return [String] Name of the config starting form root of yaml config.
|
10
15
|
attr_accessor :canonical_name
|
11
16
|
# @return [String] Name of the config.
|
@@ -20,23 +25,23 @@ module Contrast
|
|
20
25
|
def to_controlled_hash
|
21
26
|
{
|
22
27
|
canonical_name: canonical_name,
|
23
|
-
name: key,
|
24
|
-
value:
|
25
|
-
source: source,
|
26
|
-
filename: filename
|
27
|
-
}
|
28
|
+
name: key || Contrast::Utils::ObjectShare::EMPTY_STRING,
|
29
|
+
value: Contrast::Config::Diagnostics::Tools.value_to_s(value),
|
30
|
+
source: source || Contrast::Utils::ObjectShare::EMPTY_STRING,
|
31
|
+
filename: filename || Contrast::Utils::ObjectShare::EMPTY_STRING
|
32
|
+
}
|
28
33
|
end
|
29
34
|
|
30
35
|
def to_source_hash
|
31
36
|
{
|
32
37
|
canonical_name: canonical_name,
|
33
|
-
name: key,
|
34
|
-
value:
|
35
|
-
}
|
38
|
+
name: key || Contrast::Utils::ObjectShare::EMPTY_STRING,
|
39
|
+
value: Contrast::Config::Diagnostics::Tools.value_to_s(value)
|
40
|
+
}
|
36
41
|
end
|
37
42
|
|
38
43
|
# Assigns file name of the config iv viable, Currently supported formats for config file are *.yaml
|
39
|
-
# and *.yml
|
44
|
+
# and *.yml. For the config loaded from file the filename is kept as source.
|
40
45
|
#
|
41
46
|
# @param source [String] name of the source file yaml | yml
|
42
47
|
# @return [Array<String>, nil]
|