contrast-agent 7.3.2 → 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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/contrast/agent/protect/rule/base.rb +5 -1
  3. data/lib/contrast/agent/protect/rule/cmdi/cmd_injection.rb +17 -5
  4. data/lib/contrast/agent/protect/rule/input_classification/base.rb +7 -2
  5. data/lib/contrast/agent/protect/rule/input_classification/encoding.rb +1 -1
  6. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal.rb +8 -1
  7. data/lib/contrast/agent/protect/rule/sqli/sqli.rb +8 -1
  8. data/lib/contrast/agent/protect/state.rb +110 -0
  9. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +2 -0
  10. data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +2 -0
  11. data/lib/contrast/agent/reporting/reporting_events/finding.rb +1 -0
  12. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +4 -0
  13. data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +2 -0
  14. data/lib/contrast/agent/reporting/reporting_events/observed_library_usage.rb +2 -0
  15. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +9 -5
  16. data/lib/contrast/agent/reporting/reporting_events/reportable_hash.rb +30 -6
  17. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +1 -1
  18. data/lib/contrast/agent/reporting/reporting_utilities/resend.rb +1 -1
  19. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +1 -5
  20. data/lib/contrast/agent/reporting/settings/protect.rb +3 -3
  21. data/lib/contrast/agent/reporting/settings/sampling.rb +5 -4
  22. data/lib/contrast/agent/version.rb +1 -1
  23. data/lib/contrast/components/agent.rb +3 -5
  24. data/lib/contrast/components/api.rb +3 -3
  25. data/lib/contrast/components/assess_rules.rb +1 -2
  26. data/lib/contrast/components/base.rb +1 -2
  27. data/lib/contrast/components/config/sources.rb +23 -0
  28. data/lib/contrast/components/logger.rb +19 -0
  29. data/lib/contrast/components/protect.rb +55 -14
  30. data/lib/contrast/components/sampling.rb +5 -12
  31. data/lib/contrast/components/security_logger.rb +17 -0
  32. data/lib/contrast/components/settings.rb +110 -76
  33. data/lib/contrast/config/certification_configuration.rb +1 -1
  34. data/lib/contrast/config/configuration_files.rb +0 -2
  35. data/lib/contrast/config/diagnostics/config.rb +3 -3
  36. data/lib/contrast/config/diagnostics/effective_config.rb +1 -1
  37. data/lib/contrast/config/diagnostics/environment_variables.rb +21 -11
  38. data/lib/contrast/config/diagnostics/monitor.rb +1 -1
  39. data/lib/contrast/config/diagnostics/singleton_tools.rb +170 -0
  40. data/lib/contrast/config/diagnostics/source_config_value.rb +14 -9
  41. data/lib/contrast/config/diagnostics/tools.rb +23 -84
  42. data/lib/contrast/config/request_audit_configuration.rb +1 -1
  43. data/lib/contrast/config/server_configuration.rb +3 -15
  44. data/lib/contrast/configuration.rb +5 -2
  45. data/lib/contrast/framework/manager.rb +4 -3
  46. data/lib/contrast/framework/manager_extend.rb +3 -1
  47. data/lib/contrast/framework/rack/support.rb +11 -2
  48. data/lib/contrast/utils/log_utils.rb +1 -1
  49. data/lib/contrast/utils/request_utils.rb +1 -1
  50. data/lib/contrast/utils/timer.rb +1 -1
  51. metadata +4 -2
@@ -4,6 +4,8 @@
4
4
  require 'contrast/components/base'
5
5
  require 'contrast/config/exception_configuration'
6
6
  require 'contrast/config/protect_rule_configuration'
7
+ require 'contrast/utils/object_share'
8
+ require 'contrast/agent/protect/state'
7
9
 
8
10
  module Contrast
9
11
  module Components
@@ -68,7 +70,7 @@ module Contrast
68
70
  return false if forcibly_disabled?
69
71
  return true if forcibly_enabled?
70
72
 
71
- ::Contrast::SETTINGS.protect_state.enabled == true
73
+ state.enabled?
72
74
  end
73
75
 
74
76
  # Check to determine if the base64 decoding is required for user inputs.
@@ -85,12 +87,19 @@ module Contrast
85
87
  ::Contrast::CONFIG.protect.rules
86
88
  end
87
89
 
90
+ # Current Active Protect rules and the state/mode they are in.
91
+ #
92
+ # @return [Contrast::Agent::Protect::State]
93
+ def state
94
+ @_state ||= Contrast::Agent::Protect::State.new
95
+ end
96
+
88
97
  # Returns Protect array of all initialized
89
98
  # protect rules.
90
99
  #
91
100
  # @return defend_rules[Hash<Contrast::SETTINGS.protect_state.rules>]
