contrast-agent 4.13.1 → 4.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +1 -0
  3. data/lib/contrast/agent/assess/policy/policy_node.rb +6 -6
  4. data/lib/contrast/agent/assess/policy/policy_scanner.rb +5 -0
  5. data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -1
  6. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +2 -154
  7. data/lib/contrast/agent/assess/policy/trigger_method.rb +44 -7
  8. data/lib/contrast/agent/assess/policy/trigger_node.rb +14 -6
  9. data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +1 -1
  10. data/lib/contrast/agent/assess/property/tagged.rb +51 -57
  11. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +40 -6
  12. data/lib/contrast/agent/metric_telemetry_event.rb +2 -2
  13. data/lib/contrast/agent/middleware.rb +5 -75
  14. data/lib/contrast/agent/patching/policy/method_policy.rb +3 -89
  15. data/lib/contrast/agent/patching/policy/method_policy_extend.rb +111 -0
  16. data/lib/contrast/agent/patching/policy/patcher.rb +12 -8
  17. data/lib/contrast/agent/reporting/report.rb +21 -0
  18. data/lib/contrast/agent/reporting/reporter.rb +142 -0
  19. data/lib/contrast/agent/reporting/reporting_events/finding.rb +90 -0
  20. data/lib/contrast/agent/reporting/reporting_events/preflight.rb +25 -0
  21. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +56 -0
  22. data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +37 -0
  23. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +127 -0
  24. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +168 -0
  25. data/lib/contrast/agent/reporting/reporting_utilities/reporting_storage.rb +66 -0
  26. data/lib/contrast/agent/request.rb +2 -81
  27. data/lib/contrast/agent/request_context.rb +4 -128
  28. data/lib/contrast/agent/request_context_extend.rb +138 -0
  29. data/lib/contrast/agent/response.rb +2 -73
  30. data/lib/contrast/agent/startup_metrics_telemetry_event.rb +39 -16
  31. data/lib/contrast/agent/static_analysis.rb +1 -1
  32. data/lib/contrast/agent/telemetry.rb +15 -7
  33. data/lib/contrast/agent/telemetry_event.rb +8 -9
  34. data/lib/contrast/agent/thread_watcher.rb +31 -5
  35. data/lib/contrast/agent/version.rb +1 -1
  36. data/lib/contrast/agent.rb +15 -0
  37. data/lib/contrast/api/communication/connection_status.rb +10 -7
  38. data/lib/contrast/api/communication/messaging_queue.rb +37 -3
  39. data/lib/contrast/api/communication/response_processor.rb +15 -8
  40. data/lib/contrast/api/communication/service_lifecycle.rb +13 -3
  41. data/lib/contrast/api/communication/socket.rb +6 -8
  42. data/lib/contrast/api/communication/socket_client.rb +29 -12
  43. data/lib/contrast/api/communication/speedracer.rb +37 -1
  44. data/lib/contrast/api/communication/tcp_socket.rb +4 -3
  45. data/lib/contrast/api/communication/unix_socket.rb +1 -0
  46. data/lib/contrast/api/decorators/finding.rb +45 -0
  47. data/lib/contrast/components/api.rb +56 -0
  48. data/lib/contrast/components/app_context.rb +10 -65
  49. data/lib/contrast/components/app_context_extend.rb +78 -0
  50. data/lib/contrast/components/base.rb +23 -0
  51. data/lib/contrast/components/config.rb +8 -8
  52. data/lib/contrast/components/contrast_service.rb +5 -0
  53. data/lib/contrast/components/sampling.rb +2 -2
  54. data/lib/contrast/config/agent_configuration.rb +1 -1
  55. data/lib/contrast/config/api_configuration.rb +9 -4
  56. data/lib/contrast/config/api_proxy_configuration.rb +14 -0
  57. data/lib/contrast/config/application_configuration.rb +2 -3
  58. data/lib/contrast/config/assess_configuration.rb +3 -3
  59. data/lib/contrast/config/base_configuration.rb +17 -28
  60. data/lib/contrast/config/certification_configuration.rb +15 -0
  61. data/lib/contrast/config/env_variables.rb +2 -9
  62. data/lib/contrast/config/heap_dump_configuration.rb +6 -6
  63. data/lib/contrast/config/inventory_configuration.rb +1 -5
  64. data/lib/contrast/config/protect_rule_configuration.rb +1 -1
  65. data/lib/contrast/config/request_audit_configuration.rb +18 -0
  66. data/lib/contrast/config/ruby_configuration.rb +6 -6
  67. data/lib/contrast/config/service_configuration.rb +1 -2
  68. data/lib/contrast/config.rb +0 -1
  69. data/lib/contrast/configuration.rb +1 -2
  70. data/lib/contrast/extension/assess/array.rb +5 -7
  71. data/lib/contrast/framework/manager.rb +8 -32
  72. data/lib/contrast/framework/manager_extend.rb +50 -0
  73. data/lib/contrast/framework/rails/railtie.rb +1 -1
  74. data/lib/contrast/framework/sinatra/support.rb +2 -1
  75. data/lib/contrast/logger/log.rb +8 -103
  76. data/lib/contrast/utils/assess/property/tagged_utils.rb +23 -0
  77. data/lib/contrast/utils/assess/tracking_util.rb +20 -15
  78. data/lib/contrast/utils/assess/trigger_method_utils.rb +1 -1
  79. data/lib/contrast/utils/class_util.rb +18 -14
  80. data/lib/contrast/utils/findings.rb +62 -0
  81. data/lib/contrast/utils/hash_digest.rb +10 -73
  82. data/lib/contrast/utils/hash_digest_extend.rb +86 -0
  83. data/lib/contrast/utils/head_dump_utils_extend.rb +74 -0
  84. data/lib/contrast/utils/heap_dump_util.rb +2 -65
  85. data/lib/contrast/utils/invalid_configuration_util.rb +29 -0
  86. data/lib/contrast/utils/io_util.rb +1 -1
  87. data/lib/contrast/utils/log_utils.rb +108 -0
  88. data/lib/contrast/utils/middleware_utils.rb +87 -0
  89. data/lib/contrast/utils/net_http_base.rb +158 -0
  90. data/lib/contrast/utils/object_share.rb +1 -0
  91. data/lib/contrast/utils/request_utils.rb +88 -0
  92. data/lib/contrast/utils/response_utils.rb +97 -0
  93. data/lib/contrast/utils/substitution_utils.rb +167 -0
  94. data/lib/contrast/utils/tag_util.rb +9 -9
  95. data/lib/contrast/utils/telemetry.rb +4 -2
  96. data/lib/contrast/utils/telemetry_client.rb +90 -0
  97. data/lib/contrast/utils/telemetry_identifier.rb +17 -24
  98. data/ruby-agent.gemspec +5 -5
  99. metadata +48 -23
  100. data/lib/contrast/config/default_value.rb +0 -17
  101. data/lib/contrast/utils/requests_client.rb +0 -150
