contrast-agent 7.3.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/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
|