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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/ext/cs__scope/cs__scope.c +76 -7
  3. data/ext/cs__scope/cs__scope.h +4 -0
  4. data/lib/contrast/agent/inventory/policy/datastores.rb +0 -3
  5. data/lib/contrast/agent/protect/rule/base.rb +5 -1
  6. data/lib/contrast/agent/protect/rule/cmdi/cmd_injection.rb +17 -5
  7. data/lib/contrast/agent/protect/rule/input_classification/base.rb +7 -2
  8. data/lib/contrast/agent/protect/rule/input_classification/encoding.rb +1 -1
  9. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal.rb +8 -1
  10. data/lib/contrast/agent/protect/rule/sqli/sqli.rb +8 -1
  11. data/lib/contrast/agent/protect/state.rb +110 -0
  12. data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +4 -10
  13. data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +11 -12
  14. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +6 -29
  15. data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +1 -2
  16. data/lib/contrast/agent/reporting/reporting_events/application_inventory_activity.rb +2 -2
  17. data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +2 -0
  18. data/lib/contrast/agent/reporting/reporting_events/finding.rb +1 -0
  19. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +4 -0
  20. data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +4 -2
  21. data/lib/contrast/agent/reporting/reporting_events/observed_library_usage.rb +2 -0
  22. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +9 -5
  23. data/lib/contrast/agent/reporting/reporting_events/reportable_hash.rb +30 -6
  24. data/lib/contrast/agent/reporting/reporting_utilities/ng_response_extractor.rb +15 -2
  25. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +1 -1
  26. data/lib/contrast/agent/reporting/reporting_utilities/resend.rb +1 -1
  27. data/lib/contrast/agent/reporting/reporting_utilities/response.rb +0 -2
  28. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +4 -5
  29. data/lib/contrast/agent/reporting/settings/protect.rb +61 -18
  30. data/lib/contrast/agent/reporting/settings/sampling.rb +5 -4
  31. data/lib/contrast/agent/reporting/settings/server_features.rb +2 -0
  32. data/lib/contrast/agent/version.rb +1 -1
  33. data/lib/contrast/components/agent.rb +3 -5
  34. data/lib/contrast/components/api.rb +3 -3
  35. data/lib/contrast/components/assess_rules.rb +1 -2
  36. data/lib/contrast/components/base.rb +1 -2
  37. data/lib/contrast/components/config/sources.rb +23 -0
  38. data/lib/contrast/components/logger.rb +19 -0
  39. data/lib/contrast/components/protect.rb +69 -15
  40. data/lib/contrast/components/sampling.rb +5 -12
  41. data/lib/contrast/components/security_logger.rb +17 -0
  42. data/lib/contrast/components/settings.rb +114 -70
  43. data/lib/contrast/config/certification_configuration.rb +1 -1
  44. data/lib/contrast/config/configuration_files.rb +0 -2
  45. data/lib/contrast/config/diagnostics/config.rb +3 -3
  46. data/lib/contrast/config/diagnostics/effective_config.rb +1 -1
  47. data/lib/contrast/config/diagnostics/environment_variables.rb +21 -11
  48. data/lib/contrast/config/diagnostics/monitor.rb +1 -1
  49. data/lib/contrast/config/diagnostics/singleton_tools.rb +170 -0
  50. data/lib/contrast/config/diagnostics/source_config_value.rb +14 -9
  51. data/lib/contrast/config/diagnostics/tools.rb +23 -84
  52. data/lib/contrast/config/request_audit_configuration.rb +1 -1
  53. data/lib/contrast/config/server_configuration.rb +3 -15
  54. data/lib/contrast/configuration.rb +5 -2
  55. data/lib/contrast/framework/manager.rb +4 -3
  56. data/lib/contrast/framework/manager_extend.rb +3 -1
  57. data/lib/contrast/framework/rack/support.rb +11 -2
  58. data/lib/contrast/utils/log_utils.rb +1 -1
  59. data/lib/contrast/utils/reporting/application_activity_batch_utils.rb +0 -3
  60. data/lib/contrast/utils/request_utils.rb +1 -1
  61. data/lib/contrast/utils/timer.rb +1 -1
  62. data/lib/contrast.rb +1 -1
  63. metadata +4 -2