@@ -0,0 +1,88 @@
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
+ module Contrast
5
+ module Utils
6
+ # used in Contrast::Agent::Request
7
+ module RequestUtils
8
+ # Return a flattened hash of params with realized paths for keys, in
9
+ # addition to a separate, valueless, entry for each nest key.
10
+ # See RUBY-621 for more details.
11
+ # { key : { nested_key : ['x','y','z' ] } }
12
+ # becomes
13
+ # {
14
+ # key[nested_key][0] : 'x'
15
+ # key[nested_key][1] : 'y'
16
+ # key[nested_key][2] : 'z'
17
+ # key : ''
18
+ # nested_key : ''
19
+ # }
20
+ def normalize_params val, prefix: nil
21
+ # In non-recursive invocations, val should always be a Hash
22
+ # (rather than breaking this out into two methods)
23
+ case val
24
+ when Tempfile
25
+ # Skip if it's the auto-generated value from rails when it handles
26
+ # file uploads. The file name will still be sent to SR for analysis.
27
+ {}
28
+ when Hash
29
+ res = val.each_with_object({}) do |(k, v), hash|
30
+ k = Contrast::Utils::StringUtils.force_utf8(k)
31
+ nested_prefix = prefix.nil? ? k : "#{ prefix }[#{ k }]"
32
+ hash[k] = Contrast::Utils::ObjectShare::EMPTY_STRING
33
+ hash.merge! normalize_params(v, prefix: nested_prefix)
34
+ end
35
+ res[prefix] = Contrast::Utils::ObjectShare::EMPTY_STRING if prefix
36
+ res
37
+ when Enumerable
38
+ idx = 0
39
+ res = {}
40
+ while idx < val.length
41
+ res.merge! normalize_params(val[idx], prefix: "#{ prefix }[#{ idx }]")
42
+ idx += 1
43
+ end
44
+ res[prefix] = Contrast::Utils::ObjectShare::EMPTY_STRING if prefix
45
+ res
46
+ else
47
+ { prefix => Contrast::Utils::StringUtils.force_utf8(val) }
48
+ end
49
+ end
50
+
51
+ def read_body body
52
+ return body if body.is_a?(String)
53
+
54
+ begin
55
+ can_rewind = Contrast::Utils::DuckUtils.quacks_to?(body, :rewind)
56
+ # if we are after a middleware that failed to rewind
57
+ body.rewind if can_rewind
58
+ body.read
59
+ rescue StandardError => e
60
+ logger.error('Error in attempt to read body', message: e.message)
61
+ logger.trace('With Stack', e)
62
+ body.to_s
63
+ ensure
64
+ # be a good citizen and rewind
65
+ body.rewind if can_rewind
66
+ end
67
+ end
68
+
69
+ def traverse_parsed_multipart multipart_data, current_names
70
+ return current_names unless multipart_data
71
+
72
+ multipart_data.each_value do |data_value|
73
+ next unless data_value.is_a?(Hash)
74
+
75
+ tempfile = data_value[:tempfile]
76
+ if tempfile.nil?
77
+ traverse_parsed_multipart(data_value, current_names)
78
+ else
79
+ name = data_value[:name].to_s
80
+ file_name = data_value[:filename].to_s
81
+ current_names[name] = file_name
82
+ end
83
+ end
84
+ current_names
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,97 @@
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
+ module Contrast
5
+ module Utils
6
+ # used in Contrast::Agent::Response
7
+ module ResponseUtils
8
+ private
9
+
10
+ # From the dtm for normalized_response_headers:
11
+ # Key is UPPERCASE_UNDERSCORE
12
+ #
13
+ # Example: Content-Type: text/html; charset=utf-8
14
+ # "CONTENT_TYPE" => Content-Type,["text/html; charset=utf8"]
15
+ def append_pair map, key, value
16
+ return unless key && value
17
+ return if value.is_a?(Hash)
18
+
19
+ safe_key = Contrast::Utils::StringUtils.force_utf8(key)
20
+ hash_key = Contrast::Utils::StringUtils.normalized_key(safe_key)
21
+ map[hash_key] ||= Contrast::Api::Dtm::Pair.new
22
+ map[hash_key].key = safe_key
23
+ map[hash_key].values << Contrast::Utils::StringUtils.force_utf8(value)
24
+ end
25
+
26
+ HTTP_PREFIX = /^[Hh][Tt][Tt][Pp][_-]/i.cs__freeze
27
+
28
+ # Given some holder of the content of the response's body, extract that
29
+ # content and return it as a String
30
+ #
31
+ # @param body [String, Rack::File, Rack::BodyProxy,
32
+ # ActionDispatch::Response::RackBody, Rack::Response] Something that
33
+ # holds, wraps, or is the body of the Response
34
+ # @return [nil, String] the content of the body
35
+ def extract_body body
36
+ return unless body
37
+ return if defined?(Rack::File) && body.is_a?(Rack::File)
38
+
39
+ case body
40
+ when Rack::BodyProxy
41
+ handle_rack_body_proxy(body)
42
+ when ActionDispatch::Response::RackBody
43
+ extract_body(body.body) if defined?(ActionDispatch::Response::RackBody)
44
+ when Rack::Response
45
+ extract_body(body.body)
46
+ when Contrast::Utils::DuckUtils.quacks_to?(body, :each)
47
+ acc = []
48
+ body.each { |tmp| acc << read_or_string(tmp) }
49
+ acc.compact.join(Contrast::Utils::ObjectShare::NEW_LINE)
50
+ when ActionView::OutputBuffer
51
+ # https://stackoverflow.com/questions/15654676/how-to-convert-activesupportsafebuffer-to-string
52
+ body.to_str
53
+ else
54
+ read_or_string(body)
55
+ end
56
+ # if body.is_a?(Rack::BodyProxy)
57
+ # handle_rack_body_proxy(body)
58
+ # elsif (defined?(ActionDispatch::Response::RackBody) && body.is_a?(ActionDispatch::Response::RackBody)) ||
59
+ # body.is_a?(Rack::Response)
60
+ #
61
+ # extract_body(body.body)
62
+ # elsif Contrast::Utils::DuckUtils.quacks_to?(body, :each)
63
+ # acc = []
64
+ # body.each { |tmp| acc << read_or_string(tmp) }
65
+ # acc.compact.join(Contrast::Utils::ObjectShare::NEW_LINE)
66
+ # elsif ActionView::OutputBuffer
67
+ # # https://stackoverflow.com/questions/15654676/how-to-convert-activesupportsafebuffer-to-string
68
+ # body.to_str
69
+ # else
70
+ # read_or_string(body)
71
+ # end
72
+ end
73
+
74
+ def handle_rack_body_proxy body
75
+ next_body = body.instance_variable_get(:@body)
76
+ case next_body
77
+ when Array
78
+ extract_body(next_body[0])
79
+ else
80
+ extract_body(next_body)
81
+ end
82
+ end
83
+
84
+ def read_or_string obj
85
+ return unless obj
86
+
87
+ if Contrast::Utils::DuckUtils.quacks_to?(obj, :read)
88
+ tmp = obj.read
89
+ obj.rewind
90
+ tmp
91
+ else
92
+ obj.to_s
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,167 @@
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
+ module Contrast
5
+ module Utils
6
+ # used in Contrast::Agent::Assesss::Policy::Propagator::Substitution
7
+ module SubstitutionUtils
8
+ CAPTURE_GROUP_REGEXP = /\\[[:digit:]]/.cs__freeze
9
+ CAPTURE_NAME_REGEXP = /\\k<[[:alpha:]]/.cs__freeze
10
+
11
+ private
12
+
13
+ def substitution_tagger patcher, preshift, ret, block, global = true
14
+ return ret unless ret
15
+
16
+ begin
17
+ source = preshift.object
18
+ self_tracked = Contrast::Agent::Assess::Tracker.tracked?(source)
19
+ args = preshift.args[1]
20
+ incoming_tracked = args && determine_tracked(args)
21
+ return ret unless self_tracked || incoming_tracked
22
+
23
+ parent_events = []
24
+ if block
25
+ block_sub(self_tracked, source, ret)
26
+ elsif args.is_a?(String)
27
+ string_sub(parent_events, self_tracked, preshift, ret, args, incoming_tracked, global)
28
+ elsif args.is_a?(Hash)
29
+ hash_sub(self_tracked, source, ret)
30
+ else # Enumerator, only for gsub
31
+ pattern_gsub(parent_events, preshift, ret)
32
+ end
33
+
34
+ self_tracked_handle self_tracked, source, parent_events
35
+ string_build_event(parent_events, patcher, preshift, ret)
36
+ rescue StandardError => e
37
+ logger.error('Unable to apply gsub propagator', e)
38
+ end
39
+ ret
40
+ end
41
+
42
+ def self_tracked_handle self_tracked, source, parent_events
43
+ return unless self_tracked
44
+
45
+ source_properties = Contrast::Agent::Assess::Tracker.properties(source)
46
+ parent_event = source_properties&.event
47
+ parent_events.prepend(parent_event) if parent_event
48
+ end
49
+
50
+ def determine_tracked args
51
+ # if there's no arg, it can't be tracked
52
+ return false unless args
53
+
54
+ # if it's a string, just ask if it's tracked
55
+ case args
56
+ when String
57
+ Contrast::Agent::Assess::Tracker.tracked?(args)
58
+ # if it's a hash, ask if it has a tracked string
59
+ when Hash
60
+ args.values.any? { |value| value.is_a?(String) && Contrast::Agent::Assess::Tracker.tracked?(value) }
61
+ # this should never happen
62
+ else
63
+ false
64
+ end
65
+ end
66
+
67
+ def string_sub parent_events, self_tracked, preshift, ret, incoming, incoming_tracked, global
68
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
69
+
70
+ incoming_properties = Contrast::Agent::Assess::Tracker.properties(incoming)
71
+ parent_events << incoming_properties&.event if incoming_properties&.event
72
+
73
+ source = preshift.object
74
+
75
+ # We can't efficiently find the places that things were
76
+ # copied from regexp / captures. Trading accuracy for
77
+ # performance
78
+ if incoming.match?(CAPTURE_GROUP_REGEXP) || incoming.match?(CAPTURE_NAME_REGEXP)
79
+ properties.splat_from(source, ret) if self_tracked
80
+ return
81
+ end
82
+
83
+ # if it's just a straight insert, that we can do
84
+ # Copy the tags from us to the return
85
+ ranges = find_string_sub_insert(properties, preshift, incoming, ret, global)
86
+
87
+ properties.delete_tags_at_ranges(ranges)
88
+ properties.shift_tags(ranges)
89
+ return unless incoming_tracked
90
+ return unless incoming_properties
91
+
92
+ tags = incoming_properties.tag_keys
93
+ ranges.each do |range|
94
+ tags.each do |tag|
95
+ properties.add_tag(tag, range)
96
+ end
97
+ end
98
+ end
99
+
100
+ # Find the points at which the new String was placed into the original
101
+ #
102
+ # @param properties [Contrast::Agent::Assess::Properties] the Properties of the ret
103
+ # @param preshift [Contrast::Agent::Assess::PreShift] the capture of the state of the code just prior to
104
+ # the invocation of the patched method
105
+ # @param incoming [String] the new String going into the substitution
106
+ # @param ret [String] the result of the substitution
107
+ # @param global [Boolean] if this was a global or single substitution
108
+ # @return [Array<Range>] the Ranges where substitution occurred
109
+ def find_string_sub_insert properties, preshift, incoming, ret, global
110
+ pattern = preshift.args[0]
111
+ source = preshift.object
112
+
113
+ properties.copy_from(source, ret)
114
+ # Figure out where inserts occurred
115
+ last_idx = 0
116
+ ranges = []
117
+ # For each insert, move the tag ranges
118
+ while last_idx
119
+ idx = source.index(pattern, last_idx)
120
+ break unless idx
121
+
122
+ last_idx = idx ? idx + 1 : nil
123
+ start_index = idx
124
+ end_index = idx + incoming.length
125
+ ranges << (start_index...end_index)
126
+ break unless global
127
+ end
128
+ ranges
129
+ end
130
+
131
+ def block_sub self_tracked, source, ret
132
+ return unless self_tracked
133
+
134
+ properties = Contrast::Agent::Assess::Tracker.properties!(ret)
135
+ properties&.splat_from(source, ret)
136
+ end
137
+
138
+ def hash_sub self_tracked, source, ret
139
+ return unless self_tracked
140
+
141
+ properties = Contrast::Agent::Assess::Tracker.properties!(ret)
142
+ properties&.splat_from(source, ret)
143
+ end
144
+
145
+ def pattern_gsub parent_events, preshift, ret
146
+ source = preshift.object
147
+ return unless (source_properties = Contrast::Agent::Assess::Tracker.properties(source))
148
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
149
+
150
+ source_properties.tag_keys.each do |key|
151
+ properties.add_tag(key, 0...1)
152
+ end
153
+ parent_event = source_properties.event
154
+ parent_events << parent_event if parent_event
155
+ end
156
+
157
+ def string_build_event parent_events, patcher, preshift, ret
158
+ return unless Contrast::Agent::Assess::Tracker.tracked?(ret)
159
+
160
+ properties = Contrast::Agent::Assess::Tracker.properties(ret)
161
+ args = preshift.args
162
+ properties.build_event(patcher, ret, preshift.object, ret, args, 2)
163
+ properties.event.instance_variable_set(:@_parent_events, parent_events)
164
+ end
165
+ end
166
+ end
167
+ end
@@ -56,20 +56,20 @@ module Contrast
56
56
  # Any overlapping ranges are merged before returning
