contrast-agent 3.16.0 → 4.0.0
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.rb +2 -3
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +17 -6
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +3 -2
- data/lib/contrast/agent/inventory.rb +15 -0
- data/lib/contrast/agent/inventory/dependencies.rb +50 -0
- data/lib/contrast/agent/inventory/dependency_analysis.rb +37 -0
- data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +104 -0
- data/lib/contrast/agent/inventory/gemfile_digest_cache.rb +38 -0
- data/lib/contrast/agent/middleware.rb +1 -2
- data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +4 -3
- data/lib/contrast/agent/request_handler.rb +1 -1
- data/lib/contrast/agent/static_analysis.rb +2 -2
- data/lib/contrast/agent/tracepoint_hook.rb +1 -1
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/decorators.rb +3 -0
- data/lib/contrast/api/decorators/address.rb +0 -1
- data/lib/contrast/api/decorators/application_update.rb +1 -1
- data/lib/contrast/api/decorators/library.rb +53 -0
- data/lib/contrast/api/decorators/library_usage_update.rb +30 -0
- data/lib/contrast/components/agent.rb +6 -5
- data/lib/contrast/components/config.rb +29 -37
- data/lib/contrast/components/interface.rb +25 -3
- data/lib/contrast/components/inventory.rb +6 -1
- data/lib/contrast/config/inventory_configuration.rb +2 -2
- data/lib/contrast/framework/rails/support.rb +3 -0
- data/lib/contrast/logger/application.rb +1 -1
- data/lib/contrast/utils/inventory_util.rb +0 -7
- data/lib/contrast/utils/sha256_builder.rb +0 -12
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +9 -4
- data/lib/contrast/utils/boolean_util.rb +0 -30
- data/lib/contrast/utils/gemfile_reader.rb +0 -193
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ce67e5e10275cf1425bc05d510e6a2b0b436f90c180027bfce2abfeccf11e86e
|
4
|
+
data.tar.gz: 5644a75c92b7502f285030c83f3469820c6efdf8c582eb32328d8aefe2f4821a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c777194316965c247ced91d30731f9d263db482975fe4702d20332f224ef17c1f23ed281066b07398a900b5f5561019fe15ef6bb882f64c38efb05a693100073
|
7
|
+
data.tar.gz: 6a3cf5245ba8448d4023b9e29e608058747dc66c7f1296265d1b2f589ee55bedabe192058eb638839667546acb14650397a35ae36127a0ce316d2a8c876be3a8
|
data/lib/contrast/agent.rb
CHANGED
@@ -23,7 +23,6 @@ require 'contrast/extension/protect'
|
|
23
23
|
require 'contrast/extension/protect/kernel'
|
24
24
|
|
25
25
|
require 'contrast/utils/object_share'
|
26
|
-
require 'contrast/utils/boolean_util'
|
27
26
|
require 'contrast/utils/string_utils'
|
28
27
|
require 'contrast/utils/io_util'
|
29
28
|
require 'contrast/utils/os'
|
@@ -88,8 +87,8 @@ require 'contrast/agent/assess'
|
|
88
87
|
# protect rules
|
89
88
|
require 'contrast/agent/protect/rule'
|
90
89
|
|
91
|
-
# application libraries
|
92
|
-
require 'contrast/
|
90
|
+
# application libraries and technologies
|
91
|
+
require 'contrast/agent/inventory'
|
93
92
|
|
94
93
|
# rack event monitoring
|
95
94
|
require 'contrast/agent/middleware'
|
@@ -17,22 +17,33 @@ module Contrast
|
|
17
17
|
access_component :analysis
|
18
18
|
|
19
19
|
class << self
|
20
|
+
# Use the given trace_point, built from an :end event, to determine
|
21
|
+
# where the loaded code lives and scan that code for policy
|
22
|
+
# violations.
|
23
|
+
#
|
24
|
+
# @param trace_point [TracePoint] the TracePoint generated by an
|
25
|
+
# :end event at the end of a Module definition.
|
20
26
|
def scan trace_point
|
21
27
|
return unless ASSESS.enabled?
|
22
28
|
return unless ASSESS.require_scan?
|
23
29
|
|
30
|
+
provider_values = policy.providers.values
|
31
|
+
return if provider_values.all?(&:disabled?)
|
32
|
+
|
24
33
|
return unless trace_point.path
|
25
34
|
return if trace_point.path.start_with?(Gem.dir)
|
26
35
|
|
27
36
|
mod = trace_point.self
|
28
37
|
return if mod.cs__frozen? || mod.singleton_class?
|
29
38
|
|
30
|
-
# TODO: RUBY-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
provider.parse(trace_point)
|
35
|
-
|
39
|
+
# TODO: RUBY-1014 - remove non-AST approach
|
40
|
+
if RUBY_VERSION >= '2.6.0'
|
41
|
+
ast = RubyVM::AbstractSyntaxTree.parse_file(trace_point.path)
|
42
|
+
provider_values.each do |provider|
|
43
|
+
provider.parse(trace_point, ast)
|
44
|
+
end
|
45
|
+
else
|
46
|
+
provider_values.each do |provider|
|
36
47
|
provider.analyze(mod)
|
37
48
|
end
|
38
49
|
end
|
@@ -85,10 +85,11 @@ module Contrast
|
|
85
85
|
#
|
86
86
|
# @param trace_point [TracePoint] the TracePoint event created on
|
87
87
|
# the :end of a Module being loaded
|
88
|
-
|
88
|
+
# @param ast [RubyVM::AbstractSyntaxTree::Node] the abstract syntax
|
89
|
+
# tree of the Module defined in the TracePoint end event
|
90
|
+
def parse trace_point, ast
|
89
91
|
return if disabled?
|
90
92
|
|
91
|
-
ast = RubyVM::AbstractSyntaxTree.parse_file(trace_point.path)
|
92
93
|
parse_ast(trace_point.self, ast)
|
93
94
|
rescue StandardError => e
|
94
95
|
logger.error('Unable to parse AST for hardcoded keys', e, module: trace_point.self)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Contrast
|
5
|
+
module Agent
|
6
|
+
# Namespace used for inventory behavior
|
7
|
+
module Inventory
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'contrast/agent/inventory/dependencies'
|
13
|
+
require 'contrast/agent/inventory/gemfile_digest_cache'
|
14
|
+
require 'contrast/agent/inventory/dependency_usage_analysis'
|
15
|
+
require 'contrast/agent/inventory/dependency_analysis'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Contrast
|
5
|
+
module Agent
|
6
|
+
module Inventory
|
7
|
+
# this module is included in classes that need access to the applications dependencies
|
8
|
+
module Dependencies
|
9
|
+
CONTRAST_AGENT = 'contrast-agent'
|
10
|
+
|
11
|
+
# the #clone is necessary here, as a require in another thread could
|
12
|
+
# potentially result in adding a key to the loaded_specs hash during
|
13
|
+
# iteration. (as in RUBY-330)
|
14
|
+
# this takes care of filtering out contrast-only dependencies
|
15
|
+
def loaded_specs
|
16
|
+
specs = Gem.loaded_specs.clone
|
17
|
+
specs.delete_if { |name, _v| contrast?(name) }
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def contrast_gems
|
23
|
+
@_contrast_gems ||= find_contrast_gems
|
24
|
+
end
|
25
|
+
|
26
|
+
def contrast? name
|
27
|
+
contrast_gems.include?(name)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Go through all dependents, given as a pair from the DependencyList: `dependency`
|
31
|
+
# is the dependency itself, filled with all its specs. `dependents` is the array of reverse
|
32
|
+
# dependencies for the aforementioned dependency. If the dependency is also in contrast_dep_set,
|
33
|
+
# then contrast depends on it. If its array of dependents is 1, then contrast is the
|
34
|
+
# only dependency in that list. Since only contrast depends on it, we should ignore it.
|
35
|
+
def find_contrast_gems
|
36
|
+
ignore = Set.new([CONTRAST_AGENT])
|
37
|
+
contrast_specs = Gem::DependencyList.from_specs.specs.find do |dependency|
|
38
|
+
dependency.name == CONTRAST_AGENT
|
39
|
+
end
|
40
|
+
contrast_dep_set = contrast_specs.dependencies.map(&:name).to_set
|
41
|
+
|
42
|
+
Gem::DependencyList.from_specs.spec_predecessors.each_pair do |dependency, dependents|
|
43
|
+
ignore.add(dependency.name) if contrast_dep_set.include?(dependency.name) && dependents.length == 1
|
44
|
+
end
|
45
|
+
ignore
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/agent/inventory/dependencies'
|
5
|
+
require 'contrast/components/interface'
|
6
|
+
require 'contrast/utils/object_share'
|
7
|
+
|
8
|
+
module Contrast
|
9
|
+
module Agent
|
10
|
+
module Inventory
|
11
|
+
# Used to collect dependencies of the application for reporting
|
12
|
+
class DependencyAnalysis
|
13
|
+
include Singleton
|
14
|
+
include Contrast::Agent::Inventory::Dependencies
|
15
|
+
include Contrast::Components::Interface
|
16
|
+
|
17
|
+
access_component :analysis
|
18
|
+
|
19
|
+
# Report the dependencies of this application
|
20
|
+
#
|
21
|
+
# @return [Array<Contrast::Api::Dtm::Library>] protobuf form of the
|
22
|
+
# Gem::Specification that have been loaded for this application.
|
23
|
+
def library_pb_list
|
24
|
+
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless INVENTORY.enabled?
|
25
|
+
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless INVENTORY.analyze_libraries?
|
26
|
+
|
27
|
+
loaded_specs.each_with_object([]) do |(_name, spec), reported_lib_list|
|
28
|
+
next unless spec
|
29
|
+
next unless (digest = Contrast::Utils::Sha256Builder.instance.build_from_spec(spec))
|
30
|
+
|
31
|
+
reported_lib_list << Contrast::Api::Dtm::Library.build(digest, spec)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/agent/inventory/gemfile_digest_cache'
|
5
|
+
require 'contrast/agent/inventory/dependencies'
|
6
|
+
require 'contrast/components/interface'
|
7
|
+
require 'contrast/utils/object_share'
|
8
|
+
|
9
|
+
module Contrast
|
10
|
+
module Agent
|
11
|
+
module Inventory
|
12
|
+
# Used to analyze class usage for reporting
|
13
|
+
class DependencyUsageAnalysis
|
14
|
+
include Singleton
|
15
|
+
include Contrast::Components::Interface
|
16
|
+
include Contrast::Agent::Inventory::Dependencies
|
17
|
+
|
18
|
+
access_component :analysis, :config, :logging
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
return unless enabled?
|
22
|
+
|
23
|
+
@gemdigest_cache = Contrast::Agent::Inventory::GemfileDigestCache.new
|
24
|
+
end
|
25
|
+
|
26
|
+
# This method is invoked once, along with the rest of our catchup code
|
27
|
+
# to report libraries and their associated files that have already been loaded pre-contrast
|
28
|
+
def catchup
|
29
|
+
return unless enabled?
|
30
|
+
|
31
|
+
loaded_specs.each do |_name, spec|
|
32
|
+
# Get a digest of the Gem file itself
|
33
|
+
next unless (digest = Contrast::Utils::Sha256Builder.instance.build_from_spec(spec))
|
34
|
+
|
35
|
+
@gemdigest_cache.use_cache(digest) do |existing_files|
|
36
|
+
loaded_files_from_gem = $LOADED_FEATURES.select { |f| f.start_with?(spec.full_gem_path) }
|
37
|
+
loaded_files_from_gem.each do |file_path|
|
38
|
+
logger.trace('Recording loaded file for inventory analysis', line: file_path)
|
39
|
+
existing_files << adjust_path_for_reporting(file_path, spec)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# This method is invoked once per TracePoint :end - to map a specific
|
46
|
+
# file being required to the gem it belongs to
|
47
|
+
#
|
48
|
+
# @param path [String] the result of TracePoint#path from the :end
|
49
|
+
# event in which the Module was defined
|
50
|
+
def associate_file path
|
51
|
+
return unless enabled?
|
52
|
+
|
53
|
+
spec_lookup_path = adjust_path_for_spec_lookup(path)
|
54
|
+
|
55
|
+
spec = Gem::Specification.find_by_path(spec_lookup_path)
|
56
|
+
unless spec
|
57
|
+
logger.debug('Unable to resolve gem spec for path', path: path)
|
58
|
+
return
|
59
|
+
end
|
60
|
+
|
61
|
+
digest = Contrast::Utils::Sha256Builder.instance.build_from_spec(spec)
|
62
|
+
unless digest
|
63
|
+
logger.debug('Unable to resolve digest for gem spec', spec: spec.to_s)
|
64
|
+
return
|
65
|
+
end
|
66
|
+
report_path = adjust_path_for_reporting(path, spec)
|
67
|
+
@gemdigest_cache.get(digest) << report_path
|
68
|
+
rescue StandardError => e
|
69
|
+
logger.error('Unable to inventory file path', e, path: path)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Populate the library_usages filed of the Activity message using the
|
73
|
+
# data stored in the @gemdigest_cache
|
74
|
+
#
|
75
|
+
# @param activity [Contrast::Api::Dtm::Activity] the message to which
|
76
|
+
# to append the usage data
|
77
|
+
def generate_library_usage activity
|
78
|
+
return unless enabled?
|
79
|
+
return if @gemdigest_cache.empty?
|
80
|
+
|
81
|
+
@gemdigest_cache.generate_usage_data(activity)
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def adjust_path_for_spec_lookup path
|
87
|
+
idx = path.index('/lib/')
|
88
|
+
path = path[(idx + 4)..-1] if idx
|
89
|
+
path
|
90
|
+
end
|
91
|
+
|
92
|
+
def adjust_path_for_reporting path, gem_spec
|
93
|
+
path.delete_prefix(gem_spec.full_gem_path)
|
94
|
+
end
|
95
|
+
|
96
|
+
# We only use this if inventory and library analysis are enabled
|
97
|
+
def enabled?
|
98
|
+
@_enabled = INVENTORY.enabled? && INVENTORY.analyze_libraries? if @_enabled.nil?
|
99
|
+
@_enabled
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Contrast
|
5
|
+
module Agent
|
6
|
+
module Inventory
|
7
|
+
# Keeps a map of gem digest to files for reporting file usage
|
8
|
+
class GemfileDigestCache
|
9
|
+
extend Forwardable
|
10
|
+
def_delegator :@gem_spec_digest_to_files,
|
11
|
+
:empty?
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@gem_spec_digest_to_files = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate_usage_data activity
|
18
|
+
return unless activity
|
19
|
+
|
20
|
+
@gem_spec_digest_to_files.each_pair do |digest, files|
|
21
|
+
usage = Contrast::Api::Dtm::LibraryUsageUpdate.build(digest, files)
|
22
|
+
activity.library_usages[usage.hash_code] = usage if activity
|
23
|
+
end
|
24
|
+
@gem_spec_digest_to_files.clear
|
25
|
+
end
|
26
|
+
|
27
|
+
def use_cache digest
|
28
|
+
yield get(digest)
|
29
|
+
end
|
30
|
+
|
31
|
+
def get digest
|
32
|
+
@gem_spec_digest_to_files[digest] = Set.new unless @gem_spec_digest_to_files.key?(digest)
|
33
|
+
@gem_spec_digest_to_files[digest]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -94,8 +94,7 @@ module Contrast
|
|
94
94
|
if CONFIG.invalid?
|
95
95
|
AGENT.disable!
|
96
96
|
logger.error('!!! CONFIG FILE IS INVALID - DISABLING CONTRAST AGENT !!!')
|
97
|
-
elsif
|
98
|
-
AGENT.disable!
|
97
|
+
elsif AGENT.disabled?
|
99
98
|
logger.warn('Contrast disabled by configuration. Continuing without instrumentation.')
|
100
99
|
else
|
101
100
|
AGENT.enable!
|
@@ -26,7 +26,7 @@ module Contrast
|
|
26
26
|
|
27
27
|
action = properties['action']
|
28
28
|
write_marker = write?(action, *args)
|
29
|
-
possible_write = write_marker && possible_write(write_marker)
|
29
|
+
possible_write = write_marker && possible_write?(write_marker)
|
30
30
|
path_traversal_rule(path, possible_write, object, method)
|
31
31
|
|
32
32
|
# If the action was copy, we need to handle the write half of it.
|
@@ -48,7 +48,7 @@ module Contrast
|
|
48
48
|
|
49
49
|
private
|
50
50
|
|
51
|
-
def possible_write input
|
51
|
+
def possible_write? input
|
52
52
|
input.cs__respond_to?(:to_s) &&
|
53
53
|
input.to_s.include?(Contrast::Utils::ObjectShare::WRITE_FLAG)
|
54
54
|
end
|
@@ -62,7 +62,7 @@ module Contrast
|
|
62
62
|
return true if action == WRITE
|
63
63
|
|
64
64
|
write_marker = args.length > 1 ? args[1] : nil
|
65
|
-
write_marker && possible_write(write_marker)
|
65
|
+
write_marker && possible_write?(write_marker)
|
66
66
|
end
|
67
67
|
|
68
68
|
def path_traversal_rule path, possible_write, object, method
|
@@ -83,6 +83,7 @@ module Contrast
|
|
83
83
|
tmp = CS__SAFER_REL_PATHS.map { |r| "#{ pwd }/#{ r }" }
|
84
84
|
gems = ENV['GEM_PATH']
|
85
85
|
tmp += gems.split(Contrast::Utils::ObjectShare::COLON) if gems
|
86
|
+
tmp.map!(&:downcase)
|
86
87
|
tmp
|
87
88
|
else
|
88
89
|
[]
|
@@ -18,7 +18,7 @@ module Contrast
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def send_activity_messages
|
21
|
-
Contrast::
|
21
|
+
Contrast::Agent::Inventory::DependencyUsageAnalysis.instance.generate_library_usage(context.activity)
|
22
22
|
[context.server_activity, context.activity, context.observed_route].each do |message|
|
23
23
|
Contrast::Agent.messaging_queue.send_event_eventually(message)
|
24
24
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'contrast/components/interface'
|
5
|
-
require 'contrast/
|
5
|
+
require 'contrast/agent/inventory'
|
6
6
|
require 'contrast/api/decorators/application_update'
|
7
7
|
|
8
8
|
module Contrast
|
@@ -18,7 +18,7 @@ module Contrast
|
|
18
18
|
def catchup
|
19
19
|
@_catchup ||= begin
|
20
20
|
with_contrast_scope do
|
21
|
-
Contrast::
|
21
|
+
Contrast::Agent::Inventory::DependencyUsageAnalysis.instance.catchup
|
22
22
|
send_inventory_message
|
23
23
|
true
|
24
24
|
end
|
@@ -35,7 +35,7 @@ module Contrast
|
|
35
35
|
path = tracepoint_event.path
|
36
36
|
return if path&.include?('contrast')
|
37
37
|
|
38
|
-
Contrast::
|
38
|
+
Contrast::Agent::Inventory::DependencyUsageAnalysis.instance.associate_file(path) if path
|
39
39
|
Contrast::Agent::Patching::Policy::Patcher.patch_specific_module(loaded_module)
|
40
40
|
Contrast::Agent::Assess::Policy::RewriterPatch.rewrite_interpolation(loaded_module)
|
41
41
|
Contrast::Agent::Assess::Policy::PolicyScanner.scan(tracepoint_event)
|
@@ -8,11 +8,14 @@ module Contrast
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
11
|
+
|
11
12
|
require 'contrast/api/decorators/message'
|
12
13
|
require 'contrast/api/decorators/application_update'
|
13
14
|
require 'contrast/api/decorators/input_analysis'
|
14
15
|
require 'contrast/api/decorators/application_settings'
|
15
16
|
require 'contrast/api/decorators/server_features'
|
17
|
+
require 'contrast/api/decorators/library'
|
18
|
+
require 'contrast/api/decorators/library_usage_update'
|
16
19
|
require 'contrast/api/decorators/route_coverage'
|
17
20
|
require 'contrast/api/decorators/trace_event_object'
|
18
21
|
require 'contrast/api/decorators/trace_event_signature'
|
@@ -44,7 +44,7 @@ module Contrast
|
|
44
44
|
msg = new
|
45
45
|
msg.append_route_coverage_data(Contrast::Agent.framework_manager.find_route_discovery_data)
|
46
46
|
msg.append_platform_version(Contrast::Agent.framework_manager.platform_version)
|
47
|
-
msg.append_library_update(Contrast::
|
47
|
+
msg.append_library_update(Contrast::Agent::Inventory::DependencyAnalysis.instance.library_pb_list)
|
48
48
|
msg
|
49
49
|
end
|
50
50
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/utils/string_utils'
|
5
|
+
require 'contrast/utils/sha256_builder'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
module Contrast
|
9
|
+
module Api
|
10
|
+
module Decorators
|
11
|
+
# Used to decorate the Library protobuf model to handle Gem::Specification translation
|
12
|
+
module Library
|
13
|
+
def self.included klass
|
14
|
+
klass.extend(ClassMethods)
|
15
|
+
end
|
16
|
+
# Used to add class methods to the Library class on inclusion of the decorator
|
17
|
+
module ClassMethods
|
18
|
+
def build digest, gem_specification
|
19
|
+
msg = new
|
20
|
+
msg.file_path = Contrast::Utils::StringUtils.force_utf8(gem_specification.name)
|
21
|
+
msg.hash_code = Contrast::Utils::StringUtils.force_utf8(digest)
|
22
|
+
msg.version = Contrast::Utils::StringUtils.force_utf8(gem_specification.version)
|
23
|
+
msg.manifest = Contrast::Utils::StringUtils.force_utf8(build_manifest(gem_specification))
|
24
|
+
msg.external_ms = date_to_ms(gem_specification.date)
|
25
|
+
msg.internal_ms = msg.external_ms
|
26
|
+
msg.url = Contrast::Utils::StringUtils.force_utf8(gem_specification.homepage)
|
27
|
+
msg.class_count = file_count(gem_specification.full_gem_path.to_s)
|
28
|
+
msg.used_class_count = 0
|
29
|
+
msg
|
30
|
+
end
|
31
|
+
|
32
|
+
# These are all the code files that are located in the Gem directory loaded
|
33
|
+
# by the current environment; this includes more than Ruby files
|
34
|
+
def file_count path
|
35
|
+
Contrast::Utils::Sha256Builder.instance.files(path).length
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_manifest spec
|
39
|
+
Contrast::Utils::StringUtils.force_utf8(spec.to_yaml.to_s)
|
40
|
+
rescue StandardError
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def date_to_ms date
|
45
|
+
(date.to_f * 1000.0).to_i
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
Contrast::Api::Dtm::Library.include(Contrast::Api::Decorators::Library)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/utils/string_utils'
|
5
|
+
|
6
|
+
module Contrast
|
7
|
+
module Api
|
8
|
+
module Decorators
|
9
|
+
# Used to decorate the LibraryUsageUpdate protobuf
|
10
|
+
module LibraryUsageUpdate
|
11
|
+
def self.included klass
|
12
|
+
klass.extend(ClassMethods)
|
13
|
+
end
|
14
|
+
# Used to add class methods to the LibraryUsageUpdate class on inclusion of the decorator
|
15
|
+
module ClassMethods
|
16
|
+
def build digest, files
|
17
|
+
msg = new
|
18
|
+
msg.hash_code = Contrast::Utils::StringUtils.force_utf8(digest)
|
19
|
+
files.each do |required_file|
|
20
|
+
msg.class_names[required_file] = true
|
21
|
+
end
|
22
|
+
msg
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Contrast::Api::Dtm::LibraryUsageUpdate.include(Contrast::Api::Decorators::LibraryUsageUpdate)
|
@@ -17,7 +17,8 @@ module Contrast
|
|
17
17
|
access_component :analysis, :config, :settings
|
18
18
|
|
19
19
|
def enabled?
|
20
|
-
|
20
|
+
@_enabled = !false?(CONFIG.root.enable) if @_enabled.nil?
|
21
|
+
@_enabled
|
21
22
|
end
|
22
23
|
|
23
24
|
def disabled?
|
@@ -25,11 +26,11 @@ module Contrast
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def enable!
|
28
|
-
@
|
29
|
+
@_enabled = true
|
29
30
|
end
|
30
31
|
|
31
32
|
def disable!
|
32
|
-
@
|
33
|
+
@_enabled = false
|
33
34
|
end
|
34
35
|
|
35
36
|
def ruleset
|
@@ -49,12 +50,12 @@ module Contrast
|
|
49
50
|
end
|
50
51
|
|
51
52
|
def patch_yield?
|
52
|
-
@_patch_yield = !
|
53
|
+
@_patch_yield = !false?(CONFIG.root.agent.ruby.propagate_yield) if @_patch_yield.nil?
|
53
54
|
@_patch_yield
|
54
55
|
end
|
55
56
|
|
56
57
|
def interpolation_enabled?
|
57
|
-
@_interpolation_enabled = !
|
58
|
+
@_interpolation_enabled = !false?(CONFIG.root.agent.ruby.interpolate) if @_interpolation_enabled.nil?
|
58
59
|
@_interpolation_enabled
|
59
60
|
end
|
60
61
|
|
@@ -1,9 +1,7 @@
|
|
1
1
|
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require 'contrast/utils/boolean_util'
|
5
4
|
require 'contrast/utils/env_configuration_item'
|
6
|
-
require 'contrast/utils/object_share'
|
7
5
|
require 'contrast/configuration'
|
8
6
|
|
9
7
|
module Contrast
|
@@ -39,41 +37,9 @@ module Contrast
|
|
39
37
|
end
|
40
38
|
alias_method :rebuild, :build
|
41
39
|
|
42
|
-
#
|
43
|
-
# grep 'CONFIG.raw' for opportunities to refactor.
|
44
|
-
def raw
|
45
|
-
@config
|
46
|
-
end
|
47
|
-
|
40
|
+
# @return [Contrast::Config::RootConfiguration]
|
48
41
|
def root
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
def enabled?
|
53
|
-
@_enabled = !Contrast::Utils::BooleanUtil.false?(raw.enable) if @_enabled.nil?
|
54
|
-
@_enabled
|
55
|
-
end
|
56
|
-
|
57
|
-
def disabled?
|
58
|
-
!enabled?
|
59
|
-
end
|
60
|
-
|
61
|
-
def protect?
|
62
|
-
@_protect = Contrast::Utils::BooleanUtil.true?(raw.protect.enable) if @_protect.nil?
|
63
|
-
@_protect
|
64
|
-
end
|
65
|
-
|
66
|
-
def assess?
|
67
|
-
@_assess = Contrast::Utils::BooleanUtil.true?(raw.assess.enable) if @_assess.nil?
|
68
|
-
@_assess
|
69
|
-
end
|
70
|
-
|
71
|
-
def session_id
|
72
|
-
@_session_id ||= raw.application.session_id
|
73
|
-
end
|
74
|
-
|
75
|
-
def session_metadata
|
76
|
-
@_session_metadata ||= raw.application.session_metadata
|
42
|
+
@config.root
|
77
43
|
end
|
78
44
|
|
79
45
|
def valid?
|
@@ -84,6 +50,10 @@ module Contrast
|
|
84
50
|
!valid?
|
85
51
|
end
|
86
52
|
|
53
|
+
def loggable
|
54
|
+
@config.loggable
|
55
|
+
end
|
56
|
+
|
87
57
|
private
|
88
58
|
|
89
59
|
SESSION_VARIABLES = "Invalid configuration. Setting both application.session_id and application.session_metadata is not allowed.\n"
|
@@ -111,9 +81,31 @@ module Contrast
|
|
111
81
|
next unless env_key.to_s.start_with?(CONTRAST_ENV_MARKER)
|
112
82
|
|
113
83
|
config_item = Contrast::Utils::EnvConfigurationItem.new(env_key, env_value)
|
114
|
-
|
84
|
+
@config.assign_value_to_path_array(config_item.dot_path_array, config_item.value)
|
115
85
|
end
|
116
86
|
end
|
87
|
+
|
88
|
+
# Typically, this would be accessed through
|
89
|
+
# Contrast::Components::AppContext, but we're too early in the
|
90
|
+
# initialization of the Agent to use that mechanism, so we look it up
|
91
|
+
# directly for ourselves
|
92
|
+
#
|
93
|
+
# @return [String,nil] the value of the session id set in the
|
94
|
+
# configuration, or nil if unset
|
95
|
+
def session_id
|
96
|
+
@config.application.session_id
|
97
|
+
end
|
98
|
+
|
99
|
+
# Typically, this would be accessed through
|
100
|
+
# Contrast::Components::AppContext, but we're too early in the
|
101
|
+
# initialization of the Agent to use that mechanism, so we look it up
|
102
|
+
# directly for ourselves
|
103
|
+
#
|
104
|
+
# @return [String,nil] the value of the session metadata set in the
|
105
|
+
# configuration, or nil if unset
|
106
|
+
def session_metadata
|
107
|
+
@config.application.session_metadata
|
108
|
+
end
|
117
109
|
end
|
118
110
|
|
119
111
|
COMPONENT_INTERFACE = Interface.new
|
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
require 'delegate'
|
5
5
|
require 'contrast/extension/module'
|
6
|
-
require 'contrast/utils/
|
6
|
+
require 'contrast/utils/object_share'
|
7
7
|
|
8
8
|
module Contrast
|
9
9
|
# This is the base module for our components classes. It is intended to
|
@@ -49,12 +49,34 @@ module Contrast
|
|
49
49
|
end
|
50
50
|
|
51
51
|
module Methods # :nodoc:
|
52
|
+
# use this to determine if the configuration value is literally boolean
|
53
|
+
# false or some form of the word `false`, regardless of case. It should
|
54
|
+
# be used for those values which default to `true` as they should only
|
55
|
+
# treat a value explicitly set to `false` as such.
|
56
|
+
#
|
57
|
+
# @param config_param [Boolean,String] the value to check
|
58
|
+
# @return [Boolean] should the value be treated as `false`
|
52
59
|
def false? config_param
|
53
|
-
|
60
|
+
return false if config_param == true
|
61
|
+
return true if config_param == false
|
62
|
+
return false unless config_param.cs__is_a?(String)
|
63
|
+
|
64
|
+
Contrast::Utils::ObjectShare::FALSE.casecmp?(config_param)
|
54
65
|
end
|
55
66
|
|
67
|
+
# use this to determine if the configuration value is literally boolean
|
68
|
+
# true or some form of the word `true`, regardless of case. It should
|
69
|
+
# be used for those values which default to `false` as they should only
|
70
|
+
# treat a value explicitly set to `true` as such.
|
71
|
+
#
|
72
|
+
# @param config_param [Boolean,String] the value to check
|
73
|
+
# @return [Boolean] should the value be treated as `true`
|
56
74
|
def true? config_param
|
57
|
-
|
75
|
+
return false if config_param == false
|
76
|
+
return true if config_param == true
|
77
|
+
return false unless config_param.cs__is_a?(String)
|
78
|
+
|
79
|
+
Contrast::Utils::ObjectShare::TRUE.casecmp?(config_param)
|
58
80
|
end
|
59
81
|
end
|
60
82
|
end
|
@@ -13,12 +13,17 @@ module Contrast
|
|
13
13
|
include Contrast::Components::ComponentBase
|
14
14
|
include Contrast::Components::Interface
|
15
15
|
|
16
|
-
access_component :config
|
16
|
+
access_component :config, :settings
|
17
17
|
|
18
18
|
def enabled?
|
19
19
|
@_enabled = !false?(CONFIG.root.inventory.enable) if @_enabled.nil?
|
20
20
|
@_enabled
|
21
21
|
end
|
22
|
+
|
23
|
+
def analyze_libraries?
|
24
|
+
@_analyze_libraries = !false?(CONFIG.root.inventory.analyze_libraries) if @_analyze_libraries.nil?
|
25
|
+
@_analyze_libraries
|
26
|
+
end
|
22
27
|
end
|
23
28
|
|
24
29
|
COMPONENT_INTERFACE = Interface.new
|
@@ -7,8 +7,8 @@ module Contrast
|
|
7
7
|
# inventory functionality of the Agent.
|
8
8
|
class InventoryConfiguration < BaseConfiguration
|
9
9
|
KEYS = {
|
10
|
-
enable:
|
11
|
-
|
10
|
+
enable: Contrast::Config::DefaultValue.new(true),
|
11
|
+
analyze_libraries: Contrast::Config::DefaultValue.new(true),
|
12
12
|
tags: EMPTY_VALUE
|
13
13
|
}.cs__freeze
|
14
14
|
|
@@ -45,6 +45,9 @@ module Contrast
|
|
45
45
|
find_all_routes(::Rails.application, [])
|
46
46
|
end
|
47
47
|
|
48
|
+
# Find the current route, based on the provided Request wrapper
|
49
|
+
# @param request[Contrast::Agent::Request]
|
50
|
+
# @return [Contrast::Api::Dtm::RouteCoverage]
|
48
51
|
def current_route request
|
49
52
|
return unless ::Rails.cs__respond_to?(:application)
|
50
53
|
|
@@ -33,7 +33,7 @@ module Contrast
|
|
33
33
|
def application_configuration
|
34
34
|
return unless info?
|
35
35
|
|
36
|
-
loggable = CONFIG.
|
36
|
+
loggable = CONFIG.loggable
|
37
37
|
info('Current configuration', configuration: loggable)
|
38
38
|
env_keys = ENV.keys.select { |env_key| env_key&.to_s&.start_with?(Contrast::Components::Config::CONTRAST_ENV_MARKER) }
|
39
39
|
env_items = env_keys.map { |env_key| Contrast::Utils::EnvConfigurationItem.new(env_key, nil) }
|
@@ -3,7 +3,6 @@
|
|
3
3
|
|
4
4
|
require 'contrast/utils/timer'
|
5
5
|
require 'contrast/utils/object_share'
|
6
|
-
require 'contrast/utils/gemfile_reader'
|
7
6
|
require 'contrast/components/interface'
|
8
7
|
|
9
8
|
module Contrast
|
@@ -25,12 +24,6 @@ module Contrast
|
|
25
24
|
DEFAULT = 'default'
|
26
25
|
LOCALHOST = 'localhost'
|
27
26
|
|
28
|
-
def self.inventory_class class_path
|
29
|
-
Contrast::Utils::GemfileReader.instance.map_class(class_path)
|
30
|
-
rescue StandardError => e
|
31
|
-
logger.error('Unable to inventory module', e, path: class_path)
|
32
|
-
end
|
33
|
-
|
34
27
|
def self.active_record_config
|
35
28
|
return @_active_record_config if instance_variable_defined?(:@_active_record_config)
|
36
29
|
|
@@ -52,18 +52,6 @@ module Contrast
|
|
52
52
|
parent_dir = File.dirname(gems_dir)
|
53
53
|
File.join(parent_dir, Contrast::Utils::ObjectShare::CACHE)
|
54
54
|
end
|
55
|
-
|
56
|
-
def self.files path
|
57
|
-
instance.files(path)
|
58
|
-
end
|
59
|
-
|
60
|
-
def self.sha256 path
|
61
|
-
instance.sha256(path)
|
62
|
-
end
|
63
|
-
|
64
|
-
def self.build_from_spec spec
|
65
|
-
instance.build_from_spec(spec)
|
66
|
-
end
|
67
55
|
end
|
68
56
|
end
|
69
57
|
end
|
data/service_executables/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.14.
|
1
|
+
2.14.4
|
Binary file
|
Binary file
|
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:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- galen.palmer@contrastsecurity.com
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: exe
|
14
14
|
cert_chain: []
|
15
|
-
date: 2020-
|
15
|
+
date: 2020-11-05 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: amazing_print
|
@@ -770,6 +770,11 @@ files:
|
|
770
770
|
- lib/contrast/agent/deadzone/policy/policy.rb
|
771
771
|
- lib/contrast/agent/disable_reaction.rb
|
772
772
|
- lib/contrast/agent/exclusion_matcher.rb
|
773
|
+
- lib/contrast/agent/inventory.rb
|
774
|
+
- lib/contrast/agent/inventory/dependencies.rb
|
775
|
+
- lib/contrast/agent/inventory/dependency_analysis.rb
|
776
|
+
- lib/contrast/agent/inventory/dependency_usage_analysis.rb
|
777
|
+
- lib/contrast/agent/inventory/gemfile_digest_cache.rb
|
773
778
|
- lib/contrast/agent/inventory/policy/datastores.rb
|
774
779
|
- lib/contrast/agent/inventory/policy/policy.rb
|
775
780
|
- lib/contrast/agent/inventory/policy/trigger_node.rb
|
@@ -847,6 +852,8 @@ files:
|
|
847
852
|
- lib/contrast/api/decorators/application_update.rb
|
848
853
|
- lib/contrast/api/decorators/http_request.rb
|
849
854
|
- lib/contrast/api/decorators/input_analysis.rb
|
855
|
+
- lib/contrast/api/decorators/library.rb
|
856
|
+
- lib/contrast/api/decorators/library_usage_update.rb
|
850
857
|
- lib/contrast/api/decorators/message.rb
|
851
858
|
- lib/contrast/api/decorators/rasp_rule_sample.rb
|
852
859
|
- lib/contrast/api/decorators/route_coverage.rb
|
@@ -941,11 +948,9 @@ files:
|
|
941
948
|
- lib/contrast/tasks/service.rb
|
942
949
|
- lib/contrast/utils/assess/sampling_util.rb
|
943
950
|
- lib/contrast/utils/assess/tracking_util.rb
|
944
|
-
- lib/contrast/utils/boolean_util.rb
|
945
951
|
- lib/contrast/utils/class_util.rb
|
946
952
|
- lib/contrast/utils/duck_utils.rb
|
947
953
|
- lib/contrast/utils/env_configuration_item.rb
|
948
|
-
- lib/contrast/utils/gemfile_reader.rb
|
949
954
|
- lib/contrast/utils/hash_digest.rb
|
950
955
|
- lib/contrast/utils/heap_dump_util.rb
|
951
956
|
- lib/contrast/utils/invalid_configuration_util.rb
|
@@ -1,30 +0,0 @@
|
|
1
|
-
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require 'contrast/utils/object_share'
|
5
|
-
|
6
|
-
module Contrast
|
7
|
-
module Utils
|
8
|
-
# Utility methods for asserting truthy or falsy state of a value expected
|
9
|
-
# to equate to a boolean
|
10
|
-
class BooleanUtil
|
11
|
-
class << self
|
12
|
-
def false? config
|
13
|
-
return false if config == true
|
14
|
-
return true if config == false
|
15
|
-
return false unless config.cs__is_a?(String)
|
16
|
-
|
17
|
-
Contrast::Utils::ObjectShare::FALSE.casecmp?(config)
|
18
|
-
end
|
19
|
-
|
20
|
-
def true? config
|
21
|
-
return false if config == false
|
22
|
-
return true if config == true
|
23
|
-
return false unless config.cs__is_a?(String)
|
24
|
-
|
25
|
-
Contrast::Utils::ObjectShare::TRUE.casecmp?(config)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
@@ -1,193 +0,0 @@
|
|
1
|
-
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require 'set'
|
5
|
-
require 'contrast/utils/sha256_builder'
|
6
|
-
require 'contrast/utils/string_utils'
|
7
|
-
require 'contrast/components/interface'
|
8
|
-
require 'contrast/api'
|
9
|
-
|
10
|
-
module Contrast
|
11
|
-
module Utils
|
12
|
-
# GemfileReader has methods for extracting information from gem specs
|
13
|
-
# it also has a cache of library file digests to the lines of code found.
|
14
|
-
class GemfileReader
|
15
|
-
include Singleton
|
16
|
-
include Contrast::Components::Interface
|
17
|
-
|
18
|
-
access_component :config, :logging
|
19
|
-
|
20
|
-
CONTRAST_AGENT = 'contrast-agent'
|
21
|
-
|
22
|
-
def initialize
|
23
|
-
# Map of a Gem's Spec Digest to all loaded files from that Gem
|
24
|
-
@spec_to_files = {}
|
25
|
-
end
|
26
|
-
|
27
|
-
# the #clone is necessary here, as a require in another thread could
|
28
|
-
# potentially result in adding a key to the loaded_specs hash during
|
29
|
-
# iteration. (as in RUBY-330)
|
30
|
-
def loaded_specs
|
31
|
-
Gem.loaded_specs.clone
|
32
|
-
end
|
33
|
-
|
34
|
-
# indicates if there's been an update to library information, allowing us
|
35
|
-
# to only serialize this information on change.
|
36
|
-
def updated?
|
37
|
-
@updated
|
38
|
-
end
|
39
|
-
|
40
|
-
def updated!
|
41
|
-
@updated = true
|
42
|
-
end
|
43
|
-
|
44
|
-
# Once we're Contrasted, we intercept require calls to do inventory.
|
45
|
-
# In order to catch up, we do a one-time manual catchup, & inventory
|
46
|
-
# all the already-loaded gems.
|
47
|
-
def map_loaded_classes
|
48
|
-
loaded_specs.each do |name, spec|
|
49
|
-
# Don't count Contrast gems
|
50
|
-
next if contrast_gems.include? name
|
51
|
-
|
52
|
-
# Get a digest of the Gem file itself
|
53
|
-
next unless (digest = Contrast::Utils::Sha256Builder.build_from_spec(spec))
|
54
|
-
|
55
|
-
paths = get_by_digest(digest)
|
56
|
-
path = spec.full_gem_path
|
57
|
-
$LOADED_FEATURES.each do |line|
|
58
|
-
next unless line.cs__is_a?(String)
|
59
|
-
next unless line.start_with?(path)
|
60
|
-
|
61
|
-
logger.trace('Recording loaded gem for inventory analysis', line: line)
|
62
|
-
updated!
|
63
|
-
paths << adjust_lib(line)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def map_class path
|
69
|
-
path = adjust_lib(path)
|
70
|
-
|
71
|
-
return unless (spec = Gem::Specification.find_by_path(path))
|
72
|
-
return unless (digest = Contrast::Utils::Sha256Builder.build_from_spec(spec))
|
73
|
-
|
74
|
-
updated!
|
75
|
-
get_by_digest(digest) << path
|
76
|
-
end
|
77
|
-
|
78
|
-
def library_pb_list
|
79
|
-
loaded_specs.each_with_object([]) do |(name, spec), arr|
|
80
|
-
next if contrast_gems.include? name
|
81
|
-
next unless spec
|
82
|
-
next unless (digest = Contrast::Utils::Sha256Builder.build_from_spec(spec))
|
83
|
-
|
84
|
-
arr << build_library_pb(digest, spec)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def generate_library_usage activity = nil
|
89
|
-
return unless updated?
|
90
|
-
|
91
|
-
@spec_to_files.each_pair do |digest, files|
|
92
|
-
usage = Contrast::Api::Dtm::LibraryUsageUpdate.new
|
93
|
-
usage.hash_code = Contrast::Utils::StringUtils.force_utf8(digest)
|
94
|
-
activity.library_usages[usage.hash_code] = usage if activity
|
95
|
-
# TODO: RUBY-882 once TS switches to take filenames, remove the count setter and
|
96
|
-
# send the class names in usage.class_names
|
97
|
-
usage.count = files.size
|
98
|
-
end
|
99
|
-
# TODO: RUBY-882 once TS switches to take filenames, clear this and remove the
|
100
|
-
# @updated variable
|
101
|
-
|
102
|
-
# @spec_to_files.clear
|
103
|
-
@updated = false
|
104
|
-
end
|
105
|
-
|
106
|
-
private
|
107
|
-
|
108
|
-
# marker for the lib dir in an absolute file path. purposefully includes
|
109
|
-
# the trailing '/'
|
110
|
-
LIB = '/lib/'
|
111
|
-
|
112
|
-
# Kernel#load uses the absolute path, but Gems / Specs use the path after
|
113
|
-
# `/lib`. This method accounts for that and trims out the `/lib/` section
|
114
|
-
# and starts with the first `/` after and the trailing file extension, if
|
115
|
-
# present.
|
116
|
-
#
|
117
|
-
# @param path [String] the path to parse
|
118
|
-
# @return [String] the relative path of the file, after the lib directory
|
119
|
-
def adjust_lib path
|
120
|
-
idx = path.index(LIB)
|
121
|
-
path = path[(idx + 4)..-1] if idx
|
122
|
-
idx = path.rindex(Contrast::Utils::ObjectShare::PERIOD)
|
123
|
-
path = path[0..idx] if idx
|
124
|
-
path
|
125
|
-
end
|
126
|
-
|
127
|
-
def get_by_digest digest
|
128
|
-
@spec_to_files[digest] = Set.new unless @spec_to_files.key?(digest)
|
129
|
-
@spec_to_files[digest]
|
130
|
-
end
|
131
|
-
|
132
|
-
def build_library_pb digest, spec
|
133
|
-
lib = Contrast::Api::Dtm::Library.new
|
134
|
-
lib.file_path = Contrast::Utils::StringUtils.force_utf8(spec.name)
|
135
|
-
lib.hash_code = Contrast::Utils::StringUtils.force_utf8(digest)
|
136
|
-
lib.version = Contrast::Utils::StringUtils.force_utf8(spec.version)
|
137
|
-
lib.manifest = Contrast::Utils::StringUtils.force_utf8(build_manifest(spec))
|
138
|
-
lib.external_ms = date_to_ms(spec.date)
|
139
|
-
lib.internal_ms = lib.external_ms
|
140
|
-
lib.url = Contrast::Utils::StringUtils.force_utf8(spec.homepage)
|
141
|
-
# Library tags are appended in the ApplicationUpdate delegator
|
142
|
-
update_class_counts(lib, digest, spec)
|
143
|
-
lib
|
144
|
-
end
|
145
|
-
|
146
|
-
def date_to_ms date
|
147
|
-
(date.to_f * 1000.0).to_i
|
148
|
-
end
|
149
|
-
|
150
|
-
def update_class_counts lib, digest, spec
|
151
|
-
# Updating the class counts
|
152
|
-
path = spec.full_gem_path.to_s
|
153
|
-
lib.class_count = all_files(path).length
|
154
|
-
lib.used_class_count = @spec_to_files.key?(digest) ? get_by_digest(digest).size : 0
|
155
|
-
lib
|
156
|
-
end
|
157
|
-
|
158
|
-
def build_manifest spec
|
159
|
-
Contrast::Utils::StringUtils.force_utf8(spec.to_yaml.to_s) if defined?(YAML)
|
160
|
-
rescue StandardError
|
161
|
-
nil
|
162
|
-
end
|
163
|
-
|
164
|
-
# These are all the code files that are located in the Gem directory loaded
|
165
|
-
# by the current environment; this includes more than Ruby files
|
166
|
-
def all_files path
|
167
|
-
Contrast::Utils::Sha256Builder.instance.files(path)
|
168
|
-
end
|
169
|
-
|
170
|
-
# Go through all dependents, given as a pair from the DependencyList: `dependency`
|
171
|
-
# is the dependency itself, filled with all its specs. `dependents` is the array of reverse
|
172
|
-
# dependencies for the aforementioned dependency. If the dependency is also in contrast_dep_set,
|
173
|
-
# then contrast depends on it. If its array of dependents is 1, then contrast is the
|
174
|
-
# only dependency in that list. Since only contrast depends on it, we should ignore it.
|
175
|
-
def contrast_gems
|
176
|
-
@_contrast_gems ||= find_contrast_gems
|
177
|
-
end
|
178
|
-
|
179
|
-
def find_contrast_gems
|
180
|
-
ignore = Set.new([CONTRAST_AGENT])
|
181
|
-
contrast_specs = Gem::DependencyList.from_specs.specs.find do |dependency|
|
182
|
-
dependency.name == CONTRAST_AGENT
|
183
|
-
end
|
184
|
-
contrast_dep_set = contrast_specs.dependencies.map(&:name).to_set
|
185
|
-
|
186
|
-
Gem::DependencyList.from_specs.spec_predecessors.each_pair do |dependency, dependents|
|
187
|
-
ignore.add(dependency.name) if contrast_dep_set.include?(dependency.name) && dependents.length == 1
|
188
|
-
end
|
189
|
-
ignore
|
190
|
-
end
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|