contrast-agent 7.3.1 → 7.4.0

Sign up to get free protection for your applications and to get access to all the features.
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