@@ -31,7 +31,7 @@ module Contrast
31
31
  attr_reader :uri
32
32
  # @return [String] the HTTP version of this request
33
33
  attr_reader :version
34
- # @return [Integer]
34
+ # @return [String]
35
35
  attr_reader :ip
36
36
  # @return [String] Byte representation of the body
37
37
  attr_accessor :body_binary
@@ -40,7 +40,7 @@ module Contrast
40
40
 
41
41
  class << self
42
42
  # @param request [Contrast::Agent::Request]
43
- # @return [Contrast::Agent::Reporting::FindingRequest]
43
+ # @return [Contrast::Agent::Reporting::FindingRequest, nil]
44
44
  def convert request
45
45
  return unless request
46
46
 
@@ -108,6 +108,8 @@ module Contrast
108
108
  raise(ArgumentError, "#{ self } did not have a proper method. Unable to continue.")
109
109
  end
110
110
  raise(ArgumentError, "#{ self } did not have a proper uri. Unable to continue.") unless uri && !uri.empty?
111
+
112
+ nil
111
113
  end
112
114
 
113
115
  # @param request [Contrast::Agent::Request]
@@ -30,6 +30,8 @@ module Contrast
30
30
 
31
31
  def validate
32
32
  raise(ArgumentError, "#{ self } did not have observations. Unable to continue.") if observations.empty?
33
+
34
+ nil
33
35
  end
34
36
  end
35
37
  end
@@ -38,14 +38,15 @@ module Contrast
38
38
  def to_controlled_hash
39
39
  validate
40
40
  {
41
+ appLanguage: Contrast::Utils::ObjectShare::RUBY,
42
+ appName: ::Contrast::APP_CONTEXT.name, # rubocop:disable Security/Module/Name
43
+ appPath: ::Contrast::APP_CONTEXT.name, # rubocop:disable Security/Module/Name
44
+ appVersion: ::Contrast::APP_CONTEXT.version,
41
45
  code: CODE,
42
- app_language: Contrast::Utils::ObjectShare::RUBY,
43
- app_name: ::Contrast::APP_CONTEXT.name, # rubocop:disable Security/Module/Name
44
- app_version: ::Contrast::APP_CONTEXT.version,
45
46
  data: '',
46
47
  key: 0,
47
- routes: @routes.map(&:to_controlled_hash),
48
- session_id: ::Contrast::ASSESS.session_id
48
+ session_id: ::Contrast::ASSESS.session_id,
49
+ routes: @routes.map(&:to_controlled_hash)
49
50
  }
50
51
  end
51
52
 
@@ -57,6 +58,9 @@ module Contrast
57
58
  unless ::Contrast::ASSESS.session_id
58
59
  raise(ArgumentError, "#{ cs__class } did not have a proper session id. Unable to continue.")
59
60
  end
61
+ unless Contrast::APP_CONTEXT.name # rubocop:disable Security/Module/Name
62
+ raise(ArgumentError, "#{ cs__class } did not have a proper Application Name. Unable to continue.")
63
+ end
60
64
 
61
65
  nil
62
66
  end
@@ -28,12 +28,31 @@ module Contrast
28
28
  #
29
29
  # @return [String, nil] - JSON
30
30
  def event_json
