newrelic_security 0.1.0 → 0.2.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/lib/newrelic_security/agent/agent.rb +6 -2
  4. data/lib/newrelic_security/agent/configuration/manager.rb +2 -3
  5. data/lib/newrelic_security/agent/control/app_info.rb +2 -0
  6. data/lib/newrelic_security/agent/control/application_runtime_error.rb +95 -0
  7. data/lib/newrelic_security/agent/control/application_url_mappings.rb +2 -0
  8. data/lib/newrelic_security/agent/control/control_command.rb +2 -1
  9. data/lib/newrelic_security/agent/control/critical_message.rb +2 -0
  10. data/lib/newrelic_security/agent/control/error_reporting.rb +74 -0
  11. data/lib/newrelic_security/agent/control/event.rb +13 -4
  12. data/lib/newrelic_security/agent/control/event_processor.rb +21 -0
  13. data/lib/newrelic_security/agent/control/exit_event.rb +2 -0
  14. data/lib/newrelic_security/agent/control/grpc_context.rb +2 -1
  15. data/lib/newrelic_security/agent/control/health_check.rb +2 -0
  16. data/lib/newrelic_security/agent/control/http_context.rb +8 -2
  17. data/lib/newrelic_security/agent/control/iast_data_transfer_request.rb +2 -0
  18. data/lib/newrelic_security/agent/control/websocket_client.rb +5 -0
  19. data/lib/newrelic_security/agent/utils/agent_utils.rb +3 -2
  20. data/lib/newrelic_security/constants.rb +1 -0
  21. data/lib/newrelic_security/instrumentation-security/grape/chain.rb +7 -2
  22. data/lib/newrelic_security/instrumentation-security/grape/instrumentation.rb +2 -1
  23. data/lib/newrelic_security/instrumentation-security/grape/prepend.rb +7 -1
  24. data/lib/newrelic_security/instrumentation-security/padrino/chain.rb +17 -0
  25. data/lib/newrelic_security/instrumentation-security/padrino/instrumentation.rb +14 -2
  26. data/lib/newrelic_security/instrumentation-security/padrino/prepend.rb +12 -0
  27. data/lib/newrelic_security/instrumentation-security/rails/chain.rb +26 -2
  28. data/lib/newrelic_security/instrumentation-security/rails/instrumentation.rb +28 -3
  29. data/lib/newrelic_security/instrumentation-security/rails/prepend.rb +18 -0
  30. data/lib/newrelic_security/instrumentation-security/roda/chain.rb +7 -2
  31. data/lib/newrelic_security/instrumentation-security/roda/instrumentation.rb +2 -1
  32. data/lib/newrelic_security/instrumentation-security/roda/prepend.rb +7 -1
  33. data/lib/newrelic_security/instrumentation-security/sinatra/chain.rb +6 -0
  34. data/lib/newrelic_security/instrumentation-security/sinatra/instrumentation.rb +8 -0
  35. data/lib/newrelic_security/instrumentation-security/sinatra/prepend.rb +4 -0
  36. data/lib/newrelic_security/instrumentation-security/sqlite3/instrumentation.rb +4 -4
  37. data/lib/newrelic_security/version.rb +1 -1
  38. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c07c9ef96a8884449bdd0d5be44eccf3fee5ae6dde565863fe1eb24449a902f6
4
- data.tar.gz: 56f813a8034eed9fd717a0c549275cd1c1e1386c50acdb2142379fbcc980838d
3
+ metadata.gz: 5b1160c4b821177b6ce0400848a72bf09899780ff83abadb60b3e60f35b8338b
4
+ data.tar.gz: 98cfb5a7d513e842690dce03aa515a7e148a252a5f0666f16e40899e2ad511d0
5
5
  SHA512:
6
- metadata.gz: 0a3b7c2e86f9ea2c3a6c4c28da88676f904e78c5cce82fb152897aade9acda32ca9ea485d26dfa7ebb6ef1356c14df9d85407971d05a0a0f69e1ec43824227a1
7
- data.tar.gz: d3c6dcd24fe5ed0b54537b503e6994e6ed6701d5bab53f90220cce4b300e14a619db19cab4befec08560b72c941898a082a1b3c9738ed29e3fb8feae714edeb2
6
+ metadata.gz: 56dea910383ac11f0b87a29f304e0f5042336c142c1d8ca60a2cdaf15bf172102000e28e8f382fd99781afebd061a0693e3f5ff8ea9fba981cdc4de128abdc20
7
+ data.tar.gz: 04bb28178707141c66ac94158fdb3cd632ccb22a1a9c4b4f5f7d9438989ee3c6fba2124abc89ffbf135d34e249b5921cd5d4b924e50768f07363b219567867bb
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # New Relic Ruby Security Agent Release Notes
2
2
 
