newrelic_security 0.2.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/pr_ci.yml +4 -4
- data/.github/workflows/release.yml +1 -1
- data/.github/workflows/rubocop.yml +1 -1
- data/CHANGELOG.md +90 -1
- data/Gemfile_test +3 -0
- data/README.md +1 -0
- data/THIRD_PARTY_NOTICES.md +8 -0
- data/lib/newrelic_security/agent/agent.rb +22 -4
- data/lib/newrelic_security/agent/configuration/manager.rb +65 -7
- data/lib/newrelic_security/agent/control/application_runtime_error.rb +1 -1
- data/lib/newrelic_security/agent/control/collector.rb +41 -4
- data/lib/newrelic_security/agent/control/control_command.rb +2 -3
- data/lib/newrelic_security/agent/control/error_reporting.rb +8 -6
- data/lib/newrelic_security/agent/control/event.rb +15 -1
- data/lib/newrelic_security/agent/control/event_processor.rb +25 -14
- data/lib/newrelic_security/agent/control/event_subscriber.rb +6 -8
- data/lib/newrelic_security/agent/control/health_check.rb +4 -0
- data/lib/newrelic_security/agent/control/http_context.rb +10 -6
- data/lib/newrelic_security/agent/control/iast_client.rb +24 -11
- data/lib/newrelic_security/agent/control/reflected_xss.rb +3 -4
- data/lib/newrelic_security/agent/control/scan_scheduler.rb +77 -0
- data/lib/newrelic_security/agent/control/websocket_client.rb +71 -16
- data/lib/newrelic_security/agent/utils/agent_utils.rb +25 -17
- data/lib/newrelic_security/constants.rb +1 -2
- data/lib/newrelic_security/instrumentation-security/async-http/instrumentation.rb +2 -13
- data/lib/newrelic_security/instrumentation-security/curb/instrumentation.rb +1 -14
- data/lib/newrelic_security/instrumentation-security/ethon/chain.rb +0 -6
- data/lib/newrelic_security/instrumentation-security/ethon/instrumentation.rb +7 -42
- data/lib/newrelic_security/instrumentation-security/ethon/prepend.rb +0 -4
- data/lib/newrelic_security/instrumentation-security/excon/instrumentation.rb +3 -13
- data/lib/newrelic_security/instrumentation-security/grape/instrumentation.rb +1 -0
- 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/grpc/server/instrumentation.rb +3 -2
- data/lib/newrelic_security/instrumentation-security/httpclient/instrumentation.rb +4 -28
- data/lib/newrelic_security/instrumentation-security/httprb/instrumentation.rb +1 -12
- data/lib/newrelic_security/instrumentation-security/httpx/instrumentation.rb +1 -15
- data/lib/newrelic_security/instrumentation-security/instrumentation_utils.rb +0 -17
- 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/net_http/instrumentation.rb +6 -23
- data/lib/newrelic_security/instrumentation-security/net_ldap/instrumentation.rb +1 -1
- data/lib/newrelic_security/instrumentation-security/padrino/instrumentation.rb +1 -0
- data/lib/newrelic_security/instrumentation-security/patron/instrumentation.rb +2 -15
- 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/instrumentation-security/rails/instrumentation.rb +1 -0
- data/lib/newrelic_security/instrumentation-security/roda/instrumentation.rb +1 -0
- data/lib/newrelic_security/instrumentation-security/sinatra/instrumentation.rb +1 -0
- data/lib/newrelic_security/newrelic-security-api/api.rb +1 -1
- data/lib/newrelic_security/parse-cron/cron_parser.rb +294 -0
- data/lib/newrelic_security/version.rb +1 -1
- data/lib/newrelic_security/websocket-client-simple/client.rb +5 -1
- data/newrelic_security.gemspec +1 -1
- metadata +15 -7
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
|
@@ -72,6 +72,6 @@ jobs:
|
|
72
72
|
with:
|
73
73
|
token: ${{ secrets.GITHUB_TOKEN }}
|
74
74
|
resultPath: lib/coverage_results/.last_run.json
|
75
|
-
failedThreshold:
|
76
|
-
failedThresholdBranch:
|
75
|
+
failedThreshold: 67
|
76
|
+
failedThresholdBranch: 25
|
77
77
|
|
@@ -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,10 +1,99 @@
|
|
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
|
+
|
31
|
+
## v0.3.0
|
32
|
+
|
33
|
+
Version 0.3.0 introduces more control on IAST scanning through new configs(exclude_from_iast_scan, scan_schedule & scan_controllers) and
|
34
|
+
features like API inventory for gRPC server and IAST scan start related timestamps.
|
35
|
+
|
36
|
+
Updated json_version: **1.2.8**
|
37
|
+
|
38
|
+
- Feature: IAST scan exclusion for apis, http request parameters(header, query & body) & IAST detection categories and scan scheduling through delay, duration & cron schedule. [PR#131](https://github.com/newrelic/csec-ruby-agent/pull/131)
|
39
|
+
|
40
|
+
- Feature: IAST scan request rate limit to control IAST scan request firing. [PR#132](https://github.com/newrelic/csec-ruby-agent/pull/132)
|
41
|
+
|
42
|
+
- Feature: API endpoints support for gRPC server applications. [PR#143](https://github.com/newrelic/csec-ruby-agent/pull/143)
|
43
|
+
|
44
|
+
- Feature: Reporting of IAST scanning application procStartTime, trafficStartedTime & scanStartTime. [PR#136](https://github.com/newrelic/csec-ruby-agent/pull/136)
|
45
|
+
|
46
|
+
- Misc Chore: Optimised SSRF events parameters to send only URL in parameters. [PR#129](https://github.com/newrelic/csec-ruby-agent/pull/129)
|
47
|
+
|
48
|
+
##### New security configs
|
49
|
+
|
50
|
+
```yaml
|
51
|
+
security:
|
52
|
+
exclude_from_iast_scan:
|
53
|
+
api: []
|
54
|
+
http_request_parameters:
|
55
|
+
header: []
|
56
|
+
query: []
|
57
|
+
body: []
|
58
|
+
iast_detection_category:
|
59
|
+
insecure_settings: false
|
60
|
+
invalid_file_access: false
|
61
|
+
sql_injection: false
|
62
|
+
nosql_injection: false
|
63
|
+
ldap_injection: false
|
64
|
+
javascript_injection: false
|
65
|
+
command_injection: false
|
66
|
+
xpath_injection: false
|
67
|
+
ssrf: false
|
68
|
+
rxss: false
|
69
|
+
scan_schedule:
|
70
|
+
delay: 0
|
71
|
+
duration: 0
|
72
|
+
schedule: ""
|
73
|
+
always_sample_traces: false
|
74
|
+
scan_controllers:
|
75
|
+
iast_scan_request_rate_limit: 3600
|
76
|
+
```
|
77
|
+
|
78
|
+
##### Deprecated security configs (will be removed in next major release v1.0.0)
|
79
|
+
```yaml
|
80
|
+
security:
|
81
|
+
request:
|
82
|
+
body_limit: 300
|
83
|
+
detection:
|
84
|
+
rci:
|
85
|
+
enabled: true
|
86
|
+
rxss:
|
87
|
+
enabled: true
|
88
|
+
deserialization:
|
89
|
+
enabled: true
|
90
|
+
```
|
91
|
+
|
3
92
|
## v0.2.0
|
4
93
|
|
5
94
|
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
95
|
|
7
|
-
- Feature: Unhandled and 5xx error
|
96
|
+
- Feature: Unhandled and 5xx error reporting [PR#134](https://github.com/newrelic/csec-ruby-agent/pull/134)
|
8
97
|
|
9
98
|
- Bugfix: Fix for API route not present in rails7 [PR#127](https://github.com/newrelic/csec-ruby-agent/pull/127)
|
10
99
|
|
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
data/THIRD_PARTY_NOTICES.md
CHANGED
@@ -34,3 +34,11 @@ Distributed under the following license(s):
|
|
34
34
|
|
35
35
|
* [The MIT License](http://opensource.org/licenses/MIT)
|
36
36
|
|
37
|
+
|
38
|
+
## [parse-cron](https://github.com/siebertm/parse-cron)
|
39
|
+
|
40
|
+
Copyright (C) 2013 Michael Siebert
|
41
|
+
|
42
|
+
Distributed under the following license(s):
|
43
|
+
|
44
|
+
* [The MIT License](http://opensource.org/licenses/MIT)
|
@@ -19,13 +19,14 @@ require 'newrelic_security/agent/control/event_stats'
|
|
19
19
|
require 'newrelic_security/agent/control/exit_event'
|
20
20
|
require 'newrelic_security/agent/control/application_runtime_error'
|
21
21
|
require 'newrelic_security/agent/control/error_reporting'
|
22
|
+
require 'newrelic_security/agent/control/scan_scheduler'
|
22
23
|
require 'newrelic_security/instrumentation-security/instrumentation_loader'
|
23
24
|
|
24
25
|
module NewRelic::Security
|
25
26
|
module Agent
|
26
27
|
class Agent
|
27
28
|
|
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
|
29
|
+
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, :scan_scheduler
|
29
30
|
|
30
31
|
def initialize
|
31
32
|
NewRelic::Security::Agent.config
|
@@ -44,6 +45,7 @@ module NewRelic::Security
|
|
44
45
|
@rasp_event_stats = NewRelic::Security::Agent::Control::EventStats.new
|
45
46
|
@exit_event_stats = NewRelic::Security::Agent::Control::EventStats.new
|
46
47
|
@error_reporting = NewRelic::Security::Agent::Control::ErrorReporting.new
|
48
|
+
@scan_scheduler = NewRelic::Security::Agent::Control::ScanScheduler.new
|
47
49
|
end
|
48
50
|
|
49
51
|
def init
|
@@ -59,22 +61,38 @@ module NewRelic::Security
|
|
59
61
|
NewRelic::Security::Agent.logger.error "Exception in security agent init: #{exception.inspect} #{exception.backtrace}\n"
|
60
62
|
end
|
61
63
|
|
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
|
67
|
+
@iast_client&.fuzzQ&.clear
|
68
|
+
@iast_client&.completed_requests&.clear
|
69
|
+
@iast_client&.pending_request_ids&.clear
|
70
|
+
@iast_client&.iast_data_transfer_request_processor_thread&.kill
|
71
|
+
NewRelic::Security::Agent.config.disable_security
|
72
|
+
stop_websocket_client_if_open
|
73
|
+
end
|
74
|
+
|
62
75
|
def start_websocket_client
|
63
|
-
|
76
|
+
stop_websocket_client_if_open
|
64
77
|
@websocket_client = NewRelic::Security::Agent::Control::WebsocketClient.instance.connect
|
65
78
|
end
|
66
79
|
|
80
|
+
def stop_websocket_client_if_open
|
81
|
+
NewRelic::Security::Agent::Control::WebsocketClient.instance.close(false) if NewRelic::Security::Agent::Control::WebsocketClient.instance.is_open?
|
82
|
+
end
|
83
|
+
|
67
84
|
def start_event_processor
|
68
|
-
@event_processor&.
|
85
|
+
@event_processor&.event_dequeue_threads&.each { |t| t&.kill }
|
69
86
|
@event_processor&.healthcheck_thread&.kill
|
70
87
|
@event_processor = nil
|
71
88
|
@event_processor = NewRelic::Security::Agent::Control::EventProcessor.new
|
72
89
|
end
|
73
90
|
|
74
91
|
def start_iast_client
|
75
|
-
@iast_client&.iast_dequeue_threads&.each { |t| t
|
92
|
+
@iast_client&.iast_dequeue_threads&.each { |t| t&.kill }
|
76
93
|
@iast_client&.iast_data_transfer_request_processor_thread&.kill
|
77
94
|
@iast_client = nil
|
95
|
+
NewRelic::Security::Agent.logger.info "Starting IAST client now at current time: #{Time.now}"
|
78
96
|
@iast_client = NewRelic::Security::Agent::Control::IASTClient.new
|
79
97
|
end
|
80
98
|
|
@@ -27,19 +27,45 @@ module NewRelic::Security
|
|
27
27
|
@cache[:log_level] = ::NewRelic::Agent.config[:log_level]
|
28
28
|
@cache[:high_security] = ::NewRelic::Agent.config[:high_security]
|
29
29
|
@cache[:'agent.enabled'] = ::NewRelic::Agent.config[:'security.agent.enabled']
|
30
|
-
@cache[:enabled] = ::NewRelic::Agent.config[:'security.enabled']
|
30
|
+
@cache[:'security.enabled'] = ::NewRelic::Agent.config[:'security.enabled']
|
31
|
+
@cache[:enabled] = false
|
31
32
|
@cache[:mode] = ::NewRelic::Agent.config[:'security.mode']
|
32
33
|
@cache[:validator_service_url] = ::NewRelic::Agent.config[:'security.validator_service_url']
|
33
|
-
|
34
|
-
@cache[:'security.detection.
|
35
|
-
@cache[:'security.detection.
|
34
|
+
# TODO: Remove security.detection.* & security.request.body_limit in next major release
|
35
|
+
@cache[:'security.detection.rci.enabled'] = ::NewRelic::Agent.config[:'security.detection.rci.enabled'].nil? ? true : ::NewRelic::Agent.config[:'security.detection.rci.enabled']
|
36
|
+
@cache[:'security.detection.rxss.enabled'] = ::NewRelic::Agent.config[:'security.detection.rxss.enabled'].nil? ? true : ::NewRelic::Agent.config[:'security.detection.rxss.enabled']
|
37
|
+
@cache[:'security.detection.deserialization.enabled'] = ::NewRelic::Agent.config[:'security.detection.deserialization.enabled'].nil? ? true : ::NewRelic::Agent.config[:'security.detection.deserialization.enabled']
|
38
|
+
@cache[:'security.scan_controllers.iast_scan_request_rate_limit'] = ::NewRelic::Agent.config[:'security.scan_controllers.iast_scan_request_rate_limit'].to_i
|
36
39
|
@cache[:framework] = detect_framework
|
40
|
+
@cache[:app_class] = detect_app_class if @cache[:framework] == :rack
|
37
41
|
@cache[:'security.application_info.port'] = ::NewRelic::Agent.config[:'security.application_info.port'].to_i
|
38
|
-
@cache[:'security.request.body_limit'] = ::NewRelic::Agent.config[:'security.request.body_limit'].to_i > 0 ? ::NewRelic::Agent.config[:'security.request.body_limit'].to_i : 300
|
39
42
|
@cache[:listen_port] = nil
|
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.
|
44
|
+
@cache[:traffic_start_time] = nil
|
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']
|
40
48
|
@cache[:app_root] = NewRelic::Security::Agent::Utils.app_root
|
41
49
|
@cache[:jruby_objectspace_enabled] = false
|
42
|
-
@cache[:json_version] = :'1.2.
|
50
|
+
@cache[:json_version] = :'1.2.9'
|
51
|
+
@cache[:'security.exclude_from_iast_scan.api'] = convert_to_regexp_list(::NewRelic::Agent.config[:'security.exclude_from_iast_scan.api'])
|
52
|
+
@cache[:'security.exclude_from_iast_scan.http_request_parameters.header'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.http_request_parameters.header']
|
53
|
+
@cache[:'security.exclude_from_iast_scan.http_request_parameters.query'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.http_request_parameters.query']
|
54
|
+
@cache[:'security.exclude_from_iast_scan.http_request_parameters.body'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.http_request_parameters.body']
|
55
|
+
@cache[:'security.exclude_from_iast_scan.iast_detection_category.insecure_settings'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.insecure_settings']
|
56
|
+
@cache[:'security.exclude_from_iast_scan.iast_detection_category.invalid_file_access'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.invalid_file_access']
|
57
|
+
@cache[:'security.exclude_from_iast_scan.iast_detection_category.sql_injection'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.sql_injection']
|
58
|
+
@cache[:'security.exclude_from_iast_scan.iast_detection_category.nosql_injection'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.nosql_injection']
|
59
|
+
@cache[:'security.exclude_from_iast_scan.iast_detection_category.ldap_injection'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.ldap_injection']
|
60
|
+
@cache[:'security.exclude_from_iast_scan.iast_detection_category.javascript_injection'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.javascript_injection']
|
61
|
+
@cache[:'security.exclude_from_iast_scan.iast_detection_category.command_injection'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.command_injection']
|
62
|
+
@cache[:'security.exclude_from_iast_scan.iast_detection_category.xpath_injection'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.xpath_injection']
|
63
|
+
@cache[:'security.exclude_from_iast_scan.iast_detection_category.ssrf'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.ssrf']
|
64
|
+
@cache[:'security.exclude_from_iast_scan.iast_detection_category.rxss'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.rxss']
|
65
|
+
@cache[:'security.scan_schedule.delay'] = ::NewRelic::Agent.config[:'security.scan_schedule.delay'].to_i
|
66
|
+
@cache[:'security.scan_schedule.duration'] = ::NewRelic::Agent.config[:'security.scan_schedule.duration'].to_i
|
67
|
+
@cache[:'security.scan_schedule.schedule'] = ::NewRelic::Agent.config[:'security.scan_schedule.schedule']
|
68
|
+
@cache[:'security.scan_schedule.always_sample_traces'] = ::NewRelic::Agent.config[:'security.scan_schedule.always_sample_traces']
|
43
69
|
|
44
70
|
@environment_source = NewRelic::Security::Agent::Configuration::EnvironmentSource.new
|
45
71
|
@server_source = NewRelic::Security::Agent::Configuration::ServerSource.new
|
@@ -93,6 +119,14 @@ module NewRelic::Security
|
|
93
119
|
@cache[:listen_port] = listen_port
|
94
120
|
end
|
95
121
|
|
122
|
+
def traffic_start_time=(traffic_start_time)
|
123
|
+
@cache[:traffic_start_time] = traffic_start_time
|
124
|
+
end
|
125
|
+
|
126
|
+
def scan_start_time=(scan_start_time)
|
127
|
+
@cache[:scan_start_time] = scan_start_time
|
128
|
+
end
|
129
|
+
|
96
130
|
def app_server=(app_server)
|
97
131
|
@cache[:app_server] = app_server
|
98
132
|
end
|
@@ -122,6 +156,16 @@ module NewRelic::Security
|
|
122
156
|
return :sinatra if defined?(::Sinatra)
|
123
157
|
return :roda if defined?(::Roda)
|
124
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
|
125
169
|
end
|
126
170
|
|
127
171
|
def generate_uuid
|
@@ -136,7 +180,8 @@ module NewRelic::Security
|
|
136
180
|
end
|
137
181
|
::SecureRandom.uuid
|
138
182
|
rescue Exception => exception
|
139
|
-
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
|
140
185
|
end
|
141
186
|
|
142
187
|
def create_uuid
|
@@ -171,6 +216,19 @@ module NewRelic::Security
|
|
171
216
|
def generate_key(entity_guid)
|
172
217
|
::OpenSSL::PKCS5.pbkdf2_hmac(entity_guid, entity_guid[0..15], 1024, 32, SHA1)
|
173
218
|
end
|
219
|
+
|
220
|
+
def current_time_millis
|
221
|
+
(Time.now.to_f * 1000).to_i
|
222
|
+
end
|
223
|
+
|
224
|
+
def convert_to_regexp_list(value_list)
|
225
|
+
value_list.map do |value|
|
226
|
+
next unless value && !value.empty?
|
227
|
+
value = "^#{value}" if value[0] != '^'
|
228
|
+
value = "#{value}$" if value[-1] != '$'
|
229
|
+
/#{value}/
|
230
|
+
end
|
231
|
+
end
|
174
232
|
end
|
175
233
|
end
|
176
234
|
end
|
@@ -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
|
@@ -19,6 +19,8 @@ module NewRelic::Security
|
|
19
19
|
def collect(case_type, args, event_category = nil, **keyword_args)
|
20
20
|
return unless NewRelic::Security::Agent.config[:enabled]
|
21
21
|
return if NewRelic::Security::Agent::Control::HTTPContext.get_context.nil? && NewRelic::Security::Agent::Control::GRPCContext.get_context.nil?
|
22
|
+
return if check_and_exclude_from_iast_scan_for_api
|
23
|
+
return if check_and_exclude_from_iast_scan_for_detection_category(case_type)
|
22
24
|
args.map! { |file| Pathname.new(file).relative? ? File.join(Dir.pwd, file) : file } if [FILE_OPERATION, FILE_INTEGRITY].include?(case_type)
|
23
25
|
|
24
26
|
event = NewRelic::Security::Agent::Control::Event.new(case_type, args, event_category)
|
@@ -43,8 +45,9 @@ module NewRelic::Security
|
|
43
45
|
# In rails 5 method name keeps chaning for same api call (ex: _app_views_sqli_sqlinjectionattackcase_html_erb__1999281606898621405_2624809100).
|
44
46
|
# Hence, considering only frame absolute_path & lineno for apiId calculation.
|
45
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
|
46
49
|
event.apiId = "#{case_type}-#{calculate_api_id(stk[0..user_frame_index].map { |frame| "#{frame.absolute_path}:#{frame.lineno}" }, event.httpRequest[:method], route)}"
|
47
|
-
stk.delete_if {|frame| frame.path.match(/newrelic_security/) || frame.path.match(/new_relic/)}
|
50
|
+
stk.delete_if { |frame| frame.path.match?(/newrelic_security/) || frame.path.match?(/new_relic/) }
|
48
51
|
user_frame_index = get_user_frame_index(stk)
|
49
52
|
return if case_type != REFLECTED_XSS && user_frame_index == -1 # TODO: Add log message here: "Filtered because User Stk frame NOT FOUND \r\n"
|
50
53
|
if user_frame_index != -1
|
@@ -58,9 +61,9 @@ module NewRelic::Security
|
|
58
61
|
event.lineNumber = stk[0].lineno
|
59
62
|
end
|
60
63
|
event.stacktrace = stk[0..user_frame_index].map(&:to_s)
|
61
|
-
NewRelic::Security::Agent.agent.event_processor
|
62
|
-
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]
|
63
|
-
NewRelic::Security::Agent.agent.iast_client.completed_requests[event.parentId] << event.id
|
64
|
+
NewRelic::Security::Agent.agent.event_processor&.send_event(event)
|
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]
|
66
|
+
NewRelic::Security::Agent.agent.iast_client.completed_requests[event.parentId] << event.id
|
64
67
|
end
|
65
68
|
event
|
66
69
|
rescue Exception => exception
|
@@ -71,6 +74,10 @@ module NewRelic::Security
|
|
71
74
|
else
|
72
75
|
NewRelic::Security::Agent.agent.rasp_event_stats.error_count.increment
|
73
76
|
end
|
77
|
+
ensure
|
78
|
+
event = nil
|
79
|
+
stk = nil
|
80
|
+
route = nil
|
74
81
|
end
|
75
82
|
|
76
83
|
private
|
@@ -78,6 +85,7 @@ module NewRelic::Security
|
|
78
85
|
def get_user_frame_index(stk)
|
79
86
|
return -1 if NewRelic::Security::Agent.config[:app_root].nil?
|
80
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])
|
81
89
|
return index if val.path.start_with?(NewRelic::Security::Agent.config[:app_root])
|
82
90
|
end
|
83
91
|
return -1
|
@@ -111,6 +119,35 @@ module NewRelic::Security
|
|
111
119
|
}
|
112
120
|
end
|
113
121
|
|
122
|
+
def check_and_exclude_from_iast_scan_for_api
|
123
|
+
NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.api'].each do |api|
|
124
|
+
return true if api&.match?(NewRelic::Security::Agent::Control::HTTPContext.get_context.url)
|
125
|
+
end
|
126
|
+
return false
|
127
|
+
end
|
128
|
+
|
129
|
+
def check_and_exclude_from_iast_scan_for_detection_category(case_type)
|
130
|
+
case case_type
|
131
|
+
when FILE_OPERATION, FILE_INTEGRITY
|
132
|
+
NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.invalid_file_access']
|
133
|
+
when SQL_DB_COMMAND
|
134
|
+
NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.sql_injection']
|
135
|
+
when NOSQL_DB_COMMAND
|
136
|
+
NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.nosql_injection']
|
137
|
+
when LDAP
|
138
|
+
NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.ldap_injection']
|
139
|
+
when SYSTEM_COMMAND
|
140
|
+
NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.command_injection']
|
141
|
+
when XPATH
|
142
|
+
NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.xpath_injection']
|
143
|
+
when HTTP_REQUEST
|
144
|
+
NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.ssrf']
|
145
|
+
when REFLECTED_XSS
|
146
|
+
NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.rxss']
|
147
|
+
else
|
148
|
+
false
|
149
|
+
end
|
150
|
+
end
|
114
151
|
end
|
115
152
|
end
|
116
153
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'json'
|
2
|
+
require 'securerandom'
|
2
3
|
|
3
4
|
module NewRelic::Security
|
4
5
|
module Agent
|
@@ -49,8 +50,6 @@ module NewRelic::Security
|
|
49
50
|
message_object[:arguments].each { |processed_id| NewRelic::Security::Agent.agent.iast_client.completed_requests.delete(processed_id) }
|
50
51
|
when 100
|
51
52
|
NewRelic::Security::Agent.logger.debug "Control command : '100', #{message_object.to_json}"
|
52
|
-
::NewRelic::Agent.instance.events.notify(:security_policy_received, message_object[:data])
|
53
|
-
# TODO: Update policy from file here, if enabled.
|
54
53
|
when 101
|
55
54
|
|
56
55
|
when 102
|
@@ -95,7 +94,7 @@ module NewRelic::Security
|
|
95
94
|
NewRelic::Security::Agent.agent.iast_client.completed_requests.clear if NewRelic::Security::Agent.agent.iast_client
|
96
95
|
NewRelic::Security::Agent.agent.iast_client.pending_request_ids.clear if NewRelic::Security::Agent.agent.iast_client
|
97
96
|
NewRelic::Security::Agent.config.disable_security
|
98
|
-
Thread.new { NewRelic::Security::Agent.agent.reconnect(
|
97
|
+
Thread.new { NewRelic::Security::Agent.agent.reconnect(SecureRandom.random_number(10) + 5) }
|
99
98
|
end
|
100
99
|
|
101
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
|
|
@@ -30,7 +30,20 @@ module NewRelic::Security
|
|
30
30
|
@appEntityGuid = NewRelic::Security::Agent.config[:entity_guid]
|
31
31
|
@httpRequest = Hash.new
|
32
32
|
@httpResponse = Hash.new
|
33
|
-
@metaData = {
|
33
|
+
@metaData = {
|
34
|
+
:reflectedMetaData => {
|
35
|
+
:listen_port => NewRelic::Security::Agent.config[:listen_port].to_s
|
36
|
+
},
|
37
|
+
:appServerInfo => {
|
38
|
+
:applicationDirectory => NewRelic::Security::Agent.config[:app_root],
|
39
|
+
:serverBaseDirectory => NewRelic::Security::Agent.config[:app_root]
|
40
|
+
},
|
41
|
+
:skipScanParameters => {
|
42
|
+
:header => NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.http_request_parameters.header'],
|
43
|
+
:query => NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.http_request_parameters.query'],
|
44
|
+
:body => NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.http_request_parameters.body']
|
45
|
+
}
|
46
|
+
}
|
34
47
|
@linkingMetadata = add_linking_metadata
|
35
48
|
@pid = pid
|
36
49
|
@parameters = args
|
@@ -91,6 +104,7 @@ module NewRelic::Security
|
|
91
104
|
http_request[:headers] = ctxt.headers
|
92
105
|
http_request[:contentType] = ctxt.req[CONTENT_TYPE] if ctxt.req.has_key?(CONTENT_TYPE)
|
93
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
|
94
108
|
http_request[:dataTruncated] = ctxt.data_truncated
|
95
109
|
@httpRequest = http_request
|
96
110
|
@metaData[:isClientDetectedFromXFF] = ctxt.headers.has_key?(X_FORWARDED_FOR) ? true : false
|