newrelic_security 0.1.0 → 0.3.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/CHANGELOG.md +82 -0
- data/THIRD_PARTY_NOTICES.md +8 -0
- data/lib/newrelic_security/agent/agent.rb +24 -4
- data/lib/newrelic_security/agent/configuration/manager.rb +51 -8
- data/lib/newrelic_security/agent/control/app_info.rb +2 -0
- data/lib/newrelic_security/agent/control/application_runtime_error.rb +95 -0
- data/lib/newrelic_security/agent/control/application_url_mappings.rb +2 -0
- data/lib/newrelic_security/agent/control/collector.rb +34 -3
- data/lib/newrelic_security/agent/control/control_command.rb +2 -3
- data/lib/newrelic_security/agent/control/critical_message.rb +2 -0
- data/lib/newrelic_security/agent/control/error_reporting.rb +74 -0
- data/lib/newrelic_security/agent/control/event.rb +26 -4
- data/lib/newrelic_security/agent/control/event_processor.rb +26 -0
- data/lib/newrelic_security/agent/control/event_subscriber.rb +2 -8
- data/lib/newrelic_security/agent/control/exit_event.rb +2 -0
- data/lib/newrelic_security/agent/control/grpc_context.rb +2 -1
- data/lib/newrelic_security/agent/control/health_check.rb +5 -0
- data/lib/newrelic_security/agent/control/http_context.rb +16 -7
- data/lib/newrelic_security/agent/control/iast_client.rb +24 -11
- data/lib/newrelic_security/agent/control/iast_data_transfer_request.rb +2 -0
- data/lib/newrelic_security/agent/control/scan_scheduler.rb +77 -0
- data/lib/newrelic_security/agent/control/websocket_client.rb +23 -0
- data/lib/newrelic_security/agent/utils/agent_utils.rb +14 -9
- data/lib/newrelic_security/constants.rb +2 -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/chain.rb +7 -2
- data/lib/newrelic_security/instrumentation-security/grape/instrumentation.rb +3 -1
- data/lib/newrelic_security/instrumentation-security/grape/prepend.rb +7 -1
- 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/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/chain.rb +17 -0
- data/lib/newrelic_security/instrumentation-security/padrino/instrumentation.rb +15 -2
- data/lib/newrelic_security/instrumentation-security/padrino/prepend.rb +12 -0
- data/lib/newrelic_security/instrumentation-security/patron/instrumentation.rb +2 -15
- data/lib/newrelic_security/instrumentation-security/rails/chain.rb +26 -2
- data/lib/newrelic_security/instrumentation-security/rails/instrumentation.rb +29 -3
- data/lib/newrelic_security/instrumentation-security/rails/prepend.rb +18 -0
- data/lib/newrelic_security/instrumentation-security/roda/chain.rb +7 -2
- data/lib/newrelic_security/instrumentation-security/roda/instrumentation.rb +3 -1
- data/lib/newrelic_security/instrumentation-security/roda/prepend.rb +7 -1
- data/lib/newrelic_security/instrumentation-security/sinatra/chain.rb +6 -0
- data/lib/newrelic_security/instrumentation-security/sinatra/instrumentation.rb +9 -0
- data/lib/newrelic_security/instrumentation-security/sinatra/prepend.rb +4 -0
- data/lib/newrelic_security/instrumentation-security/sqlite3/instrumentation.rb +4 -4
- 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/newrelic_security.gemspec +1 -1
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82cddb6a77c7bac2c46e31b5e0577dd346aa0e78b919fc8fb3c2765a7192c0a6
|
4
|
+
data.tar.gz: cb8f47120816909cb30ae0d16f9c6c4da3f684a8497747ccab3bbf12aa36e501
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 96abdbedd151b564c30d2ccafc1e98b46185130d6028634877ef0a42c1dc8ff775cf5affbcb7fdd7eee305bbb5ca33a266dfd82ebd5b7f0d2f6e396687af7897
|
7
|
+
data.tar.gz: a69f0fcaf8a427a4ec169ed7dd33952ee88e723a89281a2e9b46841029898fdea6a21748290e2058e861629e368da84b0b6d317181248b33f88a3a33bc5049e2
|
data/.github/workflows/pr_ci.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,87 @@
|
|
1
1
|
# New Relic Ruby Security Agent Release Notes
|
2
2
|
|
3
|
+
## v0.3.0
|
4
|
+
|
5
|
+
Version 0.3.0 introduces more control on IAST scanning through new configs(exclude_from_iast_scan, scan_schedule & scan_controllers) and
|
6
|
+
features like API inventory for gRPC server and IAST scan start related timestamps.
|
7
|
+
|
8
|
+
Updated json_version: **1.2.8**
|
9
|
+
|
10
|
+
- 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)
|
11
|
+
|
12
|
+
- Feature: IAST scan request rate limit to control IAST scan request firing. [PR#132](https://github.com/newrelic/csec-ruby-agent/pull/132)
|
13
|
+
|
14
|
+
- Feature: API endpoints support for gRPC server applications. [PR#143](https://github.com/newrelic/csec-ruby-agent/pull/143)
|
15
|
+
|
16
|
+
- Feature: Reporting of IAST scanning application procStartTime, trafficStartedTime & scanStartTime. [PR#136](https://github.com/newrelic/csec-ruby-agent/pull/136)
|
17
|
+
|
18
|
+
- Misc Chore: Optimised SSRF events parameters to send only URL in parameters. [PR#129](https://github.com/newrelic/csec-ruby-agent/pull/129)
|
19
|
+
|
20
|
+
##### New security configs
|
21
|
+
|
22
|
+
```yaml
|
23
|
+
security:
|
24
|
+
exclude_from_iast_scan:
|
25
|
+
api: []
|
26
|
+
http_request_parameters:
|
27
|
+
header: []
|
28
|
+
query: []
|
29
|
+
body: []
|
30
|
+
iast_detection_category:
|
31
|
+
insecure_settings: false
|
32
|
+
invalid_file_access: false
|
33
|
+
sql_injection: false
|
34
|
+
nosql_injection: false
|
35
|
+
ldap_injection: false
|
36
|
+
javascript_injection: false
|
37
|
+
command_injection: false
|
38
|
+
xpath_injection: false
|
39
|
+
ssrf: false
|
40
|
+
rxss: false
|
41
|
+
scan_schedule:
|
42
|
+
delay: 0
|
43
|
+
duration: 0
|
44
|
+
schedule: ""
|
45
|
+
always_sample_traces: false
|
46
|
+
scan_controllers:
|
47
|
+
iast_scan_request_rate_limit: 3600
|
48
|
+
```
|
49
|
+
|
50
|
+
##### Deprecated security configs (will be removed in next major release v1.0.0)
|
51
|
+
```yaml
|
52
|
+
security:
|
53
|
+
request:
|
54
|
+
body_limit: 300
|
55
|
+
detection:
|
56
|
+
rci:
|
57
|
+
enabled: true
|
58
|
+
rxss:
|
59
|
+
enabled: true
|
60
|
+
deserialization:
|
61
|
+
enabled: true
|
62
|
+
```
|
63
|
+
|
64
|
+
## v0.2.0
|
65
|
+
|
66
|
+
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**
|
67
|
+
|
68
|
+
- Feature: Unhandled and 5xx error reporting [PR#134](https://github.com/newrelic/csec-ruby-agent/pull/134)
|
69
|
+
|
70
|
+
- Bugfix: Fix for API route not present in rails7 [PR#127](https://github.com/newrelic/csec-ruby-agent/pull/127)
|
71
|
+
|
72
|
+
- Bugfix: Fix for Sqlite3 parameters sent in wrong fromat [PR#130](https://github.com/newrelic/csec-ruby-agent/pull/130)
|
73
|
+
|
74
|
+
- Bugfix: Fix for multiple events have same id [PR#135](https://github.com/newrelic/csec-ruby-agent/pull/135)
|
75
|
+
|
76
|
+
- 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)
|
77
|
+
|
78
|
+
- Bugfix: Fix for appServerInfo fields are not present in File Operation events [PR#139](https://github.com/newrelic/csec-ruby-agent/pull/139)
|
79
|
+
|
80
|
+
- Sending security agent critical errors to APM error inbox [PR#137](https://github.com/newrelic/csec-ruby-agent/pull/137)
|
81
|
+
|
82
|
+
- Added key identifiers in entityGuid and acccountId in all json reporting [PR#101](https://github.com/newrelic/csec-ruby-agent/pull/101)
|
83
|
+
|
84
|
+
|
3
85
|
## v0.1.0
|
4
86
|
|
5
87
|
Version 0.1.0 introduces `newrelic_security` agent for public preview under Newrelic pre-release software notice.
|
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)
|
@@ -17,13 +17,16 @@ 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'
|
22
|
+
require 'newrelic_security/agent/control/scan_scheduler'
|
20
23
|
require 'newrelic_security/instrumentation-security/instrumentation_loader'
|
21
24
|
|
22
25
|
module NewRelic::Security
|
23
26
|
module Agent
|
24
27
|
class Agent
|
25
28
|
|
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
|
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
|
27
30
|
|
28
31
|
def initialize
|
29
32
|
NewRelic::Security::Agent.config
|
@@ -41,6 +44,8 @@ module NewRelic::Security
|
|
41
44
|
@iast_event_stats = NewRelic::Security::Agent::Control::EventStats.new
|
42
45
|
@rasp_event_stats = NewRelic::Security::Agent::Control::EventStats.new
|
43
46
|
@exit_event_stats = NewRelic::Security::Agent::Control::EventStats.new
|
47
|
+
@error_reporting = NewRelic::Security::Agent::Control::ErrorReporting.new
|
48
|
+
@scan_scheduler = NewRelic::Security::Agent::Control::ScanScheduler.new
|
44
49
|
end
|
45
50
|
|
46
51
|
def init
|
@@ -56,11 +61,24 @@ module NewRelic::Security
|
|
56
61
|
NewRelic::Security::Agent.logger.error "Exception in security agent init: #{exception.inspect} #{exception.backtrace}\n"
|
57
62
|
end
|
58
63
|
|
64
|
+
def shutdown_security_agent
|
65
|
+
@iast_client&.fuzzQ&.clear
|
66
|
+
@iast_client&.completed_requests&.clear
|
67
|
+
@iast_client&.pending_request_ids&.clear
|
68
|
+
@iast_client&.iast_data_transfer_request_processor_thread&.kill
|
69
|
+
NewRelic::Security::Agent.config.disable_security
|
70
|
+
stop_websocket_client_if_open
|
71
|
+
end
|
72
|
+
|
59
73
|
def start_websocket_client
|
60
|
-
|
74
|
+
stop_websocket_client_if_open
|
61
75
|
@websocket_client = NewRelic::Security::Agent::Control::WebsocketClient.instance.connect
|
62
76
|
end
|
63
77
|
|
78
|
+
def stop_websocket_client_if_open
|
79
|
+
NewRelic::Security::Agent::Control::WebsocketClient.instance.close(false) if NewRelic::Security::Agent::Control::WebsocketClient.instance.is_open?
|
80
|
+
end
|
81
|
+
|
64
82
|
def start_event_processor
|
65
83
|
@event_processor&.event_dequeue_thread&.kill
|
66
84
|
@event_processor&.healthcheck_thread&.kill
|
@@ -69,9 +87,10 @@ module NewRelic::Security
|
|
69
87
|
end
|
70
88
|
|
71
89
|
def start_iast_client
|
72
|
-
@iast_client&.iast_dequeue_threads&.each { |t| t
|
90
|
+
@iast_client&.iast_dequeue_threads&.each { |t| t&.kill }
|
73
91
|
@iast_client&.iast_data_transfer_request_processor_thread&.kill
|
74
92
|
@iast_client = nil
|
93
|
+
NewRelic::Security::Agent.logger.info "Starting IAST client now at current time: #{Time.now}"
|
75
94
|
@iast_client = NewRelic::Security::Agent::Control::IASTClient.new
|
76
95
|
end
|
77
96
|
|
@@ -93,7 +112,8 @@ module NewRelic::Security
|
|
93
112
|
def find_or_create_file_path(path)
|
94
113
|
::FileUtils.mkdir_p(path) unless ::File.directory?(path)
|
95
114
|
::File.directory?(path)
|
96
|
-
rescue
|
115
|
+
rescue => e
|
116
|
+
::NewRelic::Agent.notice_error(e)
|
97
117
|
return false
|
98
118
|
end
|
99
119
|
|
@@ -27,19 +27,42 @@ 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
|
37
40
|
@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
41
|
@cache[:listen_port] = nil
|
42
|
+
@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
|
+
@cache[:traffic_start_time] = nil
|
44
|
+
@cache[:scan_start_time] = nil
|
40
45
|
@cache[:app_root] = NewRelic::Security::Agent::Utils.app_root
|
41
46
|
@cache[:jruby_objectspace_enabled] = false
|
42
|
-
@cache[:json_version] = :'1.2.
|
47
|
+
@cache[:json_version] = :'1.2.8'
|
48
|
+
@cache[:'security.exclude_from_iast_scan.api'] = convert_to_regexp_list(::NewRelic::Agent.config[:'security.exclude_from_iast_scan.api'])
|
49
|
+
@cache[:'security.exclude_from_iast_scan.http_request_parameters.header'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.http_request_parameters.header']
|
50
|
+
@cache[:'security.exclude_from_iast_scan.http_request_parameters.query'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.http_request_parameters.query']
|
51
|
+
@cache[:'security.exclude_from_iast_scan.http_request_parameters.body'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.http_request_parameters.body']
|
52
|
+
@cache[:'security.exclude_from_iast_scan.iast_detection_category.insecure_settings'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.insecure_settings']
|
53
|
+
@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']
|
54
|
+
@cache[:'security.exclude_from_iast_scan.iast_detection_category.sql_injection'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.sql_injection']
|
55
|
+
@cache[:'security.exclude_from_iast_scan.iast_detection_category.nosql_injection'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.nosql_injection']
|
56
|
+
@cache[:'security.exclude_from_iast_scan.iast_detection_category.ldap_injection'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.ldap_injection']
|
57
|
+
@cache[:'security.exclude_from_iast_scan.iast_detection_category.javascript_injection'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.javascript_injection']
|
58
|
+
@cache[:'security.exclude_from_iast_scan.iast_detection_category.command_injection'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.command_injection']
|
59
|
+
@cache[:'security.exclude_from_iast_scan.iast_detection_category.xpath_injection'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.xpath_injection']
|
60
|
+
@cache[:'security.exclude_from_iast_scan.iast_detection_category.ssrf'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.ssrf']
|
61
|
+
@cache[:'security.exclude_from_iast_scan.iast_detection_category.rxss'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.rxss']
|
62
|
+
@cache[:'security.scan_schedule.delay'] = ::NewRelic::Agent.config[:'security.scan_schedule.delay'].to_i
|
63
|
+
@cache[:'security.scan_schedule.duration'] = ::NewRelic::Agent.config[:'security.scan_schedule.duration'].to_i
|
64
|
+
@cache[:'security.scan_schedule.schedule'] = ::NewRelic::Agent.config[:'security.scan_schedule.schedule']
|
65
|
+
@cache[:'security.scan_schedule.always_sample_traces'] = ::NewRelic::Agent.config[:'security.scan_schedule.always_sample_traces']
|
43
66
|
|
44
67
|
@environment_source = NewRelic::Security::Agent::Configuration::EnvironmentSource.new
|
45
68
|
@server_source = NewRelic::Security::Agent::Configuration::ServerSource.new
|
@@ -47,8 +70,7 @@ module NewRelic::Security
|
|
47
70
|
@yaml_source = NewRelic::Security::Agent::Configuration::YamlSource.new
|
48
71
|
@default_source = NewRelic::Security::Agent::Configuration::DefaultSource.new
|
49
72
|
rescue Exception => exception
|
50
|
-
|
51
|
-
puts "Exception in Configuration::Manager.initialize : #{exception.inspect} #{exception.backtrace}"
|
73
|
+
::NewRelic::Agent.notice_error(exception)
|
52
74
|
end
|
53
75
|
|
54
76
|
def [](key)
|
@@ -94,6 +116,14 @@ module NewRelic::Security
|
|
94
116
|
@cache[:listen_port] = listen_port
|
95
117
|
end
|
96
118
|
|
119
|
+
def traffic_start_time=(traffic_start_time)
|
120
|
+
@cache[:traffic_start_time] = traffic_start_time
|
121
|
+
end
|
122
|
+
|
123
|
+
def scan_start_time=(scan_start_time)
|
124
|
+
@cache[:scan_start_time] = scan_start_time
|
125
|
+
end
|
126
|
+
|
97
127
|
def app_server=(app_server)
|
98
128
|
@cache[:app_server] = app_server
|
99
129
|
end
|
@@ -172,6 +202,19 @@ module NewRelic::Security
|
|
172
202
|
def generate_key(entity_guid)
|
173
203
|
::OpenSSL::PKCS5.pbkdf2_hmac(entity_guid, entity_guid[0..15], 1024, 32, SHA1)
|
174
204
|
end
|
205
|
+
|
206
|
+
def current_time_millis
|
207
|
+
(Time.now.to_f * 1000).to_i
|
208
|
+
end
|
209
|
+
|
210
|
+
def convert_to_regexp_list(value_list)
|
211
|
+
value_list.map do |value|
|
212
|
+
next unless value && !value.empty?
|
213
|
+
value = "^#{value}" if value[0] != '^'
|
214
|
+
value = "#{value}$" if value[-1] != '$'
|
215
|
+
/#{value}/
|
216
|
+
end
|
217
|
+
end
|
175
218
|
end
|
176
219
|
end
|
177
220
|
end
|
@@ -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
|
@@ -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)
|
@@ -44,7 +46,7 @@ module NewRelic::Security
|
|
44
46
|
# Hence, considering only frame absolute_path & lineno for apiId calculation.
|
45
47
|
user_frame_index = get_user_frame_index(stk)
|
46
48
|
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/)}
|
49
|
+
stk.delete_if { |frame| frame.path.match?(/newrelic_security/) || frame.path.match?(/new_relic/) }
|
48
50
|
user_frame_index = get_user_frame_index(stk)
|
49
51
|
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
52
|
if user_frame_index != -1
|
@@ -59,8 +61,8 @@ module NewRelic::Security
|
|
59
61
|
end
|
60
62
|
event.stacktrace = stk[0..user_frame_index].map(&:to_s)
|
61
63
|
NewRelic::Security::Agent.agent.event_processor.send_event(event)
|
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
|
+
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
|
+
NewRelic::Security::Agent.agent.iast_client.completed_requests[event.parentId] << event.id
|
64
66
|
end
|
65
67
|
event
|
66
68
|
rescue Exception => exception
|
@@ -111,6 +113,35 @@ module NewRelic::Security
|
|
111
113
|
}
|
112
114
|
end
|
113
115
|
|
116
|
+
def check_and_exclude_from_iast_scan_for_api
|
117
|
+
NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.api'].each do |api|
|
118
|
+
return true if api&.match?(NewRelic::Security::Agent::Control::HTTPContext.get_context.url)
|
119
|
+
end
|
120
|
+
return false
|
121
|
+
end
|
122
|
+
|
123
|
+
def check_and_exclude_from_iast_scan_for_detection_category(case_type)
|
124
|
+
case case_type
|
125
|
+
when FILE_OPERATION, FILE_INTEGRITY
|
126
|
+
NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.invalid_file_access']
|
127
|
+
when SQL_DB_COMMAND
|
128
|
+
NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.sql_injection']
|
129
|
+
when NOSQL_DB_COMMAND
|
130
|
+
NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.nosql_injection']
|
131
|
+
when LDAP
|
132
|
+
NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.ldap_injection']
|
133
|
+
when SYSTEM_COMMAND
|
134
|
+
NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.command_injection']
|
135
|
+
when XPATH
|
136
|
+
NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.xpath_injection']
|
137
|
+
when HTTP_REQUEST
|
138
|
+
NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.ssrf']
|
139
|
+
when REFLECTED_XSS
|
140
|
+
NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.rxss']
|
141
|
+
else
|
142
|
+
false
|
143
|
+
end
|
144
|
+
end
|
114
145
|
end
|
115
146
|
end
|
116
147
|
end
|
@@ -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 =
|
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
|
@@ -49,8 +49,6 @@ module NewRelic::Security
|
|
49
49
|
message_object[:arguments].each { |processed_id| NewRelic::Security::Agent.agent.iast_client.completed_requests.delete(processed_id) }
|
50
50
|
when 100
|
51
51
|
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
52
|
when 101
|
55
53
|
|
56
54
|
when 102
|
@@ -104,6 +102,7 @@ module NewRelic::Security
|
|
104
102
|
|
105
103
|
def prepare_fuzz_request(message_object)
|
106
104
|
message_object[:arguments][0].gsub!(NR_CSEC_VALIDATOR_HOME_TMP, NewRelic::Security::Agent.config[:fuzz_dir_path])
|
105
|
+
message_object[:arguments][0].gsub!(NR_CSEC_VALIDATOR_HOME_TMP_URL_ENCODED, NewRelic::Security::Agent.config[:fuzz_dir_path])
|
107
106
|
message_object[:arguments][0].gsub!(NR_CSEC_VALIDATOR_FILE_SEPARATOR, ::File::SEPARATOR)
|
108
107
|
prepared_fuzz_request = ::JSON.parse(message_object[:arguments][0])
|
109
108
|
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,24 @@ 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 = {
|
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
|
+
}
|
32
47
|
@linkingMetadata = add_linking_metadata
|
33
48
|
@pid = pid
|
34
49
|
@parameters = args
|
@@ -113,7 +128,7 @@ module NewRelic::Security
|
|
113
128
|
http_request[:contentType] = "TODO: "
|
114
129
|
http_request[:isGrpc] = ctxt.is_grpc
|
115
130
|
@httpRequest = http_request
|
116
|
-
@metaData
|
131
|
+
@metaData.merge!(ctxt.metadata)
|
117
132
|
end
|
118
133
|
|
119
134
|
private
|
@@ -128,13 +143,20 @@ module NewRelic::Security
|
|
128
143
|
end
|
129
144
|
|
130
145
|
def event_id
|
131
|
-
"#{Process.pid}:#{
|
146
|
+
"#{Process.pid}:#{current_transaction&.guid}:#{thread_monotonic_ctr}"
|
147
|
+
end
|
148
|
+
|
149
|
+
def current_transaction
|
150
|
+
::NewRelic::Agent::Tracer.current_transaction if defined?(::NewRelic::Agent::Tracer)
|
132
151
|
end
|
133
152
|
|
134
153
|
def thread_monotonic_ctr
|
135
154
|
ctxt = NewRelic::Security::Agent::Control::HTTPContext.get_context if NewRelic::Security::Agent::Control::HTTPContext.get_context
|
136
155
|
ctxt = NewRelic::Security::Agent::Control::GRPCContext.get_context if NewRelic::Security::Agent::Control::GRPCContext.get_context
|
137
|
-
|
156
|
+
return unless ctxt
|
157
|
+
ctxt.mutex.synchronize do
|
158
|
+
ctxt.event_counter = ctxt.event_counter + 1
|
159
|
+
end
|
138
160
|
end
|
139
161
|
|
140
162
|
def add_linking_metadata
|