57
57
  #
58
58
  # arr: the array of tags to which we are adding
59
- # new_arr: misnomer. either an array of or a single Tag to be added
60
- def ordered_merge arr, new_arr
59
+ # add_arr: array of Tags or a single Tag to be added
60
+ def ordered_merge arr, add_arr
61
61
  # [Contrast::Agent::Assess::Tag, ...]
62
- if new_arr.is_a?(Array)
63
- return arr unless new_arr.any?
64
- return new_arr unless arr&.any?
62
+ if add_arr.is_a?(Array)
63
+ return arr unless add_arr.any?
64
+ return add_arr unless arr&.any?
65
65
 
66
- new_arr.each { |new_element| single_ordered_merge(arr, new_element) }
66
+ add_arr.each { |new_element| single_ordered_merge(arr, new_element) }
67
67
  # Contrast::Agent::Assess::Tag
68
68
  else
69
- return arr unless new_arr
70
- return [new_arr] unless arr
69
+ return arr unless add_arr
70
+ return [add_arr] unless arr
71
71
 
72
- single_ordered_merge(arr, new_arr)
72
+ single_ordered_merge(arr, add_arr)
73
73
  end
74
74
  smallerize(arr)
75
75
  end
@@ -19,8 +19,10 @@ module Contrast
19
19
  "collects usage data in order to help us improve compatibility\n" \