31
- hsh = to_controlled_hash
32
- hsh.to_json
33
- rescue ArgumentError => e
34
- # Log the error and raise it for the client to handle:
35
- logger.error(validation_error_message, e)
36
- raise(e)
31
+ return unless valid?
32
+
33
+ to_controlled_hash.to_json
34
+ end
35
+
36
+ # Check before building the json if the event is valid.
37
+ # This will also log the error if the event is invalid.
38
+ # This will save some object creation in the reporter
39
+ # in the case of an invalid event, and will stop the
40
+ # reporting of the event.
41
+ #
42
+ # @return [Boolean]
43
+ # @raise [ArgumentError]
44
+ def valid?
45
+ @_valid ||= begin
46
+ validate
47
+ # Some deeply nested validations only happens on
48
+ # the to_controlled_hash method, so we need to
49
+ # invoke it.
50
+ to_controlled_hash
51
+ true
52
+ rescue ArgumentError => e
53
+ handle_validation_error(e)
54
+ false
55
+ end
37
56
  end
38
57
 
39
58
  private
@@ -41,6 +60,11 @@ module Contrast
41
60
  def validation_error_message
42
61
  "#{ cs__class } failed validation with: "
43
62
  end
63
+
64
+ # @param error [ArgumentError]
65
+ def handle_validation_error error
66
+ logger.error(validation_error_message, error)
67
+ end
44
68
  end
45
69
  end
46
70
  end
@@ -126,10 +126,23 @@ module Contrast
126
126
  # @param response_data [Hash]
127
127
  # @param res [Contrast::Agent::Reporting::Response]
128
128
  def ng_extract_log_settings response_data, res
129
- return unless (log_level = response_data[:logLevel])
129
+ # agent_startup event defines the log level under features.
130
+ log_level = if response_data[:features]
131
+ response_data[:features][:logLevel]
132
+ else
133
+ response_data[:logLevel]
134
+ end
135
+ return unless log_level
130
136
 
131
137
  res.server_features.log_level = log_level
132
- res.server_features.log_file = response_data[:logFile] if response_data[:logFile]
138
+ log_file = if response_data[:features]
139
+ response_data[:features][:logFile]
140
+ else
141
+ response_data[:logFile]
142
+ end
143
+ return unless log_file
144
+
145
+ res.server_features.log_file = log_file
133
146
  end
134
147
  end
135
148
  end
@@ -72,8 +72,8 @@ module Contrast
72
72
  # @return response [Net::HTTP::Response, nil] response from TS if no response
73
73
  def send_event event, connection
74
74
  return unless connection
75
+ return unless event.valid?
75
76
 
76
- response = nil
77
77
  logger.debug('[Reporter] Preparing to send reporting event', event_class: event.cs__class)
78
78
  request = build_request(event)
79
79
  response = connection.request(request)
@@ -12,7 +12,7 @@ module Contrast
12
12
  RESCUE_ATTEMPTS = 3
13
13
  TIMEOUT = 5
14
14
  RETRY_ERRORS = [
15
- Net::OpenTimeout, Net::ReadTimeout, ArgumentError, EOFError, OpenSSL::SSL::SSLError, Resolv::ResolvError,
15
+ Net::OpenTimeout, Net::ReadTimeout, EOFError, OpenSSL::SSL::SSLError, Resolv::ResolvError,
16
16
  Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::ETIMEDOUT, Errno::ESHUTDOWN, Errno::EHOSTDOWN, NameError,
17
17
  Errno::EHOSTUNREACH, Errno::EISCONN, Errno::ECONNABORTED, Errno::ENETRESET, Errno::ENETUNREACH, SocketError,
18
18
  NameError, NoMethodError, Timeout::Error, RuntimeError
@@ -87,8 +87,6 @@ module Contrast
87
87
  messages: messages,
88
88
  features: server_features.nil? ? nil : server_features.to_controlled_hash,
89
89
  settings: application_settings.nil? ? nil : application_settings.to_controlled_hash,
90
- logLevel: server_features&.log_level,
91
- logFile: server_features&.log_file,
92
90
  reactions: server_features.nil? ? nil : reactions.map(&:to_controlled_hash)
93
91
  }.compact
94
92
  end
@@ -232,11 +232,10 @@ module Contrast
232
232
  return unless ::Contrast::AGENT.enabled?
233
233
 
234
234
  logger.trace_with_time('Rebuilding rule modes from TeamServer') do
