Class: Contrast::Components::Settings::Interface

Inherits:
Object
  • Object
show all
Extended by:
Config
Includes:
Logger::InstanceMethods
Defined in:
lib/contrast/components/settings.rb

Overview

This is a class.

Constant Summary

Constants included from Config

Config::CONTRAST_LOG, Config::CONTRAST_NAME, Config::DATE_TIME

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Logger::InstanceMethods

#cef_logger, #logger

Constructor Details

#initializeInterface

Returns a new instance of Interface.



94
95
96
# File 'lib/contrast/components/settings.rb', line 94

def initialize
  reset_state
end

Instance Attribute Details

#agent_stateObject (readonly)

Agent state. Used for extracting Agent level settings.

logger_path Path to the log file. logger_level Log level for the logger. cef_logger_path Path to the log file. cef_logger_level Log level for the logger.



43
44
45
# File 'lib/contrast/components/settings.rb', line 43

def agent_state
  @agent_state
end

#app_settings_last_httpdateString (readonly)

This value should be sent be TS in the Last-Modified header to sync and save resources if the two dates are the same. format: , :: GMT

Returns:

  • The last update but in string format used to build request header.



92
93
94
# File 'lib/contrast/components/settings.rb', line 92

def app_settings_last_httpdate
  @app_settings_last_httpdate
end

#application_stateObject (readonly)

Current Application State.

modes_by_id [Hash<Rule_id => Mode] Returns Hash with rules and their current mode.



68
69
70
# File 'lib/contrast/components/settings.rb', line 68

def application_state
  @application_state
end

#assess_stateObject (readonly)

Current state for Assess. enabled [Boolean] Indicate if the assess feature set is enabled for this server or not.

sampling [Hash] Hash of AssessSampling Used to control the sampling feature in the agent: { baseline [Integer] The number of baseline requests to take before switching to sampling for the window. enabled [Boolean] If the sampling feature should be used or not. frequency [Integer] The number of requests to skip before observing during the sampling window after the baseline. responseFrequency [Integer] window [Integer] }

disabled_assess_rules [array<AssessRuleID(String)>] Assess rules to disable for this application.



58
59
60
# File 'lib/contrast/components/settings.rb', line 58

def assess_state
  @assess_state
end

#excluderContrast::Agent::Excluder (readonly)

Returns a wrapper around the exclusion rules for the application.

Returns:

  • a wrapper around the exclusion rules for the application



82
83
84
# File 'lib/contrast/components/settings.rb', line 82

def excluder
  @excluder
end

#last_app_update_msInteger (readonly)

Returns the time, in ms, that application settings last changed.

Returns:

  • the time, in ms, that application settings last changed



78
79
80
# File 'lib/contrast/components/settings.rb', line 78

def last_app_update_ms
  @last_app_update_ms
end

#last_server_update_msInteger (readonly)

Returns the time, in ms, that server settings last changed.

Returns:

  • the time, in ms, that server settings last changed



80
81
82
# File 'lib/contrast/components/settings.rb', line 80

def last_server_update_ms
  @last_server_update_ms
end

#protect_stateObject (readonly)

Current State for Protect. enabled [Boolean] Indicate if the protect feature set is enabled for this server or not.

Protection rules are returned as: rules [Hash<RULE_ID => MODE>, nil] Hash with rule_id as key and mode as value



64
65
66
# File 'lib/contrast/components/settings.rb', line 64

def protect_state
  @protect_state
end

#sensitive_data_maskingContrast::Agent::Reporting::Settings::SensitiveDataMasking (readonly)

This the structure that will hold the masking rules send from TS.

Returns:



76
77
78
# File 'lib/contrast/components/settings.rb', line 76

def sensitive_data_masking
  @sensitive_data_masking
end

#server_settings_last_httpdateString (readonly)

This value should be sent be TS in the Last-Modified header to sync and save resources if the two dates are the same. format: , :: GMT

Returns:

  • The last update but in string format used to build request header.



87
88
89
# File 'lib/contrast/components/settings.rb', line 87

def server_settings_last_httpdate
  @server_settings_last_httpdate
end

#tainted_columnsObject (readonly)

tainted_columns are database columns that receive unsanitized input.



36
37
38
# File 'lib/contrast/components/settings.rb', line 36

def tainted_columns
  @tainted_columns
end

Instance Method Details

#reset_state(purge: false) ⇒ Object

Wipe state to zero.

Parameters:

  • (defaults to: false)

    If true, also purge the persistent states.



