contrast-agent 7.2.0 → 7.3.1
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/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/protect/input_analyzer/input_analyzer.rb +62 -23
- data/lib/contrast/agent/protect/input_analyzer/worth_watching_analyzer.rb +37 -4
- data/lib/contrast/agent/protect/rule/base.rb +5 -1
- data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker_input_classification.rb +27 -11
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +0 -1
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_input_classification.rb +2 -2
- data/lib/contrast/agent/protect/rule/input_classification/base.rb +191 -0
- data/lib/contrast/agent/protect/rule/input_classification/base64_statistic.rb +71 -0
- data/lib/contrast/agent/protect/rule/input_classification/cached_result.rb +37 -0
- data/lib/contrast/agent/protect/rule/input_classification/encoding.rb +109 -0
- data/lib/contrast/agent/protect/rule/input_classification/encoding_rates.rb +47 -0
- data/lib/contrast/agent/protect/rule/input_classification/extendable.rb +80 -0
- data/lib/contrast/agent/protect/rule/input_classification/lru_cache.rb +198 -0
- data/lib/contrast/agent/protect/rule/input_classification/match_rates.rb +66 -0
- data/lib/contrast/agent/protect/rule/input_classification/rates.rb +53 -0
- data/lib/contrast/agent/protect/rule/input_classification/statistics.rb +115 -0
- data/lib/contrast/agent/protect/rule/input_classification/utils.rb +23 -0
- data/lib/contrast/agent/protect/rule/no_sqli/no_sqli_input_classification.rb +17 -7
- data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_input_classification.rb +18 -15
- data/lib/contrast/agent/protect/rule/sqli/sqli_input_classification.rb +2 -2
- data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload_input_classification.rb +18 -15
- data/lib/contrast/agent/protect/rule/xss/reflected_xss_input_classification.rb +19 -17
- data/lib/contrast/agent/reporting/attack_result/attack_result.rb +6 -0
- data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +2 -7
- data/lib/contrast/agent/reporting/input_analysis/input_analysis_result.rb +11 -0
- data/lib/contrast/agent/reporting/input_analysis/input_type.rb +33 -1
- data/lib/contrast/agent/reporting/masker/masker_utils.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +1 -0
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +1 -0
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +1 -1
- data/lib/contrast/agent/telemetry/base.rb +28 -2
- data/lib/contrast/agent/telemetry/base64_hash.rb +55 -0
- data/lib/contrast/agent/telemetry/cache_hash.rb +55 -0
- data/lib/contrast/agent/telemetry/client.rb +10 -2
- data/lib/contrast/agent/telemetry/exception/obfuscate.rb +4 -3
- data/lib/contrast/agent/telemetry/{hash.rb → exception_hash.rb} +1 -1
- data/lib/contrast/agent/telemetry/identifier.rb +13 -26
- data/lib/contrast/agent/telemetry/input_analysis_cache_event.rb +27 -0
- data/lib/contrast/agent/telemetry/input_analysis_encoding_event.rb +26 -0
- data/lib/contrast/agent/telemetry/input_analysis_event.rb +91 -0
- data/lib/contrast/agent/telemetry/metric_event.rb +12 -0
- data/lib/contrast/agent/telemetry/startup_metrics_event.rb +0 -8
- 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 +6 -6
- data/lib/contrast/components/protect.rb +11 -1
- data/lib/contrast/components/sampling.rb +15 -10
- data/lib/contrast/config/diagnostics/command_line.rb +2 -2
- data/lib/contrast/config/diagnostics/environment_variables.rb +5 -2
- data/lib/contrast/config/diagnostics/tools.rb +15 -5
- data/lib/contrast/config/yaml_file.rb +8 -0
- data/lib/contrast/configuration.rb +61 -29
- data/lib/contrast/framework/rails/support.rb +3 -0
- data/lib/contrast/logger/application.rb +3 -3
- data/lib/contrast/utils/assess/event_limit_utils.rb +13 -13
- data/lib/contrast/utils/assess/propagation_method_utils.rb +2 -0
- data/lib/contrast/utils/metrics_hash.rb +1 -1
- data/lib/contrast/utils/object_share.rb +2 -1
- data/lib/contrast/utils/os.rb +1 -9
- data/lib/contrast/utils/response_utils.rb +12 -0
- data/lib/contrast/utils/timer.rb +2 -0
- data/lib/contrast.rb +9 -2
- data/resources/assess/policy.json +80 -3
- data/ruby-agent.gemspec +1 -1
- metadata +22 -6
- data/lib/contrast/utils/input_classification_base.rb +0 -169
@@ -3,6 +3,8 @@
|
|
3
3
|
|
4
4
|
require 'contrast/utils/metrics_hash'
|
5
5
|
require 'contrast/agent/telemetry/event'
|
6
|
+
require 'contrast/utils/duck_utils'
|
7
|
+
require 'contrast/utils/os'
|
6
8
|
|
7
9
|
module Contrast
|
8
10
|
module Agent
|
@@ -10,6 +12,7 @@ module Contrast
|
|
10
12
|
# This class will hold the basic information for a Telemetry Event
|
11
13
|
class MetricEvent < Contrast::Agent::Telemetry::Event
|
12
14
|
include Contrast::Utils
|
15
|
+
include Contrast::Utils::OS
|
13
16
|
|
14
17
|
attr_reader :fields
|
15
18
|
|
@@ -19,6 +22,15 @@ module Contrast
|
|
19
22
|
@fields['_filler'] = 0
|
20
23
|
end
|
21
24
|
|
25
|
+
def sys_info
|
26
|
+
@sys_info ||= get_system_information if @sys_info.nil?
|
27
|
+
@sys_info
|
28
|
+
end
|
29
|
+
|
30
|
+
def empty?
|
31
|
+
Contrast::Utils::DuckUtils.empty_duck?(@fields)
|
32
|
+
end
|
33
|
+
|
22
34
|
def to_controlled_hash **_args
|
23
35
|
super.merge!({ fields: @fields })
|
24
36
|
end
|
@@ -4,7 +4,6 @@
|
|
4
4
|
require 'contrast/utils/metrics_hash'
|
5
5
|
require 'contrast/agent/telemetry/metric_event'
|
6
6
|
require 'contrast/agent/version'
|
7
|
-
require 'contrast/utils/os'
|
8
7
|
|
9
8
|
module Contrast
|
10
9
|
module Agent
|
@@ -15,8 +14,6 @@ module Contrast
|
|
15
14
|
# application framework and version and server framework
|
16
15
|
# It will be initialized and send in Middleware#agent_startup_routine
|
17
16
|
class StartupMetricsEvent < Contrast::Agent::Telemetry::MetricEvent
|
18
|
-
include Contrast::Utils::OS
|
19
|
-
|
20
17
|
APP_AND_SERVER_DATA = ::Contrast::APP_CONTEXT.app_and_server_information.cs__freeze
|
21
18
|
# Multi-tenant Production Environments
|
22
19
|
SAAS_DEFAULT = { addr: 'app.contrastsecurity.com', type: 'SAAS_DEFAULT' }.cs__freeze
|
@@ -82,11 +79,6 @@ module Contrast
|
|
82
79
|
end
|
83
80
|
end
|
84
81
|
|
85
|
-
def sys_info
|
86
|
-
@sys_info ||= get_system_information if @sys_info.nil?
|
87
|
-
@sys_info
|
88
|
-
end
|
89
|
-
|
90
82
|
private
|
91
83
|
|
92
84
|
# Here we extract the TeamServer url type
|
@@ -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,8 +24,7 @@ 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
|
-
|
28
|
-
CONTRAST_LOG = 'contrast_agent.log'
|
27
|
+
CONTRAST_LOG = 'contrast.log'
|
29
28
|
CONTRAST_NAME = 'Contrast Agent'
|
30
29
|
DATE_TIME = '%Y-%m-%dT%H:%M:%S.%L%z'
|
31
30
|
|
@@ -63,7 +62,7 @@ module Contrast
|
|
63
62
|
env_overrides
|
64
63
|
validate
|
65
64
|
rescue ArgumentError => e
|
66
|
-
proto_logger.error('Configuration failed with error: ', e)
|
65
|
+
proto_logger.error('[PROTO_LOGGER] Configuration failed with error: ', e)
|
67
66
|
end
|
68
67
|
alias_method :rebuild, :build
|
69
68
|
|
@@ -157,7 +156,7 @@ module Contrast
|
|
157
156
|
# @return [boolean]
|
158
157
|
def valid_session_metadata?
|
159
158
|
if !session_id&.empty? && !session_metadata&.empty?
|
160
|
-
proto_logger.error(SESSION_VARIABLES)
|
159
|
+
proto_logger.error("[PROTO_LOGGER] #{ SESSION_VARIABLES }")
|
161
160
|
return false
|
162
161
|
end
|
163
162
|
true
|
@@ -172,7 +171,7 @@ module Contrast
|
|
172
171
|
msg << API_KEY unless api_key
|
173
172
|
msg << API_SERVICE_KEY unless api_service_key
|
174
173
|
msg << API_USERNAME unless api_username
|
175
|
-
msg.any? { |m| proto_logger.error(m) }
|
174
|
+
msg.any? { |m| proto_logger.error("[PROTO_LOGGER] #{ m }") }
|
176
175
|
msg.empty?
|
177
176
|
end
|
178
177
|
|
@@ -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)
|
@@ -15,12 +15,14 @@ module Contrast
|
|
15
15
|
include Contrast::Config::BaseConfiguration
|
16
16
|
|
17
17
|
CANON_NAME = 'protect'
|
18
|
-
CONFIG_VALUES = %w[enabled?].cs__freeze
|
18
|
+
CONFIG_VALUES = %w[enabled? normalize_base64?].cs__freeze
|
19
19
|
RULES = 'rules'
|
20
20
|
MODE = 'mode'
|
21
21
|
|
22
22
|
# @return [Boolean, nil]
|
23
23
|
attr_accessor :enable
|
24
|
+
# @return [Boolean, nil]
|
25
|
+
attr_accessor :normalize_base64
|
24
26
|
# @return [String]
|
25
27
|
attr_reader :canon_name
|
26
28
|
# @return [Array]
|
@@ -36,6 +38,7 @@ module Contrast
|
|
36
38
|
@_exceptions = Contrast::Config::ExceptionConfiguration.new(hsh[:exceptions])
|
37
39
|
@_rules = Contrast::Config::ProtectRulesConfiguration.new(hsh[:rules])
|
38
40
|
@enable = hsh[:enable]
|
41
|
+
@normalize_base64 = hsh[:normalize_base64]
|
39
42
|
@agent_lib = hsh[:agent_lib]
|
40
43
|
end
|
41
44
|
|
@@ -68,6 +71,13 @@ module Contrast
|
|
68
71
|
::Contrast::SETTINGS.protect_state.enabled == true
|
69
72
|
end
|
70
73
|
|
74
|
+
# Check to determine if the base64 decoding is required for user inputs.
|
75
|
+
def normalize_base64?
|
76
|
+
@normalize_base64 = Contrast::CONFIG.protect.normalize_base64 if @normalize_base64.nil?
|
77
|
+
|
78
|
+
true?(@normalize_base64)
|
79
|
+
end
|
80
|
+
|
71
81
|
# Current Configuration for the protect rules
|
72
82
|
#
|
73
83
|
# @return [Contrast::Config::ProtectRulesConfiguration]
|
@@ -2,6 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'contrast/components/base'
|
5
|
+
require 'contrast/utils/duck_utils'
|
5
6
|
|
6
7
|
module Contrast
|
7
8
|
module Components
|
@@ -27,7 +28,7 @@ module Contrast
|
|
27
28
|
config_settings = ::Contrast::CONFIG.assess&.sampling
|
28
29
|
settings = ::Contrast::SETTINGS&.assess_state&.sampling_settings
|
29
30
|
{
|
30
|
-
enabled: enabled?(config_settings, settings),
|
31
|
+
enabled: enabled?(config_settings&.enable, settings&.enabled),
|
31
32
|
baseline: check_baseline(config_settings, settings),
|
32
33
|
request_frequency: check_request_frequency(config_settings, settings),
|
33
34
|
response_frequency: check_response_frequency(config_settings, settings),
|
@@ -43,13 +44,20 @@ module Contrast
|
|
43
44
|
|
44
45
|
private
|
45
46
|
|
46
|
-
# @param
|
47
|
+
# @param config_value [Boolean, nil] the Sampling configuration as provided by
|
47
48
|
# local user input
|
48
|
-
# @param
|
49
|
+
# @param settings_value [Boolean, nil] the Sampling settings as provided by
|
49
50
|
# TeamServer
|
50
51
|
# @return [Boolean] the resolution of the config_settings, settings, and default value
|
51
|
-
def enabled?
|
52
|
-
|
52
|
+
def enabled? config_value, settings_value
|
53
|
+
# Check the Assess State received from TS:
|
54
|
+
sampling_enable = settings_value unless Contrast::Utils::DuckUtils.empty_duck?(settings_value)
|
55
|
+
# Check for local settings, YAML, ENV, CLI (it's with higher priority than Web Interface)
|
56
|
+
# see: https://docs.contrastsecurity.com/en/order-of-precedence.html
|
57
|
+
sampling_enable = config_value unless Contrast::Utils::DuckUtils.empty_duck?(config_value)
|
58
|
+
# Use default value, unless a false or true is set from local or TS settings.
|
59
|
+
sampling_enable = DEFAULT_SAMPLING_ENABLED if Contrast::Utils::DuckUtils.empty_duck?(sampling_enable)
|
60
|
+
true?(sampling_enable)
|
53
61
|
end
|
54
62
|
|
55
63
|
# @param config_settings [Contrast::Config::SamplingConfiguration] the Sampling configuration as provided by
|
@@ -106,6 +114,8 @@ module Contrast
|
|
106
114
|
NAME_PREFIX = "#{ CONTRAST }.#{ CANON_NAME }".cs__freeze
|
107
115
|
CONFIG_VALUES = %w[enable baseline request_frequency response_frequency window_ms].cs__freeze
|
108
116
|
|
117
|
+
# @return [Boolean, nil]
|
118
|
+
attr_accessor :enable
|
109
119
|
# @return [Integer, nil]
|
110
120
|
attr_accessor :baseline
|
111
121
|
# @return [Integer, nil]
|
@@ -128,11 +138,6 @@ module Contrast
|
|
128
138
|
@window_ms = hsh[:window_ms]
|
129
139
|
end
|
130
140
|
|
131
|
-
# @return [Boolean, false]
|
132
|
-
def enable
|
133
|
-
!!@enable
|
134
|
-
end
|
135
|
-
|
136
141
|
# Converts current configuration to effective config values class and appends them to
|
137
142
|
# EffectiveConfig class.
|
138
143
|
#
|
@@ -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
|
@@ -11,7 +11,9 @@ module Contrast
|
|
11
11
|
# Reads All ENV variables.
|
12
12
|
module EnvironmentVariables
|
13
13
|
class << self
|
14
|
-
NON_COMMON_ENV = %w[
|
14
|
+
NON_COMMON_ENV = %w[
|
15
|
+
CONTRAST_CONFIG_PATH CONTRAST_AGENT_TELEMETRY_OPTOUT CONTRAST_AGENT_TELEMETRY_TEST
|
16
|
+
].cs__freeze
|
15
17
|
|
16
18
|
# This method will fill the canonical name for each env var and will check for any uncommon ones.
|
17
19
|
#
|
@@ -19,7 +21,7 @@ module Contrast
|
|
19
21
|
# @return [Array] array of all the values needed to be written.
|
20
22
|
def environment_settings env
|
21
23
|
env_hash = env.select do |e|
|
22
|
-
e.to_s.start_with?(Contrast::
|
24
|
+
e.to_s.start_with?(Contrast::Configuration::CONTRAST_ENV_MARKER) || NON_COMMON_ENV.include?(e.to_s)
|
23
25
|
end
|
24
26
|
environment_settings = []
|
25
27
|
env_hash.each do |key, value|
|
@@ -39,6 +41,7 @@ module Contrast
|
|
39
41
|
Contrast::Utils::ObjectShare::EMPTY_STRING)
|
40
42
|
end
|
41
43
|
effective_value.value = Contrast::Config::Diagnostics::Tools.value_to_s(value)
|
44
|
+
effective_value.key = key
|
42
45
|
end
|
43
46
|
environment_settings << efc_value if efc_value
|
44
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)
|
@@ -11,6 +11,8 @@ module Contrast
|
|
11
11
|
# instrumentation.
|
12
12
|
module YamlFile
|
13
13
|
CONFIG_FILE_NAME = 'contrast_security'
|
14
|
+
CONTRAST_ENV_MARKER = 'CONTRAST__'
|
15
|
+
|
14
16
|
EXT = { yml: 'yml', yaml: 'yaml' }.freeze # rubocop:disable Security/Object/Freeze
|
15
17
|
|
16
18
|
POSSIBLE_TARGET_PATHS = %w[
|
@@ -89,6 +91,8 @@ module Contrast
|
|
89
91
|
#
|
90
92
|
def create
|
91
93
|
# rubocop:disable Rails/Output
|
94
|
+
return puts("\u{02C3} Contrast configuration set by ENV variables.") if env_config_set?
|
95
|
+
|
92
96
|
puts("\u{1F48E} Generating: Contrast Configuration file.")
|
93
97
|
if Contrast::Config::YamlFile.created?
|
94
98
|
puts("\u{2705} Configuration file already exists: #{ Contrast::Config::YamlFile.find! }")
|
@@ -123,6 +127,10 @@ module Contrast
|
|
123
127
|
def file_name
|
124
128
|
ENV['CONTRAST_YAML_FILE_TEST_CREATE_CONFIG_FILE_NAME_VALUE'] || CONFIG_FILE_NAME
|
125
129
|
end
|
130
|
+
|
131
|
+
def env_config_set?
|
132
|
+
ENV.keys&.select { |config| config.include?(CONTRAST_ENV_MARKER) }&.any?
|
133
|
+
end
|
126
134
|
end
|
127
135
|
end
|
128
136
|
end
|
@@ -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
|
@@ -4,6 +4,7 @@
|
|
4
4
|
require 'contrast/framework/base_support'
|
5
5
|
require 'contrast/framework/rails/patch/support'
|
6
6
|
require 'contrast/utils/string_utils'
|
7
|
+
require 'contrast/utils/duck_utils'
|
7
8
|
|
8
9
|
module Contrast
|
9
10
|
module Framework
|
@@ -129,6 +130,8 @@ module Contrast
|
|
129
130
|
if engine_route?(route)
|
130
131
|
new_req = retrieve_request(request.env)
|
131
132
|
new_req.path_info = new_req.path_info.gsub(match.to_s, '')
|
133
|
+
# solves the issue when requiring base path '/' without the slash
|
134
|
+
new_req.path_info = '/' if Contrast::Utils::DuckUtils.empty_duck?(new_req.path_info)
|
132
135
|
get_full_route(new_req, route.app.app.routes.router, path << match.to_s)
|
133
136
|
else
|
134
137
|
[match, params, route, path]
|
@@ -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|
|