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.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +1 -0
  3. data/ext/cs__assess_module/cs__assess_module.c +48 -0
  4. data/ext/cs__assess_module/cs__assess_module.h +7 -0
  5. data/ext/cs__common/cs__common.c +24 -7
  6. data/ext/cs__common/cs__common.h +12 -2
  7. data/ext/cs__contrast_patch/cs__contrast_patch.c +48 -11
  8. data/ext/cs__contrast_patch/cs__contrast_patch.h +5 -2
  9. data/ext/cs__os_information/cs__os_information.c +31 -0
  10. data/ext/cs__os_information/cs__os_information.h +7 -0
  11. data/ext/{cs__protect_kernel → cs__os_information}/extconf.rb +0 -0
  12. data/lib/contrast/agent/assess/contrast_event.rb +1 -1
  13. data/lib/contrast/agent/assess/contrast_object.rb +1 -1
  14. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +2 -0
  15. data/lib/contrast/agent/assess/policy/policy_node.rb +6 -6
  16. data/lib/contrast/agent/assess/policy/policy_scanner.rb +5 -0
  17. data/lib/contrast/agent/assess/policy/preshift.rb +19 -6
  18. data/lib/contrast/agent/assess/policy/propagation_method.rb +2 -116
  19. data/lib/contrast/agent/assess/policy/propagation_node.rb +4 -4
  20. data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -1
  21. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +2 -0
  22. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +2 -154
  23. data/lib/contrast/agent/assess/policy/source_method.rb +2 -71
  24. data/lib/contrast/agent/assess/policy/trigger_method.rb +45 -110
  25. data/lib/contrast/agent/assess/policy/trigger_node.rb +62 -21
  26. data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +1 -1
  27. data/lib/contrast/agent/assess/property/tagged.rb +66 -189
  28. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +40 -6
  29. data/lib/contrast/agent/deadzone/policy/policy.rb +6 -0
  30. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +1 -0
  31. data/lib/contrast/agent/metric_telemetry_event.rb +26 -0
  32. data/lib/contrast/agent/middleware.rb +14 -62
  33. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +0 -1
  34. data/lib/contrast/agent/patching/policy/method_policy.rb +3 -44
  35. data/lib/contrast/agent/patching/policy/method_policy_extend.rb +111 -0
  36. data/lib/contrast/agent/patching/policy/patch.rb +37 -238
  37. data/lib/contrast/agent/patching/policy/patcher.rb +15 -50
  38. data/lib/contrast/agent/reporting/report.rb +21 -0
  39. data/lib/contrast/agent/reporting/reporter.rb +142 -0
  40. data/lib/contrast/agent/reporting/reporting_events/finding.rb +90 -0
  41. data/lib/contrast/agent/reporting/reporting_events/preflight.rb +25 -0
  42. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +56 -0
  43. data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +37 -0
  44. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +127 -0
  45. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +168 -0
  46. data/lib/contrast/agent/reporting/reporting_utilities/reporting_storage.rb +66 -0
  47. data/lib/contrast/agent/request.rb +2 -81
  48. data/lib/contrast/agent/request_context.rb +18 -126
  49. data/lib/contrast/agent/request_context_extend.rb +138 -0
  50. data/lib/contrast/agent/request_handler.rb +7 -3
  51. data/lib/contrast/agent/response.rb +2 -73
  52. data/lib/contrast/agent/rule_set.rb +2 -4
  53. data/lib/contrast/agent/startup_metrics_telemetry_event.rb +94 -0
  54. data/lib/contrast/agent/static_analysis.rb +5 -3
  55. data/lib/contrast/agent/telemetry.rb +137 -0
  56. data/lib/contrast/agent/telemetry_event.rb +33 -0
  57. data/lib/contrast/agent/thread_watcher.rb +66 -11
  58. data/lib/contrast/agent/version.rb +1 -1
  59. data/lib/contrast/agent.rb +21 -1
  60. data/lib/contrast/api/communication/connection_status.rb +10 -7
  61. data/lib/contrast/api/communication/messaging_queue.rb +37 -3
  62. data/lib/contrast/api/communication/response_processor.rb +15 -8
  63. data/lib/contrast/api/communication/service_lifecycle.rb +13 -3
  64. data/lib/contrast/api/communication/socket.rb +6 -8
  65. data/lib/contrast/api/communication/socket_client.rb +29 -12
  66. data/lib/contrast/api/communication/speedracer.rb +37 -1
  67. data/lib/contrast/api/communication/tcp_socket.rb +4 -3
  68. data/lib/contrast/api/communication/unix_socket.rb +1 -0
  69. data/lib/contrast/api/decorators/finding.rb +45 -0
  70. data/lib/contrast/components/api.rb +90 -0
  71. data/lib/contrast/components/app_context.rb +10 -41
  72. data/lib/contrast/components/app_context_extend.rb +78 -0
  73. data/lib/contrast/components/assess.rb +7 -0
  74. data/lib/contrast/components/base.rb +23 -0
  75. data/lib/contrast/components/config.rb +92 -13
  76. data/lib/contrast/components/contrast_service.rb +11 -0
  77. data/lib/contrast/components/sampling.rb +2 -2
  78. data/lib/contrast/config/agent_configuration.rb +1 -1
  79. data/lib/contrast/config/api_configuration.rb +27 -0
  80. data/lib/contrast/config/api_proxy_configuration.rb +14 -0
  81. data/lib/contrast/config/application_configuration.rb +2 -3
  82. data/lib/contrast/config/assess_configuration.rb +3 -2
  83. data/lib/contrast/config/base_configuration.rb +17 -28
  84. data/lib/contrast/config/certification_configuration.rb +15 -0
  85. data/lib/contrast/config/env_variables.rb +18 -0
  86. data/lib/contrast/config/heap_dump_configuration.rb +6 -6
  87. data/lib/contrast/config/inventory_configuration.rb +1 -5
  88. data/lib/contrast/config/protect_rule_configuration.rb +1 -1
  89. data/lib/contrast/config/request_audit_configuration.rb +18 -0
  90. data/lib/contrast/config/root_configuration.rb +1 -0
  91. data/lib/contrast/config/ruby_configuration.rb +6 -6
  92. data/lib/contrast/config/service_configuration.rb +2 -2
  93. data/lib/contrast/config.rb +1 -1
  94. data/lib/contrast/configuration.rb +4 -2
  95. data/lib/contrast/extension/assess/array.rb +5 -7
  96. data/lib/contrast/framework/manager.rb +22 -44
  97. data/lib/contrast/framework/manager_extend.rb +50 -0
  98. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +9 -6
  99. data/lib/contrast/framework/rails/patch/support.rb +31 -29
  100. data/lib/contrast/framework/rails/railtie.rb +1 -1
  101. data/lib/contrast/framework/sinatra/support.rb +2 -1
  102. data/lib/contrast/logger/application.rb +4 -0
  103. data/lib/contrast/logger/log.rb +8 -103
  104. data/lib/contrast/utils/assess/propagation_method_utils.rb +129 -0
  105. data/lib/contrast/utils/assess/property/tagged_utils.rb +165 -0
  106. data/lib/contrast/utils/assess/source_method_utils.rb +83 -0
  107. data/lib/contrast/utils/assess/tracking_util.rb +20 -15
  108. data/lib/contrast/utils/assess/trigger_method_utils.rb +138 -0
  109. data/lib/contrast/utils/class_util.rb +65 -54
  110. data/lib/contrast/utils/exclude_key.rb +20 -0
  111. data/lib/contrast/utils/findings.rb +62 -0
  112. data/lib/contrast/utils/hash_digest.rb +10 -73
  113. data/lib/contrast/utils/hash_digest_extend.rb +86 -0
  114. data/lib/contrast/utils/head_dump_utils_extend.rb +74 -0
  115. data/lib/contrast/utils/heap_dump_util.rb +2 -65
  116. data/lib/contrast/utils/invalid_configuration_util.rb +29 -0
  117. data/lib/contrast/utils/io_util.rb +1 -1
  118. data/lib/contrast/utils/log_utils.rb +108 -0
  119. data/lib/contrast/utils/lru_cache.rb +4 -2
  120. data/lib/contrast/utils/metrics_hash.rb +59 -0
  121. data/lib/contrast/utils/middleware_utils.rb +87 -0
  122. data/lib/contrast/utils/net_http_base.rb +158 -0
  123. data/lib/contrast/utils/object_share.rb +1 -0
  124. data/lib/contrast/utils/os.rb +23 -0
  125. data/lib/contrast/utils/patching/policy/patch_utils.rb +232 -0
  126. data/lib/contrast/utils/patching/policy/patcher_utils.rb +54 -0
  127. data/lib/contrast/utils/request_utils.rb +88 -0
  128. data/lib/contrast/utils/response_utils.rb +97 -0
  129. data/lib/contrast/utils/substitution_utils.rb +167 -0
  130. data/lib/contrast/utils/tag_util.rb +9 -9
  131. data/lib/contrast/utils/telemetry.rb +79 -0
  132. data/lib/contrast/utils/telemetry_client.rb +90 -0
  133. data/lib/contrast/utils/telemetry_identifier.rb +130 -0
  134. data/lib/contrast.rb +19 -1
  135. data/resources/assess/policy.json +12 -6
  136. data/resources/deadzone/policy.json +86 -5
  137. data/ruby-agent.gemspec +7 -6
  138. data/service_executables/VERSION +1 -1
  139. data/service_executables/linux/contrast-service +0 -0
  140. data/service_executables/mac/contrast-service +0 -0
  141. metadata +68 -26
  142. data/ext/cs__protect_kernel/cs__protect_kernel.c +0 -47
  143. data/ext/cs__protect_kernel/cs__protect_kernel.h +0 -12
  144. data/lib/contrast/config/default_value.rb +0 -17
  145. 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