20
20
  "and security coverage. The data is anonymous and does not contain application data. It is collected\n" \
21
21
  "by Contrast and is never shared. You can opt-out of telemetry by setting the\n" \
22
- "'CONTRAST_AGENT_TELEMETRY_OPTOUT' environment variable to '1' or 'true'.\n\n" \
23
- "===================================================================================================\n\n"
22
+ "'CONTRAST_AGENT_TELEMETRY_OPTOUT' environment variable to '1' or 'true'.\n" \
23
+ 'Read more about [Contrast Security] [Ruby Agent] telemetry: ' \
24
+ "https://docs.contrastsecurity.com/en/ruby-telemetry.html \n\n" \
25
+ "===================================================================================================\n\n"
24
26
  }.cs__freeze
25
27
 
26
28
  # Here we create the .telemetry file. If the file exist we do nothing.
@@ -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 'net/http'
5
+ require 'contrast/utils/net_http_base'
6
+ require 'contrast/components/logger'
7
+ require 'contrast/utils/object_share'
8
+ require 'contrast/agent/version'
9
+ require 'json'
10
+
11
+ module Contrast
12
+ module Utils
13
+ # This module creates a Net::HTTP client and initiates a connection to the provided result
14
+ class TelemetryClient < NetHttpBase
15
+ ENDPOINT = 'api/v1/telemetry/metrics' # /TelemetryEvent.path
16
+ SERVICE_NAME = 'Telemetry'
17
+ include Contrast::Components::Logger::InstanceMethods
18
+ # This method initializes the Net::HTTP client we'll need. it will validate
19
+ # the connection and make the first request. If connection is valid and response
20
+ # is available then the open connection is returned.
21
+ #
22
+ # @param url [String]
23
+ # @return [Net::HTTP, nil] Return open connection or nil
24
+ def initialize_connection url
25
+ super(SERVICE_NAME, url, false, false)
26
+ end
27
+
28
+ # This method will be responsible for building the request. Because the telemetry collector expects to receive
29
+ # multiple events in a single request, we must always wrap the event in an array, even if there is only one.
30
+ #
31
+ # @param event [Contrast::Agent::TelemetryEvent,Contrast::Agent::StartupMetricsTelemetryEvent]
32
+ # @return [Net::HTTP::Post]
33
+ def build_request event
34
+ return unless valid_event? event
35
+
36
+ string_body = [event.to_hash].to_json
37
+ header = {
38
+ 'User-Agent' => "<#{ Contrast::Utils::ObjectShare::RUBY }>-<#{ Contrast::Agent::VERSION }>",
39
+ 'Content-Type' => 'application/json'
40
+ }
41
+ request = Net::HTTP::Post.new(build_path(event.path), header)
42
+ request.body = string_body
43
+ request
44
+ end
45
+
46
+ # This method will create the actual request and send it
47
+ # @param event[Contrast::Agent::TelemetryEvent]
48
+ # @param connection[Net::HTTP]
49
+ def send_request event, connection
50
+ return if connection.nil? || event.nil?
51
+ return unless valid_event? event
52
+
53
+ req = build_request event
54
+ connection.request req
55
+ end
56
+
57
+ # This method will handle the response from the tenant
58
+ # @param res [Net::HTTPResponse]
59
+ # @return sleep_time [Integer, nil]
60
+ def handle_response res
61
+ status_code = res.code.to_i
62
+ ready_after = if res.to_hash.keys.map(&:downcase).include?('ready-after')
63
+ res['Ready-After']
64
+ else
65
+ 60
66
+ end
67
+ ready_after if status_code == 429
68
+ end
69
+
70
+ # This method will be responsible for validating the event
71
+ # @param event[Contrast::Agent::TelemetryEvent,Contrast::Agent::StartupMetricsTelemetryEvent]
72
+ def valid_event? event
73
+ return true if event.cs__is_a?(Contrast::Agent::TelemetryEvent)
74
+ return true if event.cs__is_a?(Contrast::Agent::StartupMetricsTelemetryEvent)
75
+
76
+ false
77
+ end
78
+
79
+ private
80
+
81
+ # The telemetry instance accepts any path to #{ Contrast::Agent::Telemetry::URL }#{ ENDPOINT }, using the
82
+ # remainder of the path to segregate messages.
83
+ #
84
+ # @return [String] the fully qualified path to send the request
85
+ def build_path event_path
86
+ "#{ Contrast::Agent::Telemetry::URL }#{ ENDPOINT }#{ event_path }"
87
+ end
88
+ end
89
+ end
90
+ end
@@ -34,11 +34,8 @@ module Contrast
34
34
  # @return [String, nil] MAC address of the primary network interface or