235
- ::Contrast::SETTINGS.build_protect_rules if ::Contrast::PROTECT.enabled?
236
- ::Contrast::AGENT.reset_ruleset
237
- logger.info('Current rule settings:')
238
- ::Contrast::PROTECT.defend_rules.each { |k, v| logger.info('Protect Rule mode set', rule: k, mode: v.mode) }
239
- logger.info('Disabled Assess Rules', rules: ::Contrast::ASSESS.disabled_rules)
235
+ ::Contrast::PROTECT.state.update if ::Contrast::PROTECT.enabled?
236
+ if ::Contrast::ASSESS.enabled?
237
+ logger.info('Disabled Assess Rules', rules: ::Contrast::ASSESS.disabled_rules)
238
+ end
240
239
  end
241
240
  end
242
241
 
@@ -14,6 +14,13 @@ module Contrast
14
14
  class Protect
15
15
  # modes set by NG endpoints; block at perimeter needs to be check against the blockAtEntry boolean value
16
16
  NG_PROTECT_RULES_MODE = %w[OFF MONITORING BLOCKING].cs__freeze
17
+ ACTIVE_PROTECT_RULES_LIST = %w[
18
+ bot-blocker cmd-injection cmd-injection-command-backdoors cmd-injection-semantic-chained-commands
19
+ cmd-injection-semantic-dangerous-paths untrusted-deserialization nosql-injection path-traversal
20
+ path-traversal-semantic-file-security-bypass sql-injection sql-injection-semantic-dangerous-functions
21
+ unsafe-file-upload reflected-xss xxe
22
+ ].cs__freeze
23
+
17
24
  # The settings for each protect rule for this application
18
25
  #
19
26
  # @return protection_rules [Array<protectRule>] protectRule: {
@@ -80,30 +87,66 @@ module Contrast
80
87
  modes_by_id = {}
81
88
  protection_rules.each do |rule|
82
89
  setting_mode = rule[:mode] || rule['mode']
83
- api_mode = if NG_PROTECT_RULES_MODE.include?(setting_mode)
84
- case setting_mode
85
- when NG_PROTECT_RULES_MODE[1]
86
- :MONITOR
87
- when NG_PROTECT_RULES_MODE[2]
88
- if rule[:blockAtEntry] || rule['blockAtEntry']
89
- :BLOCK_AT_PERIMETER
90
- else
91
- :BLOCK
92
- end
93
- else
94
- :NO_ACTION
95
- end
96
- else
97
- # modes set by newer settings endpoints are of [OFF MONITOR BLOCK BLOCK_AT_PERIMETER] and
98
- # can just be cast to symbols
99
- setting_mode.to_sym
100
- end
90
+ # BlockAtEnrtry is only available for the protection_rules Array.
91
+ # It is used in both ng and non ng payloads. If the array is empty
92
+ # this method will short circuit at the very first line and return
93
+ # empty hash. this means that the #rules_settings_to_settings_hash
94
+ # will be used next to extract the settings.
95
+ bap = rule[:blockAtEntry] || rule['blockAtEntry']
96
+ api_mode = assign_mode(setting_mode, block_at_entry: !!bap == bap)
101
97
 
102
98
  id = rule[:id] || rule['id']
103
99
  modes_by_id[id] = api_mode
104
100
  end
105
101
  modes_by_id
106
102
  end