92
101
  def defend_rules
93
- ::Contrast::SETTINGS.protect_state.rules
102
+ state.rules
94
103
  end
95
104
 
96
105
  # The Contrast::CONFIG.protect.rules is object so we need to check it's
@@ -102,16 +111,27 @@ module Contrast
102
111
  # @return mode [Symbol]
103
112
  def rule_mode rule_id
104
113
  str = rule_id.tr('-', '_')
105
- ::Contrast::CONFIG.protect.rules[str]&.applicable_mode ||
106
- ::Contrast::SETTINGS.application_state.modes_by_id[rule_id] ||
107
- :NO_ACTION
114
+ config_mode = Contrast::CONFIG.protect.rules[str]&.applicable_mode
115
+ settings_mode = ::Contrast::SETTINGS.application_state.modes_by_id[rule_id]
116
+
117
+ if config_mode
118
+ update_config_for_rule(rule_id, config_mode)
119
+ return config_mode
120
+ end
121
+
122
+ if settings_mode
123
+ update_config_for_rule(rule_id, settings_mode, ui_source: true)
124
+ return settings_mode
125
+ end
126
+ :NO_ACTION
108
127
  end
109
128
 
110
129
  # Name of the protect rule
111
130
  #
112
- # @return [String]
131
+ # @param name [String]
132
+ # @return [Contrast::Agent::Protect::Rule::Base]
113
133
  def rule name
114
- ::Contrast::SETTINGS.protect_state.rules[name]
134
+ state.rules[name]
115
135
  end
116
136
 
117
137
  def report_any_command_execution?
@@ -124,7 +144,8 @@ module Contrast
124
144
 
125
145
  def report_custom_code_sysfile_access?
126
146
  if @_report_custom_code_sysfile_access.nil?
127
- name_changed = Contrast::Agent::Protect::Rule::PathTraversal::NAME.tr('-', '_')
147
+ name_changed = Contrast::Agent::Protect::Rule::PathTraversal::NAME.
148
+ tr(Contrast::Utils::ObjectShare::DASH, Contrast::Utils::ObjectShare::UNDERSCORE)
128
149
  ctrl = rule_config[name_changed]
129
150
  @_report_custom_code_sysfile_access = ctrl && true?(ctrl.detect_custom_code_accessing_system_files)
130
151
  end
@@ -150,16 +171,15 @@ module Contrast
150
171
 
151
172
  # @param effective_config [Contrast::Config::Diagnostics::EffectiveConfig]
152
173
  def protect_rules_to_effective_config effective_config
153
- return unless defend_rules
154
-
155
- defend_rules.each do |key, value|
174
+ state.rules.each do |key, value|
156
175
  next unless key && value
157
176
 
158
177
  config_prefix = "#{ CANON_NAME }.#{ RULES }.#{ key }"
159
- name_prefix = "#{ CONTRAST }.#{ CANON_NAME }.#{ RULES }.#{ key }"
160
- add_single_effective_value(effective_config, ENABLE, value.enabled?, config_prefix, name_prefix)
178
+ add_single_effective_value(effective_config, ENABLE, value.enabled?, config_prefix)
161
179
  # Get the mode by checking Current Configs or Settings received:
162
- add_single_effective_value(effective_config, MODE, rule_mode(key), config_prefix, name_prefix)
180
+ mode = rule_mode(key)
181
+ mode = :OFF if mode == :NO_ACTION
182
+ add_single_effective_value(effective_config, MODE, mode, config_prefix)
163
183
  end
164
184
  end
165
185
 
@@ -181,6 +201,27 @@ module Contrast
181
201
  def report_custom_code_sysfile_access
182
202
  report_custom_code_sysfile_access?
183
203
  end
204
+
205
+ # @param rule_id [String] the canonical name for a config entry (such as api.proxy.enable).
206
+ # @param mode [Symbol] to set the value of the config entry.
207
+ # @param ui_source [Boolean] whether to se the source as contrast ui or not.
208
+ def update_config_for_rule rule_id, mode, ui_source: false
209
+ str = rule_id.tr('-', '_').to_sym
210
+ config = Contrast::CONFIG.config.loaded_config
211
+ return unless config
212
+
213
+ config[:protect] ||= {}
214
+ config[:protect][:rules] ||= {}
215
+ config[:protect][:rules][str] ||= {}
216
+ config[:protect][:rules][str][:mode] = mode
217
+ config[:protect][:rules][str][:enable] = (mode != :NO_ACTION)
218
+ return unless ui_source
219
+
220
+ Contrast::CONFIG.sources.set("#{ CANON_NAME }.#{ RULES }.#{ str }.mode",
221
+ Contrast::Components::Config::Sources::CONTRAST_UI)
222
+ Contrast::CONFIG.sources.set("#{ CANON_NAME }.#{ RULES }.#{ str }.enable",
223
+ Contrast::Components::Config::Sources::CONTRAST_UI)
224
+ end
184
225
  end