35
35
  # the first available one, or nil if nothing found
36
36
  def self.mac
37
- @_mac = find_mac MAC_OS_PRIMARY if Contrast::Utils::OS.mac? && @_mac.nil?
38
- @_mac = find_mac LINUX_PRIMARY if Contrast::Utils::OS.linux? && @_mac.nil?
39
- # or find any available
40
- @_mac = find_mac if @_mac.nil?
41
- @_mac
37
+ primary = Contrast::Utils::OS.mac? ? MAC_OS_PRIMARY : LINUX_PRIMARY
38
+ @_mac = find_mac(primary) || find_mac
42
39
  end
43
40
 
44
41
  class << self
@@ -60,13 +57,15 @@ module Contrast
60
57
  while idx < interfaces.length
61
58
  addr = interfaces[idx].addr
62
59
  name = interfaces[idx].name # rubocop:disable Security/Module/Name
63
- # retrieving MAC address from primary network interface or first available
64
- mac = retrieve_mac name, addr, primary
65
60
  idx += 1
61
+ next if primary && !name.include?(primary)
62
+
63
+ # retrieving MAC address from primary network interface or first available
64
+ mac = retrieve_mac addr
66
65
  next unless mac
67
66
 
68
67
  result = mac if mac && (mac.match? MAC_REGEX)
