contrast-agent 7.3.0 → 7.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/cs__scope/cs__scope.c +76 -7
- data/ext/cs__scope/cs__scope.h +4 -0
- data/lib/contrast/agent/assess/policy/policy_node.rb +25 -6
- data/lib/contrast/agent/assess/policy/propagator/response.rb +64 -0
- data/lib/contrast/agent/assess/policy/propagator.rb +1 -0
- data/lib/contrast/agent/assess/policy/source_method.rb +5 -0
- data/lib/contrast/agent/assess/rule/response/body_rule.rb +22 -7
- data/lib/contrast/agent/assess/rule/response/cache_control_header_rule.rb +4 -1
- data/lib/contrast/agent/inventory/policy/datastores.rb +0 -3
- data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +4 -10
- data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +11 -12
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +4 -29
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +1 -2
- data/lib/contrast/agent/reporting/reporting_events/application_inventory_activity.rb +2 -2
- data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +2 -2
- data/lib/contrast/agent/reporting/reporting_utilities/ng_response_extractor.rb +15 -2
- data/lib/contrast/agent/reporting/reporting_utilities/response.rb +0 -2
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +5 -2
- data/lib/contrast/agent/reporting/settings/protect.rb +61 -18
- data/lib/contrast/agent/reporting/settings/server_features.rb +2 -0
- data/lib/contrast/agent/telemetry/exception/obfuscate.rb +4 -3
- data/lib/contrast/agent/telemetry/identifier.rb +13 -26
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/components/assess.rb +33 -6
- data/lib/contrast/components/base.rb +4 -2
- data/lib/contrast/components/config.rb +2 -2
- data/lib/contrast/components/protect.rb +14 -1
- data/lib/contrast/components/settings.rb +11 -1
- data/lib/contrast/config/diagnostics/command_line.rb +2 -2
- data/lib/contrast/config/diagnostics/environment_variables.rb +2 -1
- data/lib/contrast/config/diagnostics/tools.rb +15 -5
- data/lib/contrast/configuration.rb +61 -29
- data/lib/contrast/logger/application.rb +3 -3
- data/lib/contrast/utils/assess/propagation_method_utils.rb +2 -0
- data/lib/contrast/utils/os.rb +1 -9
- data/lib/contrast/utils/reporting/application_activity_batch_utils.rb +0 -3
- data/lib/contrast.rb +1 -1
- data/resources/assess/policy.json +80 -3
- metadata +3 -2
@@ -232,11 +232,14 @@ module Contrast
|
|
232
232
|
return unless ::Contrast::AGENT.enabled?
|
233
233
|
|
234
234
|
logger.trace_with_time('Rebuilding rule modes from TeamServer') do
|
235
|
-
|
235
|
+
# TODO: RUBY-999999 Make sure when updating Protect rules to reflect the mode source (always DEFAULT_VALUE)
|
236
236
|
::Contrast::AGENT.reset_ruleset
|
237
|
+
::Contrast::SETTINGS.build_protect_rules if ::Contrast::PROTECT.enabled?
|
237
238
|
logger.info('Current rule settings:')
|
238
239
|
::Contrast::PROTECT.defend_rules.each { |k, v| logger.info('Protect Rule mode set', rule: k, mode: v.mode) }
|
239
|
-
|
240
|
+
if ::Contrast::ASSESS.enabled?
|
241
|
+
logger.info('Disabled Assess Rules', rules: ::Contrast::ASSESS.disabled_rules)
|
242
|
+
end
|
240
243
|
end
|
241
244
|
end
|
242
245
|
|
@@ -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
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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::SETTINGS)
|
125
|
+
|
126
|
+
current_rules = (Contrast::PROTECT&.defend_rules&.keys if defined?(Contrast::PROTECT))
|
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
|
@@ -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
|
@@ -16,9 +16,10 @@ module Contrast
|
|
16
16
|
# is the same.
|
17
17
|
CYPHER = CHARS.chars.shuffle.join.cs__freeze
|
18
18
|
VERSION_MATCH = '[^0-9].-'
|
19
|
+
RUBY_EXT = /\.(?:rb|gemspec)$/i
|
19
20
|
|
20
21
|
# List of known places after witch a user name might appear:
|
21
|
-
KNOWN_DIRS = %w[app application project projects git github users home user].cs__freeze
|
22
|
+
KNOWN_DIRS = %w[app application lib project projects git github users home user].cs__freeze
|
22
23
|
|
23
24
|
class << self
|
24
25
|
# Returns paths for known gems.
|
@@ -65,8 +66,8 @@ module Contrast
|
|
65
66
|
name.tr(VERSION_MATCH, Contrast::Utils::ObjectShare::EMPTY_STRING).downcase)
|
66
67
|
|
67
68
|
obscure(name)
|
68
|
-
# obscure username (next dir in line)
|
69
|
-
obscure(dirs[idx + 1]) if dirs[idx + 1]
|
69
|
+
# obscure username (next dir in line), skip if it's a file name.
|
70
|
+
obscure(dirs[idx + 1]) if dirs[idx + 1] && (dirs[idx + 1] !~ RUBY_EXT)
|
70
71
|
end
|
71
72
|
cypher = dirs.join(Contrast::Utils::ObjectShare::SLASH)
|
72
73
|
return cypher if cypher
|
@@ -12,7 +12,7 @@ module Contrast
|
|
12
12
|
# Gets info about the instrumented application required to build unique identifiers,
|
13
13
|
# used in the agent's Telemetry.
|
14
14
|
module Identifier
|
15
|
-
|
15
|
+
MAC_REGEXP = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/.cs__freeze
|
16
16
|
LINUX_OS_REG = /hwaddr=.*?(([A-F0-9]{2}:){5}[A-F0-9]{2})/im.cs__freeze
|
17
17
|
MAC_OS_PRIMARY = 'en0'.cs__freeze
|
18
18
|
LINUX_PRIMARY = 'enp'.cs__freeze
|
@@ -87,7 +87,7 @@ module Contrast
|
|
87
87
|
mac = retrieve_mac(addr)
|
88
88
|
next unless mac
|
89
89
|
|
90
|
-
result = mac if mac
|
90
|
+
result = mac if mac.match?(MAC_REGEXP)
|
91
91
|
break if result
|
92
92
|
end
|
93
93
|
result
|
@@ -118,33 +118,20 @@ module Contrast
|
|
118
118
|
nil
|
119
119
|
end
|
120
120
|
|
121
|
-
# Returns array of network interfaces.
|
122
|
-
# This is OS dependent search.
|
121
|
+
# Returns array of network interfaces belonging to the expected pfamily of this OS.
|
123
122
|
#
|
124
|
-
# @return interfaces [Array]
|
125
|
-
# Socket::Ifaddr - represents a result of getifaddrs().
|
123
|
+
# @return interfaces [Array<Socket::Ifaddr>]
|
126
124
|
def interfaces
|
127
|
-
@_interfaces ||=
|
128
|
-
|
129
|
-
return @_interfaces unless @_interfaces.empty?
|
125
|
+
@_interfaces ||= Socket.getifaddrs.select { |interface| interface.addr&.pfamily == check_family }
|
126
|
+
end
|
130
127
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
check_family = 18 if Contrast::Utils::OS.mac?
|
139
|
-
check_family = 17 if Contrast::Utils::OS.linux?
|
140
|
-
if arr[idx].addr.pfamily != check_family
|
141
|
-
idx += 1
|
142
|
-
next
|
143
|
-
end
|
144
|
-
@_interfaces << arr[idx]
|
145
|
-
idx += 1
|
146
|
-
end
|
147
|
-
@_interfaces
|
128
|
+
# We need only network adapters MACs. Checking for pfamily of every socket address:
|
129
|
+
# 18 for Mac OS and 17 for Linux. Family should be an address family such as: :INET, :INET6, :UNIX, etc.
|
130
|
+
# It corresponds to the Addrinfo.pfamily value.
|
131
|
+
#
|
132
|
+
# @return [Integer]
|
133
|
+
def check_family
|
134
|
+
@_check_family ||= Contrast::Utils::OS.mac? ? 18 : 17
|
148
135
|
end
|
149
136
|
end
|
150
137
|
end
|
@@ -17,14 +17,26 @@ module Contrast
|
|
17
17
|
|
18
18
|
# @return [Boolean, nil]
|
19
19
|
attr_accessor :enable
|
20
|
-
# @return [
|
21
|
-
attr_writer :enable_scan_response
|
20
|
+
# @return [Boolean, nil]
|
21
|
+
attr_writer :enable_scan_response
|
22
|
+
# @return [Boolean, nil]
|
23
|
+
attr_writer :enable_dynamic_sources
|
24
|
+
# @return [Contrast::Components::Sampling::Interface]
|
25
|
+
attr_writer :sampling
|
26
|
+
# @return [Contrast::Components::AssessRules::Interface]
|
27
|
+
attr_writer :rules
|
28
|
+
# @return [String, nil]
|
29
|
+
attr_writer :stacktraces
|
30
|
+
# @return [Array<String>, nil]
|
31
|
+
attr_writer :tags
|
22
32
|
# @return [String]
|
23
33
|
attr_reader :canon_name
|
24
|
-
# @return [Array]
|
34
|
+
# @return [Array<String>]
|
25
35
|
attr_reader :config_values
|
26
36
|
# @return [Boolean]
|
27
37
|
attr_writer :enable_original_object
|
38
|
+
# @return [Boolean]
|
39
|
+
attr_writer :enable_response_as_source
|
28
40
|
# @return [Integer]
|
29
41
|
attr_writer :max_context_source_events
|
30
42
|
# @return [Integer]
|
@@ -46,6 +58,7 @@ module Contrast
|
|
46
58
|
enable_scan_response
|
47
59
|
enable_original_object
|
48
60
|
enable_dynamic_sources
|
61
|
+
enable_response_as_source
|
49
62
|
stacktraces
|
50
63
|
max_context_source_events
|
51
64
|
max_propagation_events
|
@@ -63,27 +76,33 @@ module Contrast
|
|
63
76
|
@enable_scan_response = hsh[:enable_scan_response]
|
64
77
|
@enable_dynamic_sources = hsh[:enable_dynamic_sources]
|
65
78
|
@enable_original_object = hsh[:enable_original_object]
|
79
|
+
@enable_response_as_source = hsh[:enable_response_as_source]
|
66
80
|
@sampling = Contrast::Components::Sampling::Interface.new(hsh[:sampling])
|
67
81
|
@rules = Contrast::Components::AssessRules::Interface.new(hsh[:rules])
|
68
82
|
@stacktraces = hsh[:stacktraces]
|
69
83
|
assign_limits(hsh)
|
70
84
|
end
|
71
85
|
|
72
|
-
# @return [Boolean
|
86
|
+
# @return [Boolean]
|
73
87
|
def enable_scan_response
|
74
88
|
@enable_scan_response.nil? ? true : @enable_scan_response
|
75
89
|
end
|
76
90
|
|
77
|
-
# @return [Boolean
|
91
|
+
# @return [Boolean]
|
78
92
|
def enable_dynamic_sources
|
79
93
|
@enable_dynamic_sources.nil? ? true : @enable_dynamic_sources
|
80
94
|
end
|
81
95
|
|
82
|
-
# @return [Boolean
|
96
|
+
# @return [Boolean]
|
83
97
|
def enable_original_object
|
84
98
|
@enable_original_object.nil? ? true : @enable_original_object
|
85
99
|
end
|
86
100
|
|
101
|
+
# @return [Boolean]
|
102
|
+
def enable_response_as_source
|
103
|
+
@enable_response_as_source.nil? ? false : @enable_response_as_source
|
104
|
+
end
|
105
|
+
|
87
106
|
# @return [Contrast::Components::Sampling::Interface]
|
88
107
|
def sampling
|
89
108
|
@sampling ||= Contrast::Components::Sampling::Interface.new
|
@@ -209,6 +228,13 @@ module Contrast
|
|
209
228
|
@_track_original_object
|
210
229
|
end
|
211
230
|
|
231
|
+
def track_response_as_source?
|
232
|
+
@track_response_as_source = !false?(enable_response_as_source) if
|
233
|
+
@track_response_as_source.nil?
|
234
|
+
|
235
|
+
@track_response_as_source
|
236
|
+
end
|
237
|
+
|
212
238
|
# The id for this process, based on the session metadata or id provided by the user, as indicated in
|
213
239
|
# application startup.
|
214
240
|
def session_id
|
@@ -234,6 +260,7 @@ module Contrast
|
|
234
260
|
end
|
235
261
|
|
236
262
|
# Sets Event limits from configuration and converts string numbers to integers.
|
263
|
+
# @param hsh [Hash] the configuration hash
|
237
264
|
def assign_limits hsh
|
238
265
|
return unless hsh
|
239
266
|
|
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
require 'contrast/config/diagnostics/tools'
|
5
5
|
require 'contrast/utils/object_share'
|
6
|
+
require 'contrast/utils/duck_utils'
|
6
7
|
|
7
8
|
module Contrast
|
8
9
|
module Components
|
@@ -89,12 +90,13 @@ module Contrast
|
|
89
90
|
add_effective_config_values(effective_config, config_values, canon_name, "#{ CONTRAST }.#{ canon_name }")
|
90
91
|
end
|
91
92
|
|
92
|
-
# attempts to
|
93
|
+
# attempts to stringify the config value if it is an array with the join char
|
94
|
+
#
|
93
95
|
# @param val[Object] val to stringify
|
94
96
|
# @param join_char[String, ','] join character defaults to ','
|
95
97
|
# @return [String, Object] the stringified val or the object as is
|
96
98
|
def stringify_array val, join_char = ','
|
97
|
-
return val.join(join_char) if val.cs__is_a?(Array)
|
99
|
+
return val.join(join_char) if val.cs__is_a?(Array) && val.any?
|
98
100
|
|
99
101
|
val
|
100
102
|
end
|
@@ -24,7 +24,6 @@ module Contrast
|
|
24
24
|
# it should break LOUDLY. Better to waste half an hour of the sysadmin's
|
25
25
|
# time than to silently fail to deliver functionality.
|
26
26
|
module Config
|
27
|
-
CONTRAST_ENV_MARKER = 'CONTRAST__'
|
28
27
|
CONTRAST_LOG = 'contrast.log'
|
29
28
|
CONTRAST_NAME = 'Contrast Agent'
|
30
29
|
DATE_TIME = '%Y-%m-%dT%H:%M:%S.%L%z'
|
@@ -180,7 +179,8 @@ module Contrast
|
|
180
179
|
# For env variables resembling CONTRAST__WHATEVER__NESTED_VALUE
|
181
180
|
# override raw.whatever.nested_value
|
182
181
|
ENV.each do |env_key, env_value|
|
183
|
-
next unless env_key.to_s.start_with?(CONTRAST_ENV_MARKER)
|
182
|
+
next unless env_key.to_s.start_with?(Contrast::Configuration::CONTRAST_ENV_MARKER)
|
183
|
+
next if Contrast::Configuration::DEPRECATED_PROPERTIES.include?(env_key.to_s)
|
184
184
|
|
185
185
|
config_item = Contrast::Utils::EnvConfigurationItem.new(env_key, env_value)
|
186
186
|
assign_value_to_path_array(self, config_item.dot_path_array, config_item.value)
|
@@ -10,7 +10,7 @@ module Contrast
|
|
10
10
|
module Protect
|
11
11
|
# A wrapper build around the Common Agent Configuration project to allow for access of the values contained in
|
12
12
|
# its parent_configuration_spec.yaml. Specifically, this allows for querying the state of the Protect product.
|
13
|
-
class Interface
|
13
|
+
class Interface # rubocop:disable Metrics/ClassLength
|
14
14
|
include Contrast::Components::ComponentBase
|
15
15
|
include Contrast::Config::BaseConfiguration
|
16
16
|
|
@@ -168,6 +168,19 @@ module Contrast
|
|
168
168
|
|
169
169
|
@_forcibly_enabled ||= true?(::Contrast::CONFIG.protect.enable)
|
170
170
|
end
|
171
|
+
|
172
|
+
# Used for conversion to contrast metrics hash:
|
173
|
+
def forcibly_enabled
|
174
|
+
forcibly_enabled?
|
175
|
+
end
|
176
|
+
|
177
|
+
def forcibly_disabled
|
178
|
+
forcibly_disabled?
|
179
|
+
end
|
180
|
+
|
181
|
+
def report_custom_code_sysfile_access
|
182
|
+
report_custom_code_sysfile_access?
|
183
|
+
end
|
171
184
|
end
|
172
185
|
end
|
173
186
|
end
|
@@ -156,7 +156,7 @@ module Contrast
|
|
156
156
|
def update_from_application_settings settings_response
|
157
157
|
return unless (app_settings = settings_response&.application_settings)
|
158
158
|
|
159
|
-
|
159
|
+
extract_protect_app_settings(app_settings)
|
160
160
|
update_exclusion_matchers(app_settings.exclusions)
|
161
161
|
app_settings.protect.virtual_patches = app_settings.protect.virtual_patches unless
|
162
162
|
settings_empty?(app_settings.protect.virtual_patches)
|
@@ -294,6 +294,16 @@ module Contrast
|
|
294
294
|
level[parts[-1]] = value
|
295
295
|
Contrast::CONFIG.sources.set(parts.join('.'), Contrast::Components::Config::Sources::CONTRAST_UI)
|
296
296
|
end
|
297
|
+
|
298
|
+
# Extract the rules modes from protection_rules or rules_settings fields.
|
299
|
+
#
|
300
|
+
# @param app_settings [Contrast::Agent::Reporting::Settings::ApplicationSettings]
|
301
|
+
def extract_protect_app_settings app_settings
|
302
|
+
modes_by_id = app_settings.protect.protection_rules_to_settings_hash
|
303
|
+
modes_by_id = app_settings.protect.rules_settings_to_settings_hash if settings_empty?(modes_by_id)
|
304
|
+
# Preserve previous state if no new settings are extracted:
|
305
|
+
@application_state.modes_by_id = modes_by_id unless settings_empty?(modes_by_id)
|
306
|
+
end
|
297
307
|
end
|
298
308
|
end
|
299
309
|
end
|
@@ -13,9 +13,9 @@ module Contrast
|
|
13
13
|
class << self
|
14
14
|
def command_line_settings
|
15
15
|
cli = Contrast::Config::Diagnostics::Tools.flatten_settings(Contrast::CONFIG.sources.
|
16
|
-
for(Contrast::Components::Config::Sources::COMMAND_LINE))
|
16
|
+
for(Contrast::Components::Config::Sources::COMMAND_LINE), cli: true)
|
17
17
|
|
18
|
-
Contrast::Config::Diagnostics::Tools.to_config_values(cli, source: true)
|
18
|
+
Contrast::Config::Diagnostics::Tools.to_config_values(cli, source: true, cli: true)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
@@ -21,7 +21,7 @@ module Contrast
|
|
21
21
|
# @return [Array] array of all the values needed to be written.
|
22
22
|
def environment_settings env
|
23
23
|
env_hash = env.select do |e|
|
24
|
-
e.to_s.start_with?(Contrast::
|
24
|
+
e.to_s.start_with?(Contrast::Configuration::CONTRAST_ENV_MARKER) || NON_COMMON_ENV.include?(e.to_s)
|
25
25
|
end
|
26
26
|
environment_settings = []
|
27
27
|
env_hash.each do |key, value|
|
@@ -41,6 +41,7 @@ module Contrast
|
|
41
41
|
Contrast::Utils::ObjectShare::EMPTY_STRING)
|
42
42
|
end
|
43
43
|
effective_value.value = Contrast::Config::Diagnostics::Tools.value_to_s(value)
|
44
|
+
effective_value.key = key
|
44
45
|
end
|
45
46
|
environment_settings << efc_value if efc_value
|
46
47
|
end
|
@@ -11,13 +11,15 @@ module Contrast
|
|
11
11
|
# Diagnostics tools to be included in config components.
|
12
12
|
module Tools
|
13
13
|
CHECK = 'd'
|
14
|
+
CONTRAST_MARK = 'CONTRAST_'
|
14
15
|
class << self
|
15
16
|
# Creates new config instances for each read config entry from the flat generated configs.
|
16
17
|
#
|
17
18
|
# @param flats [Array] of flatten configs produced by #flatten_settings
|
18
19
|
# @param source [Boolean] flag to set the desired value class, it may be a effective or source value.
|
20
|
+
# @param cli [Boolean] flag to check if the value comes from cli.
|
19
21
|
# @return [Array<Contrast::Config::Diagnostics::SourceConfigValue>]
|
20
|
-
def to_config_values flats, source: false
|
22
|
+
def to_config_values flats, source: false, cli: false
|
21
23
|
config_value_klass = if source
|
22
24
|
Contrast::Config::Diagnostics::SourceConfigValue
|
23
25
|
else
|
@@ -27,7 +29,11 @@ module Contrast
|
|
27
29
|
flats.each do |entry|
|
28
30
|
entry.each do |key, value|
|
29
31
|
efc_value = config_value_klass.new.tap do |config_value|
|
30
|
-
config_value.canonical_name = Contrast::Utils::ObjectShare::CONTRAST_DOT + key
|
32
|
+
config_value.canonical_name = Contrast::Utils::ObjectShare::CONTRAST_DOT + key unless cli
|
33
|
+
if cli && key.to_s.include?(CONTRAST_MARK)
|
34
|
+
config_value.canonical_name = key.gsub(Contrast::Utils::ObjectShare::DOUBLE_UNDERSCORE,
|
35
|
+
Contrast::Utils::ObjectShare::PERIOD).downcase
|
36
|
+
end
|
31
37
|
config_value.key = key
|
32
38
|
config_value.value = value_to_s(value)
|
33
39
|
end
|
@@ -40,17 +46,21 @@ module Contrast
|
|
40
46
|
# Flattens out the read settings from file, env or contrast ui.
|
41
47
|
# example: {"agent.polling.server_settings_ms"=>"50000"}
|
42
48
|
#
|
49
|
+
# If cli is set we avoid adding the path and additional '.' to the key.
|
50
|
+
#
|
43
51
|
# @param data [Hash, nil]
|
44
52
|
# @param path [String] where to look for settings.
|
45
53
|
# @param config [Hash] symbolized config to fetch keys from.
|
46
|
-
|
54
|
+
# @param cli [Boolean] does the config come from cli.
|
55
|
+
def flatten_settings data, path = [], config: Contrast::CONFIG.config.loaded_config, cli: false
|
47
56
|
return [] unless data
|
48
57
|
|
49
58
|
data.each_with_object([]) do |(k, v), entries|
|
50
59
|
if v.cs__is_a?(Hash)
|
51
60
|
entries.concat(flatten_settings(v, path.dup.append(k.to_sym)))
|
52
61
|
else
|
53
|
-
entries << {
|
62
|
+
entries << { k.to_s => config.dig(*path, k).to_s } if cli
|
63
|
+
entries << { "#{ path.join('.') }.#{ k }" => config.dig(*path, k).to_s } unless cli
|
54
64
|
end
|
55
65
|
end.flatten # rubocop:disable Style/MethodCalledOnDoEndBlock
|
56
66
|
end
|
@@ -62,7 +72,7 @@ module Contrast
|
|
62
72
|
return if value.nil?
|
63
73
|
return value if value.cs__is_a?(String)
|
64
74
|
|
65
|
-
value
|
75
|
+
value&.each_with_object({}) do |(k, v), m| # rubocop:disable Style/HashTransformValues
|
66
76
|
m[k] = if v.cs__is_a?(Hash)
|
67
77
|
value_to_s(v)
|
68
78
|
elsif v.cs__is_a?(Array)
|
@@ -64,44 +64,29 @@ module Contrast
|
|
64
64
|
KEYS_TO_REDACT = %i[api_key url service_key user_name].cs__freeze
|
65
65
|
REDACTED = '**REDACTED**'
|
66
66
|
|
67
|
-
|
67
|
+
DEPRECATED_PROPERTIES = %w[
|
68
|
+
CONTRAST__AGENT__SERVICE__ENABLE CONTRAST__AGENT__SERVICE__LOGGER__LEVEL
|
69
|
+
CONTRAST__AGENT__SERVICE__LOGGER__PATH CONTRAST__AGENT__SERVICE__LOGGER__STDOUT
|
70
|
+
].cs__freeze
|
71
|
+
|
72
|
+
def initialize cli_options = nil, default_name = DEFAULT_YAML_PATH
|
68
73
|
@default_name = default_name
|
69
74
|
|
70
75
|
# Load config_kv from file
|
71
76
|
config_kv = Contrast::Utils::HashUtils.deep_symbolize_all_keys(load_config)
|
72
|
-
unless cli_options
|
73
|
-
cli_options = {}
|
74
|
-
ENV.each do |key, value|
|
75
|
-
next unless key.to_s.start_with?(CONTRAST_ENV_MARKER)
|
76
77
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
# Overlay CLI options - they take precedence over config file
|
82
|
-
cli_options = Contrast::Utils::HashUtils.deep_symbolize_all_keys(cli_options)
|
83
|
-
if cli_options
|
84
|
-
config_kv = Contrast::Utils::HashUtils.precedence_merge(cli_options, config_kv)
|
85
|
-
@_source_file_extensions = Contrast::Utils::HashUtils.
|
86
|
-
precedence_merge(assign_source_to(cli_options,
|
87
|
-
Contrast::Components::Config::Sources::COMMAND_LINE),
|
88
|
-
@_source_file_extensions)
|
89
|
-
end
|
78
|
+
# Load cli options from env
|
79
|
+
cli_options ||= cli_to_hash
|
80
|
+
config_kv = Contrast::Utils::HashUtils.precedence_merge(config_kv, cli_options)
|
81
|
+
update_sources_from_cli(cli_options)
|
90
82
|
|
91
83
|
# Some in-flight rewrites to maintain backwards compatibility
|
92
84
|
config_kv = update_prop_keys(config_kv)
|
85
|
+
@sources = Contrast::Components::Config::Sources.new(source_file_extensions)
|
93
86
|
@loaded_config = config_kv
|
94
87
|
|
95
|
-
|
96
|
-
|
97
|
-
@api = Contrast::Components::Api::Interface.new(config_kv[:api])
|
98
|
-
@enable = config_kv[:enable]
|
99
|
-
@agent = Contrast::Components::Agent::Interface.new(config_kv[:agent])
|
100
|
-
@application = Contrast::Components::AppContext::Interface.new(config_kv[:application])
|
101
|
-
@server = Contrast::Config::ServerConfiguration.new(config_kv[:server])
|
102
|
-
@assess = Contrast::Components::Assess::Interface.new(config_kv[:assess])
|
103
|
-
@inventory = Contrast::Components::Inventory::Interface.new(config_kv[:inventory])
|
104
|
-
@protect = Contrast::Components::Protect::Interface.new(config_kv[:protect])
|
88
|
+
# requires loaded_config:
|
89
|
+
create_config_components
|
105
90
|
end
|
106
91
|
|
107
92
|
# Get a loggable YAML format of this configuration
|
@@ -155,7 +140,7 @@ module Contrast
|
|
155
140
|
# reverse order of precedence (first is most important).
|
156
141
|
def configuration_paths
|
157
142
|
@_configuration_paths ||= begin
|
158
|
-
basename = default_name.split('.')
|
143
|
+
basename = default_name.split('.')[0]
|
159
144
|
# Order of extensions comes from here:
|
160
145
|
extensions = Contrast::Components::Config::Sources::APP_CONFIGURATION_EXTENSIONS
|
161
146
|
|
@@ -263,6 +248,18 @@ module Contrast
|
|
263
248
|
|
264
249
|
private
|
265
250
|
|
251
|
+
# Creates and updates the config components with the loaded config values.
|
252
|
+
def create_config_components
|
253
|
+
@api = Contrast::Components::Api::Interface.new(loaded_config[:api])
|
254
|
+
@enable = loaded_config[:enable]
|
255
|
+
@agent = Contrast::Components::Agent::Interface.new(loaded_config[:agent])
|
256
|
+
@application = Contrast::Components::AppContext::Interface.new(loaded_config[:application])
|
257
|
+
@server = Contrast::Config::ServerConfiguration.new(loaded_config[:server])
|
258
|
+
@assess = Contrast::Components::Assess::Interface.new(loaded_config[:assess])
|
259
|
+
@inventory = Contrast::Components::Inventory::Interface.new(loaded_config[:inventory])
|
260
|
+
@protect = Contrast::Components::Protect::Interface.new(loaded_config[:protect])
|
261
|
+
end
|
262
|
+
|
266
263
|
# We cannot use all access components at this point, unfortunately, as they
|
267
264
|
# may not have been initialized. Instead, we need to access the logger
|
268
265
|
# directly.
|
@@ -367,5 +364,40 @@ module Contrast
|
|
367
364
|
end
|
368
365
|
end
|
369
366
|
end
|
367
|
+
|
368
|
+
# Update the source mapping to reflect the cli values passed. Using raw string rather than path values.
|
369
|
+
#
|
370
|
+
# @param cli_options[Hash<Symbol, String>]
|
371
|
+
def update_sources_from_cli cli_options
|
372
|
+
@_source_file_extensions = Contrast::Utils::HashUtils.
|
373
|
+
precedence_merge(assign_source_to(cli_options,
|
374
|
+
Contrast::Components::Config::Sources::COMMAND_LINE),
|
375
|
+
@_source_file_extensions)
|
376
|
+
end
|
377
|
+
|
378
|
+
# Find all the set Contrast environment variables and cast them to their hash form. Keys will be split on __ and
|
379
|
+
# converted to symbols to match parsing of the YAML file
|
380
|
+
#
|
381
|
+
# @return [Hash<Symbol, (Hash, String)>]
|
382
|
+
def cli_to_hash
|
383
|
+
cli_options ||= ENV.select do |name, _value|
|
384
|
+
name.to_s.start_with?(CONTRAST_ENV_MARKER) && !DEPRECATED_PROPERTIES.include?(name.to_s)
|
385
|
+
end
|
386
|
+
|
387
|
+
converted = {}
|
388
|
+
cli_options&.each do |key, value|
|
389
|
+
# Split the env key into path components
|
390
|
+
path = key.to_s.split(Contrast::Utils::ObjectShare::DOUBLE_UNDERSCORE)
|
391
|
+
# Remove the `CONTRAST` start
|
392
|
+
path&.shift
|
393
|
+
# Convert it to hash form, with lowercase symbol keys
|
394
|
+
as_hash = path&.reverse&.reduce(value) do |assigned_value, path_segment|
|
395
|
+
{ path_segment.downcase.to_sym => assigned_value }
|
396
|
+
end
|
397
|
+
# And join it w/ the parsed keys
|
398
|
+
Contrast::Utils::HashUtils.precedence_merge!(converted, as_hash)
|
399
|
+
end
|
400
|
+
converted
|
401
|
+
end
|
370
402
|
end
|
371
403
|
end
|
@@ -17,8 +17,8 @@ module Contrast
|
|
17
17
|
ENV.each do |env_key, env_value|
|
18
18
|
env_key = env_key.to_s
|
19
19
|
next unless ENV_KEYS.include?(env_key) ||
|
20
|
-
(env_key.start_with?(Contrast::
|
21
|
-
!env_key.start_with?("#{ Contrast::
|
20
|
+
(env_key.start_with?(Contrast::Configuration::CONTRAST_ENV_MARKER) &&
|
21
|
+
!env_key.start_with?("#{ Contrast::Configuration::CONTRAST_ENV_MARKER }API"))
|
22
22
|
|
23
23
|
info('Environment settings', key: env_key, value: env_value)
|
24
24
|
end
|
@@ -30,7 +30,7 @@ module Contrast
|
|
30
30
|
loggable = ::Contrast::CONFIG.loggable
|
31
31
|
info('Current configuration', configuration: loggable)
|
32
32
|
env_keys = ENV.keys.select do |env_key|
|
33
|
-
env_key&.to_s&.start_with?(Contrast::
|
33
|
+
env_key&.to_s&.start_with?(Contrast::Configuration::CONTRAST_ENV_MARKER)
|
34
34
|
end
|
35
35
|
env_items = env_keys.map { |env_key| Contrast::Utils::EnvConfigurationItem.new(env_key, nil) }
|
36
36
|
env_translations = env_items.each_with_object({}) do |conversion, hash|
|