newrelic_security 0.3.0 → 0.4.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.
- checksums.yaml +4 -4
- data/.github/workflows/pr_ci.yml +2 -2
- data/.github/workflows/release.yml +1 -1
- data/.github/workflows/rubocop.yml +1 -1
- data/CHANGELOG.md +28 -0
- data/Gemfile_test +3 -0
- data/README.md +1 -0
- data/lib/newrelic_security/agent/agent.rb +3 -1
- data/lib/newrelic_security/agent/configuration/manager.rb +16 -2
- data/lib/newrelic_security/agent/control/application_runtime_error.rb +1 -1
- data/lib/newrelic_security/agent/control/collector.rb +7 -1
- data/lib/newrelic_security/agent/control/control_command.rb +2 -1
- data/lib/newrelic_security/agent/control/error_reporting.rb +8 -6
- data/lib/newrelic_security/agent/control/event.rb +1 -0
- data/lib/newrelic_security/agent/control/event_processor.rb +20 -14
- data/lib/newrelic_security/agent/control/event_subscriber.rb +5 -1
- data/lib/newrelic_security/agent/control/health_check.rb +1 -0
- data/lib/newrelic_security/agent/control/http_context.rb +2 -1
- data/lib/newrelic_security/agent/control/iast_client.rb +1 -1
- data/lib/newrelic_security/agent/control/reflected_xss.rb +3 -4
- data/lib/newrelic_security/agent/control/websocket_client.rb +53 -16
- data/lib/newrelic_security/agent/utils/agent_utils.rb +14 -10
- data/lib/newrelic_security/instrumentation-security/graphql/chain.rb +26 -0
- data/lib/newrelic_security/instrumentation-security/graphql/instrumentation.rb +28 -0
- data/lib/newrelic_security/instrumentation-security/graphql/prepend.rb +18 -0
- data/lib/newrelic_security/instrumentation-security/io/chain.rb +2 -2
- data/lib/newrelic_security/instrumentation-security/io/prepend.rb +1 -1
- data/lib/newrelic_security/instrumentation-security/rack/chain.rb +24 -0
- data/lib/newrelic_security/instrumentation-security/rack/instrumentation.rb +44 -0
- data/lib/newrelic_security/instrumentation-security/rack/prepend.rb +18 -0
- data/lib/newrelic_security/version.rb +1 -1
- data/lib/newrelic_security/websocket-client-simple/client.rb +5 -1
- metadata +11 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 90213be8f4f16d3ac2402f570f734466927d341581a73e3382f8d843d0a4486e
|
|
4
|
+
data.tar.gz: a329513f2be86620ecccd38f2811b842b897028c06027e47930b3dea4e8b7b4c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 565fbe75ccb52096f5e1e1549f5dfdea51f96435a2ee0a8819723e1c996c731f6090a6edb3bb1673318a9273288b031a3645e7d64f81a56cd0212181d64647e4
|
|
7
|
+
data.tar.gz: d176f0228f7f448e2fd08abd28906f59c4e0cac88abcd41bd7a2c02b30c427a78c990cc08a41d3626d5e4ec1cb62809cc2d029bcce7b8f88aa20736c883373c6
|
data/.github/workflows/pr_ci.yml
CHANGED
|
@@ -22,7 +22,7 @@ jobs:
|
|
|
22
22
|
run: sudo apt-get update; sudo apt-get install -y --no-install-recommends libcurl4-nss-dev libxslt1-dev libc6-dev openjdk-11-jdk
|
|
23
23
|
|
|
24
24
|
- name: Install Ruby ${{ matrix.ruby-version }}
|
|
25
|
-
uses: ruby/setup-ruby@
|
|
25
|
+
uses: ruby/setup-ruby@086ffb1a2090c870a3f881cc91ea83aa4243d408 # v1.195.0
|
|
26
26
|
with:
|
|
27
27
|
ruby-version: ${{ matrix.ruby-version }}
|
|
28
28
|
|
|
@@ -53,7 +53,7 @@ jobs:
|
|
|
53
53
|
- name: Configure git
|
|
54
54
|
run: 'git config --global init.defaultBranch main'
|
|
55
55
|
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # tag v4.1.2
|
|
56
|
-
- uses: ruby/setup-ruby@
|
|
56
|
+
- uses: ruby/setup-ruby@086ffb1a2090c870a3f881cc91ea83aa4243d408 # v1.195.0
|
|
57
57
|
with:
|
|
58
58
|
ruby-version: '3.1'
|
|
59
59
|
- run: bundle
|
|
@@ -10,7 +10,7 @@ jobs:
|
|
|
10
10
|
- name: Configure git
|
|
11
11
|
run: 'git config --global init.defaultBranch main'
|
|
12
12
|
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # tag v4.1.2
|
|
13
|
-
- uses: ruby/setup-ruby@
|
|
13
|
+
- uses: ruby/setup-ruby@086ffb1a2090c870a3f881cc91ea83aa4243d408 # v1.195.0
|
|
14
14
|
with:
|
|
15
15
|
ruby-version: '3.3'
|
|
16
16
|
- run: bundle
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# New Relic Ruby Security Agent Release Notes
|
|
2
2
|
|
|
3
|
+
## v0.4.0
|
|
4
|
+
|
|
5
|
+
Version 0.4.0 introuduces Rack framework support, GraphQL support & CI/CD integration as part of security. Updated json_version: **1.2.9**
|
|
6
|
+
|
|
7
|
+
- Feature: Rack framework support [PR#149](https://github.com/newrelic/csec-ruby-agent/pull/149)
|
|
8
|
+
|
|
9
|
+
- Feature: GraphQL support [PR#133](https://github.com/newrelic/csec-ruby-agent/pull/133)
|
|
10
|
+
|
|
11
|
+
- Feature: CI/CD integration support [PR#146](https://github.com/newrelic/csec-ruby-agent/pull/146)
|
|
12
|
+
|
|
13
|
+
- Bugfix: Fix for Agent is sending 404 http error in runtime error json [PR#147](https://github.com/newrelic/csec-ruby-agent/pull/147)
|
|
14
|
+
|
|
15
|
+
- Bugfix: Fix for Exception occurred in generating unhandled exception in Sinatra jruby app [PR#150](https://github.com/newrelic/csec-ruby-agent/pull/150)
|
|
16
|
+
|
|
17
|
+
- Bugfix: Fix for Do not send empty url endpoint json & Reconnect in random interval between 5-15 secs [PR#151](https://github.com/newrelic/csec-ruby-agent/pull/151)
|
|
18
|
+
|
|
19
|
+
- Bugfix: Fix for Wrong formated critical error message [PR#148](https://github.com/newrelic/csec-ruby-agent/pull/148)
|
|
20
|
+
|
|
21
|
+
- BugFix: Fix for Vulnerability is missed in text/json because of small stackTrace [PR#155](https://github.com/newrelic/csec-ruby-agent/pull/155)
|
|
22
|
+
|
|
23
|
+
- BugFix: Fix for API endpoints not detected in grape framework in JRuby environment [PR#158](https://github.com/newrelic/csec-ruby-agent/pull/158)
|
|
24
|
+
|
|
25
|
+
- BugFix: Fix for XML content type vulnerability not detected [PR#159](https://github.com/newrelic/csec-ruby-agent/pull/159)
|
|
26
|
+
|
|
27
|
+
- BugFix: Fix for Concurrency issue observed in JRuby environment, BufferOverflowException observed in writing events to websocket [PR#161](https://github.com/newrelic/csec-ruby-agent/pull/161)
|
|
28
|
+
|
|
29
|
+
- Memory usage optimisations [PR#153](https://github.com/newrelic/csec-ruby-agent/pull/153)
|
|
30
|
+
|
|
3
31
|
## v0.3.0
|
|
4
32
|
|
|
5
33
|
Version 0.3.0 introduces more control on IAST scanning through new configs(exclude_from_iast_scan, scan_schedule & scan_controllers) and
|
data/Gemfile_test
CHANGED
|
@@ -13,7 +13,9 @@ gem 'rubocop'
|
|
|
13
13
|
gem 'rubocop-minitest'
|
|
14
14
|
gem 'rubocop-rake'
|
|
15
15
|
gem 'simplecov'
|
|
16
|
+
gem 'concurrent-ruby', '1.3.4'
|
|
16
17
|
gem 'railties'
|
|
18
|
+
gem 'ffi', '1.15.5' if RUBY_VERSION < '3.1.0'
|
|
17
19
|
if RUBY_VERSION >= '3.0.0'
|
|
18
20
|
gem 'rails', '~>6.0.0'
|
|
19
21
|
elsif RUBY_VERSION < '2.5.0'
|
|
@@ -23,6 +25,7 @@ else
|
|
|
23
25
|
end
|
|
24
26
|
gem 'loofah', '~> 2.19.0'
|
|
25
27
|
gem 'sinatra'
|
|
28
|
+
gem 'tilt', '~> 2.4.0' if RUBY_VERSION < '2.5.0'
|
|
26
29
|
gem 'padrino'
|
|
27
30
|
gem 'grape'
|
|
28
31
|
gem 'roda'
|
data/README.md
CHANGED
|
@@ -62,6 +62,8 @@ module NewRelic::Security
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def shutdown_security_agent
|
|
65
|
+
NewRelic::Security::Agent.logger.info "Flushing eventQ (#{NewRelic::Security::Agent.agent.event_processor.eventQ.size} events) and closing websocket connection"
|
|
66
|
+
NewRelic::Security::Agent.agent.event_processor&.eventQ&.clear
|
|
65
67
|
@iast_client&.fuzzQ&.clear
|
|
66
68
|
@iast_client&.completed_requests&.clear
|
|
67
69
|
@iast_client&.pending_request_ids&.clear
|
|
@@ -80,7 +82,7 @@ module NewRelic::Security
|
|
|
80
82
|
end
|
|
81
83
|
|
|
82
84
|
def start_event_processor
|
|
83
|
-
@event_processor&.
|
|
85
|
+
@event_processor&.event_dequeue_threads&.each { |t| t&.kill }
|
|
84
86
|
@event_processor&.healthcheck_thread&.kill
|
|
85
87
|
@event_processor = nil
|
|
86
88
|
@event_processor = NewRelic::Security::Agent::Control::EventProcessor.new
|
|
@@ -37,14 +37,17 @@ module NewRelic::Security
|
|
|
37
37
|
@cache[:'security.detection.deserialization.enabled'] = ::NewRelic::Agent.config[:'security.detection.deserialization.enabled'].nil? ? true : ::NewRelic::Agent.config[:'security.detection.deserialization.enabled']
|
|
38
38
|
@cache[:'security.scan_controllers.iast_scan_request_rate_limit'] = ::NewRelic::Agent.config[:'security.scan_controllers.iast_scan_request_rate_limit'].to_i
|
|
39
39
|
@cache[:framework] = detect_framework
|
|
40
|
+
@cache[:app_class] = detect_app_class if @cache[:framework] == :rack
|
|
40
41
|
@cache[:'security.application_info.port'] = ::NewRelic::Agent.config[:'security.application_info.port'].to_i
|
|
41
42
|
@cache[:listen_port] = nil
|
|
42
43
|
@cache[:process_start_time] = current_time_millis # TODO: Ruby doesn't provide process start time in pure ruby implementation using agent loading time for now.
|
|
43
44
|
@cache[:traffic_start_time] = nil
|
|
44
45
|
@cache[:scan_start_time] = nil
|
|
46
|
+
@cache[:'security.scan_controllers.scan_instance_count'] = ::NewRelic::Agent.config[:'security.scan_controllers.scan_instance_count']
|
|
47
|
+
@cache[:'security.iast_test_identifier'] = ::NewRelic::Agent.config[:'security.iast_test_identifier']
|
|
45
48
|
@cache[:app_root] = NewRelic::Security::Agent::Utils.app_root
|
|
46
49
|
@cache[:jruby_objectspace_enabled] = false
|
|
47
|
-
@cache[:json_version] = :'1.2.
|
|
50
|
+
@cache[:json_version] = :'1.2.9'
|
|
48
51
|
@cache[:'security.exclude_from_iast_scan.api'] = convert_to_regexp_list(::NewRelic::Agent.config[:'security.exclude_from_iast_scan.api'])
|
|
49
52
|
@cache[:'security.exclude_from_iast_scan.http_request_parameters.header'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.http_request_parameters.header']
|
|
50
53
|
@cache[:'security.exclude_from_iast_scan.http_request_parameters.query'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.http_request_parameters.query']
|
|
@@ -153,6 +156,16 @@ module NewRelic::Security
|
|
|
153
156
|
return :sinatra if defined?(::Sinatra)
|
|
154
157
|
return :roda if defined?(::Roda)
|
|
155
158
|
return :grape if defined?(::Grape)
|
|
159
|
+
return :rack if defined?(::Rack) && defined?(Rack::Builder)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def detect_app_class
|
|
163
|
+
target_class = nil
|
|
164
|
+
ObjectSpace.each_object(::Rack::Builder) do |z| target_class = z.instance_variable_get(:@run).target end
|
|
165
|
+
target_class
|
|
166
|
+
rescue StandardError => exception
|
|
167
|
+
NewRelic::Security::Agent.logger.error "Exception in detect_app_class : #{exception.inspect} #{exception.backtrace}"
|
|
168
|
+
nil
|
|
156
169
|
end
|
|
157
170
|
|
|
158
171
|
def generate_uuid
|
|
@@ -167,7 +180,8 @@ module NewRelic::Security
|
|
|
167
180
|
end
|
|
168
181
|
::SecureRandom.uuid
|
|
169
182
|
rescue Exception => exception
|
|
170
|
-
NewRelic::Security::Agent.logger.
|
|
183
|
+
NewRelic::Security::Agent.logger.warn "Error in generate_uuid, generating it through default approach : #{exception.inspect} #{exception.backtrace}"
|
|
184
|
+
::SecureRandom.uuid
|
|
171
185
|
end
|
|
172
186
|
|
|
173
187
|
def create_uuid
|
|
@@ -81,7 +81,7 @@ module NewRelic::Security
|
|
|
81
81
|
|
|
82
82
|
def generate_trace_id(ctxt, category)
|
|
83
83
|
@exception[:stackTrace]
|
|
84
|
-
method, route = ctxt.route.split(AT_THE_RATE) if ctxt
|
|
84
|
+
method, route = ctxt.route.split(AT_THE_RATE) if ctxt&.route
|
|
85
85
|
if @exception[:stackTrace]
|
|
86
86
|
::Digest::SHA256.hexdigest("#{@exception[:stackTrace].join(PIPE)}#{category}#{route}#{method}").to_s
|
|
87
87
|
else
|
|
@@ -45,6 +45,7 @@ module NewRelic::Security
|
|
|
45
45
|
# In rails 5 method name keeps chaning for same api call (ex: _app_views_sqli_sqlinjectionattackcase_html_erb__1999281606898621405_2624809100).
|
|
46
46
|
# Hence, considering only frame absolute_path & lineno for apiId calculation.
|
|
47
47
|
user_frame_index = get_user_frame_index(stk)
|
|
48
|
+
route = route&.gsub(/\d+/, EMPTY_STRING) if NewRelic::Security::Agent.config[:framework] == :rack || NewRelic::Security::Agent.config[:framework] == :roda
|
|
48
49
|
event.apiId = "#{case_type}-#{calculate_api_id(stk[0..user_frame_index].map { |frame| "#{frame.absolute_path}:#{frame.lineno}" }, event.httpRequest[:method], route)}"
|
|
49
50
|
stk.delete_if { |frame| frame.path.match?(/newrelic_security/) || frame.path.match?(/new_relic/) }
|
|
50
51
|
user_frame_index = get_user_frame_index(stk)
|
|
@@ -60,7 +61,7 @@ module NewRelic::Security
|
|
|
60
61
|
event.lineNumber = stk[0].lineno
|
|
61
62
|
end
|
|
62
63
|
event.stacktrace = stk[0..user_frame_index].map(&:to_s)
|
|
63
|
-
NewRelic::Security::Agent.agent.event_processor
|
|
64
|
+
NewRelic::Security::Agent.agent.event_processor&.send_event(event)
|
|
64
65
|
if event.httpRequest[:headers].key?(NR_CSEC_FUZZ_REQUEST_ID) && event.apiId == event.httpRequest[:headers][NR_CSEC_FUZZ_REQUEST_ID].split(COLON_IAST_COLON)[0] && NewRelic::Security::Agent.agent.iast_client.completed_requests[event.parentId]
|
|
65
66
|
NewRelic::Security::Agent.agent.iast_client.completed_requests[event.parentId] << event.id
|
|
66
67
|
end
|
|
@@ -73,6 +74,10 @@ module NewRelic::Security
|
|
|
73
74
|
else
|
|
74
75
|
NewRelic::Security::Agent.agent.rasp_event_stats.error_count.increment
|
|
75
76
|
end
|
|
77
|
+
ensure
|
|
78
|
+
event = nil
|
|
79
|
+
stk = nil
|
|
80
|
+
route = nil
|
|
76
81
|
end
|
|
77
82
|
|
|
78
83
|
private
|
|
@@ -80,6 +85,7 @@ module NewRelic::Security
|
|
|
80
85
|
def get_user_frame_index(stk)
|
|
81
86
|
return -1 if NewRelic::Security::Agent.config[:app_root].nil?
|
|
82
87
|
stk.each_with_index do |val, index|
|
|
88
|
+
next if stk[index + 1] && stk[index + 1].path.start_with?(NewRelic::Security::Agent.config[:app_root])
|
|
83
89
|
return index if val.path.start_with?(NewRelic::Security::Agent.config[:app_root])
|
|
84
90
|
end
|
|
85
91
|
return -1
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'json'
|
|
2
|
+
require 'securerandom'
|
|
2
3
|
|
|
3
4
|
module NewRelic::Security
|
|
4
5
|
module Agent
|
|
@@ -93,7 +94,7 @@ module NewRelic::Security
|
|
|
93
94
|
NewRelic::Security::Agent.agent.iast_client.completed_requests.clear if NewRelic::Security::Agent.agent.iast_client
|
|
94
95
|
NewRelic::Security::Agent.agent.iast_client.pending_request_ids.clear if NewRelic::Security::Agent.agent.iast_client
|
|
95
96
|
NewRelic::Security::Agent.config.disable_security
|
|
96
|
-
Thread.new { NewRelic::Security::Agent.agent.reconnect(
|
|
97
|
+
Thread.new { NewRelic::Security::Agent.agent.reconnect(SecureRandom.random_number(10) + 5) }
|
|
97
98
|
end
|
|
98
99
|
|
|
99
100
|
def current_time_millis
|
|
@@ -40,22 +40,24 @@ module NewRelic::Security
|
|
|
40
40
|
# TODO: when do refactoring of ctxt.route, use both route and method to generate key
|
|
41
41
|
ctxt.route&.+ response_code.to_s
|
|
42
42
|
else
|
|
43
|
-
application_runtime_error.exception[:type]
|
|
43
|
+
application_runtime_error.exception[:type]&.+ application_runtime_error.exception[:stackTrace]&.first
|
|
44
44
|
end
|
|
45
|
+
return if key.nil? || key.empty?
|
|
45
46
|
application_runtime_error.counter = @exceptions_map[key].counter + 1 if @exceptions_map.key?(key)
|
|
46
47
|
@exceptions_map[key] = application_runtime_error
|
|
47
|
-
rescue
|
|
48
|
+
rescue StandardError => exception
|
|
48
49
|
NewRelic::Security::Agent.logger.error "Exception in generating unhandled exception: #{exception.inspect} #{exception.backtrace}\n"
|
|
49
50
|
end
|
|
50
51
|
|
|
51
|
-
def extract_noticed_error(current_transaction, ctxt,
|
|
52
|
+
def extract_noticed_error(current_transaction, ctxt, http_response_code)
|
|
53
|
+
return if http_response_code&.between?(400, 499)
|
|
52
54
|
# TODO: Below operation is expensive, talk to APM to get optimized way to do this
|
|
53
55
|
current_transaction.exceptions.each do |_, span|
|
|
54
56
|
current_transaction.segments.each do |segment|
|
|
55
|
-
generate_unhandled_exception(segment.noticed_error, ctxt,
|
|
57
|
+
generate_unhandled_exception(segment.noticed_error, ctxt, http_response_code) if span[:span_id] == segment.guid
|
|
56
58
|
end
|
|
57
59
|
end
|
|
58
|
-
rescue
|
|
60
|
+
rescue StandardError => exception
|
|
59
61
|
NewRelic::Security::Agent.logger.error "Exception in extract_noticed_error: #{exception.inspect} #{exception.backtrace}\n"
|
|
60
62
|
end
|
|
61
63
|
|
|
@@ -64,7 +66,7 @@ module NewRelic::Security
|
|
|
64
66
|
if current_transaction.exceptions.empty? && http_response_code&.between?(500, 599)
|
|
65
67
|
generate_unhandled_exception(nil, ctxt, response_code)
|
|
66
68
|
else
|
|
67
|
-
extract_noticed_error(current_transaction, ctxt,
|
|
69
|
+
extract_noticed_error(current_transaction, ctxt, http_response_code) unless current_transaction.exceptions.empty?
|
|
68
70
|
end
|
|
69
71
|
end
|
|
70
72
|
|
|
@@ -104,6 +104,7 @@ module NewRelic::Security
|
|
|
104
104
|
http_request[:headers] = ctxt.headers
|
|
105
105
|
http_request[:contentType] = ctxt.req[CONTENT_TYPE] if ctxt.req.has_key?(CONTENT_TYPE)
|
|
106
106
|
http_request[:headers][CONTENT_TYPE1] = ctxt.req[CONTENT_TYPE] if ctxt.req.has_key?(CONTENT_TYPE)
|
|
107
|
+
http_request[:customDataType] = ctxt.custom_data_type
|
|
107
108
|
http_request[:dataTruncated] = ctxt.data_truncated
|
|
108
109
|
@httpRequest = http_request
|
|
109
110
|
@metaData[:isClientDetectedFromXFF] = ctxt.headers.has_key?(X_FORWARDED_FOR) ? true : false
|
|
@@ -9,7 +9,7 @@ module NewRelic::Security
|
|
|
9
9
|
|
|
10
10
|
class EventProcessor
|
|
11
11
|
|
|
12
|
-
attr_accessor :eventQ, :
|
|
12
|
+
attr_accessor :eventQ, :event_dequeue_threads, :healthcheck_thread
|
|
13
13
|
|
|
14
14
|
def initialize
|
|
15
15
|
@first_event = true
|
|
@@ -24,18 +24,22 @@ module NewRelic::Security
|
|
|
24
24
|
NewRelic::Security::Agent.init_logger.info "[STEP-3] => Gathering information about the application"
|
|
25
25
|
app_info = NewRelic::Security::Agent::Control::AppInfo.new
|
|
26
26
|
app_info.update_app_info
|
|
27
|
-
|
|
28
|
-
NewRelic::Security::Agent.
|
|
27
|
+
app_info_json = app_info.to_json
|
|
28
|
+
NewRelic::Security::Agent.logger.info "Sending application info : #{app_info_json}"
|
|
29
|
+
NewRelic::Security::Agent.init_logger.info "Sending application info : #{app_info_json}"
|
|
29
30
|
enqueue(app_info)
|
|
30
31
|
app_info = nil
|
|
32
|
+
app_info_json = nil
|
|
31
33
|
end
|
|
32
34
|
|
|
33
35
|
def send_application_url_mappings
|
|
34
36
|
application_url_mappings = NewRelic::Security::Agent::Control::ApplicationURLMappings.new
|
|
35
37
|
application_url_mappings.update_application_url_mappings
|
|
36
|
-
|
|
38
|
+
application_url_mappings_json = application_url_mappings.to_json
|
|
39
|
+
NewRelic::Security::Agent.logger.info "Sending application URL Mappings : #{application_url_mappings_json}"
|
|
37
40
|
enqueue(application_url_mappings)
|
|
38
41
|
application_url_mappings = nil
|
|
42
|
+
application_url_mappings_json = nil
|
|
39
43
|
end
|
|
40
44
|
|
|
41
45
|
def send_event(event)
|
|
@@ -65,7 +69,7 @@ module NewRelic::Security
|
|
|
65
69
|
if exc
|
|
66
70
|
exception = {}
|
|
67
71
|
exception[:message] = exc.message
|
|
68
|
-
exception[:cause] = exc.cause
|
|
72
|
+
exception[:cause] = { :message => exc.cause }
|
|
69
73
|
exception[:stackTrace] = exc.backtrace.map(&:to_s)
|
|
70
74
|
end
|
|
71
75
|
critical_message = NewRelic::Security::Agent::Control::CriticalMessage.new(message, level, caller, thread_name, exception)
|
|
@@ -87,15 +91,17 @@ module NewRelic::Security
|
|
|
87
91
|
private
|
|
88
92
|
|
|
89
93
|
def create_dequeue_threads
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
Thread.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
@event_dequeue_threads = []
|
|
95
|
+
3.times do |t|
|
|
96
|
+
@event_dequeue_threads<< Thread.new do
|
|
97
|
+
Thread.current.name = "newrelic_security_event_thread-#{t}"
|
|
98
|
+
loop do
|
|
99
|
+
begin
|
|
100
|
+
data_to_be_sent = @eventQ.pop
|
|
101
|
+
NewRelic::Security::Agent::Control::WebsocketClient.instance.send(data_to_be_sent)
|
|
102
|
+
rescue => exception
|
|
103
|
+
NewRelic::Security::Agent.logger.error "Exception in event pop operation : #{exception.inspect}"
|
|
104
|
+
end
|
|
99
105
|
end
|
|
100
106
|
end
|
|
101
107
|
end
|
|
@@ -8,7 +8,11 @@ module NewRelic::Security
|
|
|
8
8
|
NewRelic::Security::Agent.init_logger.info "NewRelic server_source_configuration_added for pid : #{Process.pid}, Parent Pid : #{Process.ppid}"
|
|
9
9
|
NewRelic::Security::Agent.config.update_server_config
|
|
10
10
|
if NewRelic::Security::Agent.config[:'security.enabled'] && !NewRelic::Security::Agent.config[:high_security]
|
|
11
|
-
|
|
11
|
+
NewRelic::Security::Agent.agent.event_processor&.event_dequeue_threads&.each { |t| t&.kill }
|
|
12
|
+
NewRelic::Security::Agent.agent.event_processor = nil
|
|
13
|
+
@csec_agent_main_thread&.kill
|
|
14
|
+
@csec_agent_main_thread = nil
|
|
15
|
+
@csec_agent_main_thread = Thread.new { NewRelic::Security::Agent.agent.scan_scheduler.init_via_scan_scheduler }
|
|
12
16
|
else
|
|
13
17
|
NewRelic::Security::Agent.logger.info "New Relic Security is disabled by one of the user provided config `security.enabled` or `high_security`."
|
|
14
18
|
NewRelic::Security::Agent.init_logger.info "New Relic Security is disabled by one of the user provided config `security.enabled` or `high_security`."
|
|
@@ -38,6 +38,7 @@ module NewRelic::Security
|
|
|
38
38
|
@procStartTime = NewRelic::Security::Agent.config[:process_start_time]
|
|
39
39
|
@trafficStartedTime = NewRelic::Security::Agent.config[:traffic_start_time]
|
|
40
40
|
@scanStartTime = NewRelic::Security::Agent.config[:scan_start_time]
|
|
41
|
+
@iastTestIdentifer = NewRelic::Security::Agent.config[:'security.iast_test_identifier']
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
def as_json
|
|
@@ -19,7 +19,7 @@ module NewRelic::Security
|
|
|
19
19
|
|
|
20
20
|
class HTTPContext
|
|
21
21
|
|
|
22
|
-
attr_accessor :time_stamp, :req, :method, :headers, :params, :body, :data_truncated, :route, :cache, :fuzz_files, :event_counter, :mutex, :url
|
|
22
|
+
attr_accessor :time_stamp, :req, :method, :headers, :params, :body, :data_truncated, :route, :cache, :fuzz_files, :event_counter, :custom_data_type, :mutex, :url
|
|
23
23
|
|
|
24
24
|
def initialize(env)
|
|
25
25
|
@time_stamp = current_time_millis
|
|
@@ -48,6 +48,7 @@ module NewRelic::Security
|
|
|
48
48
|
@data_truncated = @body && @body.size >= REQUEST_BODY_LIMIT * 1024
|
|
49
49
|
strio&.rewind
|
|
50
50
|
@body = @body.force_encoding(Encoding::UTF_8) if @body.is_a?(String)
|
|
51
|
+
@custom_data_type = {}
|
|
51
52
|
@cache = Hash.new
|
|
52
53
|
@fuzz_files = ::Set.new
|
|
53
54
|
@event_counter = 0
|
|
@@ -99,7 +99,7 @@ module NewRelic::Security
|
|
|
99
99
|
end
|
|
100
100
|
iast_data_transfer_request.pendingRequestIds = pending_request_ids.to_a
|
|
101
101
|
iast_data_transfer_request.completedRequests = completed_requests
|
|
102
|
-
NewRelic::Security::Agent.agent.event_processor&.send_iast_data_transfer_request(iast_data_transfer_request)
|
|
102
|
+
NewRelic::Security::Agent.agent.event_processor&.send_iast_data_transfer_request(iast_data_transfer_request) if NewRelic::Security::Agent::Control::WebsocketClient.instance.is_open?
|
|
103
103
|
end
|
|
104
104
|
end
|
|
105
105
|
end
|
|
@@ -108,8 +108,8 @@ module NewRelic::Security
|
|
|
108
108
|
processed_data.add(body)
|
|
109
109
|
end
|
|
110
110
|
when APPLICATION_XML
|
|
111
|
-
|
|
112
|
-
processed_data.add(
|
|
111
|
+
xml_data = ::CGI.unescapeHTML(body)
|
|
112
|
+
processed_data.add(xml_data)
|
|
113
113
|
when APPLICATION_X_WWW_FORM_URLENCODED
|
|
114
114
|
body = ::CGI.unescape(body, UTF_8)
|
|
115
115
|
processed_data.add(body)
|
|
@@ -134,7 +134,7 @@ module NewRelic::Security
|
|
|
134
134
|
# do while loop in java code here
|
|
135
135
|
old_processed_body = processed_body
|
|
136
136
|
body = ::JSON.parse(processed_body)
|
|
137
|
-
processed_data.add(body) if old_processed_body != body && body.to_s.include?(LESS_THAN)
|
|
137
|
+
processed_data.add(body.to_s) if old_processed_body != body && body.to_s.include?(LESS_THAN)
|
|
138
138
|
when APPLICATION_XML
|
|
139
139
|
# Unescaping of xml data is remaining
|
|
140
140
|
processed_data.add(processed_data)
|
|
@@ -176,7 +176,6 @@ module NewRelic::Security
|
|
|
176
176
|
start_pos = 0
|
|
177
177
|
tmp_curr_pos = 0
|
|
178
178
|
tmp_start_pos = 0
|
|
179
|
-
|
|
180
179
|
while curr_pos < data.length
|
|
181
180
|
matcher = TAG_NAME_REGEX.match(data, curr_pos)
|
|
182
181
|
is_attack_construct = false
|
|
@@ -23,6 +23,8 @@ module NewRelic::Security
|
|
|
23
23
|
NR_CSEC_IAST_DATA_TRANSFER_MODE = 'NR-CSEC-IAST-DATA-TRANSFER-MODE'
|
|
24
24
|
NR_CSEC_IGNORED_VUL_CATEGORIES = 'NR-CSEC-IGNORED-VUL-CATEGORIES'
|
|
25
25
|
NR_CSEC_PROCESS_START_TIME = 'NR-CSEC-PROCESS-START-TIME'
|
|
26
|
+
NR_CSEC_IAST_SCAN_INSTANCE_COUNT = 'NR-CSEC-IAST-SCAN-INSTANCE-COUNT'
|
|
27
|
+
NR_CSEC_IAST_TEST_IDENTIFIER = 'NR-CSEC-IAST-TEST-IDENTIFIER'
|
|
26
28
|
|
|
27
29
|
class WebsocketClient
|
|
28
30
|
include Singleton
|
|
@@ -47,6 +49,11 @@ module NewRelic::Security
|
|
|
47
49
|
headers[NR_CSEC_IAST_DATA_TRANSFER_MODE] = PULL
|
|
48
50
|
headers[NR_CSEC_IGNORED_VUL_CATEGORIES] = ingnored_vul_categories.join(COMMA)
|
|
49
51
|
headers[NR_CSEC_PROCESS_START_TIME] = NewRelic::Security::Agent.config[:process_start_time]
|
|
52
|
+
headers[NR_CSEC_IAST_SCAN_INSTANCE_COUNT] = NewRelic::Security::Agent.config[:'security.scan_controllers.scan_instance_count']
|
|
53
|
+
if NewRelic::Security::Agent.config[:'security.iast_test_identifier'] && !NewRelic::Security::Agent.config[:'security.iast_test_identifier'].empty?
|
|
54
|
+
headers[NR_CSEC_IAST_TEST_IDENTIFIER] = NewRelic::Security::Agent.config[:'security.iast_test_identifier']
|
|
55
|
+
headers[NR_CSEC_IAST_SCAN_INSTANCE_COUNT] = 1
|
|
56
|
+
end
|
|
50
57
|
|
|
51
58
|
begin
|
|
52
59
|
cert_store = ::OpenSSL::X509::Store.new
|
|
@@ -54,13 +61,16 @@ module NewRelic::Security
|
|
|
54
61
|
NewRelic::Security::Agent.logger.info "Websocket connection URL : #{NewRelic::Security::Agent.config[:validator_service_url]}"
|
|
55
62
|
connection = NewRelic::Security::WebSocket::Client::Simple.connect NewRelic::Security::Agent.config[:validator_service_url], headers: headers, cert_store: cert_store
|
|
56
63
|
@ws = connection
|
|
64
|
+
@mutex = Mutex.new
|
|
57
65
|
|
|
58
66
|
connection.on :open do
|
|
67
|
+
headers = nil
|
|
59
68
|
NewRelic::Security::Agent.logger.debug "Websocket connected with IC, AgentEventMachine #{NewRelic::Security::Agent::Utils.filtered_log(connection.inspect)}"
|
|
60
69
|
NewRelic::Security::Agent.init_logger.info "[STEP-4] => Web socket connection to SaaS validator established successfully"
|
|
61
70
|
NewRelic::Security::Agent.agent.event_processor.send_app_info
|
|
62
71
|
NewRelic::Security::Agent.agent.event_processor.send_application_url_mappings
|
|
63
72
|
NewRelic::Security::Agent.config.enable_security
|
|
73
|
+
NewRelic::Security::Agent::Control::WebsocketClient.instance.start_ping_thread
|
|
64
74
|
end
|
|
65
75
|
|
|
66
76
|
connection.on :message do |msg|
|
|
@@ -75,13 +85,14 @@ module NewRelic::Security
|
|
|
75
85
|
connection.on :close do |e|
|
|
76
86
|
NewRelic::Security::Agent.logger.info "Closing websocket connection : #{e.inspect}\n"
|
|
77
87
|
NewRelic::Security::Agent.config.disable_security
|
|
78
|
-
|
|
88
|
+
reconnect_interval = e.instance_of?(TrueClass) ? 0 : 15
|
|
89
|
+
Thread.new { NewRelic::Security::Agent.agent.reconnect(reconnect_interval) } if e
|
|
79
90
|
end
|
|
80
91
|
|
|
81
92
|
connection.on :error do |e|
|
|
82
93
|
NewRelic::Security::Agent.logger.error "Error in websocket connection : #{e.inspect} #{e.backtrace}"
|
|
83
94
|
::NewRelic::Agent.notice_error(e)
|
|
84
|
-
Thread.new { NewRelic::Security::Agent::Control::WebsocketClient.instance.close(
|
|
95
|
+
Thread.new { NewRelic::Security::Agent::Control::WebsocketClient.instance.close(e) }
|
|
85
96
|
end
|
|
86
97
|
rescue Errno::EPIPE => exception
|
|
87
98
|
NewRelic::Security::Agent.logger.error "Unable to connect to validator_service: #{exception.inspect}"
|
|
@@ -107,25 +118,37 @@ module NewRelic::Security
|
|
|
107
118
|
end
|
|
108
119
|
|
|
109
120
|
def send(message)
|
|
110
|
-
message_json =
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
121
|
+
message_json = nil
|
|
122
|
+
begin
|
|
123
|
+
message_json = message.to_json
|
|
124
|
+
NewRelic::Security::Agent.logger.debug "Sending #{message.jsonName} : #{message_json}"
|
|
125
|
+
@mutex.synchronize do
|
|
126
|
+
res = @ws.send(message_json)
|
|
127
|
+
if res && message.jsonName == :Event
|
|
128
|
+
NewRelic::Security::Agent.agent.event_sent_count.increment
|
|
129
|
+
if NewRelic::Security::Agent::Utils.is_IAST_request?(message.httpRequest[:headers])
|
|
130
|
+
NewRelic::Security::Agent.agent.iast_event_stats.sent.increment
|
|
131
|
+
else
|
|
132
|
+
NewRelic::Security::Agent.agent.rasp_event_stats.sent.increment
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
NewRelic::Security::Agent.agent.exit_event_stats.sent.increment if res && message.jsonName == :'exit-event'
|
|
119
136
|
end
|
|
137
|
+
rescue Exception => exception
|
|
138
|
+
NewRelic::Security::Agent.logger.error "Exception in sending message : #{exception.inspect} #{exception.backtrace}, message: #{message_json}"
|
|
139
|
+
NewRelic::Security::Agent.agent.event_drop_count.increment if message.jsonName == :Event
|
|
140
|
+
NewRelic::Security::Agent.agent.event_processor.send_critical_message(exception.message, "SEVERE", caller_locations[0].to_s, Thread.current.name, exception)
|
|
141
|
+
ensure
|
|
142
|
+
message_json = nil
|
|
120
143
|
end
|
|
121
|
-
NewRelic::Security::Agent.agent.exit_event_stats.sent.increment if res && message.jsonName == :'exit-event'
|
|
122
|
-
rescue Exception => exception
|
|
123
|
-
NewRelic::Security::Agent.logger.error "Exception in sending message : #{exception.inspect} #{exception.backtrace}"
|
|
124
|
-
NewRelic::Security::Agent.agent.event_drop_count.increment if message.jsonName == :Event
|
|
125
|
-
NewRelic::Security::Agent.agent.event_processor.send_critical_message(exception.message, "SEVERE", caller_locations[0].to_s, Thread.current.name, exception)
|
|
126
144
|
end
|
|
127
145
|
|
|
128
146
|
def close(reconnect = true)
|
|
147
|
+
NewRelic::Security::Agent.config.disable_security
|
|
148
|
+
NewRelic::Security::Agent.logger.info "Flushing eventQ (#{NewRelic::Security::Agent.agent.event_processor.eventQ.size} events) and closing websocket connection"
|
|
149
|
+
NewRelic::Security::Agent.agent.event_processor&.eventQ&.clear
|
|
150
|
+
@iast_client&.iast_data_transfer_request_processor_thread&.kill
|
|
151
|
+
stop_ping_thread
|
|
129
152
|
@ws.close(reconnect) if @ws
|
|
130
153
|
end
|
|
131
154
|
|
|
@@ -134,8 +157,22 @@ module NewRelic::Security
|
|
|
134
157
|
false
|
|
135
158
|
end
|
|
136
159
|
|
|
160
|
+
def start_ping_thread
|
|
161
|
+
@ping_thread = Thread.new do
|
|
162
|
+
loop do
|
|
163
|
+
sleep 30
|
|
164
|
+
@ws.send(EMPTY_STRING, :type => :ping)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
137
169
|
private
|
|
138
170
|
|
|
171
|
+
def stop_ping_thread
|
|
172
|
+
@ping_thread&.kill
|
|
173
|
+
@ping_thread = nil
|
|
174
|
+
end
|
|
175
|
+
|
|
139
176
|
def ingnored_vul_categories
|
|
140
177
|
list = []
|
|
141
178
|
list << FILE_OPERATION << FILE_INTEGRITY if NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.invalid_file_access']
|
|
@@ -114,12 +114,14 @@ module NewRelic::Security
|
|
|
114
114
|
end
|
|
115
115
|
end
|
|
116
116
|
when :grape
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
117
|
+
if defined?(::Grape::API)
|
|
118
|
+
ObjectSpace.each_object(Class).select { |klass| klass < ::Grape::API }.each do |api_class|
|
|
119
|
+
api_class.routes.each do |route|
|
|
120
|
+
http_method = route.request_method || route.options[:method]
|
|
121
|
+
NewRelic::Security::Agent.agent.route_map << "#{http_method}@#{route.pattern.origin}"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
123
125
|
when :padrino
|
|
124
126
|
if router.instance_of?(::Padrino::PathRouter::Router)
|
|
125
127
|
router.instance_variable_get(:@routes).each do |route|
|
|
@@ -127,17 +129,19 @@ module NewRelic::Security
|
|
|
127
129
|
end
|
|
128
130
|
end
|
|
129
131
|
when :roda
|
|
130
|
-
NewRelic::Security::Agent.logger.
|
|
132
|
+
NewRelic::Security::Agent.logger.debug "TODO: Roda is a routing tree web toolkit, which generates route dynamically, hence route extraction is not possible."
|
|
131
133
|
when :grpc
|
|
132
134
|
router.owner.superclass.public_instance_methods(false).each do |m|
|
|
133
135
|
NewRelic::Security::Agent.agent.route_map << "*@/#{router.owner}/#{m}"
|
|
134
136
|
end
|
|
137
|
+
when :rack
|
|
138
|
+
# TODO: API enpointes(routes) extraction for rack
|
|
135
139
|
else
|
|
136
140
|
NewRelic::Security::Agent.logger.error "Unable to get app routes as Framework not detected"
|
|
137
141
|
end
|
|
138
142
|
disable_object_space_in_jruby if NewRelic::Security::Agent.config[:jruby_objectspace_enabled]
|
|
139
143
|
NewRelic::Security::Agent.logger.debug "ALL ROUTES : #{NewRelic::Security::Agent.agent.route_map}"
|
|
140
|
-
NewRelic::Security::Agent.agent.event_processor&.send_application_url_mappings
|
|
144
|
+
NewRelic::Security::Agent.agent.event_processor&.send_application_url_mappings unless NewRelic::Security::Agent.agent.route_map.empty?
|
|
141
145
|
rescue Exception => exception
|
|
142
146
|
NewRelic::Security::Agent.logger.error "Error in get app routes : #{exception.inspect} #{exception.backtrace}"
|
|
143
147
|
end
|
|
@@ -199,14 +203,14 @@ module NewRelic::Security
|
|
|
199
203
|
end
|
|
200
204
|
|
|
201
205
|
def enable_object_space_in_jruby
|
|
202
|
-
if RUBY_ENGINE == 'jruby' && !JRuby.objectspace
|
|
206
|
+
if RUBY_ENGINE == 'jruby' && JRuby.respond_to?(:objectspace) && !JRuby.objectspace
|
|
203
207
|
JRuby.objectspace = true
|
|
204
208
|
NewRelic::Security::Agent.config.jruby_objectspace_enabled = true
|
|
205
209
|
end
|
|
206
210
|
end
|
|
207
211
|
|
|
208
212
|
def disable_object_space_in_jruby
|
|
209
|
-
if RUBY_ENGINE == 'jruby' && JRuby.objectspace
|
|
213
|
+
if RUBY_ENGINE == 'jruby' && JRuby.respond_to?(:objectspace) && JRuby.objectspace
|
|
210
214
|
JRuby.objectspace = false
|
|
211
215
|
NewRelic::Security::Agent.config.jruby_objectspace_enabled = false
|
|
212
216
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module NewRelic::Security
|
|
2
|
+
module Instrumentation
|
|
3
|
+
module GraphQL
|
|
4
|
+
module Query
|
|
5
|
+
module Executor
|
|
6
|
+
module Chain
|
|
7
|
+
def self.instrument!
|
|
8
|
+
::GraphQL::Query::Executor.class_eval do
|
|
9
|
+
class << self
|
|
10
|
+
include NewRelic::Security::Instrumentation::GraphQL::Query::Executor
|
|
11
|
+
|
|
12
|
+
alias_method :execute_without_security, :execute
|
|
13
|
+
|
|
14
|
+
def execute
|
|
15
|
+
execute_on_enter { return execute_without_security }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require_relative 'prepend'
|
|
2
|
+
require_relative 'chain'
|
|
3
|
+
|
|
4
|
+
module NewRelic::Security
|
|
5
|
+
module Instrumentation
|
|
6
|
+
module GraphQL::Query::Executor
|
|
7
|
+
|
|
8
|
+
GRAPHQL_QUERY = 'GRAPHQL_QUERY'.freeze
|
|
9
|
+
GRAPHQL_VARIABLE = 'GRAPHQL_VARIABLE'.freeze
|
|
10
|
+
STAR_DOT_QUERY = '*.query'.freeze
|
|
11
|
+
STAR_DOT_VARIABLES = '*.variables'.freeze
|
|
12
|
+
|
|
13
|
+
def execute_on_enter
|
|
14
|
+
NewRelic::Security::Agent.logger.debug "OnEnter : #{self.class}.#{__method__}"
|
|
15
|
+
ctxt = NewRelic::Security::Agent::Control::HTTPContext.get_context
|
|
16
|
+
ctxt.custom_data_type[STAR_DOT_QUERY] = GRAPHQL_QUERY if query.query_string
|
|
17
|
+
ctxt.custom_data_type[STAR_DOT_VARIABLES] = GRAPHQL_VARIABLE if query.instance_variable_get(:@provided_variables)
|
|
18
|
+
rescue => exception
|
|
19
|
+
NewRelic::Security::Agent.logger.error "Exception in hook in #{self.class}.#{__method__}, #{exception.inspect}, #{exception.backtrace}"
|
|
20
|
+
ensure
|
|
21
|
+
yield
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
NewRelic::Security::Instrumentation::InstrumentationLoader.install_instrumentation(:graphql, ::GraphQL::Query::Executor, ::NewRelic::Security::Instrumentation::GraphQL::Query::Executor)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module NewRelic::Security
|
|
2
|
+
module Instrumentation
|
|
3
|
+
module GraphQL
|
|
4
|
+
module Query
|
|
5
|
+
module Executor
|
|
6
|
+
module Prepend
|
|
7
|
+
include NewRelic::Security::Instrumentation::GraphQL::Query::Executor
|
|
8
|
+
|
|
9
|
+
def execute
|
|
10
|
+
execute_on_enter { return super }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -99,9 +99,9 @@ module NewRelic::Security
|
|
|
99
99
|
|
|
100
100
|
alias_method :popen_without_security, :popen
|
|
101
101
|
|
|
102
|
-
def popen(*var)
|
|
102
|
+
def popen(*var, &block)
|
|
103
103
|
retval = nil
|
|
104
|
-
event = popen_on_enter(*var) { retval = popen_without_security(*var) }
|
|
104
|
+
event = popen_on_enter(*var) { retval = popen_without_security(*var, &block) }
|
|
105
105
|
popen_on_exit(event) { return retval }
|
|
106
106
|
end
|
|
107
107
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module NewRelic::Security
|
|
2
|
+
module Instrumentation
|
|
3
|
+
module Rack
|
|
4
|
+
module Builder
|
|
5
|
+
module Chain
|
|
6
|
+
def self.instrument!
|
|
7
|
+
::Rack::Builder.class_eval do
|
|
8
|
+
include NewRelic::Security::Instrumentation::Rack::Builder
|
|
9
|
+
|
|
10
|
+
alias_method :call_without_security, :call
|
|
11
|
+
|
|
12
|
+
def call(env)
|
|
13
|
+
retval = nil
|
|
14
|
+
event = call_on_enter(env) { retval = call_without_security(env) }
|
|
15
|
+
call_on_exit(event, retval) { return retval }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require_relative 'prepend'
|
|
2
|
+
require_relative 'chain'
|
|
3
|
+
|
|
4
|
+
module NewRelic::Security
|
|
5
|
+
module Instrumentation
|
|
6
|
+
module Rack::Builder
|
|
7
|
+
|
|
8
|
+
def call_on_enter(env)
|
|
9
|
+
event = nil
|
|
10
|
+
NewRelic::Security::Agent.logger.debug "OnEnter : #{self.class}.#{__method__}"
|
|
11
|
+
return unless NewRelic::Security::Agent.config[:enabled]
|
|
12
|
+
NewRelic::Security::Agent.config.update_port = NewRelic::Security::Agent::Utils.app_port(env) unless NewRelic::Security::Agent.config[:listen_port]
|
|
13
|
+
NewRelic::Security::Agent::Utils.get_app_routes(:rack) if NewRelic::Security::Agent.agent.route_map.empty?
|
|
14
|
+
NewRelic::Security::Agent::Control::HTTPContext.set_context(env)
|
|
15
|
+
ctxt = NewRelic::Security::Agent::Control::HTTPContext.get_context
|
|
16
|
+
ctxt.route = "#{env[REQUEST_METHOD]}@#{env[PATH_INFO]}" if ctxt
|
|
17
|
+
NewRelic::Security::Agent::Utils.parse_fuzz_header(NewRelic::Security::Agent::Control::HTTPContext.get_context)
|
|
18
|
+
rescue => exception
|
|
19
|
+
NewRelic::Security::Agent.logger.error "Exception in hook in #{self.class}.#{__method__}, #{exception.inspect}, #{exception.backtrace}"
|
|
20
|
+
ensure
|
|
21
|
+
yield
|
|
22
|
+
return event
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def call_on_exit(event, retval)
|
|
26
|
+
NewRelic::Security::Agent.logger.debug "OnExit : #{self.class}.#{__method__}"
|
|
27
|
+
# NewRelic::Security::Agent.logger.debug "\n\nHTTP Context : #{::NewRelic::Agent::Tracer.current_transaction.instance_variable_get(:@security_context_data).inspect}\n\n"
|
|
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
|
+
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])
|
|
31
|
+
NewRelic::Security::Agent::Control::HTTPContext.reset_context
|
|
32
|
+
NewRelic::Security::Agent.logger.debug "Exit event : #{event}"
|
|
33
|
+
rescue => exception
|
|
34
|
+
NewRelic::Security::Agent.logger.error "Exception in hook in #{self.class}.#{__method__}, #{exception.inspect}, #{exception.backtrace}"
|
|
35
|
+
ensure
|
|
36
|
+
yield
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
NewRelic::Security::Instrumentation::InstrumentationLoader.install_instrumentation(:rack, NewRelic::Security::Agent.config[:app_class].class, ::NewRelic::Security::Instrumentation::Rack::Builder) if NewRelic::Security::Agent.config[:framework] == :rack
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module NewRelic::Security
|
|
2
|
+
module Instrumentation
|
|
3
|
+
module Rack
|
|
4
|
+
module Builder
|
|
5
|
+
module Prepend
|
|
6
|
+
include NewRelic::Security::Instrumentation::Rack::Builder
|
|
7
|
+
|
|
8
|
+
def call(env, &block)
|
|
9
|
+
retval = nil
|
|
10
|
+
event = call_on_enter(env) { retval = super }
|
|
11
|
+
call_on_exit(event, retval) { return retval }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
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.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Prateek Sen
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2025-01-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: newrelic_rpm
|
|
@@ -208,6 +208,9 @@ files:
|
|
|
208
208
|
- lib/newrelic_security/instrumentation-security/grape/chain.rb
|
|
209
209
|
- lib/newrelic_security/instrumentation-security/grape/instrumentation.rb
|
|
210
210
|
- lib/newrelic_security/instrumentation-security/grape/prepend.rb
|
|
211
|
+
- lib/newrelic_security/instrumentation-security/graphql/chain.rb
|
|
212
|
+
- lib/newrelic_security/instrumentation-security/graphql/instrumentation.rb
|
|
213
|
+
- lib/newrelic_security/instrumentation-security/graphql/prepend.rb
|
|
211
214
|
- lib/newrelic_security/instrumentation-security/grpc/client/chain.rb
|
|
212
215
|
- lib/newrelic_security/instrumentation-security/grpc/client/instrumentation.rb
|
|
213
216
|
- lib/newrelic_security/instrumentation-security/grpc/client/prepend.rb
|
|
@@ -258,6 +261,9 @@ files:
|
|
|
258
261
|
- lib/newrelic_security/instrumentation-security/pty/chain.rb
|
|
259
262
|
- lib/newrelic_security/instrumentation-security/pty/instrumentation.rb
|
|
260
263
|
- lib/newrelic_security/instrumentation-security/pty/prepend.rb
|
|
264
|
+
- lib/newrelic_security/instrumentation-security/rack/chain.rb
|
|
265
|
+
- lib/newrelic_security/instrumentation-security/rack/instrumentation.rb
|
|
266
|
+
- lib/newrelic_security/instrumentation-security/rack/prepend.rb
|
|
261
267
|
- lib/newrelic_security/instrumentation-security/rails/chain.rb
|
|
262
268
|
- lib/newrelic_security/instrumentation-security/rails/instrumentation.rb
|
|
263
269
|
- lib/newrelic_security/instrumentation-security/rails/prepend.rb
|
|
@@ -324,7 +330,7 @@ metadata:
|
|
|
324
330
|
documentation_uri: https://docs.newrelic.com/docs/iast/introduction/
|
|
325
331
|
source_code_uri: https://github.com/newrelic/csec-ruby-agent
|
|
326
332
|
homepage_uri: https://github.com/newrelic/csec-ruby-agent
|
|
327
|
-
post_install_message:
|
|
333
|
+
post_install_message:
|
|
328
334
|
rdoc_options: []
|
|
329
335
|
require_paths:
|
|
330
336
|
- lib
|
|
@@ -340,7 +346,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
340
346
|
version: 1.3.1
|
|
341
347
|
requirements: []
|
|
342
348
|
rubygems_version: 3.4.19
|
|
343
|
-
signing_key:
|
|
349
|
+
signing_key:
|
|
344
350
|
specification_version: 4
|
|
345
351
|
summary: Extension for newrelic_rpm with security feature
|
|
346
352
|
test_files: []
|