103
+
104
+ # Converts settings into Agent Settings understandable hash {RULE_ID => MODE}
105
+ # Takes Hash<String, Contrast::Agent::Reporting::Settings::ProtectRule> and converts it
106
+ # to Hash<RULE_ID => MODE>
107
+ #
108
+ # @return rules [Hash<RULE_ID => MODE>, nil] Hash with rule_id as key and mode as value
109
+ def rules_settings_to_settings_hash
110
+ return {} if rule_settings.empty?
111
+
112
+ modes_by_id = {}
113
+ rule_settings.each do |rule_id, rule_mode|
114
+ next unless active_defend_rules.include?(rule_id.to_s)
115
+
116
+ modes_by_id[rule_id.to_s] = assign_mode(rule_mode.mode)
117
+ end
118
+ modes_by_id
119
+ end
120
+
121
+ # Returns list of actively used protection rules to be updated, or default list.
122
+ # This will be used to query the received settings for the ones used by the Agent.
123
+ def active_defend_rules
124
+ return ACTIVE_PROTECT_RULES_LIST unless defined?(Contrast::PROTECT)
125
+
126
+ current_rules = Contrast::PROTECT.defend_rules.keys
127
+ return current_rules unless current_rules.empty?
128
+
129
+ ACTIVE_PROTECT_RULES_LIST
130
+ end
131
+
132
+ private
133
+
134
+ # Assigns the received settings mode to be used as actual config.
135
+ # @param setting_mode []
136
+ def assign_mode setting_mode, block_at_entry: false
137
+ # modes set by newer settings endpoints are of [OFF MONITOR BLOCK BLOCK_AT_PERIMETER] and
138
+ # can just be cast to symbols
139
+ return setting_mode.to_sym unless NG_PROTECT_RULES_MODE.include?(setting_mode)
140
+
141
+ case setting_mode
142
+ when NG_PROTECT_RULES_MODE[1]
143
+ :MONITOR
144
+ when NG_PROTECT_RULES_MODE[2]
145
+ block_at_entry ? :BLOCK_AT_PERIMETER : :BLOCK
146
+ else
147
+ :NO_ACTION
148
+ end
149
+ end
107
150
  end
108
151
  end
109
152
  end
@@ -24,10 +24,11 @@ module Contrast
24
24
 
25
25
  def initialize hsh
26
26
  @baseline = hsh[:baseline]
27
- @enabled = hsh[:enabled]
28
- @request_frequency = hsh[:frequency]
29
- @response_frequency = hsh[:responseFrequency]
30
- @window_ms = hsh[:window]
27
+ # NG endpoints vs non-ng
28
+ @enabled = hsh[:enabled] || hsh[:enable]
29
+ @request_frequency = hsh[:frequency] || hsh[:request_frequency]
30
+ @response_frequency = hsh[:responseFrequency] || hsh[:response_frequency]
31
+ @window_ms = hsh[:window] || hsh[:window_ms]
31
32
  end
32
33
 
33
34
  def to_controlled_hash
@@ -85,6 +85,8 @@ module Contrast
85
85
  security_logger: security_logger.settings_blank? ? nil : security_logger.to_controlled_hash,
86
86
  assessment: @_assess ? assess.to_controlled_hash : {},
87
87
  defend: @_protect ? protect.to_controlled_hash : {},
88
+ logLevel: log_level,
89
+ logFile: log_file,
88
90
  telemetry: telemetry
89
91
  }.compact
90
92
  end
@@ -3,6 +3,6 @@
3
3
 
4
4
  module Contrast
5
5
  module Agent
6
- VERSION = '7.3.1'
6
+ VERSION = '7.4.0'
7
7
  end
8
8
  end
@@ -26,6 +26,8 @@ module Contrast
26
26
  attr_reader :config_values
27
27
  # @return [Boolean]
28
28
  attr_accessor :enable
29
+ # @return [Boolean]
30
+ attr_accessor :omit_body
29
31
 
30
32
  CANON_NAME = 'agent'
31
33
  CONFIG_VALUES = %w[enabled? omit_body?].cs__freeze
@@ -95,16 +97,12 @@ module Contrast
95
97
  @_ruleset ||= Contrast::Agent::RuleSet.new(retrieve_protect_ruleset.values)
96
98
  end
97
99
 
98
- def reset_ruleset
99
- @_ruleset = nil
100
- end
101
-
102
100
  def patch_yield?
103
101
  !false?(ruby.propagate_yield)
104
102
  end
105
103
 
106
104
  def omit_body?
107
- @_omit_body
105
+ true?(@_omit_body)
108
106
  end
109
107
 
110
108
  def disable_agent!
