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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/pr_ci.yml +2 -2
  3. data/CHANGELOG.md +62 -1
  4. data/THIRD_PARTY_NOTICES.md +8 -0
  5. data/lib/newrelic_security/agent/agent.rb +19 -3
  6. data/lib/newrelic_security/agent/configuration/manager.rb +50 -6
  7. data/lib/newrelic_security/agent/control/collector.rb +34 -3
  8. data/lib/newrelic_security/agent/control/control_command.rb +0 -2
  9. data/lib/newrelic_security/agent/control/event.rb +14 -1
  10. data/lib/newrelic_security/agent/control/event_processor.rb +5 -0
  11. data/lib/newrelic_security/agent/control/event_subscriber.rb +2 -8
  12. data/lib/newrelic_security/agent/control/health_check.rb +3 -0
  13. data/lib/newrelic_security/agent/control/http_context.rb +9 -6
  14. data/lib/newrelic_security/agent/control/iast_client.rb +24 -11
  15. data/lib/newrelic_security/agent/control/scan_scheduler.rb +77 -0
  16. data/lib/newrelic_security/agent/control/websocket_client.rb +18 -0
  17. data/lib/newrelic_security/agent/utils/agent_utils.rb +11 -7
  18. data/lib/newrelic_security/constants.rb +1 -2
  19. data/lib/newrelic_security/instrumentation-security/async-http/instrumentation.rb +2 -13
  20. data/lib/newrelic_security/instrumentation-security/curb/instrumentation.rb +1 -14
  21. data/lib/newrelic_security/instrumentation-security/ethon/chain.rb +0 -6
  22. data/lib/newrelic_security/instrumentation-security/ethon/instrumentation.rb +7 -42
  23. data/lib/newrelic_security/instrumentation-security/ethon/prepend.rb +0 -4
  24. data/lib/newrelic_security/instrumentation-security/excon/instrumentation.rb +3 -13
  25. data/lib/newrelic_security/instrumentation-security/grape/instrumentation.rb +1 -0
  26. data/lib/newrelic_security/instrumentation-security/grpc/server/instrumentation.rb +3 -2
  27. data/lib/newrelic_security/instrumentation-security/httpclient/instrumentation.rb +4 -28
  28. data/lib/newrelic_security/instrumentation-security/httprb/instrumentation.rb +1 -12
  29. data/lib/newrelic_security/instrumentation-security/httpx/instrumentation.rb +1 -15
  30. data/lib/newrelic_security/instrumentation-security/instrumentation_utils.rb +0 -17
  31. data/lib/newrelic_security/instrumentation-security/net_http/instrumentation.rb +6 -23
  32. data/lib/newrelic_security/instrumentation-security/net_ldap/instrumentation.rb +1 -1
  33. data/lib/newrelic_security/instrumentation-security/padrino/instrumentation.rb +1 -0
  34. data/lib/newrelic_security/instrumentation-security/patron/instrumentation.rb +2 -15
  35. data/lib/newrelic_security/instrumentation-security/rails/instrumentation.rb +1 -0
  36. data/lib/newrelic_security/instrumentation-security/roda/instrumentation.rb +1 -0
  37. data/lib/newrelic_security/instrumentation-security/sinatra/instrumentation.rb +1 -0
  38. data/lib/newrelic_security/newrelic-security-api/api.rb +1 -1
  39. data/lib/newrelic_security/parse-cron/cron_parser.rb +294 -0
  40. data/lib/newrelic_security/version.rb +1 -1
  41. data/newrelic_security.gemspec +1 -1
  42. metadata +6 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5b1160c4b821177b6ce0400848a72bf09899780ff83abadb60b3e60f35b8338b
4
- data.tar.gz: 98cfb5a7d513e842690dce03aa515a7e148a252a5f0666f16e40899e2ad511d0
3
+ metadata.gz: 82cddb6a77c7bac2c46e31b5e0577dd346aa0e78b919fc8fb3c2765a7192c0a6
4
+ data.tar.gz: cb8f47120816909cb30ae0d16f9c6c4da3f684a8497747ccab3bbf12aa36e501
5
5
  SHA512:
