newrelic_security 0.2.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 +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
|