221
222
223
224
225
226
227
228
229
230
231
# File 'lib/contrast/components/settings.rb', line 221

def reset_state purge: false
  @agent_state = AGENT_STATE_BASE.dup
  # Keep the protect state, since once set the rules depend ont it.
  # The state will be update on first settings response from TS.
  @protect_state = PROTECT_STATE_BASE.dup if purge || @protect_state.nil?
  update_assess_state
  @application_state = APPLICATION_STATE_BASE.dup
  @tainted_columns = {}
  @sensitive_data_masking = SENSITIVE_DATA_MASKING_BASE.dup
  @excluder = Contrast::Agent::Excluder.new
end

#update_agent_lib_log(new_log_level) ⇒ Object

Update AgentLib log level



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/contrast/components/settings.rb', line 170

def update_agent_lib_log new_log_level
  agent_lib_log_level = Contrast::AgentLib::InterfaceBase::LOG_LEVEL[0] if new_log_level.empty?
  agent_lib_log_level ||= Contrast::AgentLib::InterfaceBase::LOG_LEVEL.key(new_log_level.upcase)

  # detect if the provided level is invalid and log if it is
  # by default if we pass invalid log level - it will leave the last active
  unless Contrast::AgentLib::InterfaceBase::LOG_LEVEL.value?(new_log_level.upcase)
    cur_active = Contrast::AGENT_LIB.log_level
    logger.debug('The provided level was invalid, so the logger stays to the last active: ',
                 active: cur_active,
                 provided_level: new_log_level)
  end

  Contrast::AGENT_LIB.change_log_options(true, agent_lib_log_level)
end

#update_assess_server_features(assess) ⇒ Object

Update Assess server features

Parameters:



124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/contrast/components/settings.rb', line 124

def update_assess_server_features assess
  return if settings_empty?(assess.enabled?)

  @assess_state.enabled = assess.enabled?
  update_config_from_settings(%i[assess enable], assess.enabled?)
  @assess_state.sampling_settings = assess.sampling

  samplings_path = Contrast::Components::Sampling::Interface::CANON_NAME.split('.').map(&:to_sym)
  Contrast::Components::Sampling::Interface::CONFIG_VALUES.each do |field|
    lookup_field = field == 'enable' ? :enabled : field.to_sym
    update_config_from_settings(samplings_path + [field.to_sym], assess.sampling.send(lookup_field))
  end
end

#update_assess_stateStruct

We save the session_id, reset and set it again if available. This done so that reporting between updates won't trigger argument error for missing session_id given one is already set and used with the first application create response received from TS.

Returns:



239
240
241
242
243
244
245
246
# File 'lib/contrast/components/settings.rb', line 239

def update_assess_state
  current_session_id = @assess_state&.session_id
  @assess_state = ASSESS_STATE_BASE.dup
  # There is application settings update for the session id if new is received.
  # Here we make sure not to delete the already set one.
  @assess_state&.session_id = current_session_id unless current_session_id&.empty?
  @assess_state
end

#update_exclusion_matchers(exclusions) ⇒ Object

Parameters:



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/contrast/components/settings.rb', line 249

def update_exclusion_matchers exclusions
  matchers = []
  exclusions.url_exclusions.each do |exclusion|
    matchers << Contrast::Agent::ExclusionMatcher.new(exclusion)
  end
  exclusions.input_exclusions.each do |exclusion|
    matchers << Contrast::Agent::ExclusionMatcher.new(exclusion)
  end
  # Do not populate the matchers unless we have any. There are certain checks in
  # SourceMethod that will safe-guard return if there are no exclusions received.
  # The matching operation is expensive, and the excluder calls are made for each
  # source, and we do not want to check for exclusions if they are empty. This is
  # probably redundant as all exclusions default to empty, but will save useless
  # new object creation at very least.
  return if Contrast::Utils::DuckUtils.empty_duck?(matchers)

  @excluder = Contrast::Agent::Excluder.new(matchers)
end

#update_from_application_settings(settings_response) ⇒ Object

Parameters:



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/contrast/components/settings.rb', line 187