6
- metadata.gz: 56dea910383ac11f0b87a29f304e0f5042336c142c1d8ca60a2cdaf15bf172102000e28e8f382fd99781afebd061a0693e3f5ff8ea9fba981cdc4de128abdc20
7
- data.tar.gz: 04bb28178707141c66ac94158fdb3cd632ccb22a1a9c4b4f5f7d9438989ee3c6fba2124abc89ffbf135d34e249b5921cd5d4b924e50768f07363b219567867bb
6
+ metadata.gz: 96abdbedd151b564c30d2ccafc1e98b46185130d6028634877ef0a42c1dc8ff775cf5affbcb7fdd7eee305bbb5ca33a266dfd82ebd5b7f0d2f6e396687af7897
7
+ data.tar.gz: a69f0fcaf8a427a4ec169ed7dd33952ee88e723a89281a2e9b46841029898fdea6a21748290e2058e861629e368da84b0b6d317181248b33f88a3a33bc5049e2
@@ -72,6 +72,6 @@ jobs:
72
72
  with:
73
73
  token: ${{ secrets.GITHUB_TOKEN }}
74
74
  resultPath: lib/coverage_results/.last_run.json
75
- failedThreshold: 70
76
- failedThresholdBranch: 33
75
+ failedThreshold: 67
76
+ failedThresholdBranch: 25
77
77
 
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 reproting [PR#134](https://github.com/newrelic/csec-ruby-agent/pull/134)
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
 
@@ -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
- NewRelic::Security::Agent::Control::WebsocketClient.instance.close(false) if NewRelic::Security::Agent::Control::WebsocketClient.instance.is_open?
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.kill if 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
- @cache[:'security.detection.rci.enabled'] = ::NewRelic::Agent.config[:'security.detection.rci.enabled']
34
- @cache[:'security.detection.rxss.enabled'] = ::NewRelic::Agent.config[:'security.detection.rxss.enabled']
35
- @cache[:'security.detection.deserialization.enabled'] = ::NewRelic::Agent.config[:'security.detection.deserialization.enabled']
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.4'
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 if NewRelic::Security::Agent.agent.iast_client.completed_requests[event.parentId]
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 = { :reflectedMetaData => { :listen_port => NewRelic::Security::Agent.config[:listen_port].to_s }, :appServerInfo => { :applicationDirectory => NewRelic::Security::Agent.config[:app_root], :serverBaseDirectory => NewRelic::Security::Agent.config[:app_root] } }
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.init
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(NewRelic::Security::Agent.config[:'security.request.body_limit'] * 1024) #after read, offset changes
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(NewRelic::Security::Agent.config[:'security.request.body_limit'] * 1024)
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(NewRelic::Security::Agent.config[:'security.request.body_limit'] * 1024)
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(NewRelic::Security::Agent.config[:'security.request.body_limit'] * 1024)
46
+ @body = strio.read(REQUEST_BODY_LIMIT * 1024)
44
47
  end
45
- @data_truncated = @body && @body.size >= NewRelic::Security::Agent.config[:'security.request.body_limit'] * 1024
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
- sleep NewRelic::Security::Agent.config[:policy][VULNERABILITY_SCAN][IAST_SCAN][PROBING][INTERVAL]
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.send_iast_data_transfer_request(iast_data_transfer_request)
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 = service.rpc_stub_class.new("localhost:#{request[SERVER_PORT_1]}", :this_channel_is_insecure) unless @stub
138
+ @stub ||= service.rpc_stub_class.new("localhost:#{request[SERVER_PORT_1]}", :this_channel_is_insecure)
126
139
 
127
- parsed_body = request[BODY][1..-2].split(',')
128
- if reflected_metadata[IS_GRPC_CLIENT_STREAM]
129
- chunks_enum = Enumerator.new do |y|
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
- else
135
- chunks_enum = Object.const_get(reflected_metadata[INPUT_CLASS]).decode_json(request[BODY])
136
- end
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