contrast-agent 7.3.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/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/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/resources/assess/policy.json +80 -3
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4223bb2218df4bdf98b2600c77c4fa148e71b830575e938a968d41e89138fb81
|
4
|
+
data.tar.gz: cc559a6b364c5019d9c50d8ee6b8237486ac1596ec8ade76d8e6ae4e51e1b906
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6b88f94ad140a8dd0e8099e2d4f3a396f2221de6ba9a3cbdfaa7e9327644703b7dc275c369ffe4efa29109619bf9eb182f2a0f7b85103c67e2a83246c49c137a
|
7
|
+
data.tar.gz: eaf65ba9546f37ace248a29657e690966deaf02aa26426b169f1f3e9639aa3f7b355ea858afd4ef0a5787bd9e2549d7927b3db89894eb6ef4bcd26daf01382e9
|
@@ -16,6 +16,7 @@ module Contrast
|
|
16
16
|
class PolicyNode < Contrast::Agent::Patching::Policy::PolicyNode
|
17
17
|
include Contrast::Components::Logger::InstanceMethods
|
18
18
|
include PolicyNodeUtils
|
19
|
+
|
19
20
|
JSON_TAGS = 'tags'
|
20
21
|
JSON_DATAFLOW = 'dataflow'
|
21
22
|
# The keys used to read from policy.json to create the individual
|
@@ -48,6 +49,9 @@ module Contrast
|
|
48
49
|
].cs__freeze
|
49
50
|
TO_S = %w[to_s to_str].cs__freeze
|
50
51
|
|
52
|
+
# Here are all Responses that will be tracked as sources, or methods they use, like body.
|
53
|
+
RESPONSE_SOURCES = %w[Net::HTTPResponse Rack::Response Sinatra::Response].cs__freeze
|
54
|
+
|
51
55
|
def initialize policy_hash = {}
|
52
56
|
super(policy_hash)
|
53
57
|
@source_string = policy_hash[JSON_SOURCE]
|
@@ -57,13 +61,14 @@ module Contrast
|
|
57
61
|
@targets = convert_policy_markers(target_string)
|
58
62
|
@_use_original_object = ORIGINAL_OBJECT_METHODS.include?(@method_name)
|
59
63
|
@_use_original_on_bang_method = assign_on_bang_check(policy_hash)
|
64
|
+
@_use_response_as_source = RESPONSE_SOURCES.include?(@class_name)
|
60
65
|
end
|
61
66
|
|
67
|
+
# If we have KEEP action on String, and the method is to_s, that method would return self:
|
68
|
+
# String#to_s => self or string. This method is included here to cover the situations such as
|
69
|
+
# String.to_s.html_safe, where normally the dynamic sources properties get lost. To solve this
|
70
|
+
# we will simply return the original object here.
|
62
71
|
def assign_on_bang_check policy_hash
|
63
|
-
# If we have KEEP action on String, and the method is to_s, that method would return self:
|
64
|
-
# String#to_s => self or string. This method is included here to cover the situations such as
|
65
|
-
# String.to_s.html_safe, where normally the dynamic sources properties get lost. To solve this
|
66
|
-
# we will simply return the original object here.
|
67
72
|
return true if @_use_original_object && TO_S.include?(policy_hash[JSON_METHOD_NAME])
|
68
73
|
|
69
74
|
@_use_original_object &&
|
@@ -166,7 +171,7 @@ module Contrast
|
|
166
171
|
# that the method is without bang - it does not change the source, but rather
|
167
172
|
# creates a copy of it.
|
168
173
|
#
|
169
|
-
# @return
|
174
|
+
# @return [Boolean]
|
170
175
|
def use_original_object?
|
171
176
|
@_use_original_object && Contrast::ASSESS.track_original_object?
|
172
177
|
end
|
@@ -175,10 +180,24 @@ module Contrast
|
|
175
180
|
# that the target return is the same as object - a bang method modifying the
|
176
181
|
# source.
|
177
182
|
#
|
178
|
-
# @return
|
183
|
+
# @return [Boolean]
|
179
184
|
def use_original_on_bang_method?
|
180
185
|
@_use_original_on_bang_method && Contrast::ASSESS.track_original_object?
|
181
186
|
end
|
187
|
+
|
188
|
+
# This method will check if policy is fit to use response as source.
|
189
|
+
#
|
190
|
+
# @return [Boolean]
|
191
|
+
def use_response_as_source?
|
192
|
+
Contrast::ASSESS.track_response_as_source?
|
193
|
+
end
|
194
|
+
|
195
|
+
# This method will check if the policy node is for response method.
|
196
|
+
#
|
197
|
+
# @return [Boolean]
|
198
|
+
def response_source_node?
|
199
|
+
@_use_response_as_source
|
200
|
+
end
|
182
201
|
end
|
183
202
|
end
|
184
203
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/agent/assess/policy/propagator/select'
|
5
|
+
require 'contrast/utils/duck_utils'
|
6
|
+
|
7
|
+
module Contrast
|
8
|
+
module Agent
|
9
|
+
module Assess
|
10
|
+
module Policy
|
11
|
+
module Propagator
|
12
|
+
# Propagation that results in all the tags of the source being
|
13
|
+
# applied to the target at the point of insertion. The target's
|
14
|
+
# preexisting tags are shifted to account for this insertion.
|
15
|
+
class Response < Contrast::Agent::Assess::Policy::Propagator::Base
|
16
|
+
class << self
|
17
|
+
# This will path the Net::HTTP.request method. It takes two parameters:
|
18
|
+
# - req: Net::HTTPGenericRequest
|
19
|
+
# - body: String
|
20
|
+
# As body may be optional, we need to check if it's nil or not.
|
21
|
+
#
|
22
|
+
# @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode]
|
23
|
+
# @param preshift [Contrast::Agent::Assess::Preshift]
|
24
|
+
# @param ret [Object] Return targer from method invocation.
|
25
|
+
# @param _block [nil, {}] block passed.
|
26
|
+
def net_response_keep propagation_node, preshift, ret, _block
|
27
|
+
return unless Contrast::ASSESS.track_response_as_source?
|
28
|
+
|
29
|
+
# Check to see if the argument is of correct type, and whether the body is tracked or not.
|
30
|
+
# if it's tracked and the body is not nil, then copy the properties from the source's body
|
31
|
+
# to the target's body.
|
32
|
+
source_body = if preshift.args.length == 2
|
33
|
+
preshift.args[1]
|
34
|
+
else
|
35
|
+
preshift.args[0]&.body
|
36
|
+
end
|
37
|
+
copy_body_tags(propagation_node, source_body, ret)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Copy the properties form source body to the response body, if one is present.
|
43
|
+
#
|
44
|
+
# @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode]
|
45
|
+
# @param source_body [String] the tracked body to copy from.
|
46
|
+
# @param ret [String] the return target from method invocation.
|
47
|
+
# @return [String, nil]
|
48
|
+
def copy_body_tags propagation_node, source_body, ret
|
49
|
+
return if Contrast::Utils::DuckUtils.empty_duck?(source_body)
|
50
|
+
return unless ret&.body&.cs__is_a?(String)
|
51
|
+
return unless source_body&.cs__is_a?(String)
|
52
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret.body))
|
53
|
+
|
54
|
+
# KEEP
|
55
|
+
properties.copy_from(source_body, ret.body, 0, propagation_node.untags)
|
56
|
+
ret
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -31,6 +31,7 @@ module Contrast
|
|
31
31
|
require 'contrast/agent/assess/policy/propagator/substitution'
|
32
32
|
require 'contrast/agent/assess/policy/propagator/trim'
|
33
33
|
require 'contrast/agent/assess/policy/propagator/buffer'
|
34
|
+
require 'contrast/agent/assess/policy/propagator/response'
|
34
35
|
end
|
35
36
|
end
|
36
37
|
end
|
@@ -46,6 +46,11 @@ module Contrast
|
|
46
46
|
# Exclusions makes method slow:
|
47
47
|
return if excluded_by_url?
|
48
48
|
|
49
|
+
# Check to see if the source node is to be used for response as source.
|
50
|
+
if method_policy.source_node.response_source_node? && !method_policy.source_node.use_response_as_source?
|
51
|
+
return
|
52
|
+
end
|
53
|
+
|
49
54
|
# used to hold the object and ret
|
50
55
|
source_data = Contrast::Agent::Assess::Events::EventData.new(nil, nil, object, ret, nil)
|
51
56
|
|
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
require 'rack'
|
5
5
|
require 'contrast/utils/hash_digest'
|
6
|
+
require 'contrast/utils/duck_utils'
|
6
7
|
require 'contrast/utils/string_utils'
|
7
8
|
require 'contrast/agent/assess/rule/response/base_rule'
|
8
9
|
|
@@ -44,21 +45,35 @@ module Contrast
|
|
44
45
|
# @param element_start_str [String] element to find in html section
|
45
46
|
# @return [Array<Hash>] the found elements of this section, as well as their start and end indexes.
|
46
47
|
def html_elements section, element_start_str = '', capture_overflow: false
|
48
|
+
return [] unless section
|
49
|
+
return [] unless (potentials = potential_elements(section, element_start_str).flatten).any?
|
50
|
+
|
47
51
|
elements = []
|
48
52
|
section_start = 0
|
49
|
-
return [] unless section
|
50
53
|
|
51
|
-
|
54
|
+
potentials.each do |potential_element|
|
52
55
|
next unless potential_element
|
53
56
|
next unless element_openings.any? { |opening| potential_element.start_with?(opening) }
|
54
57
|
|
55
|
-
|
56
|
-
next
|
58
|
+
start = section&.index(element_start_str, section_start)
|
59
|
+
next if Contrast::Utils::DuckUtils.empty_duck?(start)
|
60
|
+
|
61
|
+
stop = potential_element.index('>').to_i
|
62
|
+
next if Contrast::Utils::DuckUtils.empty_duck?(stop)
|
57
63
|
|
58
|
-
|
59
|
-
|
64
|
+
section_close = start + 6 + stop
|
65
|
+
# Now we have valid tag section with start and stop.
|
66
|
+
# Save new boundaries. This is to make sure that If
|
67
|
+
# on previous iteration there were non valid section,
|
68
|
+
# the start_section will be assigned to nil, thus making
|
69
|
+
# the detection of new section not possible, and throwing
|
70
|
+
# an error. To that end old values are kept safe.
|
71
|
+
#
|
72
|
+
# Assign new start index.
|
73
|
+
section_start = start
|
74
|
+
# Assign new end index.
|
75
|
+
element_stop = stop
|
60
76
|
|
61
|
-
section_close = section_start + 6 + element_stop
|
62
77
|
elements << capture(section, section_start, section_close, element_stop, overflow: capture_overflow)
|
63
78
|
section_start = section_close
|
64
79
|
end
|
@@ -70,7 +70,10 @@ module Contrast
|
|
70
70
|
# @param response [Contrast::Agent::Response] the response of the application
|
71
71
|
# @return [Array<Hash<String,String>]
|
72
72
|
def cache_meta_tags response
|
73
|
-
|
73
|
+
head_tag = response.body&.split(HEAD_TAG)&.last
|
74
|
+
return [] unless head_tag
|
75
|
+
|
76
|
+
html_elements(head_tag, META_START_STR, capture_overflow: false).
|
74
77
|
select { |tag| cache_control_tag?(tag[HTML_PROP]) }
|
75
78
|
end
|
76
79
|
|
@@ -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)
|
@@ -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|
|
@@ -18,6 +18,7 @@ module Contrast
|
|
18
18
|
REPLACE_ACTION = 'REPLACE'
|
19
19
|
REMOVE_ACTION = 'REMOVE'
|
20
20
|
REVERSE_ACTION = 'REVERSE'
|
21
|
+
RESPONSE_ACTION = 'RESPONSE'
|
21
22
|
SPLAT_ACTION = 'SPLAT'
|
22
23
|
SPLIT_ACTION = 'SPLIT'
|
23
24
|
DB_WRITE_ACTION = 'DB_WRITE'
|
@@ -37,6 +38,7 @@ module Contrast
|
|
37
38
|
REPLACE_ACTION => Contrast::Agent::Assess::Policy::Propagator::Replace,
|
38
39
|
REMOVE_ACTION => Contrast::Agent::Assess::Policy::Propagator::Remove,
|
39
40
|
REVERSE_ACTION => Contrast::Agent::Assess::Policy::Propagator::Reverse,
|
41
|
+
RESPONSE_ACTION => Contrast::Agent::Assess::Policy::Propagator::Response,
|
40
42
|
SPLAT_ACTION => Contrast::Agent::Assess::Policy::Propagator::Splat,
|
41
43
|
SPLIT_ACTION => Contrast::Agent::Assess::Policy::Propagator::Split
|
42
44
|
}.cs__freeze
|
data/lib/contrast/utils/os.rb
CHANGED
@@ -8,7 +8,7 @@ module Contrast
|
|
8
8
|
module Utils
|
9
9
|
# Simple utility used to make OS calls and determine state. For that state
|
10
10
|
# which will not change at runtime, such as the operating system, the
|
11
|
-
# Utility
|
11
|
+
# Utility memoizes to avoid multiple lookups.
|
12
12
|
module OS
|
13
13
|
extend Contrast::Components::Scope::InstanceMethods
|
14
14
|
|
@@ -25,14 +25,6 @@ module Contrast
|
|
25
25
|
@_mac = RUBY_PLATFORM.include?('darwin') if @_mac.nil?
|
26
26
|
@_mac
|
27
27
|
end
|
28
|
-
|
29
|
-
def unix?
|
30
|
-
!windows?
|
31
|
-
end
|
32
|
-
|
33
|
-
def linux?
|
34
|
-
(unix? and !mac?)
|
35
|
-
end
|
36
28
|
end
|
37
29
|
end
|
38
30
|
end
|
@@ -8,7 +8,35 @@
|
|
8
8
|
"target":"R",
|
9
9
|
"type":"PARAMETER",
|
10
10
|
"tags":["CROSS_SITE"]
|
11
|
-
},
|
11
|
+
},
|
12
|
+
{
|
13
|
+
"class_name":"Net::HTTPResponse",
|
14
|
+
"instance_method": true,
|
15
|
+
"method_visibility": "public",
|
16
|
+
"method_name":"body",
|
17
|
+
"target":"R",
|
18
|
+
"type":"BODY",
|
19
|
+
"tags":["CROSS_SITE"]
|
20
|
+
},
|
21
|
+
{
|
22
|
+
"class_name":"Rack::Response",
|
23
|
+
"instance_method": true,
|
24
|
+
"method_visibility": "public",
|
25
|
+
"method_name":"body",
|
26
|
+
"target":"R",
|
27
|
+
"type":"BODY",
|
28
|
+
"tags":["CROSS_SITE"]
|
29
|
+
},
|
30
|
+
{
|
31
|
+
"class_name":"Sinatra::Response",
|
32
|
+
"instance_method": true,
|
33
|
+
"method_visibility": "public",
|
34
|
+
"method_name":"body",
|
35
|
+
"target":"R",
|
36
|
+
"type":"BODY",
|
37
|
+
"tags":["CROSS_SITE"]
|
38
|
+
},
|
39
|
+
{
|
12
40
|
"class_name":"Rack::Request::Helpers",
|
13
41
|
"instance_method": true,
|
14
42
|
"method_visibility": "public",
|
@@ -990,7 +1018,35 @@
|
|
990
1018
|
"source": "P0",
|
991
1019
|
"target": "R",
|
992
1020
|
"action": "SPLAT"
|
993
|
-
},
|
1021
|
+
},
|
1022
|
+
{
|
1023
|
+
"class_name": "ActiveSupport::JSON",
|
1024
|
+
"method_name": "encode",
|
1025
|
+
"instance_method": false,
|
1026
|
+
"method_visibility": "public",
|
1027
|
+
"source": "P0",
|
1028
|
+
"target": "R",
|
1029
|
+
"action": "SPLAT"
|
1030
|
+
},
|
1031
|
+
{
|
1032
|
+
"class_name": "JSON",
|
1033
|
+
"method_name": "generate",
|
1034
|
+
"instance_method": false,
|
1035
|
+
"method_visibility": "public",
|
1036
|
+
"source": "P0",
|
1037
|
+
"target": "R",
|
1038
|
+
"action": "SPLAT"
|
1039
|
+
},
|
1040
|
+
{
|
1041
|
+
"class_name": "JSON",
|
1042
|
+
"method_name": "pretty_generate",
|
1043
|
+
"instance_method": false,
|
1044
|
+
"method_visibility": "public",
|
1045
|
+
"source": "P0",
|
1046
|
+
"target": "R",
|
1047
|
+
"action": "SPLAT"
|
1048
|
+
},
|
1049
|
+
{
|
994
1050
|
"class_name": "Zlib::Deflate",
|
995
1051
|
"method_name": "deflate",
|
996
1052
|
"instance_method": false,
|
@@ -1082,7 +1138,28 @@
|
|
1082
1138
|
"source": "P0",
|
1083
1139
|
"target": "R",
|
1084
1140
|
"action": "SPLAT"
|
1085
|
-
},
|
1141
|
+
},
|
1142
|
+
{
|
1143
|
+
"class_name":"Net::HTTPRequest",
|
1144
|
+
"instance_method": true,
|
1145
|
+
"method_visibility": "public",
|
1146
|
+
"method_name":"body=",
|
1147
|
+
"source":"P0",
|
1148
|
+
"target":"O",
|
1149
|
+
"action":"KEEP"
|
1150
|
+
},
|
1151
|
+
{
|
1152
|
+
"class_name":"Net::HTTP",
|
1153
|
+
"instance_method": true,
|
1154
|
+
"method_visibility": "public",
|
1155
|
+
"method_name":"request",
|
1156
|
+
"action": "CUSTOM",
|
1157
|
+
"patch_class": "Contrast::Agent::Assess::Policy::Propagator::Response",
|
1158
|
+
"patch_method": "net_response_keep",
|
1159
|
+
"source": "O,P1",
|
1160
|
+
"target": "R"
|
1161
|
+
},
|
1162
|
+
{
|
1086
1163
|
"class_name": "URI::Generic",
|
1087
1164
|
"method_name": "initialize",
|
1088
1165
|
"instance_method": true,
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: contrast-agent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 7.3.
|
4
|
+
version: 7.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- galen.palmer@contrastsecurity.com
|
@@ -13,7 +13,7 @@ authors:
|
|
13
13
|
autorequire:
|
14
14
|
bindir: exe
|
15
15
|
cert_chain: []
|
16
|
-
date: 2023-
|
16
|
+
date: 2023-08-04 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: bundler
|
@@ -930,6 +930,7 @@ files:
|
|
930
930
|
- lib/contrast/agent/assess/policy/propagator/rack_protection.rb
|
931
931
|
- lib/contrast/agent/assess/policy/propagator/remove.rb
|
932
932
|
- lib/contrast/agent/assess/policy/propagator/replace.rb
|
933
|
+
- lib/contrast/agent/assess/policy/propagator/response.rb
|
933
934
|
- lib/contrast/agent/assess/policy/propagator/reverse.rb
|
934
935
|
- lib/contrast/agent/assess/policy/propagator/select.rb
|
935
936
|
- lib/contrast/agent/assess/policy/propagator/splat.rb
|