@@ -129,7 +129,7 @@ module Contrast
129
129
  #
130
130
  # @param effective_config [Contrast::Config::Diagnostics::EffectiveConfig]
131
131
  def to_effective_config effective_config
132
- add_effective_config_values(effective_config, CONFIG_VALUES, CANON_NAME, CONTRAST)
132
+ add_effective_config_values(effective_config, CONFIG_VALUES, CANON_NAME, CANON_NAME)
133
133
  effective_proxy(effective_config)
134
134
  request_audit&.to_effective_config(effective_config)
135
135
  certificate&.to_effective_config(effective_config)
@@ -139,10 +139,10 @@ module Contrast
139
139
 
140
140
  # @param effective_config [Contrast::Config::Diagnostics::EffectiveConfig]
141
141
  def effective_proxy effective_config
142
- add_single_effective_value(effective_config, ENABLE, proxy_enable.to_s, PROXY_NAME, "#{ CONTRAST }.proxy")
142
+ add_single_effective_value(effective_config, ENABLE, proxy_enable.to_s, PROXY_NAME)
143
143
  return unless proxy_url && proxy_enable
144
144
 
145
- add_single_effective_value(effective_config, 'url', proxy_url, PROXY_NAME, "#{ CONTRAST }.proxy")
145
+ add_single_effective_value(effective_config, 'url', proxy_url, PROXY_NAME)
146
146
  end
147
147
 
148
148
  def certification_truly_enabled? config_path
@@ -16,7 +16,6 @@ module Contrast
16
16
 
17
17
  SPEC_KEY = :disabled_rules.cs__freeze
18
18
  CANON_NAME = 'assess.rules'
19
- NAME_PREFIX = "#{ CONTRAST }.#{ CANON_NAME }".cs__freeze
20
19
 
21
20
  # @return [Array, nil] list of disabled assess rules
22
21
  attr_accessor :disabled_rules
@@ -36,7 +35,7 @@ module Contrast
36
35
  #
37
36
  # @param effective_config [Contrast::Config::Diagnostics::EffectiveConfig]
38
37
  def to_effective_config effective_config
39
- add_single_effective_value(effective_config, SPEC_KEY.to_s, disabled_rules, canon_name, NAME_PREFIX)
38
+ add_single_effective_value(effective_config, SPEC_KEY.to_s, disabled_rules || [], canon_name)
40
39
  end
41
40
 
42
41
  private
@@ -12,7 +12,6 @@ module Contrast
12
12
  module ComponentBase
13
13
  include Contrast::Config::Diagnostics::Tools
14
14
 
15
- CONTRAST = 'contrast'
16
15
  ENABLE = 'enable'
17
16
 
18
17
  # Used for config diagnostics. Override per rule.
@@ -87,7 +86,7 @@ module Contrast
87
86
  #
88
87
  # @param effective_config [Contrast::Config::Diagnostics::EffectiveConfig]
89
88
  def to_effective_config effective_config
90
- add_effective_config_values(effective_config, config_values, canon_name, "#{ CONTRAST }.#{ canon_name }")
89
+ add_effective_config_values(effective_config, config_values, canon_name)
91
90
  end
92
91
 
93
92
  # attempts to stringify the config value if it is an array with the join char
@@ -11,6 +11,8 @@ module Contrast
11
11
  # This component encapsulates storing the source for each entry in the config,
12
12
  # so that we can report on where the value was set from.
13
13
  class Sources
14
+ # [ CORPORATE_RULE, COMMAND_LINE, JAVA_SYSTEM_PROPERTY, ENVIRONMENT_VARIABLE, APP_CONFIGURATION_FILE,
15
+ # USER_CONFIGURATION_FILE, CONTRAST_UI, DEFAULT_VALUE ]
14
16
  ENVIRONMENT_VARIABLE = 'ENVIRONMENT_VARIABLE'
15
17
  COMMAND_LINE = 'COMMAND_LINE'
16
18
  CONTRAST_UI = 'CONTRAST_UI'
