newrelic_security 0.2.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 +62 -1
- data/THIRD_PARTY_NOTICES.md +8 -0
- data/lib/newrelic_security/agent/agent.rb +19 -3
- data/lib/newrelic_security/agent/configuration/manager.rb +50 -6
- data/lib/newrelic_security/agent/control/collector.rb +34 -3
- data/lib/newrelic_security/agent/control/control_command.rb +0 -2
- data/lib/newrelic_security/agent/control/event.rb +14 -1
- data/lib/newrelic_security/agent/control/event_processor.rb +5 -0
- data/lib/newrelic_security/agent/control/event_subscriber.rb +2 -8
- data/lib/newrelic_security/agent/control/health_check.rb +3 -0
- data/lib/newrelic_security/agent/control/http_context.rb +9 -6
- data/lib/newrelic_security/agent/control/iast_client.rb +24 -11
- data/lib/newrelic_security/agent/control/scan_scheduler.rb +77 -0
- data/lib/newrelic_security/agent/control/websocket_client.rb +18 -0
- data/lib/newrelic_security/agent/utils/agent_utils.rb +11 -7
- 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/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/instrumentation.rb +1 -0
- data/lib/newrelic_security/instrumentation-security/patron/instrumentation.rb +2 -15
- 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/newrelic_security.gemspec +1 -1
- metadata +6 -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,10 +1,71 @@
|
|
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
|
+
|
3
64
|
## v0.2.0
|
4
65
|
|
5
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**
|
6
67
|
|
7
|
-
- Feature: Unhandled and 5xx error
|
68
|
+
- Feature: Unhandled and 5xx error reporting [PR#134](https://github.com/newrelic/csec-ruby-agent/pull/134)
|
8
69
|
|
9
70
|
- Bugfix: Fix for API route not present in rails7 [PR#127](https://github.com/newrelic/csec-ruby-agent/pull/127)
|
10
71
|
|
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,11 +61,24 @@ 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
|
+
@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
|
+
|
62
73
|
def start_websocket_client
|
63
|
-
|
74
|
+
stop_websocket_client_if_open
|
64
75
|
@websocket_client = NewRelic::Security::Agent::Control::WebsocketClient.instance.connect
|
65
76
|
end
|
66
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
|
+
|
67
82
|
def start_event_processor
|
68
83
|
@event_processor&.event_dequeue_thread&.kill
|
69
84
|
@event_processor&.healthcheck_thread&.kill
|
@@ -72,9 +87,10 @@ module NewRelic::Security
|
|
72
87
|
end
|
73
88
|
|
74
89
|
def start_iast_client
|
75
|
-
@iast_client&.iast_dequeue_threads&.each { |t| t
|
90
|
+
@iast_client&.iast_dequeue_threads&.each { |t| t&.kill }
|
76
91
|
@iast_client&.iast_data_transfer_request_processor_thread&.kill
|
77
92
|
@iast_client = nil
|
93
|
+
NewRelic::Security::Agent.logger.info "Starting IAST client now at current time: #{Time.now}"
|
78
94
|
@iast_client = NewRelic::Security::Agent::Control::IASTClient.new
|
79
95
|
end
|
80
96
|
|
@@ -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
|
@@ -93,6 +116,14 @@ module NewRelic::Security
|
|
93
116
|
@cache[:listen_port] = listen_port
|
94
117
|
end
|
95
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
|
+
|
96
127
|
def app_server=(app_server)
|
97
128
|
@cache[:app_server] = app_server
|
98
129
|
end
|
@@ -171,6 +202,19 @@ module NewRelic::Security
|
|
171
202
|
def generate_key(entity_guid)
|
172
203
|
::OpenSSL::PKCS5.pbkdf2_hmac(entity_guid, entity_guid[0..15], 1024, 32, SHA1)
|
173
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
|
174
218
|
end
|
175
219
|
end
|
176
220
|
end
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -48,6 +48,7 @@ module NewRelic::Security
|
|
48
48
|
enqueue(event)
|
49
49
|
if @first_event
|
50
50
|
NewRelic::Security::Agent.init_logger.info "[STEP-8] => First event sent for validation. Security agent started successfully : #{event.to_json}"
|
51
|
+
NewRelic::Security::Agent.config.traffic_start_time = current_time_millis unless NewRelic::Security::Agent.config[:traffic_start_time]
|
51
52
|
@first_event = false
|
52
53
|
end
|
53
54
|
event = nil
|
@@ -130,6 +131,10 @@ module NewRelic::Security
|
|
130
131
|
NewRelic::Security::Agent.logger.error "Exception in health check thread, #{exception.inspect}"
|
131
132
|
end
|
132
133
|
|
134
|
+
def current_time_millis
|
135
|
+
(Time.now.to_f * 1000).to_i
|
136
|
+
end
|
137
|
+
|
133
138
|
def create_error_reporting_thread
|
134
139
|
@error_reporting_thread = Thread.new {
|
135
140
|
Thread.current.name = "newrelic_security_error_reporting_thread"
|
@@ -7,19 +7,13 @@ module NewRelic::Security
|
|
7
7
|
NewRelic::Security::Agent.logger.info "NewRelic server_source_configuration_added for pid : #{Process.pid}, Parent Pid : #{Process.ppid}"
|
8
8
|
NewRelic::Security::Agent.init_logger.info "NewRelic server_source_configuration_added for pid : #{Process.pid}, Parent Pid : #{Process.ppid}"
|
9
9
|
NewRelic::Security::Agent.config.update_server_config
|
10
|
-
if NewRelic::Security::Agent.config[:enabled] && !NewRelic::Security::Agent.config[:high_security]
|
11
|
-
NewRelic::Security::Agent.agent.
|
10
|
+
if NewRelic::Security::Agent.config[:'security.enabled'] && !NewRelic::Security::Agent.config[:high_security]
|
11
|
+
Thread.new { NewRelic::Security::Agent.agent.scan_scheduler.init_via_scan_scheduler }
|
12
12
|
else
|
13
13
|
NewRelic::Security::Agent.logger.info "New Relic Security is disabled by one of the user provided config `security.enabled` or `high_security`."
|
14
14
|
NewRelic::Security::Agent.init_logger.info "New Relic Security is disabled by one of the user provided config `security.enabled` or `high_security`."
|
15
15
|
end
|
16
16
|
}
|
17
|
-
::NewRelic::Agent.instance.events.subscribe(:security_policy_received) { |received_policy|
|
18
|
-
NewRelic::Security::Agent.logger.info "security_policy_received pid ::::::: #{Process.pid} #{Process.ppid}, #{received_policy}"
|
19
|
-
NewRelic::Security::Agent.config[:policy].merge!(received_policy)
|
20
|
-
NewRelic::Security::Agent.init_logger.info "[STEP-7] => Received and applied policy/configuration : #{received_policy}"
|
21
|
-
NewRelic::Security::Agent.agent.start_iast_client if NewRelic::Security::Agent::Utils.is_IAST?
|
22
|
-
}
|
23
17
|
end
|
24
18
|
end
|
25
19
|
end
|
@@ -35,6 +35,9 @@ module NewRelic::Security
|
|
35
35
|
@iastEventStats = {}
|
36
36
|
@raspEventStats = {}
|
37
37
|
@exitEventStats = {}
|
38
|
+
@procStartTime = NewRelic::Security::Agent.config[:process_start_time]
|
39
|
+
@trafficStartedTime = NewRelic::Security::Agent.config[:traffic_start_time]
|
40
|
+
@scanStartTime = NewRelic::Security::Agent.config[:scan_start_time]
|
38
41
|
end
|
39
42
|
|
40
43
|
def as_json
|
@@ -12,17 +12,20 @@ module NewRelic::Security
|
|
12
12
|
REQUEST_METHOD = 'REQUEST_METHOD'
|
13
13
|
HTTP_HOST = 'HTTP_HOST'
|
14
14
|
PATH_INFO = 'PATH_INFO'
|
15
|
+
QUERY_STRING = 'QUERY_STRING'
|
15
16
|
RACK_INPUT = 'rack.input'
|
16
17
|
CGI_VARIABLES = ::Set.new(%W[ AUTH_TYPE CONTENT_LENGTH CONTENT_TYPE GATEWAY_INTERFACE HTTPS HTTP_HOST PATH_INFO PATH_TRANSLATED REQUEST_URI QUERY_STRING REMOTE_ADDR REMOTE_HOST REMOTE_IDENT REMOTE_USER REQUEST_METHOD SCRIPT_NAME SERVER_NAME SERVER_PORT SERVER_PROTOCOL SERVER_SOFTWARE rack.url_scheme ])
|
18
|
+
REQUEST_BODY_LIMIT = 500 #KB
|
17
19
|
|
18
20
|
class HTTPContext
|
19
21
|
|
20
|
-
attr_accessor :time_stamp, :req, :method, :headers, :params, :body, :data_truncated, :route, :cache, :fuzz_files, :event_counter, :mutex
|
22
|
+
attr_accessor :time_stamp, :req, :method, :headers, :params, :body, :data_truncated, :route, :cache, :fuzz_files, :event_counter, :mutex, :url
|
21
23
|
|
22
24
|
def initialize(env)
|
23
25
|
@time_stamp = current_time_millis
|
24
26
|
@req = env.select { |key, _| CGI_VARIABLES.include? key}
|
25
27
|
@method = @req[REQUEST_METHOD]
|
28
|
+
@url = "#{@req[PATH_INFO]}?#{@req[QUERY_STRING]}"
|
26
29
|
@headers = env.select { |key, _| key.include?(HTTP_) }
|
27
30
|
@headers = @headers.transform_keys{ |key| key[5..-1].gsub(UNDERSCORE, HYPHEN).downcase }
|
28
31
|
request = Rack::Request.new(env) unless env.empty?
|
@@ -31,18 +34,18 @@ module NewRelic::Security
|
|
31
34
|
strio = env[RACK_INPUT]
|
32
35
|
if strio.instance_of?(::StringIO)
|
33
36
|
offset = strio.tell
|
34
|
-
@body = strio.read(
|
37
|
+
@body = strio.read(REQUEST_BODY_LIMIT * 1024) #after read, offset changes
|
35
38
|
strio.seek(offset)
|
36
39
|
# In case of Grape and Roda strio.read giving empty result, added below approach to handle such cases
|
37
40
|
@body = strio.string if @body.nil? && strio.size > 0
|
38
41
|
elsif defined?(::Rack) && defined?(::Rack::Lint::InputWrapper) && strio.instance_of?(::Rack::Lint::InputWrapper)
|
39
|
-
@body = strio.read(
|
42
|
+
@body = strio.read(REQUEST_BODY_LIMIT * 1024)
|
40
43
|
elsif defined?(::Protocol::Rack::Input) && defined?(::Protocol::Rack::Input) && strio.instance_of?(::Protocol::Rack::Input)
|
41
|
-
@body = strio.read(
|
44
|
+
@body = strio.read(REQUEST_BODY_LIMIT * 1024)
|
42
45
|
elsif defined?(::PhusionPassenger::Utils::TeeInput) && strio.instance_of?(::PhusionPassenger::Utils::TeeInput)
|
43
|
-
@body = strio.read(
|
46
|
+
@body = strio.read(REQUEST_BODY_LIMIT * 1024)
|
44
47
|
end
|
45
|
-
@data_truncated = @body && @body.size >=
|
48
|
+
@data_truncated = @body && @body.size >= REQUEST_BODY_LIMIT * 1024
|
46
49
|
strio&.rewind
|
47
50
|
@body = @body.force_encoding(Encoding::UTF_8) if @body.is_a?(String)
|
48
51
|
@cache = Hash.new
|
@@ -17,9 +17,8 @@ module NewRelic::Security
|
|
17
17
|
IS_GRPC = 'isGrpc'
|
18
18
|
INPUT_CLASS = 'inputClass'
|
19
19
|
SERVER_PORT_1 = 'serverPort'
|
20
|
-
PROBING = 'probing'
|
21
|
-
INTERVAL = 'interval'
|
22
20
|
IS_GRPC_CLIENT_STREAM = 'isGrpcClientStream'
|
21
|
+
PROBING_INTERVAL = 5
|
23
22
|
|
24
23
|
class IASTClient
|
25
24
|
|
@@ -54,6 +53,7 @@ module NewRelic::Security
|
|
54
53
|
Thread.current.name = "newrelic_security_iast_thread-#{t}"
|
55
54
|
loop do
|
56
55
|
fuzz_request = @fuzzQ.deq #thread blocks when the queue is empty
|
56
|
+
NewRelic::Security::Agent.config.scan_start_time = current_time_millis unless NewRelic::Security::Agent.config[:scan_start_time]
|
57
57
|
if fuzz_request.request[IS_GRPC]
|
58
58
|
fire_grpc_request(fuzz_request.id, fuzz_request.request, fuzz_request.reflected_metadata)
|
59
59
|
else
|
@@ -71,7 +71,8 @@ module NewRelic::Security
|
|
71
71
|
@iast_data_transfer_request_processor_thread = Thread.new do
|
72
72
|
Thread.current.name = "newrelic_security_iast_data_transfer_request_processor"
|
73
73
|
loop do
|
74
|
-
|
74
|
+
# TODO: Check & remove this probing interval if not required, earlier this was used from policy sent by SE.
|
75
|
+
sleep PROBING_INTERVAL
|
75
76
|
current_timestamp = current_time_millis
|
76
77
|
cooldown_sleep_time = @cooldown_till_timestamp - current_timestamp
|
77
78
|
sleep cooldown_sleep_time/1000 if cooldown_sleep_time > 0
|
@@ -84,9 +85,21 @@ module NewRelic::Security
|
|
84
85
|
if batch_size > 100 && remaining_record_capacity > batch_size
|
85
86
|
iast_data_transfer_request = NewRelic::Security::Agent::Control::IASTDataTransferRequest.new
|
86
87
|
iast_data_transfer_request.batchSize = batch_size * 2
|
88
|
+
# TODO: Below calculation of batch_size overrides above logic and can be removed once below one is stablises or rate limit feature is released.
|
89
|
+
if NewRelic::Security::Agent.config[:'security.scan_controllers.iast_scan_request_rate_limit']
|
90
|
+
batch_size =
|
91
|
+
if NewRelic::Security::Agent.config[:'security.scan_controllers.iast_scan_request_rate_limit'] < 12
|
92
|
+
1
|
93
|
+
elsif NewRelic::Security::Agent.config[:'security.scan_controllers.iast_scan_request_rate_limit'] > 3600
|
94
|
+
300
|
95
|
+
else
|
96
|
+
NewRelic::Security::Agent.config[:'security.scan_controllers.iast_scan_request_rate_limit'] / 12
|
97
|
+
end
|
98
|
+
iast_data_transfer_request.batchSize = batch_size
|
99
|
+
end
|
87
100
|
iast_data_transfer_request.pendingRequestIds = pending_request_ids.to_a
|
88
101
|
iast_data_transfer_request.completedRequests = completed_requests
|
89
|
-
NewRelic::Security::Agent.agent.event_processor
|
102
|
+
NewRelic::Security::Agent.agent.event_processor&.send_iast_data_transfer_request(iast_data_transfer_request)
|
90
103
|
end
|
91
104
|
end
|
92
105
|
end
|
@@ -122,18 +135,18 @@ module NewRelic::Security
|
|
122
135
|
def fire_grpc_request(fuzz_request_id, request, reflected_metadata)
|
123
136
|
service = Object.const_get(request[METHOD].split(SLASH)[0]).superclass
|
124
137
|
method = request[METHOD].split(SLASH)[1]
|
125
|
-
@stub
|
138
|
+
@stub ||= service.rpc_stub_class.new("localhost:#{request[SERVER_PORT_1]}", :this_channel_is_insecure)
|
126
139
|
|
127
|
-
parsed_body =
|
128
|
-
if reflected_metadata[IS_GRPC_CLIENT_STREAM]
|
129
|
-
|
140
|
+
parsed_body = request[BODY][1..-2].split(',')
|
141
|
+
chunks_enum = if reflected_metadata[IS_GRPC_CLIENT_STREAM]
|
142
|
+
Enumerator.new do |y|
|
130
143
|
parsed_body.each do |b|
|
131
144
|
y << Object.const_get(reflected_metadata[INPUT_CLASS]).decode_json(b)
|
132
145
|
end
|
133
146
|
end
|
134
|
-
|
135
|
-
|
136
|
-
|
147
|
+
else
|
148
|
+
Object.const_get(reflected_metadata[INPUT_CLASS]).decode_json(request[BODY])
|
149
|
+
end
|
137
150
|
response = @stub.public_send(method, chunks_enum, metadata: request[HEADERS])
|
138
151
|
# response = @stub.send(method, JSON.parse(request['body'], object_class: OpenStruct))
|
139
152
|
# request[HEADERS].delete(VERSION) if request[HEADERS].key?(VERSION)
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'newrelic_security/parse-cron/cron_parser'
|
2
|
+
|
3
|
+
module NewRelic::Security
|
4
|
+
module Agent
|
5
|
+
module Control
|
6
|
+
class ScanScheduler
|
7
|
+
def init_via_scan_scheduler
|
8
|
+
if NewRelic::Security::Agent.config[:'security.scan_schedule.delay'].positive?
|
9
|
+
NewRelic::Security::Agent.logger.info "IAST delay is set to: #{NewRelic::Security::Agent.config[:'security.scan_schedule.delay']}, current time: #{Time.now}"
|
10
|
+
start_agent_with_delay(NewRelic::Security::Agent.config[:'security.scan_schedule.delay']*60)
|
11
|
+
elsif !NewRelic::Security::Agent.config[:'security.scan_schedule.schedule'].to_s.empty?
|
12
|
+
cron_expression_task(NewRelic::Security::Agent.config[:'security.scan_schedule.schedule'], NewRelic::Security::Agent.config[:'security.scan_schedule.duration']*60)
|
13
|
+
else
|
14
|
+
NewRelic::Security::Agent.agent.init
|
15
|
+
NewRelic::Security::Agent.agent.start_iast_client if NewRelic::Security::Agent::Utils.is_IAST?
|
16
|
+
shutdown_at_duration_reached(NewRelic::Security::Agent.config[:'security.scan_schedule.duration']*60)
|
17
|
+
end
|
18
|
+
rescue StandardError => exception
|
19
|
+
NewRelic::Security::Agent.logger.error "Exception in IAST scan scheduler: #{exception.inspect} #{exception.backtrace}"
|
20
|
+
::NewRelic::Agent.notice_error(exception)
|
21
|
+
end
|
22
|
+
|
23
|
+
def start_agent_with_delay(delay)
|
24
|
+
NewRelic::Security::Agent.logger.info "Security Agent delay scan time is set to: #{(delay/60).ceil} minutes when always_sample_traces is #{NewRelic::Security::Agent.config[:'security.scan_schedule.always_sample_traces']}, current time: #{Time.now}"
|
25
|
+
if NewRelic::Security::Agent.config[:'security.scan_schedule.always_sample_traces']
|
26
|
+
NewRelic::Security::Agent.agent.init unless NewRelic::Security::Agent.config[:enabled]
|
27
|
+
sleep delay if NewRelic::Security::Agent.config[:'security.scan_schedule.always_sample_traces']
|
28
|
+
else
|
29
|
+
sleep delay
|
30
|
+
NewRelic::Security::Agent.agent.init
|
31
|
+
end
|
32
|
+
NewRelic::Security::Agent.agent.start_iast_client if NewRelic::Security::Agent::Utils.is_IAST?
|
33
|
+
shutdown_at_duration_reached(NewRelic::Security::Agent.config[:'security.scan_schedule.duration']*60)
|
34
|
+
end
|
35
|
+
|
36
|
+
def shutdown_at_duration_reached(duration)
|
37
|
+
shutdown_at = Time.now.to_i + duration
|
38
|
+
shut_down_time = (Time.now + duration).strftime("%a %d %b %Y %H:%M:%S")
|
39
|
+
return if duration <= 0
|
40
|
+
NewRelic::Security::Agent.logger.info "IAST Duration is set to: #{duration/60} minutes, timestamp: #{shut_down_time} time, current time: #{Time.now}"
|
41
|
+
@shutdown_monitor_thread = Thread.new do
|
42
|
+
Thread.current.name = "newrelic_security_shutdown_monitor_thread"
|
43
|
+
loop do
|
44
|
+
sleep 1
|
45
|
+
next if Time.now.to_i < shutdown_at
|
46
|
+
if NewRelic::Security::Agent.config[:'security.scan_schedule.always_sample_traces']
|
47
|
+
NewRelic::Security::Agent.logger.info "Shutdown IAST Data transfer request processor only as 'security.scan_schedule.always_sample_traces' is #{NewRelic::Security::Agent.config[:'security.scan_schedule.always_sample_traces']} now at current time: #{Time.now}"
|
48
|
+
NewRelic::Security::Agent.agent.iast_client&.iast_data_transfer_request_processor_thread&.kill
|
49
|
+
else
|
50
|
+
NewRelic::Security::Agent.logger.info "Shutdown IAST agent now at current time: #{Time.now}"
|
51
|
+
::NewRelic::Agent.notice_error(StandardError.new("WS Connection closed by local"))
|
52
|
+
NewRelic::Security::Agent.agent.shutdown_security_agent
|
53
|
+
end
|
54
|
+
break
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def cron_expression_task(schedule, duration)
|
60
|
+
@cron_parser = NewRelic::Security::ParseCron::CronParser.new(schedule)
|
61
|
+
loop do
|
62
|
+
next_run = @cron_parser.next(Time.now)
|
63
|
+
NewRelic::Security::Agent.logger.info "Next init via cron exp: #{schedule}, is scheduled at : #{next_run}"
|
64
|
+
delay = next_run - Time.now
|
65
|
+
if NewRelic::Security::Agent.agent.iast_client&.iast_data_transfer_request_processor_thread&.alive?
|
66
|
+
sleep delay > 2 ? delay - 2 : delay
|
67
|
+
else
|
68
|
+
start_agent_with_delay(delay)
|
69
|
+
end
|
70
|
+
return if duration <= 0
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|