185
226
  end
186
227
  end
@@ -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, NAME_PREFIX)
149
- add_single_effective_value(effective_config, 'baseline', sampling_control[:baseline], canon_name, NAME_PREFIX)
150
- add_single_effective_value(effective_config, 'window_ms', sampling_control[:window], canon_name, NAME_PREFIX)
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
- APPLICATION_STATE_BASE = Struct.new(:modes_by_id).new(Hash.new(:NO_ACTION))
18
- PROTECT_STATE_BASE = Struct.new(:enabled, :rules).new(false, {})
19
- ASSESS_STATE_BASE = Struct.new(:enabled, :sampling_settings, :disabled_assess_rules, :session_id).new(false, nil,
20
- [], nil) do
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 # rubocop:disable Metrics/AbcSize
99
+ def update_from_server_features features_response
91
100
  return unless (server_features = features_response&.server_features)
92
101
 
93
- log_file = server_features.log_file
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
- store_in_config(%i[protect enable], server_features.protect.enabled?)
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
190
  extract_protect_app_settings(app_settings)
160
- update_exclusion_matchers(app_settings.exclusions)
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)
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
- @assess_state.session_id = new_session_id if new_session_id && !new_session_id.blank?
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
- def reset_state
173
- @protect_state = PROTECT_STATE_BASE.dup
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,23 +312,6 @@ module Contrast
278
312
  Contrast::Agent.reporter.client.response_handler.last_application_modified
279
313
  end
280
314
 
281
- # Update the stored config values to ensure that we know about the correct values,
282
- # and that the sources are correct for entries updated from the UI.
283
- #
284
- # @param parts [Array] the path to the setting in config
285
- # @param value [String, Integer, Array, nil] the value for the configuration setting
286
- def store_in_config parts, value
287
- level = Contrast::CONFIG.config.loaded_config
288
- parts[0...-1].each do |segment|
289
- level[segment] ||= {}
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)
296
- end
297
-
298
315
  # Extract the rules modes from protection_rules or rules_settings fields.
299
316
  #
300
317
  # @param app_settings [Contrast::Agent::Reporting::Settings::ApplicationSettings]
@@ -302,7 +319,24 @@ module Contrast
302
319
  modes_by_id = app_settings.protect.protection_rules_to_settings_hash
303
320
  modes_by_id = app_settings.protect.rules_settings_to_settings_hash if settings_empty?(modes_by_id)
304
321
  # Preserve previous state if no new settings are extracted:
305
- @application_state.modes_by_id = modes_by_id unless settings_empty?(modes_by_id)
322
+ return if settings_empty?(modes_by_id)
323
+
324
+ @application_state.modes_by_id = modes_by_id
325
+ end
326
+
327
+ # Update the stored config values to ensure that we know about the correct values,
328
+ # and that the sources are correct for entries updated from the UI.
329
+ #
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)
335
+ # @param value [String, Integer, Array, nil] the value for the configuration setting
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)
306
340
  end
307
341
  end
308
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, CONTRAST)
43
+ add_effective_config_values(effective_config, CONFIG_VALUES, CANON_NAME)
44
44
  end
45
45
  end
46
46
  end
@@ -29,8 +29,6 @@ module Contrast
29
29
 
30
30
  # This class will hold all the info about the read file.
31
31
  class LocalSourceValue
32
- YML_EXT = '.yml'
33
- YAML_EXT = '.yaml'
34
32
  # @return [String]
35
33
  attr_reader :path
36
34
  # @return [Hash]
@@ -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(&:to_controlled_hash),
56
- command_line: Contrast::Config::Diagnostics::CommandLine.command_line_settings.map(&:to_controlled_hash),
57
- contrast_ui: Contrast::Config::Diagnostics::ContrastUI.contrast_ui_settings.map(&:to_controlled_hash)
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
@@ -20,7 +20,7 @@ module Contrast
20
20
  end
21
21
 
22
22
  def to_controlled_hash
23
- { values: @values&.map(&:to_controlled_hash) }
23
+ @values&.map(&:to_controlled_hash)
24
24
  end
25
25
  end
26
26
  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 effective_value.canonical_name
39
- effective_value.key =
40
- effective_value.canonical_name.gsub(Contrast::Utils::ObjectShare::CONTRAST_DOT,
41
- Contrast::Utils::ObjectShare::EMPTY_STRING)
42
- end
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
- environment_settings << efc_value if efc_value
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::EMPTY_STRING }))
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