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