def update_from_application_settings settings_response
  return unless (app_settings = settings_response&.application_settings)

  extract_protect_app_settings(app_settings)
  update_matchers_and_sensitive_data(app_settings)
  @assess_state.disabled_assess_rules = app_settings.assess.disabled_rules
  update_config_from_settings(%i[assess rules disabled_rules], app_settings.assess.disabled_rules)
  new_session_id = app_settings.assess.session_id
  unless settings_empty?(new_session_id)
    @assess_state.session_id = new_session_id
    # TODO: RUBY-99999 Update the default values and effective config update from TS.
    # Using the session_id from the settings response to update the config.
    # The Effective Config sources values are fetched from the
    # Contrast::CONFIG.config.loaded_config. Some values are displayed from
    # their components, however not updated here. Using this may cause some
    # specs to fails check the update of all values from TS.
    # Contrast::CONFIG.application.session_id = new_session_id
    update_config_from_settings(%i[application session_id], new_session_id)
  end
  @last_app_update_ms = Contrast::Utils::Timer.now_ms
  @app_settings_last_httpdate = header_application_last_update
end

#update_from_server_features(features_response) ⇒ Object

Parameters:



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/contrast/components/settings.rb', line 99

def update_from_server_features features_response
  return unless (server_features = features_response&.server_features)

  update_loggers(server_features)
  # TODO: RUBY-99999 Update Bot-Blocker from server settings - check enable value.
  # For now all protection rules are rebuild on Application update. Bot blocker uses the default
  # enable from the base rule, and update it's mode on app settings update.
  # Here we receive also bots for that rule.
  unless settings_empty?(server_features.protect.enabled?)
    @protect_state.enabled = server_features.protect.enabled?
    update_config_from_settings(%i[protect enable], server_features.protect.enabled?)
  end
  update_assess_server_features(server_features.assess)
  @last_server_update_ms = Contrast::Utils::Timer.now_ms
  # update via response header. We receive header from TS with last update info, setting the
  # next request's header with the same time will save needless update of settings if there
  # are no new server features updates after the said time.
  @server_settings_last_httpdate = header_server_last_update
rescue StandardError => e
  logger.warn('The following error occurred from server update: ', e: e)
end

#update_loggers(server_features) ⇒ Object

Updates logging settings

Parameters:



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/contrast/components/settings.rb', line 140

def update_loggers server_features
  log_file = server_features.log_file
  log_level = server_features.log_level
  # Update logger:
  Contrast::Logger::Log.instance.update(log_file, log_level) if log_file || log_level
  unless settings_empty?(log_file)
    update_config_from_settings(%i[agent logger path], log_file)
    @agent_state.logger_path = log_file
  end
  unless settings_empty?(log_level)
    update_config_from_settings(%i[agent logger level], log_level)
    @agent_state.logger_level = log_level
  end
  # Update AgentLib Logger
  update_agent_lib_log(log_level.to_s)
  # Update CEFlogger:
  return if server_features.security_logger.settings_blank?

  cef_logger.build_logger(server_features.security_logger.log_level, server_features.security_logger.log_file)
  unless settings_empty?(log_file)
    update_config_from_settings(%i[agent security_logger path], log_file)
    @agent_state.cef_logger_level = log_file
  end
  return unless settings_empty?(log_level)

  update_config_from_settings(%i[agent security_logger level], log_level)
  @agent_state.cef_logger_level = log_level
end

#update_matchers_and_sensitive_data(app_settings) ⇒ Object

Parameters:



211
212
213
214
215
216
# File 'lib/contrast/components/settings.rb', line 211

def update_matchers_and_sensitive_data app_settings
  update_exclusion_matchers(app_settings.exclusions)
  app_settings.protect.virtual_patches = app_settings.protect.virtual_patches unless
    settings_empty?(app_settings.protect.virtual_patches)
  update_sensitive_data_policy(app_settings.sensitive_data_masking)
end

#update_sensitive_data_policy(sensitive_data_masking) ⇒ Object

Update the sensitive data masking policy from settings, received from TS. In case the settings are empty, keep current ones.

Ts Response settings for sensitive data masking policy

Parameters:



274
275
276
277
278
279
280
281
282
283
284
# File 'lib/contrast/components/settings.rb', line 274

def update_sensitive_data_policy sensitive_data_masking
  @sensitive_data_masking.mask_http_body = sensitive_data_masking.mask_http_body? unless
    settings_empty?(sensitive_data_masking.mask_http_body?)
  @sensitive_data_masking.mask_attack_vector = sensitive_data_masking.mask_attack_vector? unless
    settings_empty?(sensitive_data_masking.mask_attack_vector?)
  return if settings_empty?(sensitive_data_masking.rules)

  @sensitive_data_masking.rules = sensitive_data_masking.rules
  # update with the newly received rules.
  Contrast::Agent::Reporting::Masker.send(:update_dictionary)
end