69
- break if result && !primary
68
+ break if result
70
69
  end
71
70
  result
72
71
  end
@@ -74,35 +73,26 @@ module Contrast
74
73
  # Retrieves MAC address for primary or any network interface.
75
74
  # This is OS dependent search.
76
75
  #
77
- # @param name [Sting] interface name of ifaddr
78
76
  # @param addr [String] address info
79
77
  # example: #<Addrinfo: LINK[en0 aa:bb:cc:00:11:22]>
80
- # @param primary [nil, String] optional param if set look only for primary
81
- # network adapter's name
82
78
  # @return mac [nil, String] MAC address of primary network interface,
83
79
  # any network interface, or nil if no interface is found.
84
- def retrieve_mac name, addr, primary
85
- mac = nil
80
+ def retrieve_mac addr
86
81
  # Mac OS allow us to use getnameinfo(sockaddr [, flags]) => [hostname, servicename]
87
82
  #
88
83
  # returned address:
89
84
  # <Socket::Ifaddr en0 UP,BROADCAST,RUNNING,NOTRAILERS,SIMPLEX,MULTICAST LINK[en0 aa:bb:cc:00:11:22]>
90
- if Contrast::Utils::OS.mac?
91
- mac = addr.getnameinfo[0] unless primary
92
- mac = addr.getnameinfo[0] if primary && name.include?(primary)
93
- end
85
+ return addr.getnameinfo[0] if Contrast::Utils::OS.mac?
86
+
94
87
  # In Linux using Socket::addr#getnameinfo results in ai_family not supported exception.
