contrast-agent 4.11.0 → 4.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.simplecov +1 -0
- data/ext/cs__assess_module/cs__assess_module.c +48 -0
- data/ext/cs__assess_module/cs__assess_module.h +7 -0
- data/ext/cs__common/cs__common.c +24 -7
- data/ext/cs__common/cs__common.h +12 -2
- data/ext/cs__contrast_patch/cs__contrast_patch.c +48 -11
- data/ext/cs__contrast_patch/cs__contrast_patch.h +5 -2
- data/ext/cs__os_information/cs__os_information.c +31 -0
- data/ext/cs__os_information/cs__os_information.h +7 -0
- data/ext/{cs__protect_kernel → cs__os_information}/extconf.rb +0 -0
- data/lib/contrast/agent/assess/contrast_event.rb +1 -1
- data/lib/contrast/agent/assess/contrast_object.rb +1 -1
- data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +2 -0
- data/lib/contrast/agent/assess/policy/policy_node.rb +6 -6
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +5 -0
- data/lib/contrast/agent/assess/policy/preshift.rb +19 -6
- data/lib/contrast/agent/assess/policy/propagation_method.rb +2 -116
- data/lib/contrast/agent/assess/policy/propagation_node.rb +4 -4
- data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagator/database_write.rb +2 -0
- data/lib/contrast/agent/assess/policy/propagator/substitution.rb +2 -154
- data/lib/contrast/agent/assess/policy/source_method.rb +2 -71
- data/lib/contrast/agent/assess/policy/trigger_method.rb +45 -110
- data/lib/contrast/agent/assess/policy/trigger_node.rb +62 -21
- data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +1 -1
- data/lib/contrast/agent/assess/property/tagged.rb +66 -189
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +40 -6
- data/lib/contrast/agent/deadzone/policy/policy.rb +6 -0
- data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +1 -0
- data/lib/contrast/agent/metric_telemetry_event.rb +26 -0
- data/lib/contrast/agent/middleware.rb +14 -62
- data/lib/contrast/agent/patching/policy/after_load_patcher.rb +0 -1
- data/lib/contrast/agent/patching/policy/method_policy.rb +3 -44
- data/lib/contrast/agent/patching/policy/method_policy_extend.rb +111 -0
- data/lib/contrast/agent/patching/policy/patch.rb +37 -238
- data/lib/contrast/agent/patching/policy/patcher.rb +15 -50
- data/lib/contrast/agent/reporting/report.rb +21 -0
- data/lib/contrast/agent/reporting/reporter.rb +142 -0
- data/lib/contrast/agent/reporting/reporting_events/finding.rb +90 -0
- data/lib/contrast/agent/reporting/reporting_events/preflight.rb +25 -0
- data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +56 -0
- data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +37 -0
- data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +127 -0
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +168 -0
- data/lib/contrast/agent/reporting/reporting_utilities/reporting_storage.rb +66 -0
- data/lib/contrast/agent/request.rb +2 -81
- data/lib/contrast/agent/request_context.rb +18 -126
- data/lib/contrast/agent/request_context_extend.rb +138 -0
- data/lib/contrast/agent/request_handler.rb +7 -3
- data/lib/contrast/agent/response.rb +2 -73
- data/lib/contrast/agent/rule_set.rb +2 -4
- data/lib/contrast/agent/startup_metrics_telemetry_event.rb +94 -0
- data/lib/contrast/agent/static_analysis.rb +5 -3
- data/lib/contrast/agent/telemetry.rb +137 -0
- data/lib/contrast/agent/telemetry_event.rb +33 -0
- data/lib/contrast/agent/thread_watcher.rb +66 -11
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/agent.rb +21 -1
- data/lib/contrast/api/communication/connection_status.rb +10 -7
- data/lib/contrast/api/communication/messaging_queue.rb +37 -3
- data/lib/contrast/api/communication/response_processor.rb +15 -8
- data/lib/contrast/api/communication/service_lifecycle.rb +13 -3
- data/lib/contrast/api/communication/socket.rb +6 -8
- data/lib/contrast/api/communication/socket_client.rb +29 -12
- data/lib/contrast/api/communication/speedracer.rb +37 -1
- data/lib/contrast/api/communication/tcp_socket.rb +4 -3
- data/lib/contrast/api/communication/unix_socket.rb +1 -0
- data/lib/contrast/api/decorators/finding.rb +45 -0
- data/lib/contrast/components/api.rb +90 -0
- data/lib/contrast/components/app_context.rb +10 -41
- data/lib/contrast/components/app_context_extend.rb +78 -0
- data/lib/contrast/components/assess.rb +7 -0
- data/lib/contrast/components/base.rb +23 -0
- data/lib/contrast/components/config.rb +92 -13
- data/lib/contrast/components/contrast_service.rb +11 -0
- data/lib/contrast/components/sampling.rb +2 -2
- data/lib/contrast/config/agent_configuration.rb +1 -1
- data/lib/contrast/config/api_configuration.rb +27 -0
- data/lib/contrast/config/api_proxy_configuration.rb +14 -0
- data/lib/contrast/config/application_configuration.rb +2 -3
- data/lib/contrast/config/assess_configuration.rb +3 -2
- data/lib/contrast/config/base_configuration.rb +17 -28
- data/lib/contrast/config/certification_configuration.rb +15 -0
- data/lib/contrast/config/env_variables.rb +18 -0
- data/lib/contrast/config/heap_dump_configuration.rb +6 -6
- data/lib/contrast/config/inventory_configuration.rb +1 -5
- data/lib/contrast/config/protect_rule_configuration.rb +1 -1
- data/lib/contrast/config/request_audit_configuration.rb +18 -0
- data/lib/contrast/config/root_configuration.rb +1 -0
- data/lib/contrast/config/ruby_configuration.rb +6 -6
- data/lib/contrast/config/service_configuration.rb +2 -2
- data/lib/contrast/config.rb +1 -1
- data/lib/contrast/configuration.rb +4 -2
- data/lib/contrast/extension/assess/array.rb +5 -7
- data/lib/contrast/framework/manager.rb +22 -44
- data/lib/contrast/framework/manager_extend.rb +50 -0
- data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +9 -6
- data/lib/contrast/framework/rails/patch/support.rb +31 -29
- data/lib/contrast/framework/rails/railtie.rb +1 -1
- data/lib/contrast/framework/sinatra/support.rb +2 -1
- data/lib/contrast/logger/application.rb +4 -0
- data/lib/contrast/logger/log.rb +8 -103
- data/lib/contrast/utils/assess/propagation_method_utils.rb +129 -0
- data/lib/contrast/utils/assess/property/tagged_utils.rb +165 -0
- data/lib/contrast/utils/assess/source_method_utils.rb +83 -0
- data/lib/contrast/utils/assess/tracking_util.rb +20 -15
- data/lib/contrast/utils/assess/trigger_method_utils.rb +138 -0
- data/lib/contrast/utils/class_util.rb +65 -54
- data/lib/contrast/utils/exclude_key.rb +20 -0
- data/lib/contrast/utils/findings.rb +62 -0
- data/lib/contrast/utils/hash_digest.rb +10 -73
- data/lib/contrast/utils/hash_digest_extend.rb +86 -0
- data/lib/contrast/utils/head_dump_utils_extend.rb +74 -0
- data/lib/contrast/utils/heap_dump_util.rb +2 -65
- data/lib/contrast/utils/invalid_configuration_util.rb +29 -0
- data/lib/contrast/utils/io_util.rb +1 -1
- data/lib/contrast/utils/log_utils.rb +108 -0
- data/lib/contrast/utils/lru_cache.rb +4 -2
- data/lib/contrast/utils/metrics_hash.rb +59 -0
- data/lib/contrast/utils/middleware_utils.rb +87 -0
- data/lib/contrast/utils/net_http_base.rb +158 -0
- data/lib/contrast/utils/object_share.rb +1 -0
- data/lib/contrast/utils/os.rb +23 -0
- data/lib/contrast/utils/patching/policy/patch_utils.rb +232 -0
- data/lib/contrast/utils/patching/policy/patcher_utils.rb +54 -0
- data/lib/contrast/utils/request_utils.rb +88 -0
- data/lib/contrast/utils/response_utils.rb +97 -0
- data/lib/contrast/utils/substitution_utils.rb +167 -0
- data/lib/contrast/utils/tag_util.rb +9 -9
- data/lib/contrast/utils/telemetry.rb +79 -0
- data/lib/contrast/utils/telemetry_client.rb +90 -0
- data/lib/contrast/utils/telemetry_identifier.rb +130 -0
- data/lib/contrast.rb +19 -1
- data/resources/assess/policy.json +12 -6
- data/resources/deadzone/policy.json +86 -5
- data/ruby-agent.gemspec +7 -6
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +68 -26
- data/ext/cs__protect_kernel/cs__protect_kernel.c +0 -47
- data/ext/cs__protect_kernel/cs__protect_kernel.h +0 -12
- data/lib/contrast/config/default_value.rb +0 -17
- data/lib/contrast/extension/protect/kernel.rb +0 -29
@@ -0,0 +1,142 @@
|
|
1
|
+
# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/agent/worker_thread'
|
5
|
+
require 'contrast/agent/reporting/report'
|
6
|
+
require 'contrast/components/logger'
|
7
|
+
require 'contrast/utils/object_share'
|
8
|
+
require 'contrast/agent/version'
|
9
|
+
require 'base64'
|
10
|
+
|
11
|
+
module Contrast
|
12
|
+
module Agent
|
13
|
+
# This module will hold everything essential to reporting to TeamServer
|
14
|
+
class Reporter < WorkerThread
|
15
|
+
include Contrast::Components::Logger::InstanceMethods
|
16
|
+
include Contrast::Utils::ObjectShare
|
17
|
+
|
18
|
+
class << self
|
19
|
+
include Contrast::Components::Logger::InstanceMethods
|
20
|
+
|
21
|
+
# check if we can report to TS
|
22
|
+
#
|
23
|
+
# @return[Boolean] true if bypass is enabled, or false if bypass disabled
|
24
|
+
def enabled?
|
25
|
+
@_enabled = Contrast::CONTRAST_SERVICE.use_agent_communication? if @_enabled.nil?
|
26
|
+
@_enabled
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def headers
|
31
|
+
@_headers ||= {
|
32
|
+
app_name: Base64.encode64(Contrast::APP_CONTEXT.app_name).chomp!,
|
33
|
+
api_key: Contrast::API.api_key,
|
34
|
+
agent_version: [RUBY, Contrast::Agent::VERSION].join(SPACE),
|
35
|
+
app_language: RUBY,
|
36
|
+
app_path: Base64.encode64(Contrast::APP_CONTEXT.path).chomp!,
|
37
|
+
app_version: Contrast::APP_CONTEXT.app_version,
|
38
|
+
authorization: Base64.encode64("#{ Contrast::API.username }:#{ Contrast::API.service_key }").chomp!,
|
39
|
+
server_name: Base64.encode64(Contrast::APP_CONTEXT.server_name).chomp!,
|
40
|
+
server_path: Base64.encode64(Contrast::APP_CONTEXT.server_path).chomp!,
|
41
|
+
server_type: Base64.encode64(Contrast::APP_CONTEXT.server_type).chomp!,
|
42
|
+
content_type: 'application/json',
|
43
|
+
encoding: 'base64'
|
44
|
+
}.cs__freeze
|
45
|
+
end
|
46
|
+
|
47
|
+
def client
|
48
|
+
@_client ||= Contrast::Utils::ReporterClient.new headers
|
49
|
+
end
|
50
|
+
|
51
|
+
def connection
|
52
|
+
@_connection ||= client.initialize_connection
|
53
|
+
end
|
54
|
+
|
55
|
+
def audit
|
56
|
+
@_audit ||= Contrast::Agent::Reporting::Audit.new
|
57
|
+
end
|
58
|
+
|
59
|
+
def attempt_to_start?
|
60
|
+
unless cs__class.enabled?
|
61
|
+
logger.warn('Reporter service is disabled!')
|
62
|
+
return false
|
63
|
+
end
|
64
|
+
|
65
|
+
logger.debug('Attempting to start Reporter thread') unless running?
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
def start_thread!
|
70
|
+
return if running?
|
71
|
+
|
72
|
+
@_thread = Contrast::Agent::Thread.new do
|
73
|
+
logger.debug('Starting background Reporter thread.')
|
74
|
+
event = queue.pop
|
75
|
+
|
76
|
+
begin
|
77
|
+
logger.debug('This is the current processed event', event)
|
78
|
+
client.send_event event, connection
|
79
|
+
rescue StandardError => e
|
80
|
+
logger.error('Could not send message to service from Reporter queue.', e)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def send_event event
|
86
|
+
if ::Contrast::AGENT.disabled?
|
87
|
+
logger.warn('Attempted to queue event with Agent disabled', caller: caller, event: event)
|
88
|
+
return
|
89
|
+
end
|
90
|
+
|
91
|
+
return unless cs__class.enabled?
|
92
|
+
|
93
|
+
logger.debug('Enqueued event for sending', event_type: event.cs__class)
|
94
|
+
|
95
|
+
result = queue << event if event
|
96
|
+
return result unless ::Contrast::API.request_audit_enable
|
97
|
+
|
98
|
+
audit&.audit_event(event)
|
99
|
+
result
|
100
|
+
end
|
101
|
+
|
102
|
+
# Use this to bypass the messaging queue and leave response processing to the caller
|
103
|
+
def send_event_immediately event
|
104
|
+
if ::Contrast::AGENT.disabled?
|
105
|
+
logger.warn('Reporter attempted to send event immediately with Agent disabled', caller: caller, event: event)
|
106
|
+
return
|
107
|
+
end
|
108
|
+
response_data = client.send_event event, connection, true
|
109
|
+
if response_data.status == 200
|
110
|
+
# handle_response
|
111
|
+
client.send(:handle_response, event, response_data, connection)
|
112
|
+
end
|
113
|
+
|
114
|
+
return unless ::Contrast::API.request_audit_enable
|
115
|
+
|
116
|
+
audit&.audit_event(event, response_data)
|
117
|
+
response_data
|
118
|
+
rescue StandardError => e
|
119
|
+
logger.error('Could not send message to service from Reporter queue.', e)
|
120
|
+
end
|
121
|
+
|
122
|
+
def delete_queue!
|
123
|
+
@_queue&.clear
|
124
|
+
@_queue&.close
|
125
|
+
@_queue = nil
|
126
|
+
end
|
127
|
+
|
128
|
+
def stop!
|
129
|
+
return unless running?
|
130
|
+
|
131
|
+
super
|
132
|
+
delete_queue!
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def queue
|
138
|
+
@_queue ||= Queue.new
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
require 'contrast/components/logger'
|
6
|
+
require 'contrast/agent/reporting/reporting_events/reporting_event'
|
7
|
+
|
8
|
+
module Contrast
|
9
|
+
module Agent
|
10
|
+
module Reporting
|
11
|
+
# This is the new Findings class which will include all the needed information
|
12
|
+
# for the new reporting system
|
13
|
+
class Finding < Contrast::Agent::Reporting::ReportingEvent
|
14
|
+
attr_accessor :events, :properties, :request, :hash_code
|
15
|
+
attr_reader :rule_id
|
16
|
+
|
17
|
+
def initialize rule_id
|
18
|
+
super
|
19
|
+
@event_type = :report_vulnerability
|
20
|
+
@events = []
|
21
|
+
@platform = Contrast::Utils::ObjectShare::RUBY
|
22
|
+
@rule_id = Contrast::Utils::StringUtils.truncate(rule_id)
|
23
|
+
@properties = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def attach_data trigger_node, source, object, ret, request, *args
|
27
|
+
@events << Contrast::Agent::Assess::Events::EventFactory.build(trigger_node, source, object, ret, args)
|
28
|
+
@request = request
|
29
|
+
from_properties source, @events
|
30
|
+
attach_routes request
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_controlled_hash **_args
|
34
|
+
validate
|
35
|
+
{
|
36
|
+
platform: @platform,
|
37
|
+
ruleId: @rule_id,
|
38
|
+
request: request,
|
39
|
+
properties: properties,
|
40
|
+
events: events,
|
41
|
+
routes: routes
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def validate
|
46
|
+
raise(ArgumentError, "#{ self } did not have a proper platform. Unable to continue.") unless @platform
|
47
|
+
raise(ArgumentError, "#{ self } did not have a proper rule. Unable to continue.") unless @rule_id
|
48
|
+
raise(ArgumentError, "#{ self } did not have proper events. Unable to continue.") if events.empty?
|
49
|
+
raise(ArgumentError, "#{ self } did not have proper properties. Unable to continue.") if properties.empty?
|
50
|
+
raise(ArgumentError, "#{ self } did not have a proper request. Unable to continue.") unless request
|
51
|
+
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def from_properties source, events
|
58
|
+
return unless source
|
59
|
+
return unless Contrast::Agent::Assess::Tracker.trackable?(source)
|
60
|
+
|
61
|
+
properties = Contrast::Agent::Assess::Tracker.properties(source)
|
62
|
+
|
63
|
+
build_events events, properties.event if properties.event
|
64
|
+
@properties = properties if properties
|
65
|
+
end
|
66
|
+
|
67
|
+
def build_events events, event
|
68
|
+
return unless event
|
69
|
+
|
70
|
+
event.parent_events&.each do |parent_event|
|
71
|
+
build_events(events, parent_event)
|
72
|
+
end
|
73
|
+
|
74
|
+
events << event
|
75
|
+
end
|
76
|
+
|
77
|
+
def attach_routes request
|
78
|
+
context = Contrast::Agent::REQUEST_TRACKER.current
|
79
|
+
if context
|
80
|
+
@routes << context.route if context.route
|
81
|
+
elsif request&.route
|
82
|
+
@routes << request.route
|
83
|
+
elsif request&.path
|
84
|
+
@routes << request.path
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/agent/reporting/reporting_events/reporting_event'
|
5
|
+
|
6
|
+
module Contrast
|
7
|
+
module Agent
|
8
|
+
module Reporting
|
9
|
+
# This class here will hold the needed preflights we will send for certain trace/traces
|
10
|
+
class Preflight < Contrast::Agent::Reporting::ReportingEvent
|
11
|
+
attr_accessor :messages
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
super
|
15
|
+
@event_type = :preflight
|
16
|
+
@messages = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_controlled_hash **_args
|
20
|
+
{ messages: @messages }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/components/logger'
|
5
|
+
require 'contrast/agent/reporting/reporting_events/reporting_event'
|
6
|
+
|
7
|
+
module Contrast
|
8
|
+
module Agent
|
9
|
+
module Reporting
|
10
|
+
# This is the new PreflightMessage class which will include all the needed information
|
11
|
+
# for the new reporting system to report a single message, part of the main Preflight
|
12
|
+
class PreflightMessage < Contrast::Agent::Reporting::ReportingEvent
|
13
|
+
attr_accessor :data, :routes, :hash_code
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
super
|
17
|
+
@event_type = :preflight_message
|
18
|
+
@app_language = Contrast::Utils::ObjectShare::RUBY
|
19
|
+
@app_name = ::Contrast::APP_CONTEXT.app_name
|
20
|
+
@app_version = ::Contrast::APP_CONTEXT.app_version
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_controlled_hash **_args
|
24
|
+
validate
|
25
|
+
super.merge!({
|
26
|
+
app_language: @app_language,
|
27
|
+
app_name: @app_name,
|
28
|
+
app_version: @app_version,
|
29
|
+
data: '',
|
30
|
+
key: 0,
|
31
|
+
routes: @routes,
|
32
|
+
session_id: @agent_session_id_value
|
33
|
+
})
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate
|
37
|
+
raise(ArgumentError, "#{ cs__class } did not have a proper data. Unable to continue.") unless data
|
38
|
+
unless @app_name
|
39
|
+
raise(ArgumentError, "#{ cs__class } did not have a proper application name. Unable to continue.")
|
40
|
+
end
|
41
|
+
unless @app_language
|
42
|
+
raise(ArgumentError, "#{ cs__class } did not have a proper application language. Unable to continue.")
|
43
|
+
end
|
44
|
+
unless @app_version
|
45
|
+
raise(ArgumentError, "#{ cs__class } did not have a proper application version. Unable to continue.")
|
46
|
+
end
|
47
|
+
unless @agent_session_id_value
|
48
|
+
raise(ArgumentError, "#{ cs__class } did not have a proper session id. Unable to continue.")
|
49
|
+
end
|
50
|
+
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/components/logger'
|
5
|
+
require 'contrast/utils/assess/trigger_method_utils'
|
6
|
+
|
7
|
+
module Contrast
|
8
|
+
module Agent
|
9
|
+
module Reporting
|
10
|
+
# This is the new ReportingEvent class which will include all the needed and mutual information
|
11
|
+
# for the new reporting system
|
12
|
+
# @abstract
|
13
|
+
class ReportingEvent
|
14
|
+
attr_reader :routes
|
15
|
+
attr_accessor :event_type
|
16
|
+
|
17
|
+
CODE = :TRACE
|
18
|
+
|
19
|
+
def initialize(*) # rubocop:disable Style/MethodDefParentheses
|
20
|
+
@event_type = Contrast::Utils::ObjectShare::EMPTY_STRING
|
21
|
+
@agent_session_id_value = 0
|
22
|
+
@routes = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_controlled_hash **_args
|
26
|
+
{ code: CODE, routes: routes }
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate
|
30
|
+
raise(ArgumentError, "#{ self } did not have a proper routes. Unable to continue.") if routes.empty?
|
31
|
+
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/components/logger'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
module Contrast
|
9
|
+
module Agent
|
10
|
+
module Reporting
|
11
|
+
# This class will facilitate the Audit functionality and it will be
|
12
|
+
# controlled from the configuration classes
|
13
|
+
class Audit
|
14
|
+
include Contrast::Components::Logger::InstanceMethods
|
15
|
+
|
16
|
+
attr_reader :path_for_requests, :path_for_responses
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
generate_paths if enabled? && Contrast::CONTRAST_SERVICE.use_agent_communication?
|
20
|
+
end
|
21
|
+
|
22
|
+
# This method will be handling the auditing of the requests and responses we send to SpeedRacer. If the audit
|
23
|
+
# feature is enabled, we'll log to file the request and/or response protobuf objects.
|
24
|
+
#
|
25
|
+
# @param event [Contrast::Api::Dtm] One of the DTMs valid for the event field of
|
26
|
+
# Contrast::Api::Dtm::Message|Contrast::Api::Dtm::Activity
|
27
|
+
# @param response_data [Contrast::Api::Settings::AgentSettings,nil]
|
28
|
+
def audit_event event, response_data = nil
|
29
|
+
return unless ::Contrast::API.request_audit_requests || ::Contrast::API.request_audit_responses
|
30
|
+
|
31
|
+
type = event.cs__respond_to?(:file_name) ? event.file_name : event.cs__class.cs__name.to_s.downcase
|
32
|
+
if ::Contrast::API.request_audit_requests
|
33
|
+
data = event.to_s
|
34
|
+
log_data :request, type, data if data
|
35
|
+
end
|
36
|
+
return unless ::Contrast::API.request_audit_responses
|
37
|
+
|
38
|
+
data = response_data.to_s || event.http_response.try(:body) || 'There is no available response'
|
39
|
+
log_data :response, type, data
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# This method will proceed with passing the data with to the writing method
|
45
|
+
# @param type [Symbol] This is the type of the file /:request, :response/
|
46
|
+
# @param data_type[String] DTM type String representation
|
47
|
+
# @param data[String] String representation if the logged data
|
48
|
+
def log_data type, data_type, data = nil
|
49
|
+
return unless enabled?
|
50
|
+
return unless Contrast::CONTRAST_SERVICE.use_agent_communication?
|
51
|
+
|
52
|
+
write_to_file type, data_type, data
|
53
|
+
end
|
54
|
+
|
55
|
+
# This method will be actually writing to the file
|
56
|
+
# @param type [Symbol] This is the type of the file /:request, :response/
|
57
|
+
# @param data_type [String] Data type /Activity/Finding../
|
58
|
+
# @param data [any] The data to be written to the file
|
59
|
+
def write_to_file type, data_type, data = nil
|
60
|
+
time = Time.now.to_i
|
61
|
+
destination = type == :request ? path_for_requests : path_for_responses
|
62
|
+
# If the feature is disabled or we have yet to create the directory structure, then we could have a nil
|
63
|
+
# destination. In that case, take no action
|
64
|
+
return unless destination
|
65
|
+
|
66
|
+
filename = "#{ time }-#{ data_type.gsub('::', '_') }-teamserver.json"
|
67
|
+
filepath = File.join(destination, filename)
|
68
|
+
# Here is use append mode, because of a slightly possibility of overwriting an existing file
|
69
|
+
File.open(filepath, 'a') do |f|
|
70
|
+
f.write({ data_type: Contrast::Utils::StringUtils.force_utf8(data) }.to_json)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Here we will generate the directories for the requests and responses
|
75
|
+
def generate_paths
|
76
|
+
message_directories = File.expand_path(path_to_audits)
|
77
|
+
FileUtils.mkdir_p(message_directories) unless Dir.exist?(message_directories)
|
78
|
+
|
79
|
+
requests_destination = File.expand_path(File.join(message_directories, '/requests'))
|
80
|
+
responses_destination = File.expand_path(File.join(message_directories, '/responses'))
|
81
|
+
|
82
|
+
Dir.mkdir(requests_destination) if enabled_for_requests? && !Dir.exist?(requests_destination)
|
83
|
+
Dir.mkdir(responses_destination) if enabled_for_responses? && !Dir.exist?(responses_destination)
|
84
|
+
|
85
|
+
@path_for_requests ||= requests_destination if enabled_for_requests?
|
86
|
+
@path_for_responses ||= responses_destination if enabled_for_responses?
|
87
|
+
rescue StandardError => e
|
88
|
+
logger.warn('Generating the paths failed with: ', e: e)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Retrieves the configuration value if the request audit is enabled
|
92
|
+
# @return [Boolean]
|
93
|
+
def enabled?
|
94
|
+
::Contrast::API.request_audit_enable
|
95
|
+
end
|
96
|
+
|
97
|
+
# The boolean values for the requests and the responses should be taken under
|
98
|
+
# consideration only if it's in combination with enabled
|
99
|
+
# So in order for us to actually audit the requests, we need:
|
100
|
+
# - enabled? -> ture and enabled_for_requests? -> true
|
101
|
+
# The same is for the responses
|
102
|
+
#
|
103
|
+
#
|
104
|
+
# Retrieve the configuration value if the audit for requests is enabled
|
105
|
+
# @return [Boolean]
|
106
|
+
def enabled_for_requests?
|
107
|
+
::Contrast::API.request_audit_requests
|
108
|
+
end
|
109
|
+
|
110
|
+
# Retrieve the configuration value if the audit for responses is enabled
|
111
|
+
# @return [Boolean]
|
112
|
+
def enabled_for_responses?
|
113
|
+
::Contrast::API.request_audit_requests
|
114
|
+
end
|
115
|
+
|
116
|
+
# Retrieve the configuration value for the path of the audits
|
117
|
+
# The value will be read from the configuration yml
|
118
|
+
# but if it isn't defined any - here will be returned the default path from
|
119
|
+
# Contrast::Config::RequestAuditConfiguration::DEFAULT_PATH
|
120
|
+
# @return [String]
|
121
|
+
def path_to_audits
|
122
|
+
::Contrast::API.request_audit_path
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
require 'net/http'
|
6
|
+
require 'contrast/components/logger'
|
7
|
+
require 'contrast/utils/net_http_base'
|
8
|
+
require 'contrast/agent/reporting/reporting_events/finding'
|
9
|
+
require 'contrast/agent/reporting/reporting_events/preflight_message'
|
10
|
+
require 'contrast/agent/reporting/reporting_events/reporting_event'
|
11
|
+
|
12
|
+
module Contrast
|
13
|
+
module Utils
|
14
|
+
# This module creates a Net::HTTP client and initiates a connection to the provided result
|
15
|
+
class ReporterClient < NetHttpBase
|
16
|
+
include ObjectShare
|
17
|
+
SERVICE_NAME = 'Reporter'
|
18
|
+
ENDPOINTS = {
|
19
|
+
application_activity: '/api/ng/activity/application',
|
20
|
+
application_create: '/api/ng/applications/create',
|
21
|
+
agent_startup: '/api/ng/servers/',
|
22
|
+
preflight: '/api/ng/preflight/',
|
23
|
+
report_vulnerability: '/api/ng/traces',
|
24
|
+
server_activity: '/api/ng/servers/',
|
25
|
+
server_update: '/api/ng/update/application'
|
26
|
+
}.cs__freeze
|
27
|
+
# EVENT_TYPES = {
|
28
|
+
# agent_startup: Contrast::Api::Dtm::AgentStartup,
|
29
|
+
# report_vulnerability: Contrast::Agent::Reporting::Finding,
|
30
|
+
# preflight: Contrast::Agent::Reporting::Preflight
|
31
|
+
# }.cs__freeze
|
32
|
+
include Contrast::Components::Logger::InstanceMethods
|
33
|
+
# This method initializes the Net::HTTP client we'll need. it will validate
|
34
|
+
# the connection and make the first request. If connection is valid and response
|
35
|
+
# is available then the open connection is returned.
|
36
|
+
#
|
37
|
+
# @return [Net::HTTP, nil] Return open connection or nil
|
38
|
+
def initialize_connection
|
39
|
+
# for this client we would use proxy and custom certificate file if available
|
40
|
+
super(SERVICE_NAME, Contrast::API.api_url, true, true)
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param headers [Hash] all the headers needed for communication with TS
|
44
|
+
def initialize headers
|
45
|
+
@headers = headers
|
46
|
+
super()
|
47
|
+
end
|
48
|
+
|
49
|
+
# Send Agent Startup event
|
50
|
+
#
|
51
|
+
# @param event [Contrast::Api::Dtm] One of the DTMs valid for the event field of
|
52
|
+
# Contrast::Api::Dtm::Message|Contrast::Api::Dtm::Activity
|
53
|
+
# @param connection [Net::HTTP] open connection
|
54
|
+
# @return response [Net::HTTP::Response] response from TS
|
55
|
+
def send_agent_startup event, connection
|
56
|
+
logger.debug('Preparing to send startup messages')
|
57
|
+
request = build_request ENDPOINTS[:agent_startup], event
|
58
|
+
response = connection.request(request)
|
59
|
+
logger.debug('Successfully sent startup messages to service.') if response
|
60
|
+
response
|
61
|
+
end
|
62
|
+
|
63
|
+
# Check event type and send it to appropriate TS endpoint
|
64
|
+
#
|
65
|
+
# @param event [Contrast::Api::Dtm,Contrast::Agent::Reporting::ReportingEvent] One of the DTMs valid for the event
|
66
|
+
# field of Contrast::Api::Dtm::Message|Contrast::Api::Dtm::Activity
|
67
|
+
# @param connection [Net::HTTP] open connection
|
68
|
+
# @param send_immediately [Boolean] flag for the logger
|
69
|
+
# @return response [Net::HTTP::Response, nil] response from TS if no response
|
70
|
+
def send_event event, connection, send_immediately = false
|
71
|
+
event_type = symbolized_event_type event.event_type
|
72
|
+
@response = send_events event, event_type, connection
|
73
|
+
log_send_event event if send_immediately
|
74
|
+
@response
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# This method will build headers of the request required for TS communication
|
80
|
+
#
|
81
|
+
# @param request [Net::HTTP::Post]
|
82
|
+
def build_headers request
|
83
|
+
@app_version = @headers[:app_version]
|
84
|
+
request['API-Key'] = @headers[:api_key]
|
85
|
+
request['Application-Language'] = @headers[:app_language]
|
86
|
+
request['Application-Name'] = @headers[:app_name]
|
87
|
+
request['Application-Path'] = @headers[:app_path]
|
88
|
+
request['Application-Version'] = @app_version if @app_version
|
89
|
+
request['Authorization'] = @headers[:authorization]
|
90
|
+
request['Content-Type'] = @headers[:content_type]
|
91
|
+
request['Server-Name'] = @headers[:server_name]
|
92
|
+
request['Server-Path'] = @headers[:server_path]
|
93
|
+
request['Server-Type'] = @headers[:server_type]
|
94
|
+
request['X-Contrast-Agent'] = @headers[:agent_version]
|
95
|
+
request['X-Contrast-Header-Encoding'] = @headers[:encoding]
|
96
|
+
request
|
97
|
+
end
|
98
|
+
|
99
|
+
# build the request headers and assign endpoint
|
100
|
+
#
|
101
|
+
# @param endpoint [String] endpoint to report to
|
102
|
+
# @param event [Contrast::Api::Dtm,Contrast::Agent::Reporting::ReportingEvent]
|
103
|
+
# One of the DTMs valid for the event field of Contrast::Api::Dtm::Message|Contrast::Api::Dtm::Activity
|
104
|
+
# @return [Net::HTTP::Post]
|
105
|
+
def build_request endpoint, event
|
106
|
+
@request = build_headers Net::HTTP::Post.new(endpoint)
|
107
|
+
@request.body = if event.cs__is_a?(Contrast::Agent::Reporting::ReportingEvent)
|
108
|
+
event.to_controlled_hash.to_json
|
109
|
+
else
|
110
|
+
event.to_json
|
111
|
+
end
|
112
|
+
@request
|
113
|
+
end
|
114
|
+
|
115
|
+
# log the event sent immediately
|
116
|
+
#
|
117
|
+
# @param event [Contrast::Api::Dtm,Contrast::Agent::Reporting::ReportingEvent] One of the DTMs valid for the
|
118
|
+
# event field of Contrast::Api::Dtm::Message|Contrast::Api::Dtm::Activity
|
119
|
+
def log_send_event event
|
120
|
+
logger.debug('Immediately sending event.', event_id: event.__id__, event_type: event.cs__class.cs__name)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Sent different events to different endpoints
|
124
|
+
#
|
125
|
+
# @param event [Contrast::Api::Dtm,Contrast::Agent::Reporting::ReportingEvent] Main reporting event, all events
|
126
|
+
# inherit it
|
127
|
+
# @param event_type [Symbol] We'll use this symbol to match the urls
|
128
|
+
# @param connection [Net::HTTP] open connection
|
129
|
+
def send_events event, event_type, connection
|
130
|
+
logger.debug("Preparing to send #{ event_type.to_s.capitalize } messages")
|
131
|
+
request = build_request ENDPOINTS[event_type], event
|
132
|
+
response = connection.request(request)
|
133
|
+
logger.debug("Successfully sent #{ event_type.to_s.capitalize } messages to service.") if response
|
134
|
+
response
|
135
|
+
end
|
136
|
+
|
137
|
+
# Eventually here we'll handle more response types and etc
|
138
|
+
|
139
|
+
# @param event [Contrast::Agent::Reporting::Preflight] the preflight we handle here
|
140
|
+
# @param response [Net::HTTP::Response,nil] The response we handle and read from
|
141
|
+
# @param connection [Net::HTTP] open connection
|
142
|
+
def handle_response event, response, connection
|
143
|
+
return unless event || response || connection
|
144
|
+
|
145
|
+
preflight_message = event.messages[0]
|
146
|
+
event_type = symbolized_event_type preflight_message.event_type
|
147
|
+
return unless event_type == :preflight_message
|
148
|
+
|
149
|
+
# for handling multiple findings
|
150
|
+
# we'll only extract the indexes without *
|
151
|
+
# findings_to_return = response.body.split(',').delete_if { |el| el.include?('*') }
|
152
|
+
# after that we'll do some magic and return them the same way we do for corresponding_finding
|
153
|
+
corresponding_finding = Contrast::Agent::Reporting::ReportingStorage.delete(preflight_message.hash_code)
|
154
|
+
return unless corresponding_finding
|
155
|
+
|
156
|
+
send_event corresponding_finding, connection, true
|
157
|
+
nil
|
158
|
+
end
|
159
|
+
|
160
|
+
def symbolized_event_type event_type
|
161
|
+
return unless event_type
|
162
|
+
return event_type unless event_type.cs__is_a?(String)
|
163
|
+
|
164
|
+
event_type.downcase.to_sym
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|