newrelic_security 0.1.0 → 0.3.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 +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
|