95
88
  # In this case we are relying on match filtering of addresses.
96
89
  #
97
90
  # returned address:
98
91
  # #<Socket::Ifaddr eth0 UP,BROADCAST,RUNNING,MULTICAST,0x10000
99
92
  # PACKET[protocol=0 eth0 hatype=1 HOST hwaddr=aa:bb:cc:00:11:22]>
100
- if primary && Contrast::Utils::OS.linux?
101
- mac = Regexp.last_match(1) if addr.inspect =~ LINUX_OS_REG && name.include?(primary)
102
- elsif primary.nil? && Contrast::Utils::OS.linux?
103
- mac = Regexp.last_match(1) if addr.inspect =~ LINUX_OS_REG
104
- end
105
- mac
93
+ return Regexp.last_match(1) if addr.inspect =~ LINUX_OS_REG
94
+
95
+ nil
106
96
  end
107
97
 
108
98
  # Returns array of network interfaces.
@@ -111,7 +101,10 @@ module Contrast
111
101
  # @return interfaces [Array] Returns an array of interface addresses.
112
102
  # Socket::Ifaddr - represents a result of getifaddrs().
113
103
  def interfaces
114
- @_interfaces = []
104
+ @_interfaces ||= []
105
+
106
+ return @_interfaces unless @_interfaces.empty?
107
+
115
108
  arr = Socket.getifaddrs
116
109
  idx = 0
117
110
  check_family = 0
data/ruby-agent.gemspec CHANGED
@@ -89,11 +89,11 @@ end
89
89
 
90
90
  # Dependencies used to run all of our Rubocop during the linting phase.
91
91
  def self.add_rubocop spec
92
- spec.add_development_dependency 'rubocop', '1.13.0'
93
- spec.add_development_dependency 'rubocop-performance', '1.11.0'
94
- spec.add_development_dependency 'rubocop-rails', '2.9.1'
95
- spec.add_development_dependency 'rubocop-rake', '0.5.1'
96
- spec.add_development_dependency 'rubocop-rspec', '2.2.0'
92
+ spec.add_development_dependency 'rubocop', '1.22.3'
93
+ spec.add_development_dependency 'rubocop-performance', '1.12.0'
94
+ spec.add_development_dependency 'rubocop-rails', '2.12.4'
95
+ spec.add_development_dependency 'rubocop-rake', '0.6.0'
96
+ spec.add_development_dependency 'rubocop-rspec', '2.6.0'
97
97
  end
98
98
 
99
99
  # Dependencies not mocked out during RSpec that we test real code of, beyond just frameworks.