@@ -57,6 +59,27 @@ module Contrast
57
59
  deep_select(data.dup, type, [])
58
60
  end
59
61
 
62
+ # @param path [String] the canonical name for the config entry (such as api.proxy.enable) or source
63
+ # if the source(CONTRAST_UI, ENVIRONMENT_VARIABLE...) flag is set true.
64
+ # @param source [Boolean] flag to specify we are passing in a source, no need to look for it.
65
+ # @return [Boolean] true if the entry is from a YAML file
66
+ def configuration_file_source? path, source: false
67
+ s = source ? path : Contrast::CONFIG.sources.get(path)
68
+ return true if s.include?(APP_CONFIGURATION_EXTENSIONS[0]) || s.include?(APP_CONFIGURATION_EXTENSIONS[1])
69
+
70
+ false
71
+ end
72
+
73
+ # Check to see whether the source has been overridden by local settings.
74
+ # @param path [String] the canonical name for the config entry (such as api.proxy.enable)
75
+ def source_overridden? path
76
+ source = Contrast::CONFIG.sources.get(path)
77
+ return true if [ENVIRONMENT_VARIABLE, COMMAND_LINE].include?(source)
78
+ return true if configuration_file_source?(source, source: true)
79
+
80
+ false
81
+ end
82
+
60
83
  private
61
84
 
62
85
  # @param path [String] the canonical name for a config entry (such as api.proxy.enable)
@@ -33,6 +33,8 @@ module Contrast
33
33
  include InstanceMethods
34
34
  include Contrast::Components::ComponentBase
35
35
 
36
+ DEFAULT_NAME = 'contrast.log'
37
+ DEFAULT_LEVEL = 'INFO'
36
38
  CANON_NAME = 'agent.logger'
37
39
  CONFIG_VALUES = %w[path level progname].cs__freeze
38
40
 
@@ -56,6 +58,23 @@ module Contrast
56
58
  @level = hsh[:level]
57
59
  @progname = hsh[:progname]
58
60
  end
61
+
62
+ def to_effective_config effective_config
63
+ path_setting = nil
64
+ level_setting = nil
65
+
66
+ if defined?(Contrast::SETTINGS)
67
+ path_setting = Contrast::SETTINGS.agent_state.logger_path
68
+ level_setting = Contrast::SETTINGS.agent_state.logger_level
69
+ end
70
+
71
+ path_setting ||= DEFAULT_NAME
72
+ level_setting ||= DEFAULT_LEVEL
73
+
74
+ add_single_effective_value(effective_config, config_values[0], path || path_setting, CANON_NAME)
75
+ add_single_effective_value(effective_config, config_values[1], level || level_setting, CANON_NAME)
76
+ add_single_effective_value(effective_config, config_values[2], progname, CANON_NAME)
77
+ end
59
78
  end
60
79
  end
61
80
  end
@@ -4,13 +4,15 @@
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
10
12
  module Protect
11
13
  # A wrapper build around the Common Agent Configuration project to allow for access of the values contained in
12
14
  # its parent_configuration_spec.yaml. Specifically, this allows for querying the state of the Protect product.
13
- class Interface
15
+ class Interface # rubocop:disable Metrics/ClassLength
14
16
  include Contrast::Components::ComponentBase
15
17
  include Contrast::Config::BaseConfiguration
16
18
 
@@ -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
 
@@ -168,6 +188,40 @@ module Contrast
168
188
 
169
189
  @_forcibly_enabled ||= true?(::Contrast::CONFIG.protect.enable)
170
190
  end
191
+
192
+ # Used for conversion to contrast metrics hash:
193
+ def forcibly_enabled
194
+ forcibly_enabled?
195
+ end
196
+
197
+ def forcibly_disabled
198
+ forcibly_disabled?
199
+ end
200
+
201
+ def report_custom_code_sysfile_access
202
+ report_custom_code_sysfile_access?
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
171
225
  end
172
226
  end
173
227
  end