3
+ ## v0.2.0
4
+
5
+ Version 0.2.0 introuduces Error reporting as part of security. Any unhandled or 5xx errors in application runtime will now be visible in IAST capability UI. Updated json_version: **1.2.4**
6
+
7
+ - Feature: Unhandled and 5xx error reproting [PR#134](https://github.com/newrelic/csec-ruby-agent/pull/134)
8
+
9
+ - Bugfix: Fix for API route not present in rails7 [PR#127](https://github.com/newrelic/csec-ruby-agent/pull/127)
10
+
11
+ - Bugfix: Fix for Sqlite3 parameters sent in wrong fromat [PR#130](https://github.com/newrelic/csec-ruby-agent/pull/130)
12
+
13
+ - Bugfix: Fix for multiple events have same id [PR#135](https://github.com/newrelic/csec-ruby-agent/pull/135)
14
+
15
+ - Bugfix: Fix for NR_CSEC_VALIDATOR_HOME_TMP placeholder value not replaced during File Access fuzzing [PR#138](https://github.com/newrelic/csec-ruby-agent/pull/138)
16
+
17
+ - Bugfix: Fix for appServerInfo fields are not present in File Operation events [PR#139](https://github.com/newrelic/csec-ruby-agent/pull/139)
18
+
19
+ - Sending security agent critical errors to APM error inbox [PR#137](https://github.com/newrelic/csec-ruby-agent/pull/137)
20
+
21
+ - Added key identifiers in entityGuid and acccountId in all json reporting [PR#101](https://github.com/newrelic/csec-ruby-agent/pull/101)
22
+
23
+
3
24
  ## v0.1.0
4
25
 
5
26
  Version 0.1.0 introduces `newrelic_security` agent for public preview under Newrelic pre-release software notice.
@@ -17,13 +17,15 @@ require 'newrelic_security/agent/control/critical_message'
17
17
  require 'newrelic_security/agent/control/event_counter'
18
18
  require 'newrelic_security/agent/control/event_stats'
19
19
  require 'newrelic_security/agent/control/exit_event'
20
+ require 'newrelic_security/agent/control/application_runtime_error'
21
+ require 'newrelic_security/agent/control/error_reporting'
20
22
  require 'newrelic_security/instrumentation-security/instrumentation_loader'
21
23
 
22
24
  module NewRelic::Security
23
25
  module Agent
24
26
  class Agent
25
27
 
26
- attr_accessor :websocket_client, :event_processor, :iast_client, :http_request_count, :event_processed_count, :event_sent_count, :event_drop_count, :route_map, :iast_event_stats, :rasp_event_stats, :exit_event_stats
28
+ attr_accessor :websocket_client, :event_processor, :iast_client, :http_request_count, :event_processed_count, :event_sent_count, :event_drop_count, :route_map, :iast_event_stats, :rasp_event_stats, :exit_event_stats, :error_reporting
27
29
 
28
30
  def initialize
29
31
  NewRelic::Security::Agent.config
@@ -41,6 +43,7 @@ module NewRelic::Security
41
43
  @iast_event_stats = NewRelic::Security::Agent::Control::EventStats.new
42
44
  @rasp_event_stats = NewRelic::Security::Agent::Control::EventStats.new
43
45
  @exit_event_stats = NewRelic::Security::Agent::Control::EventStats.new
46
+ @error_reporting = NewRelic::Security::Agent::Control::ErrorReporting.new
44
47
  end
45
48
 
46
49
  def init
@@ -93,7 +96,8 @@ module NewRelic::Security
93
96
  def find_or_create_file_path(path)
94
97
  ::FileUtils.mkdir_p(path) unless ::File.directory?(path)
95
98
  ::File.directory?(path)
96
- rescue
99
+ rescue => e
100
+ ::NewRelic::Agent.notice_error(e)
97
101
  return false
98
102
  end
99
103
 
@@ -39,7 +39,7 @@ module NewRelic::Security
39
39
  @cache[:listen_port] = nil
40
40
  @cache[:app_root] = NewRelic::Security::Agent::Utils.app_root
41
41
  @cache[:jruby_objectspace_enabled] = false
42
- @cache[:json_version] = :'1.2.0'
42
+ @cache[:json_version] = :'1.2.4'
43
43
 
44
44
  @environment_source = NewRelic::Security::Agent::Configuration::EnvironmentSource.new
45
45
  @server_source = NewRelic::Security::Agent::Configuration::ServerSource.new
@@ -47,8 +47,7 @@ module NewRelic::Security
47
47
  @yaml_source = NewRelic::Security::Agent::Configuration::YamlSource.new
48
48
  @default_source = NewRelic::Security::Agent::Configuration::DefaultSource.new
49
49
  rescue Exception => exception
50
- # TODO: remove this puts once agent stablizes
51
- puts "Exception in Configuration::Manager.initialize : #{exception.inspect} #{exception.backtrace}"
50
+ ::NewRelic::Agent.notice_error(exception)
52
51
  end
53
52
 
54
53
  def [](key)
@@ -26,6 +26,8 @@ module NewRelic::Security
26
26
  @jsonVersion = NewRelic::Security::Agent.config[:json_version]
27
27
  @startTime = current_time_millis
28
28
  @applicationUUID = NewRelic::Security::Agent.config[:uuid]
29
+ @appAccountId = NewRelic::Security::Agent.config[:account_id]
30
+ @appEntityGuid = NewRelic::Security::Agent.config[:entity_guid]
29
31
  @framework = NewRelic::Security::Agent.config[:framework]
30
32
  @groupName = NewRelic::Security::Agent.config[:mode]
31
33
  @userProvidedApplicationInfo = Hash.new
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'digest'
5
+
6
+ module NewRelic::Security
7
+ module Agent
8
+ module Control
9
+ class ApplicationRuntimeError
10
+ attr_reader :jsonName, :exception
11
+ attr_accessor :counter
12
+
13
+ def initialize(exception, ctxt, response_code, category)
14
+ @collectorType = RUBY
15
+ @language = Ruby
16
+ @jsonName = :'application-runtime-error'
17
+ @eventType = :'application-runtime-error'
18
+ @collectorVersion = NewRelic::Security::VERSION
19
+ @buildNumber = nil
20
+ @jsonVersion = NewRelic::Security::Agent.config[:json_version]
21
+ @timestamp = current_time_millis
22
+ @applicationUUID = NewRelic::Security::Agent.config[:uuid]
23
+ @appAccountId = NewRelic::Security::Agent.config[:account_id]
24
+ @appEntityGuid = NewRelic::Security::Agent.config[:entity_guid]
25
+ @framework = NewRelic::Security::Agent.config[:framework]
26
+ @groupName = NewRelic::Security::Agent.config[:mode]
27
+ @policyVersion = nil
28
+ @linkingMetadata = add_linking_metadata
29
+ @httpRequest = get_http_request_data(ctxt)
30
+ @exception = exception
31
+ @counter = 1
32
+ @responseCode = response_code
33
+ @category = category
34
+ @traceId = generate_trace_id(ctxt, category)
35
+ end
36
+
37
+ def as_json
38
+ instance_variables.map! do |ivar|
39
+ [ivar[1..-1].to_sym, instance_variable_get(ivar)]
40
+ end.to_h
41
+ end
42
+
43
+ def to_json(*_args)
44
+ as_json.to_json
45
+ end
46
+
47
+ private
48
+
49
+ def current_time_millis
50
+ (Time.now.to_f * 1000).to_i
51
+ end
52
+
53
+ def add_linking_metadata
54
+ linking_metadata = {}
55
+ linking_metadata[:agentRunId] = NewRelic::Security::Agent.config[:agent_run_id]
56
+ linking_metadata.merge!(NewRelic::Security::Agent.config[:linking_metadata])
57
+ # TODO: add other fields as well in linking metadata, for event and heathcheck as well
58
+ end
59
+
60
+ def get_http_request_data(ctxt)
61
+ return if ctxt.nil?
62
+ http_request = {}
63
+ http_request[:parameterMap] = {}
64
+ http_request[:body] = ctxt.body
65
+ http_request[:generationTime] = ctxt.time_stamp
66
+ http_request[:dataTruncated] = false
67
+ http_request[:method] = ctxt.method
68
+ http_request[:route] = ctxt.route.split(AT_THE_RATE)[1] if ctxt.route
69
+ http_request[:url] = URI(ctxt.req[REQUEST_URI]).respond_to?(:request_uri) ? URI(ctxt.req[REQUEST_URI]).request_uri : ctxt.req[REQUEST_URI]
70
+ http_request[:requestURI] = "#{ctxt.req[RACK_URL_SCHEME]}://#{ctxt.req[HTTP_HOST]}#{ctxt.req[PATH_INFO]}"
71
+ http_request[:clientIP] = ctxt.headers.key?(X_FORWARDED_FOR) ? ctxt.headers[X_FORWARDED_FOR].split(COMMA)[0].to_s : ctxt.req[REMOTE_ADDR].to_s
72
+ http_request[:serverPort] = ctxt.req[SERVER_PORT].to_i
73
+ http_request[:protocol] = ctxt.req[RACK_URL_SCHEME]
74
+ http_request[:contextPath] = ROOT_PATH
75
+ http_request[:headers] = ctxt.headers
76
+ http_request[:contentType] = ctxt.req[CONTENT_TYPE] if ctxt.req.key?(CONTENT_TYPE)
77
+ http_request[:headers][CONTENT_TYPE1] = ctxt.req[CONTENT_TYPE] if ctxt.req.key?(CONTENT_TYPE)
78
+ http_request[:dataTruncated] = ctxt.data_truncated
79
+ http_request
80
+ end
81
+
82
+ def generate_trace_id(ctxt, category)
83
+ @exception[:stackTrace]
84
+ method, route = ctxt.route.split(AT_THE_RATE) if ctxt.route
85
+ if @exception[:stackTrace]
86
+ ::Digest::SHA256.hexdigest("#{@exception[:stackTrace].join(PIPE)}#{category}#{route}#{method}").to_s
87
+ else
88
+ ::Digest::SHA256.hexdigest("#{category}#{route}#{method}").to_s
89
+ end
90
+ end
91
+
92
+ end
93
+ end
94
+ end
95
+ end
@@ -21,6 +21,8 @@ module NewRelic::Security
21
21
  @jsonVersion = NewRelic::Security::Agent.config[:json_version]
22
22
  @timestamp = current_time_millis
23
23
  @applicationUUID = NewRelic::Security::Agent.config[:uuid]
24
+ @appAccountId = NewRelic::Security::Agent.config[:account_id]
25
+ @appEntityGuid = NewRelic::Security::Agent.config[:entity_guid]
24
26
  @framework = NewRelic::Security::Agent.config[:framework]
25
27
  @groupName = NewRelic::Security::Agent.config[:mode]
26
28
  @policyVersion = nil
@@ -32,7 +32,7 @@ module NewRelic::Security
32
32
  fuzz_request = NewRelic::Security::Agent::Control::FuzzRequest.new(message_object[:id])
33
33
  fuzz_request.request = prepare_fuzz_request(message_object)
34
34
  fuzz_request.case_type = message_object[:arguments][1]
35
- fuzz_request.reflected_metadata = message_object[:reflectedMetaData]
35
+ fuzz_request.reflected_metadata = message_object[:reflectedMetaData]
36
36
  NewRelic::Security::Agent.agent.iast_client.pending_request_ids << message_object[:id]
37
37
  NewRelic::Security::Agent.agent.iast_client.enqueue(fuzz_request)
38
38
  fuzz_request = nil
@@ -104,6 +104,7 @@ module NewRelic::Security
104
104
 
105
105
  def prepare_fuzz_request(message_object)
106
106
  message_object[:arguments][0].gsub!(NR_CSEC_VALIDATOR_HOME_TMP, NewRelic::Security::Agent.config[:fuzz_dir_path])
107
+ message_object[:arguments][0].gsub!(NR_CSEC_VALIDATOR_HOME_TMP_URL_ENCODED, NewRelic::Security::Agent.config[:fuzz_dir_path])
107
108
  message_object[:arguments][0].gsub!(NR_CSEC_VALIDATOR_FILE_SEPARATOR, ::File::SEPARATOR)
108
109
  prepared_fuzz_request = ::JSON.parse(message_object[:arguments][0])
109
110
  prepared_fuzz_request[HEADERS][NR_CSEC_PARENT_ID] = message_object[:id]
@@ -21,6 +21,8 @@ module NewRelic::Security
21
21
  @buildNumber = nil
22
22
  @jsonVersion = NewRelic::Security::Agent.config[:json_version]
23
23
  @applicationUUID = NewRelic::Security::Agent.config[:uuid]
24
+ @appAccountId = NewRelic::Security::Agent.config[:account_id]
25
+ @appEntityGuid = NewRelic::Security::Agent.config[:entity_guid]
24
26
  @linkingMetadata = add_linking_metadata
25
27
  @timestamp = current_time_millis
26
28
  @message = message
@@ -0,0 +1,74 @@
1
+ module NewRelic::Security
2
+ module Agent
3
+ module Control
4
+ class ErrorReporting
5
+
6
+ STATUS_CODES_5XX = {
7
+ 500 => "Internal Server Error",
8
+ 501 => "Not Implemented",
9
+ 502 => "Bad Gateway",
10
+ 503 => "Service Unavailable",
11
+ 504 => "Gateway Timeout",
12
+ 505 => "HTTP Version Not Supported",
13
+ 506 => "Variant Also Negotiates",
14
+ 507 => "Insufficient Storage",
15
+ 508 => "Loop Detected",
16
+ 509 => "Bandwidth Limit Exceeded",
17
+ 510 => "Not Extended",
18
+ 511 => "Network Authentication Required"
19
+ }.freeze
20
+
21
+ attr_accessor :exceptions_map
22
+
23
+ def initialize
24
+ @exceptions_map = {}
25
+ end
26
+
27
+ def generate_unhandled_exception(noticed_error, ctxt, response_code)
28
+ unhandled_exception = {}
29
+ category = nil
30
+ if noticed_error
31
+ unhandled_exception[:message] = noticed_error.message
32
+ unhandled_exception[:cause] = nil
33
+ unhandled_exception[:type] = noticed_error.exception_class_name
34
+ unhandled_exception[:stackTrace] = noticed_error.stack_trace
35
+ category = noticed_error.exception_class_name
36
+ end
37
+ category = STATUS_CODES_5XX[response_code] if response_code
38
+ application_runtime_error = NewRelic::Security::Agent::Control::ApplicationRuntimeError.new(unhandled_exception, ctxt, response_code, category)
39
+ key = if response_code
40
+ # TODO: when do refactoring of ctxt.route, use both route and method to generate key
41
+ ctxt.route&.+ response_code.to_s
42
+ else
43
+ application_runtime_error.exception[:type] + application_runtime_error.exception[:stackTrace][0]
44
+ end
45
+ application_runtime_error.counter = @exceptions_map[key].counter + 1 if @exceptions_map.key?(key)
46
+ @exceptions_map[key] = application_runtime_error
47
+ rescue Exception => exception
48
+ NewRelic::Security::Agent.logger.error "Exception in generating unhandled exception: #{exception.inspect} #{exception.backtrace}\n"
49
+ end
50
+
51
+ def extract_noticed_error(current_transaction, ctxt, response_code)
52
+ # TODO: Below operation is expensive, talk to APM to get optimized way to do this
53
+ current_transaction.exceptions.each do |_, span|
54
+ current_transaction.segments.each do |segment|
55
+ generate_unhandled_exception(segment.noticed_error, ctxt, response_code) if span[:span_id] == segment.guid
56
+ end
57
+ end
58
+ rescue Exception => exception
59
+ NewRelic::Security::Agent.logger.error "Exception in extract_noticed_error: #{exception.inspect} #{exception.backtrace}\n"
60
+ end
61
+
62
+ def report_unhandled_or_5xx_exceptions(current_transaction, ctxt, response_code = nil)
63
+ http_response_code = response_code || current_transaction&.http_response_code
64
+ if current_transaction.exceptions.empty? && http_response_code&.between?(500, 599)
65
+ generate_unhandled_exception(nil, ctxt, response_code)
66
+ else
67
+ extract_noticed_error(current_transaction, ctxt, response_code) unless current_transaction.exceptions.empty?
68
+ end
69
+ end
70
+
71
+ end
72
+ end
73
+ end
74
+ end
@@ -26,9 +26,11 @@ module NewRelic::Security
26
26
  @buildNumber = nil
27
27
  @jsonVersion = NewRelic::Security::Agent.config[:json_version]
28
28
  @applicationUUID = NewRelic::Security::Agent.config[:uuid]
29
+ @appAccountId = NewRelic::Security::Agent.config[:account_id]
30
+ @appEntityGuid = NewRelic::Security::Agent.config[:entity_guid]
29
31
  @httpRequest = Hash.new
30
32
  @httpResponse = Hash.new
31
- @metaData = { :reflectedMetaData => { :listen_port => NewRelic::Security::Agent.config[:listen_port].to_s } }
33
+ @metaData = { :reflectedMetaData => { :listen_port => NewRelic::Security::Agent.config[:listen_port].to_s }, :appServerInfo => { :applicationDirectory => NewRelic::Security::Agent.config[:app_root], :serverBaseDirectory => NewRelic::Security::Agent.config[:app_root] } }
32
34
  @linkingMetadata = add_linking_metadata
33
35
  @pid = pid
34
36
  @parameters = args
@@ -113,7 +115,7 @@ module NewRelic::Security
113
115
  http_request[:contentType] = "TODO: "
114
116
  http_request[:isGrpc] = ctxt.is_grpc
115
117
  @httpRequest = http_request
116
- @metaData = ctxt.metadata
118
+ @metaData.merge!(ctxt.metadata)
117
119
  end
118
120
 
119
121
  private
@@ -128,13 +130,20 @@ module NewRelic::Security
128
130
  end
129
131
 
130
132
  def event_id
131
- "#{Process.pid}:#{::Thread.current.object_id}:#{thread_monotonic_ctr}"
133
+ "#{Process.pid}:#{current_transaction&.guid}:#{thread_monotonic_ctr}"
134
+ end
135
+
136
+ def current_transaction
137
+ ::NewRelic::Agent::Tracer.current_transaction if defined?(::NewRelic::Agent::Tracer)
132
138
  end
133
139
 
134
140
  def thread_monotonic_ctr
135
141
  ctxt = NewRelic::Security::Agent::Control::HTTPContext.get_context if NewRelic::Security::Agent::Control::HTTPContext.get_context
136
142
  ctxt = NewRelic::Security::Agent::Control::GRPCContext.get_context if NewRelic::Security::Agent::Control::GRPCContext.get_context
137
- ctxt.event_counter = ctxt.event_counter + 1 if ctxt
143
+ return unless ctxt
144
+ ctxt.mutex.synchronize do
145
+ ctxt.event_counter = ctxt.event_counter + 1
146
+ end
138
147
  end
139
148
 
140
149
  def add_linking_metadata
@@ -5,6 +5,7 @@ module NewRelic::Security
5
5
  module Control
6
6
  EVENT_QUEUE_SIZE = 10_000
7
7
  HEALTH_INTERVAL = 300
8
+ ERROR_REPORTING_INTERVAL = 30
8
9
 
9
10
  class EventProcessor
10
11
 
@@ -15,6 +16,7 @@ module NewRelic::Security
15
16
  @eventQ = ::SizedQueue.new(EVENT_QUEUE_SIZE)
16
17
  create_dequeue_threads
17
18
  create_keep_alive_thread
19
+ create_error_reporting_thread
18
20
  NewRelic::Security::Agent.init_logger.info "[STEP-5] => Security agent components started"
19
21
  end
20
22
 
@@ -128,6 +130,25 @@ module NewRelic::Security
128
130
  NewRelic::Security::Agent.logger.error "Exception in health check thread, #{exception.inspect}"
129
131
  end
130
132
 
133
+ def create_error_reporting_thread
134
+ @error_reporting_thread = Thread.new {
135
+ Thread.current.name = "newrelic_security_error_reporting_thread"
136
+ while true do
137
+ sleep ERROR_REPORTING_INTERVAL
138
+ send_error_reporting_event if NewRelic::Security::Agent::Control::WebsocketClient.instance.is_open?
139
+ end
140
+ }
141
+ rescue Exception => exception
142
+ NewRelic::Security::Agent.logger.error "Exception in error_reporting thread, #{exception.inspect}"
143
+ end
144
+
145
+ def send_error_reporting_event
146
+ NewRelic::Security::Agent.agent.error_reporting&.exceptions_map&.each_value do |exception|
147
+ NewRelic::Security::Agent::Control::WebsocketClient.instance.send(exception)
148
+ end
149
+ NewRelic::Security::Agent.agent.error_reporting&.exceptions_map&.clear
150
+ end
151
+
131
152
  end
132
153
  end
133
154
  end
@@ -14,6 +14,8 @@ module NewRelic::Security
14
14
  @collectorVersion = NewRelic::Security::VERSION
15
15
  @buildNumber = nil
16
16
  @applicationUUID = NewRelic::Security::Agent.config[:uuid]
17
+ @appAccountId = NewRelic::Security::Agent.config[:account_id]
18
+ @appEntityGuid = NewRelic::Security::Agent.config[:entity_guid]
17
19
  @groupName = NewRelic::Security::Agent.config[:mode]
18
20
  @jsonVersion = NewRelic::Security::Agent.config[:json_version]
19
21
  @policyVersion = nil
@@ -8,7 +8,7 @@ module NewRelic::Security
8
8
 
9
9
  class GRPCContext
10
10
 
11
- attr_accessor :time_stamp, :method, :headers, :body, :route, :cache, :fuzz_files, :url, :server_name, :server_port, :client_ip, :client_port, :is_grpc, :metadata, :event_counter
11
+ attr_accessor :time_stamp, :method, :headers, :body, :route, :cache, :fuzz_files, :url, :server_name, :server_port, :client_ip, :client_port, :is_grpc, :metadata, :event_counter, :mutex
12
12
 
13
13
  def initialize(grpc_request)
14
14
  @time_stamp = current_time_millis
@@ -32,6 +32,7 @@ module NewRelic::Security
32
32
  @cache = {}
33
33
  @fuzz_files = ::Set.new
34
34
  @event_counter = 0
35
+ @mutex = Mutex.new
35
36
  NewRelic::Security::Agent.agent.http_request_count.increment
36
37
  end
37
38
 
@@ -18,6 +18,8 @@ module NewRelic::Security
18
18
  @framework = NewRelic::Security::Agent.config[:framework]
19
19
  @protectedServer = nil
20
20
  @applicationUUID = NewRelic::Security::Agent.config[:uuid]
21
+ @appAccountId = NewRelic::Security::Agent.config[:account_id]
22
+ @appEntityGuid = NewRelic::Security::Agent.config[:entity_guid]
21
23
  @collectorVersion = NewRelic::Security::VERSION
22
24
  @buildNumber = nil
23
25
  @jsonVersion = NewRelic::Security::Agent.config[:json_version]
@@ -10,13 +10,14 @@ module NewRelic::Security
10
10
  UNDERSCORE = '_'
11
11
  HYPHEN = '-'
12
12
  REQUEST_METHOD = 'REQUEST_METHOD'
13
+ HTTP_HOST = 'HTTP_HOST'
13
14
  PATH_INFO = 'PATH_INFO'
14
15
  RACK_INPUT = 'rack.input'
15
- CGI_VARIABLES = ::Set.new(%W[ AUTH_TYPE CONTENT_LENGTH CONTENT_TYPE GATEWAY_INTERFACE HTTPS PATH_INFO PATH_TRANSLATED REQUEST_URI QUERY_STRING REMOTE_ADDR REMOTE_HOST REMOTE_IDENT REMOTE_USER REQUEST_METHOD SCRIPT_NAME SERVER_NAME SERVER_PORT SERVER_PROTOCOL SERVER_SOFTWARE rack.url_scheme ])
16
+ CGI_VARIABLES = ::Set.new(%W[ AUTH_TYPE CONTENT_LENGTH CONTENT_TYPE GATEWAY_INTERFACE HTTPS HTTP_HOST PATH_INFO PATH_TRANSLATED REQUEST_URI QUERY_STRING REMOTE_ADDR REMOTE_HOST REMOTE_IDENT REMOTE_USER REQUEST_METHOD SCRIPT_NAME SERVER_NAME SERVER_PORT SERVER_PROTOCOL SERVER_SOFTWARE rack.url_scheme ])
16
17
 
17
18
  class HTTPContext
18
19
 
19
- attr_accessor :time_stamp, :req, :method, :headers, :params, :body, :data_truncated, :route, :cache, :fuzz_files, :event_counter
20
+ attr_accessor :time_stamp, :req, :method, :headers, :params, :body, :data_truncated, :route, :cache, :fuzz_files, :event_counter, :mutex
20
21
 
21
22
  def initialize(env)
22
23
  @time_stamp = current_time_millis
@@ -47,6 +48,7 @@ module NewRelic::Security
47
48
  @cache = Hash.new
48
49
  @fuzz_files = ::Set.new
49
50
  @event_counter = 0
51
+ @mutex = Mutex.new
50
52
  NewRelic::Security::Agent.agent.http_request_count.increment
51
53
  NewRelic::Security::Agent.agent.iast_client.completed_requests[@headers[NR_CSEC_PARENT_ID]] = [] if @headers.key?(NR_CSEC_PARENT_ID)
52
54
  end
@@ -66,6 +68,10 @@ module NewRelic::Security
66
68
  def self.reset_context
67
69
  ::NewRelic::Agent::Tracer.current_transaction.remove_instance_variable(:@security_context_data) if ::NewRelic::Agent::Tracer.current_transaction.instance_variable_defined?(:@security_context_data)
68
70
  end
71
+
72
+ def self.get_current_transaction
73
+ ::NewRelic::Agent::Tracer.current_transaction
74
+ end
69
75
  end
70
76
 
71
77
  end
@@ -11,6 +11,8 @@ module NewRelic::Security
11
11
  def initialize
12
12
  @jsonName = :'iast-data-request'
13
13
  @applicationUUID = NewRelic::Security::Agent.config[:uuid]
14
+ @appAccountId = NewRelic::Security::Agent.config[:account_id]
15
+ @appEntityGuid = NewRelic::Security::Agent.config[:entity_guid]
14
16
  @batchSize = 10
15
17
  @pendingRequestIds = []
16
18
  @completedRequests = Hash.new
@@ -76,21 +76,26 @@ module NewRelic::Security
76
76
 
77
77
  connection.on :error do |e|
78
78
  NewRelic::Security::Agent.logger.error "Error in websocket connection : #{e.inspect} #{e.backtrace}"
79
+ ::NewRelic::Agent.notice_error(e)
79
80
  Thread.new { NewRelic::Security::Agent::Control::WebsocketClient.instance.close(true) }
80
81
  end
81
82
  rescue Errno::EPIPE => exception
82
83
  NewRelic::Security::Agent.logger.error "Unable to connect to validator_service: #{exception.inspect}"
84
+ ::NewRelic::Agent.notice_error(exception)
83
85
  NewRelic::Security::Agent.config.disable_security
84
86
  rescue Errno::ECONNRESET => exception
85
87
  NewRelic::Security::Agent.logger.error "Unable to connect to validator_service: #{exception.inspect}"
88
+ ::NewRelic::Agent.notice_error(exception)
86
89
  NewRelic::Security::Agent.config.disable_security
87
90
  Thread.new { NewRelic::Security::Agent.agent.reconnect(15) }
88
91
  rescue Errno::ECONNREFUSED => exception
89
92
  NewRelic::Security::Agent.logger.error "Unable to connect to validator_service: #{exception.inspect}"
93
+ ::NewRelic::Agent.notice_error(exception)
90
94
  NewRelic::Security::Agent.config.disable_security
91
95
  Thread.new { NewRelic::Security::Agent.agent.reconnect(15) }
92
96
  rescue => exception
93
97
  NewRelic::Security::Agent.logger.error "Exception in websocket init: #{exception.inspect} #{exception.backtrace}"
98
+ ::NewRelic::Agent.notice_error(exception)
94
99
  NewRelic::Security::Agent.config.disable_security
95
100
  Thread.new { NewRelic::Security::Agent.agent.reconnect(15) }
96
101
  end
@@ -36,10 +36,11 @@ module NewRelic::Security
36
36
  decrypted_data.split(COMMA).each do |filename|
37
37
  begin
38
38
  filename.gsub!(NR_CSEC_VALIDATOR_HOME_TMP, NewRelic::Security::Agent.config[:fuzz_dir_path])
39
+ filename.gsub!(NR_CSEC_VALIDATOR_HOME_TMP_URL_ENCODED, NewRelic::Security::Agent.config[:fuzz_dir_path])
39
40
  filename.gsub!(NR_CSEC_VALIDATOR_FILE_SEPARATOR, ::File::SEPARATOR)
40
41
  dirname = ::File.dirname(filename)
41
42
  ::FileUtils.mkdir_p(dirname, :mode => 0770) unless ::File.directory?(dirname)
42
- ctxt&.fuzz_files << filename
43
+ ctxt&.fuzz_files&.<< filename
43
44
  ::File.open(filename, ::File::WRONLY | ::File::CREAT | ::File::EXCL) do |fd|
44
45
  # puts "Ownership acquired by : #{Process.pid}"
45
46
  end unless ::File.exist?(filename)
@@ -115,7 +116,7 @@ module NewRelic::Security
115
116
  elsif framework == :grape
116
117
  ObjectSpace.each_object(::Grape::Endpoint) { |z|
117
118
  z.instance_variable_get(:@routes)&.each { |route|
118
- http_method = route.instance_variable_get(:@request_method) ? route.instance_variable_get(:@request_method) : route.instance_variable_get(:@options)[:method]
119
+ http_method = route.instance_variable_get(:@request_method) || route.instance_variable_get(:@options)[:method]
119
120
  NewRelic::Security::Agent.agent.route_map << "#{http_method}@#{route.pattern.origin}"
120
121
  }
121
122
  }
@@ -7,6 +7,7 @@ module NewRelic::Security
7
7
  LANGUAGE_COLLECTOR = 'LANGUAGE_COLLECTOR'
8
8
  STANDARD_OUT = 'STDOUT'
9
9
  NR_CSEC_VALIDATOR_HOME_TMP = '{{NR_CSEC_VALIDATOR_HOME_TMP}}'
10
+ NR_CSEC_VALIDATOR_HOME_TMP_URL_ENCODED = '%7B%7BNR_CSEC_VALIDATOR_HOME_TMP%7D%7D'
10
11
  NR_CSEC_VALIDATOR_FILE_SEPARATOR = '{{NR_CSEC_VALIDATOR_FILE_SEPARATOR}}'
11
12
  SEC_HOME_PATH = 'nr-security-home'
12
13
  LOGS_DIR = 'logs'
@@ -12,10 +12,15 @@ module NewRelic::Security
12
12
 
13
13
  def call(env)
14
14
  retval = nil
15
- event = call_on_enter(env) { retval = call_without_security(env) }
15
+ event = call_on_enter(env) do
16
+ begin
17
+ retval = call_without_security(env)
18
+ ensure
19
+ NewRelic::Security::Agent.agent.error_reporting&.report_unhandled_or_5xx_exceptions(NewRelic::Security::Agent::Control::HTTPContext.get_current_transaction, NewRelic::Security::Agent::Control::HTTPContext.get_context, nil)
20
+ end
21
+ end
16
22
  call_on_exit(event, retval) { return retval }
17
23
  end
18
-
19
24
  end
20
25
  end
21
26
  end
@@ -24,6 +24,7 @@ module NewRelic::Security
24
24
  # NewRelic::Security::Agent.logger.debug "\n\nHTTP Context : #{::NewRelic::Agent::Tracer.current_transaction.instance_variable_get(:@security_context_data).inspect}\n\n"
25
25
  NewRelic::Security::Agent::Control::ReflectedXSS.check_xss(NewRelic::Security::Agent::Control::HTTPContext.get_context, retval) if NewRelic::Security::Agent.config[:'security.detection.rxss.enabled']
26
26
  NewRelic::Security::Agent::Utils.delete_created_files(NewRelic::Security::Agent::Control::HTTPContext.get_context)
27
+ NewRelic::Security::Agent.agent.error_reporting&.report_unhandled_or_5xx_exceptions(NewRelic::Security::Agent::Control::HTTPContext.get_current_transaction, NewRelic::Security::Agent::Control::HTTPContext.get_context, retval[0])
27
28
  NewRelic::Security::Agent::Control::HTTPContext.reset_context
28
29
  NewRelic::Security::Agent.logger.debug "Exit event : #{event}"
29
30
  rescue => exception
@@ -40,7 +41,7 @@ module NewRelic::Security
40
41
  def prepare_env_from_route_on_enter(route)
41
42
  NewRelic::Security::Agent.logger.debug "OnEnter : #{self.class}.#{__method__}"
42
43
  ctxt = NewRelic::Security::Agent::Control::HTTPContext.get_context
43
- http_method = route.instance_variable_get(:@request_method) ? route.instance_variable_get(:@request_method) : route.instance_variable_get(:@options)[:method]
44
+ http_method = route.instance_variable_get(:@request_method) || route.instance_variable_get(:@options)[:method]
44
45
  ctxt.route = "#{http_method}@#{route.options[:namespace]}" unless ctxt.nil?
45
46
  rescue => exception
46
47
  NewRelic::Security::Agent.logger.error "Exception in hook in #{self.class}.#{__method__}, #{exception.inspect}, #{exception.backtrace}"
@@ -8,7 +8,13 @@ module NewRelic::Security
8
8
 
9
9
  def call(env)
10
10
  retval = nil
11
- event = call_on_enter(env) { retval = super(env) }
11
+ event = call_on_enter(env) do
12
+ begin
13
+ retval = super(env)
14
+ ensure
15
+ NewRelic::Security::Agent.agent.error_reporting&.report_unhandled_or_5xx_exceptions(NewRelic::Security::Agent::Control::HTTPContext.get_current_transaction, NewRelic::Security::Agent::Control::HTTPContext.get_context, nil)
16
+ end
17
+ end
12
18
  call_on_exit(event, retval) { return retval }
13
19
  end
14
20
 
@@ -21,6 +21,23 @@ module NewRelic::Security
21
21
  end
22
22
  end
23
23
  end
24
+
25
+ module Router
26
+ module Chain
27
+ def self.instrument!
28
+ ::Padrino::Router.class_eval do
29
+ include NewRelic::Security::Instrumentation::Padrino::Router
30
+
31
+ alias_method :call_without_security, :call
32
+
33
+ def call(env, &block)
34
+ retval = call_without_security(env, &block)
35
+ call_on_exit(retval) { return retval }
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
24
41
  end
25
42
  end
26
43
  end
@@ -13,7 +13,7 @@ module NewRelic::Security
13
13
  extracted_env = env.instance_variable_get(:@env)
14
14
  NewRelic::Security::Agent::Control::HTTPContext.set_context(extracted_env)
15
15
  ctxt = NewRelic::Security::Agent::Control::HTTPContext.get_context
16
- ctxt.route = "#{extracted_env[REQUEST_METHOD].to_s}@#{extracted_env[PATH_INFO].to_s}" if ctxt
16
+ ctxt.route = "#{extracted_env[REQUEST_METHOD]}@#{extracted_env[PATH_INFO]}" if ctxt
17
17
  NewRelic::Security::Agent::Utils.parse_fuzz_header(NewRelic::Security::Agent::Control::HTTPContext.get_context)
18
18
  rescue => exception
19
19
  NewRelic::Security::Agent.logger.error "Exception in hook in #{self.class}.#{__method__}, #{exception.inspect}, #{exception.backtrace}"
@@ -27,6 +27,7 @@ module NewRelic::Security
27
27
  # NewRelic::Security::Agent.logger.debug "\n\nHTTP Context : #{::NewRelic::Agent::Tracer.current_transaction.instance_variable_get(:@security_context_data).inspect}\n\n"
28
28
  NewRelic::Security::Agent::Control::ReflectedXSS.check_xss(NewRelic::Security::Agent::Control::HTTPContext.get_context, retval) if NewRelic::Security::Agent.config[:'security.detection.rxss.enabled']
29
29
  NewRelic::Security::Agent::Utils.delete_created_files(NewRelic::Security::Agent::Control::HTTPContext.get_context)
30
+ NewRelic::Security::Agent.agent.error_reporting&.report_unhandled_or_5xx_exceptions(NewRelic::Security::Agent::Control::HTTPContext.get_current_transaction, NewRelic::Security::Agent::Control::HTTPContext.get_context, retval[0])
30
31
  NewRelic::Security::Agent::Control::HTTPContext.reset_context
31
32
  NewRelic::Security::Agent.logger.debug "Exit event : #{event}"
32
33
  rescue => exception
@@ -34,9 +35,20 @@ module NewRelic::Security
34
35
  ensure
35
36
  yield
36
37
  end
37
-
38
+ end
39
+
40
+ module Padrino::Router
41
+ def call_on_exit(retval)
42
+ NewRelic::Security::Agent.logger.debug "OnExit : #{self.class}.#{__method__}"
43
+ NewRelic::Security::Agent.agent.error_reporting&.report_unhandled_or_5xx_exceptions(NewRelic::Security::Agent::Control::HTTPContext.get_current_transaction, NewRelic::Security::Agent::Control::HTTPContext.get_context, retval[0])
44
+ rescue => exception
45
+ NewRelic::Security::Agent.logger.error "Exception in hook in #{self.class}.#{__method__}, #{exception.inspect}, #{exception.backtrace}"
46
+ ensure
47
+ yield
48
+ end
38
49
  end
39
50
  end
40
51
  end
41
52
 
42
53
  NewRelic::Security::Instrumentation::InstrumentationLoader.install_instrumentation(:padrino, ::Padrino::PathRouter::Router, ::NewRelic::Security::Instrumentation::Padrino::PathRouter::Router)
54
+ NewRelic::Security::Instrumentation::InstrumentationLoader.install_instrumentation(:padrino, ::Padrino::Router, ::NewRelic::Security::Instrumentation::Padrino::Router)
@@ -15,6 +15,18 @@ module NewRelic::Security
15
15
  end
16
16
  end
17
17
  end
18
+
19
+ module Router
20
+ module Prepend
21
+ include NewRelic::Security::Instrumentation::Padrino::Router
22
+
23
+ def call(env, &block)
24
+ retval = super
25
+ call_on_exit(retval) { return retval }
26
+ end
27
+
28
+ end
29
+ end
18
30
  end
19
31
  end
20
32
  end
@@ -29,12 +29,36 @@ module NewRelic::Security
29
29
  include NewRelic::Security::Instrumentation::ActionDispatch::Journey::Router
30
30
 
31
31
  alias_method :find_routes_without_security, :find_routes
32
-
32
+
33
33
  def find_routes(req)
34
34
  retval = nil
35
35
  event = find_routes_on_enter(req) { retval = find_routes_without_security(req) }
36
36
  find_routes_on_exit(event, retval) { return retval }
37
- end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ module ActionDispatch
46
+ module Routing
47
+ module RouteSet
48
+ module Dispatcher
49
+ module Chain
50
+ def self.instrument!
51
+ ::ActionDispatch::Routing::RouteSet::Dispatcher.class_eval do
52
+ include NewRelic::Security::Instrumentation::ActionDispatch::Routing::RouteSet::Dispatcher
53
+
54
+ alias_method :serve_without_security, :serve
55
+
56
+ def serve(req)
57
+ retval = nil
58
+ event = serve_on_enter(req) { retval = serve_without_security(req) }
59
+ serve_on_exit(event, retval) { return retval }
60
+ end
61
+ end
38
62
  end
39
63
  end
40
64
  end
@@ -24,6 +24,7 @@ module NewRelic::Security
24
24
  # NewRelic::Security::Agent.logger.debug "\n\nHTTP Context : #{::NewRelic::Agent::Tracer.current_transaction.instance_variable_get(:@security_context_data).inspect}\n\n"
25
25
  NewRelic::Security::Agent::Control::ReflectedXSS.check_xss(NewRelic::Security::Agent::Control::HTTPContext.get_context, retval) if NewRelic::Security::Agent.config[:'security.detection.rxss.enabled']
26
26
  NewRelic::Security::Agent::Utils.delete_created_files(NewRelic::Security::Agent::Control::HTTPContext.get_context)
27
+ NewRelic::Security::Agent.agent.error_reporting&.report_unhandled_or_5xx_exceptions(NewRelic::Security::Agent::Control::HTTPContext.get_current_transaction, NewRelic::Security::Agent::Control::HTTPContext.get_context, retval[0])
27
28
  NewRelic::Security::Agent::Control::HTTPContext.reset_context
28
29
  NewRelic::Security::Agent.logger.debug "Exit event : #{event}"
29
30
  rescue => exception
@@ -31,11 +32,11 @@ module NewRelic::Security
31
32
  ensure
32
33
  yield
33
34
  end
34
-
35
+
35
36
  end
36
37
 
37
38
  module ActionDispatch::Journey::Router
38
-
39
+
39
40
  def find_routes_on_enter(req)
40
41
  event = nil
41
42
  NewRelic::Security::Agent.logger.debug "OnEnter : #{self.class}.#{__method__}"
@@ -45,7 +46,7 @@ module NewRelic::Security
45
46
  yield
46
47
  return event
47
48
  end
48
-
49
+
49
50
  def find_routes_on_exit(event, retval)
50
51
  NewRelic::Security::Agent.logger.debug "OnExit : #{self.class}.#{__method__}"
51
52
 
@@ -60,8 +61,32 @@ module NewRelic::Security
60
61
  yield
61
62
  end
62
63
  end
64
+
65
+ module ActionDispatch::Routing::RouteSet::Dispatcher
66
+
67
+ def serve_on_enter(req)
68
+ event = nil
69
+ NewRelic::Security::Agent.logger.debug "OnEnter : #{self.class}.#{__method__}"
70
+ ctxt = NewRelic::Security::Agent::Control::HTTPContext.get_context
71
+ ctxt.route = "#{ctxt.method}@#{req.route_uri_pattern.to_s.gsub(/\(\.:format\)/, EMPTY_STRING)}" if ctxt && req.respond_to?(:route_uri_pattern)
72
+ rescue => exception
73
+ NewRelic::Security::Agent.logger.error "Exception in hook in #{self.class}.#{__method__}, #{exception.inspect}, #{exception.backtrace}"
74
+ ensure
75
+ yield
76
+ return event
77
+ end
78
+
79
+ def serve_on_exit(event, retval)
80
+ NewRelic::Security::Agent.logger.debug "OnExit : #{self.class}.#{__method__}"
81
+ rescue => exception
82
+ NewRelic::Security::Agent.logger.error "Exception in hook in #{self.class}.#{__method__}, #{exception.inspect}, #{exception.backtrace}"
83
+ ensure
84
+ yield
85
+ end
86
+ end
63
87
  end
64
88
  end
65
89
 
66
90
  NewRelic::Security::Instrumentation::InstrumentationLoader.install_instrumentation(:rails, ::Rails::Engine, ::NewRelic::Security::Instrumentation::Rails::Engine)
67
91
  NewRelic::Security::Instrumentation::InstrumentationLoader.install_instrumentation(:rails, ::ActionDispatch::Journey::Router, ::NewRelic::Security::Instrumentation::ActionDispatch::Journey::Router)
92
+ NewRelic::Security::Instrumentation::InstrumentationLoader.install_instrumentation(:rails, ::ActionDispatch::Routing::RouteSet::Dispatcher, ::NewRelic::Security::Instrumentation::ActionDispatch::Routing::RouteSet::Dispatcher)
@@ -29,5 +29,23 @@ module NewRelic::Security
29
29
  end
30
30
  end
31
31
  end
32
+
33
+ module ActionDispatch
34
+ module Routing
35
+ module RouteSet
36
+ module Dispatcher
37
+ module Prepend
38
+ include NewRelic::Security::Instrumentation::ActionDispatch::Routing::RouteSet::Dispatcher
39
+
40
+ def serve(req)
41
+ retval = nil
42
+ event = serve_on_enter(req) { retval = super }
43
+ serve_on_exit(event, retval) { return retval }
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
32
50
  end
33
51
  end
@@ -10,10 +10,15 @@ module NewRelic::Security
10
10
 
11
11
  def _roda_handle_main_route(*args)
12
12
  retval = nil
13
- event = _roda_handle_main_route_on_enter(self.env) { retval = _roda_handle_main_route_without_security(*args) }
13
+ event = _roda_handle_main_route_on_enter(env) do
14
+ begin
15
+ retval = _roda_handle_main_route_without_security(*args)
16
+ ensure
17
+ NewRelic::Security::Agent.agent.error_reporting&.report_unhandled_or_5xx_exceptions(NewRelic::Security::Agent::Control::HTTPContext.get_current_transaction, NewRelic::Security::Agent::Control::HTTPContext.get_context, nil)
18
+ end
19
+ end
14
20
  _roda_handle_main_route_on_exit(event, retval) { return retval }
15
21
  end
16
-
17
22
  end
18
23
  end
19
24
  end
@@ -12,7 +12,7 @@ module NewRelic::Security
12
12
  NewRelic::Security::Agent::Utils.get_app_routes(:roda) if NewRelic::Security::Agent.agent.route_map.empty?
13
13
  NewRelic::Security::Agent::Control::HTTPContext.set_context(env)
14
14
  ctxt = NewRelic::Security::Agent::Control::HTTPContext.get_context
15
- ctxt.route = "#{env[REQUEST_METHOD].to_s}@#{env[PATH_INFO].to_s}" if ctxt
15
+ ctxt.route = "#{env[REQUEST_METHOD]}@#{env[PATH_INFO]}" if ctxt
16
16
  NewRelic::Security::Agent::Utils.parse_fuzz_header(NewRelic::Security::Agent::Control::HTTPContext.get_context)
17
17
  rescue => exception
18
18
  NewRelic::Security::Agent.logger.error "Exception in hook in #{self.class}.#{__method__}, #{exception.inspect}, #{exception.backtrace}"
@@ -26,6 +26,7 @@ module NewRelic::Security
26
26
  # NewRelic::Security::Agent.logger.debug "\n\nHTTP Context : #{::NewRelic::Agent::Tracer.current_transaction.instance_variable_get(:@security_context_data).inspect}\n\n"
27
27
  NewRelic::Security::Agent::Control::ReflectedXSS.check_xss(NewRelic::Security::Agent::Control::HTTPContext.get_context, retval) if NewRelic::Security::Agent.config[:'security.detection.rxss.enabled']
28
28
  NewRelic::Security::Agent::Utils.delete_created_files(NewRelic::Security::Agent::Control::HTTPContext.get_context)
29
+ NewRelic::Security::Agent.agent.error_reporting&.report_unhandled_or_5xx_exceptions(NewRelic::Security::Agent::Control::HTTPContext.get_current_transaction, NewRelic::Security::Agent::Control::HTTPContext.get_context, retval[0])
29
30
  NewRelic::Security::Agent::Control::HTTPContext.reset_context
30
31
  NewRelic::Security::Agent.logger.debug "Exit event : #{event}"
31
32
  rescue => exception
@@ -6,7 +6,13 @@ module NewRelic::Security
6
6
 
7
7
  def _roda_handle_main_route(*args)
8
8
  retval = nil
9
- event = _roda_handle_main_route_on_enter(self.env) { retval = super }
9
+ event = _roda_handle_main_route_on_enter(env) do
10
+ begin
11
+ retval = super
12
+ ensure
13
+ NewRelic::Security::Agent.agent.error_reporting&.report_unhandled_or_5xx_exceptions(NewRelic::Security::Agent::Control::HTTPContext.get_current_transaction, NewRelic::Security::Agent::Control::HTTPContext.get_context, nil)
14
+ end
15
+ end
10
16
  _roda_handle_main_route_on_exit(event, retval) { return retval }
11
17
  end
12
18
 
@@ -20,6 +20,12 @@ module NewRelic::Security
20
20
  def route_eval(&block)
21
21
  route_eval_on_enter { route_eval_without_security(&block) }
22
22
  end
23
+
24
+ alias_method :dispatch_without_security, :dispatch!
25
+
26
+ def dispatch!
27
+ dispatch_on_enter { dispatch_without_security }
28
+ end
23
29
  end
24
30
  end
25
31
  end
@@ -24,6 +24,7 @@ module NewRelic::Security
24
24
  # NewRelic::Security::Agent.logger.debug "\n\nHTTP Context : #{::NewRelic::Agent::Tracer.current_transaction.instance_variable_get(:@security_context_data).inspect}\n\n"
25
25
  NewRelic::Security::Agent::Control::ReflectedXSS.check_xss(NewRelic::Security::Agent::Control::HTTPContext.get_context, retval) if NewRelic::Security::Agent.config[:'security.detection.rxss.enabled']
26
26
  NewRelic::Security::Agent::Utils.delete_created_files(NewRelic::Security::Agent::Control::HTTPContext.get_context)
27
+ NewRelic::Security::Agent.agent.error_reporting&.report_unhandled_or_5xx_exceptions(NewRelic::Security::Agent::Control::HTTPContext.get_current_transaction, NewRelic::Security::Agent::Control::HTTPContext.get_context, retval[0])
27
28
  NewRelic::Security::Agent::Control::HTTPContext.reset_context
28
29
  NewRelic::Security::Agent.logger.debug "Exit event : #{event}"
29
30
  rescue => exception
@@ -41,6 +42,13 @@ module NewRelic::Security
41
42
  ensure
42
43
  yield
43
44
  end
45
+
46
+ def dispatch_on_enter
47
+ NewRelic::Security::Agent.logger.debug "OnEnter : #{self.class}.#{__method__}"
48
+ yield
49
+ ensure
50
+ NewRelic::Security::Agent.agent.error_reporting&.report_unhandled_or_5xx_exceptions(NewRelic::Security::Agent::Control::HTTPContext.get_current_transaction, NewRelic::Security::Agent::Control::HTTPContext.get_context)
51
+ end
44
52
 
45
53
  end
46
54
  end
@@ -14,6 +14,10 @@ module NewRelic::Security
14
14
  def route_eval
15
15
  route_eval_on_enter { super }
16
16
  end
17
+
18
+ def dispatch!
19
+ dispatch_on_enter { super }
20
+ end
17
21
  end
18
22
  end
19
23
  end
@@ -10,7 +10,7 @@ module NewRelic::Security
10
10
  NewRelic::Security::Agent.logger.debug "OnEnter : #{self.class}.#{__method__}"
11
11
  hash = {}
12
12
  hash[:sql] = sql
13
- hash[:parameters] = bind_vars.is_a?(String) ? [bind_vars] : bind_vars.map(&:to_s)
13
+ hash[:parameters] = bind_vars.is_a?(String) ? [bind_vars] : bind_vars.flatten
14
14
  hash[:parameters] = hash[:parameters] + args.map(&:to_s) unless args.empty?
15
15
  event = NewRelic::Security::Agent::Control::Collector.collect(SQL_DB_COMMAND, [hash], SQLITE) unless NewRelic::Security::Instrumentation::InstrumentationUtils.sql_filter_events?(hash[:sql])
16
16
  rescue => exception
@@ -34,7 +34,7 @@ module NewRelic::Security
34
34
  NewRelic::Security::Agent.logger.debug "OnEnter : #{self.class}.#{__method__}"
35
35
  hash = {}
36
36
  hash[:sql] = sql
37
- hash[:parameters] = bind_vars.is_a?(String) ? [bind_vars] : bind_vars.map(&:to_s)
37
+ hash[:parameters] = bind_vars.is_a?(String) ? [bind_vars] : bind_vars.flatten
38
38
  hash[:parameters] = hash[:parameters] + args unless args.empty?
39
39
  event = NewRelic::Security::Agent::Control::Collector.collect(SQL_DB_COMMAND, [hash], SQLITE) unless NewRelic::Security::Instrumentation::InstrumentationUtils.sql_filter_events?(hash[:sql])
40
40
  rescue => exception
@@ -102,7 +102,7 @@ module NewRelic::Security
102
102
  def bind_params_on_enter(*bind_vars)
103
103
  event = nil
104
104
  NewRelic::Security::Agent.logger.debug "OnEnter : #{self.class}.#{__method__}"
105
- NewRelic::Security::Agent::Control::HTTPContext.get_context.cache[self.object_id][:parameters] = bind_vars.map(&:to_s) if NewRelic::Security::Agent::Control::HTTPContext.get_context && NewRelic::Security::Agent::Control::HTTPContext.get_context.cache.key?(self.object_id)
105
+ NewRelic::Security::Agent::Control::HTTPContext.get_context.cache[self.object_id][:parameters] = bind_vars.flatten if NewRelic::Security::Agent::Control::HTTPContext.get_context && NewRelic::Security::Agent::Control::HTTPContext.get_context.cache.key?(self.object_id)
106
106
  rescue => exception
107
107
  NewRelic::Security::Agent.logger.error "Exception in hook in #{self.class}.#{__method__}, #{exception.inspect}, #{exception.backtrace}"
108
108
  ensure
@@ -129,7 +129,7 @@ module NewRelic::Security
129
129
  else
130
130
  hash = {}
131
131
  hash[:sql] = NewRelic::Security::Agent::Control::HTTPContext.get_context.cache[key][:sql]
132
- hash[:parameters] = bind_vars.map(&:to_s)
132
+ hash[:parameters] = bind_vars.flatten
133
133
  ic_args.push(hash)
134
134
  end
135
135
  end
@@ -1,5 +1,5 @@
1
1
  module NewRelic
2
2
  module Security
3
- VERSION = "0.1.0"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: newrelic_security
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Prateek Sen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-07-29 00:00:00.000000000 Z
11
+ date: 2024-10-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: newrelic_rpm
@@ -151,10 +151,12 @@ files:
151
151
  - lib/newrelic_security/agent/configuration/server_source.rb
152
152
  - lib/newrelic_security/agent/configuration/yaml_source.rb
153
153
  - lib/newrelic_security/agent/control/app_info.rb
154
+ - lib/newrelic_security/agent/control/application_runtime_error.rb
154
155
  - lib/newrelic_security/agent/control/application_url_mappings.rb
155
156
  - lib/newrelic_security/agent/control/collector.rb
156
157
  - lib/newrelic_security/agent/control/control_command.rb
157
158
  - lib/newrelic_security/agent/control/critical_message.rb
159
+ - lib/newrelic_security/agent/control/error_reporting.rb
158
160
  - lib/newrelic_security/agent/control/event.rb
159
161
  - lib/newrelic_security/agent/control/event_counter.rb
160
162
  - lib/newrelic_security/agent/control/event_processor.rb