newrelic_security 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/pr_ci.yml +2 -2
  3. data/CHANGELOG.md +82 -0
  4. data/THIRD_PARTY_NOTICES.md +8 -0
  5. data/lib/newrelic_security/agent/agent.rb +24 -4
  6. data/lib/newrelic_security/agent/configuration/manager.rb +51 -8
  7. data/lib/newrelic_security/agent/control/app_info.rb +2 -0
  8. data/lib/newrelic_security/agent/control/application_runtime_error.rb +95 -0
  9. data/lib/newrelic_security/agent/control/application_url_mappings.rb +2 -0
  10. data/lib/newrelic_security/agent/control/collector.rb +34 -3
  11. data/lib/newrelic_security/agent/control/control_command.rb +2 -3
  12. data/lib/newrelic_security/agent/control/critical_message.rb +2 -0
  13. data/lib/newrelic_security/agent/control/error_reporting.rb +74 -0
  14. data/lib/newrelic_security/agent/control/event.rb +26 -4
  15. data/lib/newrelic_security/agent/control/event_processor.rb +26 -0
  16. data/lib/newrelic_security/agent/control/event_subscriber.rb +2 -8
  17. data/lib/newrelic_security/agent/control/exit_event.rb +2 -0
  18. data/lib/newrelic_security/agent/control/grpc_context.rb +2 -1
  19. data/lib/newrelic_security/agent/control/health_check.rb +5 -0
  20. data/lib/newrelic_security/agent/control/http_context.rb +16 -7
  21. data/lib/newrelic_security/agent/control/iast_client.rb +24 -11
  22. data/lib/newrelic_security/agent/control/iast_data_transfer_request.rb +2 -0
  23. data/lib/newrelic_security/agent/control/scan_scheduler.rb +77 -0
  24. data/lib/newrelic_security/agent/control/websocket_client.rb +23 -0
  25. data/lib/newrelic_security/agent/utils/agent_utils.rb +14 -9
  26. data/lib/newrelic_security/constants.rb +2 -2
  27. data/lib/newrelic_security/instrumentation-security/async-http/instrumentation.rb +2 -13
  28. data/lib/newrelic_security/instrumentation-security/curb/instrumentation.rb +1 -14
  29. data/lib/newrelic_security/instrumentation-security/ethon/chain.rb +0 -6
  30. data/lib/newrelic_security/instrumentation-security/ethon/instrumentation.rb +7 -42
  31. data/lib/newrelic_security/instrumentation-security/ethon/prepend.rb +0 -4
  32. data/lib/newrelic_security/instrumentation-security/excon/instrumentation.rb +3 -13
  33. data/lib/newrelic_security/instrumentation-security/grape/chain.rb +7 -2
  34. data/lib/newrelic_security/instrumentation-security/grape/instrumentation.rb +3 -1
  35. data/lib/newrelic_security/instrumentation-security/grape/prepend.rb +7 -1
  36. data/lib/newrelic_security/instrumentation-security/grpc/server/instrumentation.rb +3 -2
  37. data/lib/newrelic_security/instrumentation-security/httpclient/instrumentation.rb +4 -28
  38. data/lib/newrelic_security/instrumentation-security/httprb/instrumentation.rb +1 -12
  39. data/lib/newrelic_security/instrumentation-security/httpx/instrumentation.rb +1 -15
  40. data/lib/newrelic_security/instrumentation-security/instrumentation_utils.rb +0 -17
  41. data/lib/newrelic_security/instrumentation-security/net_http/instrumentation.rb +6 -23
  42. data/lib/newrelic_security/instrumentation-security/net_ldap/instrumentation.rb +1 -1
  43. data/lib/newrelic_security/instrumentation-security/padrino/chain.rb +17 -0
  44. data/lib/newrelic_security/instrumentation-security/padrino/instrumentation.rb +15 -2
  45. data/lib/newrelic_security/instrumentation-security/padrino/prepend.rb +12 -0
  46. data/lib/newrelic_security/instrumentation-security/patron/instrumentation.rb +2 -15
  47. data/lib/newrelic_security/instrumentation-security/rails/chain.rb +26 -2
  48. data/lib/newrelic_security/instrumentation-security/rails/instrumentation.rb +29 -3
  49. data/lib/newrelic_security/instrumentation-security/rails/prepend.rb +18 -0
  50. data/lib/newrelic_security/instrumentation-security/roda/chain.rb +7 -2
  51. data/lib/newrelic_security/instrumentation-security/roda/instrumentation.rb +3 -1
  52. data/lib/newrelic_security/instrumentation-security/roda/prepend.rb +7 -1
  53. data/lib/newrelic_security/instrumentation-security/sinatra/chain.rb +6 -0
  54. data/lib/newrelic_security/instrumentation-security/sinatra/instrumentation.rb +9 -0
  55. data/lib/newrelic_security/instrumentation-security/sinatra/prepend.rb +4 -0
  56. data/lib/newrelic_security/instrumentation-security/sqlite3/instrumentation.rb +4 -4
  57. data/lib/newrelic_security/newrelic-security-api/api.rb +1 -1
  58. data/lib/newrelic_security/parse-cron/cron_parser.rb +294 -0
  59. data/lib/newrelic_security/version.rb +1 -1
  60. data/newrelic_security.gemspec +1 -1
  61. metadata +8 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c07c9ef96a8884449bdd0d5be44eccf3fee5ae6dde565863fe1eb24449a902f6
4
- data.tar.gz: 56f813a8034eed9fd717a0c549275cd1c1e1386c50acdb2142379fbcc980838d
3
+ metadata.gz: 82cddb6a77c7bac2c46e31b5e0577dd346aa0e78b919fc8fb3c2765a7192c0a6
4
+ data.tar.gz: cb8f47120816909cb30ae0d16f9c6c4da3f684a8497747ccab3bbf12aa36e501
5
5
  SHA512:
6
- metadata.gz: 0a3b7c2e86f9ea2c3a6c4c28da88676f904e78c5cce82fb152897aade9acda32ca9ea485d26dfa7ebb6ef1356c14df9d85407971d05a0a0f69e1ec43824227a1
7
- data.tar.gz: d3c6dcd24fe5ed0b54537b503e6994e6ed6701d5bab53f90220cce4b300e14a619db19cab4befec08560b72c941898a082a1b3c9738ed29e3fb8feae714edeb2
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,5 +1,87 @@
1
1
  # New Relic Ruby Security Agent Release Notes
2
2
 
3
+ ## v0.3.0
4
+
5
+ Version 0.3.0 introduces more control on IAST scanning through new configs(exclude_from_iast_scan, scan_schedule & scan_controllers) and
6
+ features like API inventory for gRPC server and IAST scan start related timestamps.
7
+
8
+ Updated json_version: **1.2.8**
9
+
10
+ - Feature: IAST scan exclusion for apis, http request parameters(header, query & body) & IAST detection categories and scan scheduling through delay, duration & cron schedule. [PR#131](https://github.com/newrelic/csec-ruby-agent/pull/131)
11
+
12
+ - Feature: IAST scan request rate limit to control IAST scan request firing. [PR#132](https://github.com/newrelic/csec-ruby-agent/pull/132)
13
+
14
+ - Feature: API endpoints support for gRPC server applications. [PR#143](https://github.com/newrelic/csec-ruby-agent/pull/143)
15
+
16
+ - Feature: Reporting of IAST scanning application procStartTime, trafficStartedTime & scanStartTime. [PR#136](https://github.com/newrelic/csec-ruby-agent/pull/136)
17
+
18
+ - Misc Chore: Optimised SSRF events parameters to send only URL in parameters. [PR#129](https://github.com/newrelic/csec-ruby-agent/pull/129)
19
+
20
+ ##### New security configs
21
+
22
+ ```yaml
23
+ security:
24
+ exclude_from_iast_scan:
25
+ api: []
26
+ http_request_parameters:
27
+ header: []
28
+ query: []
29
+ body: []
30
+ iast_detection_category:
31
+ insecure_settings: false
32
+ invalid_file_access: false
33
+ sql_injection: false
34
+ nosql_injection: false
35
+ ldap_injection: false
36
+ javascript_injection: false
37
+ command_injection: false
38
+ xpath_injection: false
39
+ ssrf: false
40
+ rxss: false
41
+ scan_schedule:
42
+ delay: 0
43
+ duration: 0
44
+ schedule: ""
45
+ always_sample_traces: false
46
+ scan_controllers:
47
+ iast_scan_request_rate_limit: 3600
48
+ ```
49
+
50
+ ##### Deprecated security configs (will be removed in next major release v1.0.0)
51
+ ```yaml
52
+ security:
53
+ request:
54
+ body_limit: 300
55
+ detection:
56
+ rci:
57
+ enabled: true
58
+ rxss:
59
+ enabled: true
60
+ deserialization:
61
+ enabled: true
62
+ ```
63
+
64
+ ## v0.2.0
65
+
66
+ Version 0.2.0 introuduces Error reporting as part of security. Any unhandled or 5xx errors in application runtime will now be visible in IAST capability UI. Updated json_version: **1.2.4**
67
+
68
+ - Feature: Unhandled and 5xx error reporting [PR#134](https://github.com/newrelic/csec-ruby-agent/pull/134)
69
+
70
+ - Bugfix: Fix for API route not present in rails7 [PR#127](https://github.com/newrelic/csec-ruby-agent/pull/127)
71
+
72
+ - Bugfix: Fix for Sqlite3 parameters sent in wrong fromat [PR#130](https://github.com/newrelic/csec-ruby-agent/pull/130)
73
+
74
+ - Bugfix: Fix for multiple events have same id [PR#135](https://github.com/newrelic/csec-ruby-agent/pull/135)
75
+
76
+ - Bugfix: Fix for NR_CSEC_VALIDATOR_HOME_TMP placeholder value not replaced during File Access fuzzing [PR#138](https://github.com/newrelic/csec-ruby-agent/pull/138)
77
+
78
+ - Bugfix: Fix for appServerInfo fields are not present in File Operation events [PR#139](https://github.com/newrelic/csec-ruby-agent/pull/139)
79
+
80
+ - Sending security agent critical errors to APM error inbox [PR#137](https://github.com/newrelic/csec-ruby-agent/pull/137)
81
+
82
+ - Added key identifiers in entityGuid and acccountId in all json reporting [PR#101](https://github.com/newrelic/csec-ruby-agent/pull/101)
83
+
84
+
3
85
  ## v0.1.0
4
86
 
5
87
  Version 0.1.0 introduces `newrelic_security` agent for public preview under Newrelic pre-release software notice.
@@ -34,3 +34,11 @@ Distributed under the following license(s):
34
34
 
35
35
  * [The MIT License](http://opensource.org/licenses/MIT)
36
36
 
37
+
38
+ ## [parse-cron](https://github.com/siebertm/parse-cron)
39
+
40
+ Copyright (C) 2013 Michael Siebert
41
+
42
+ Distributed under the following license(s):
43
+
44
+ * [The MIT License](http://opensource.org/licenses/MIT)
@@ -17,13 +17,16 @@ require 'newrelic_security/agent/control/critical_message'
17
17
  require 'newrelic_security/agent/control/event_counter'
18
18
  require 'newrelic_security/agent/control/event_stats'
19
19
  require 'newrelic_security/agent/control/exit_event'
20
+ require 'newrelic_security/agent/control/application_runtime_error'
21
+ require 'newrelic_security/agent/control/error_reporting'
22
+ require 'newrelic_security/agent/control/scan_scheduler'
20
23
  require 'newrelic_security/instrumentation-security/instrumentation_loader'
21
24
 
22
25
  module NewRelic::Security
23
26
  module Agent
24
27
  class Agent
25
28
 
26
- attr_accessor :websocket_client, :event_processor, :iast_client, :http_request_count, :event_processed_count, :event_sent_count, :event_drop_count, :route_map, :iast_event_stats, :rasp_event_stats, :exit_event_stats
29
+ attr_accessor :websocket_client, :event_processor, :iast_client, :http_request_count, :event_processed_count, :event_sent_count, :event_drop_count, :route_map, :iast_event_stats, :rasp_event_stats, :exit_event_stats, :error_reporting, :scan_scheduler
27
30
 
28
31
  def initialize
29
32
  NewRelic::Security::Agent.config
@@ -41,6 +44,8 @@ module NewRelic::Security
41
44
  @iast_event_stats = NewRelic::Security::Agent::Control::EventStats.new
42
45
  @rasp_event_stats = NewRelic::Security::Agent::Control::EventStats.new
43
46
  @exit_event_stats = NewRelic::Security::Agent::Control::EventStats.new
47
+ @error_reporting = NewRelic::Security::Agent::Control::ErrorReporting.new
48
+ @scan_scheduler = NewRelic::Security::Agent::Control::ScanScheduler.new
44
49
  end
45
50
 
46
51
  def init
@@ -56,11 +61,24 @@ module NewRelic::Security
56
61
  NewRelic::Security::Agent.logger.error "Exception in security agent init: #{exception.inspect} #{exception.backtrace}\n"
57
62
  end
58
63
 
64
+ def shutdown_security_agent
65
+ @iast_client&.fuzzQ&.clear
66
+ @iast_client&.completed_requests&.clear
67
+ @iast_client&.pending_request_ids&.clear
68
+ @iast_client&.iast_data_transfer_request_processor_thread&.kill
69
+ NewRelic::Security::Agent.config.disable_security
70
+ stop_websocket_client_if_open
71
+ end
72
+
59
73
  def start_websocket_client
60
- NewRelic::Security::Agent::Control::WebsocketClient.instance.close(false) if NewRelic::Security::Agent::Control::WebsocketClient.instance.is_open?
74
+ stop_websocket_client_if_open
61
75
  @websocket_client = NewRelic::Security::Agent::Control::WebsocketClient.instance.connect
62
76
  end
63
77
 
78
+ def stop_websocket_client_if_open
79
+ NewRelic::Security::Agent::Control::WebsocketClient.instance.close(false) if NewRelic::Security::Agent::Control::WebsocketClient.instance.is_open?
80
+ end
81
+
64
82
  def start_event_processor
65
83
  @event_processor&.event_dequeue_thread&.kill
66
84
  @event_processor&.healthcheck_thread&.kill
@@ -69,9 +87,10 @@ module NewRelic::Security
69
87
  end
70
88
 
71
89
  def start_iast_client
72
- @iast_client&.iast_dequeue_threads&.each { |t| t.kill if t }
90
+ @iast_client&.iast_dequeue_threads&.each { |t| t&.kill }
73
91
  @iast_client&.iast_data_transfer_request_processor_thread&.kill
74
92
  @iast_client = nil
93
+ NewRelic::Security::Agent.logger.info "Starting IAST client now at current time: #{Time.now}"
75
94
  @iast_client = NewRelic::Security::Agent::Control::IASTClient.new
76
95
  end
77
96
 
@@ -93,7 +112,8 @@ module NewRelic::Security
93
112
  def find_or_create_file_path(path)
94
113
  ::FileUtils.mkdir_p(path) unless ::File.directory?(path)
95
114
  ::File.directory?(path)
96
- rescue
115
+ rescue => e
116
+ ::NewRelic::Agent.notice_error(e)
97
117
  return false
98
118
  end
99
119
 
@@ -27,19 +27,42 @@ module NewRelic::Security
27
27
  @cache[:log_level] = ::NewRelic::Agent.config[:log_level]
28
28
  @cache[:high_security] = ::NewRelic::Agent.config[:high_security]
29
29
  @cache[:'agent.enabled'] = ::NewRelic::Agent.config[:'security.agent.enabled']
30
- @cache[:enabled] = ::NewRelic::Agent.config[:'security.enabled']
30
+ @cache[:'security.enabled'] = ::NewRelic::Agent.config[:'security.enabled']
31
+ @cache[:enabled] = false
31
32
  @cache[:mode] = ::NewRelic::Agent.config[:'security.mode']
32
33
  @cache[:validator_service_url] = ::NewRelic::Agent.config[:'security.validator_service_url']
33
- @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.0'
47
+ @cache[:json_version] = :'1.2.8'
48
+ @cache[:'security.exclude_from_iast_scan.api'] = convert_to_regexp_list(::NewRelic::Agent.config[:'security.exclude_from_iast_scan.api'])
49
+ @cache[:'security.exclude_from_iast_scan.http_request_parameters.header'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.http_request_parameters.header']
50
+ @cache[:'security.exclude_from_iast_scan.http_request_parameters.query'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.http_request_parameters.query']
51
+ @cache[:'security.exclude_from_iast_scan.http_request_parameters.body'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.http_request_parameters.body']
52
+ @cache[:'security.exclude_from_iast_scan.iast_detection_category.insecure_settings'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.insecure_settings']
53
+ @cache[:'security.exclude_from_iast_scan.iast_detection_category.invalid_file_access'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.invalid_file_access']
54
+ @cache[:'security.exclude_from_iast_scan.iast_detection_category.sql_injection'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.sql_injection']
55
+ @cache[:'security.exclude_from_iast_scan.iast_detection_category.nosql_injection'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.nosql_injection']
56
+ @cache[:'security.exclude_from_iast_scan.iast_detection_category.ldap_injection'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.ldap_injection']
57
+ @cache[:'security.exclude_from_iast_scan.iast_detection_category.javascript_injection'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.javascript_injection']
58
+ @cache[:'security.exclude_from_iast_scan.iast_detection_category.command_injection'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.command_injection']
59
+ @cache[:'security.exclude_from_iast_scan.iast_detection_category.xpath_injection'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.xpath_injection']
60
+ @cache[:'security.exclude_from_iast_scan.iast_detection_category.ssrf'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.ssrf']
61
+ @cache[:'security.exclude_from_iast_scan.iast_detection_category.rxss'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.rxss']
62
+ @cache[:'security.scan_schedule.delay'] = ::NewRelic::Agent.config[:'security.scan_schedule.delay'].to_i
63
+ @cache[:'security.scan_schedule.duration'] = ::NewRelic::Agent.config[:'security.scan_schedule.duration'].to_i
64
+ @cache[:'security.scan_schedule.schedule'] = ::NewRelic::Agent.config[:'security.scan_schedule.schedule']
65
+ @cache[:'security.scan_schedule.always_sample_traces'] = ::NewRelic::Agent.config[:'security.scan_schedule.always_sample_traces']
43
66
 
44
67
  @environment_source = NewRelic::Security::Agent::Configuration::EnvironmentSource.new
45
68
  @server_source = NewRelic::Security::Agent::Configuration::ServerSource.new
@@ -47,8 +70,7 @@ module NewRelic::Security
47
70
  @yaml_source = NewRelic::Security::Agent::Configuration::YamlSource.new
48
71
  @default_source = NewRelic::Security::Agent::Configuration::DefaultSource.new
49
72
  rescue Exception => exception
50
- # TODO: remove this puts once agent stablizes
51
- puts "Exception in Configuration::Manager.initialize : #{exception.inspect} #{exception.backtrace}"
73
+ ::NewRelic::Agent.notice_error(exception)
52
74
  end
53
75
 
54
76
  def [](key)
@@ -94,6 +116,14 @@ module NewRelic::Security
94
116
  @cache[:listen_port] = listen_port
95
117
  end
96
118
 
119
+ def traffic_start_time=(traffic_start_time)
120
+ @cache[:traffic_start_time] = traffic_start_time
121
+ end
122
+
123
+ def scan_start_time=(scan_start_time)
124
+ @cache[:scan_start_time] = scan_start_time
125
+ end
126
+
97
127
  def app_server=(app_server)
98
128
  @cache[:app_server] = app_server
99
129
  end
@@ -172,6 +202,19 @@ module NewRelic::Security
172
202
  def generate_key(entity_guid)
173
203
  ::OpenSSL::PKCS5.pbkdf2_hmac(entity_guid, entity_guid[0..15], 1024, 32, SHA1)
174
204
  end
205
+
206
+ def current_time_millis
207
+ (Time.now.to_f * 1000).to_i
208
+ end
209
+
210
+ def convert_to_regexp_list(value_list)
211
+ value_list.map do |value|
212
+ next unless value && !value.empty?
213
+ value = "^#{value}" if value[0] != '^'
214
+ value = "#{value}$" if value[-1] != '$'
215
+ /#{value}/
216
+ end
217
+ end
175
218
  end
176
219
  end
177
220
  end
@@ -26,6 +26,8 @@ module NewRelic::Security
26
26
  @jsonVersion = NewRelic::Security::Agent.config[:json_version]
27
27
  @startTime = current_time_millis
28
28
  @applicationUUID = NewRelic::Security::Agent.config[:uuid]
29
+ @appAccountId = NewRelic::Security::Agent.config[:account_id]
30
+ @appEntityGuid = NewRelic::Security::Agent.config[:entity_guid]
29
31
  @framework = NewRelic::Security::Agent.config[:framework]
30
32
  @groupName = NewRelic::Security::Agent.config[:mode]
31
33
  @userProvidedApplicationInfo = Hash.new
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'digest'
5
+
6
+ module NewRelic::Security
7
+ module Agent
8
+ module Control
9
+ class ApplicationRuntimeError
10
+ attr_reader :jsonName, :exception
11
+ attr_accessor :counter
12
+
13
+ def initialize(exception, ctxt, response_code, category)
14
+ @collectorType = RUBY
15
+ @language = Ruby
16
+ @jsonName = :'application-runtime-error'
17
+ @eventType = :'application-runtime-error'
18
+ @collectorVersion = NewRelic::Security::VERSION
19
+ @buildNumber = nil
20
+ @jsonVersion = NewRelic::Security::Agent.config[:json_version]
21
+ @timestamp = current_time_millis
22
+ @applicationUUID = NewRelic::Security::Agent.config[:uuid]
23
+ @appAccountId = NewRelic::Security::Agent.config[:account_id]
24
+ @appEntityGuid = NewRelic::Security::Agent.config[:entity_guid]
25
+ @framework = NewRelic::Security::Agent.config[:framework]
26
+ @groupName = NewRelic::Security::Agent.config[:mode]
27
+ @policyVersion = nil
28
+ @linkingMetadata = add_linking_metadata
29
+ @httpRequest = get_http_request_data(ctxt)
30
+ @exception = exception
31
+ @counter = 1
32
+ @responseCode = response_code
33
+ @category = category
34
+ @traceId = generate_trace_id(ctxt, category)
35
+ end
36
+
37
+ def as_json
38
+ instance_variables.map! do |ivar|
39
+ [ivar[1..-1].to_sym, instance_variable_get(ivar)]
40
+ end.to_h
41
+ end
42
+
43
+ def to_json(*_args)
44
+ as_json.to_json
45
+ end
46
+
47
+ private
48
+
49
+ def current_time_millis
50
+ (Time.now.to_f * 1000).to_i
51
+ end
52
+
53
+ def add_linking_metadata
54
+ linking_metadata = {}
55
+ linking_metadata[:agentRunId] = NewRelic::Security::Agent.config[:agent_run_id]
56
+ linking_metadata.merge!(NewRelic::Security::Agent.config[:linking_metadata])
57
+ # TODO: add other fields as well in linking metadata, for event and heathcheck as well
58
+ end
59
+
60
+ def get_http_request_data(ctxt)
61
+ return if ctxt.nil?
62
+ http_request = {}
63
+ http_request[:parameterMap] = {}
64
+ http_request[:body] = ctxt.body
65
+ http_request[:generationTime] = ctxt.time_stamp
66
+ http_request[:dataTruncated] = false
67
+ http_request[:method] = ctxt.method
68
+ http_request[:route] = ctxt.route.split(AT_THE_RATE)[1] if ctxt.route
69
+ http_request[:url] = URI(ctxt.req[REQUEST_URI]).respond_to?(:request_uri) ? URI(ctxt.req[REQUEST_URI]).request_uri : ctxt.req[REQUEST_URI]
70
+ http_request[:requestURI] = "#{ctxt.req[RACK_URL_SCHEME]}://#{ctxt.req[HTTP_HOST]}#{ctxt.req[PATH_INFO]}"
71
+ http_request[:clientIP] = ctxt.headers.key?(X_FORWARDED_FOR) ? ctxt.headers[X_FORWARDED_FOR].split(COMMA)[0].to_s : ctxt.req[REMOTE_ADDR].to_s
72
+ http_request[:serverPort] = ctxt.req[SERVER_PORT].to_i
73
+ http_request[:protocol] = ctxt.req[RACK_URL_SCHEME]
74
+ http_request[:contextPath] = ROOT_PATH
75
+ http_request[:headers] = ctxt.headers
76
+ http_request[:contentType] = ctxt.req[CONTENT_TYPE] if ctxt.req.key?(CONTENT_TYPE)
77
+ http_request[:headers][CONTENT_TYPE1] = ctxt.req[CONTENT_TYPE] if ctxt.req.key?(CONTENT_TYPE)
78
+ http_request[:dataTruncated] = ctxt.data_truncated
79
+ http_request
80
+ end
81
+
82
+ def generate_trace_id(ctxt, category)
83
+ @exception[:stackTrace]
84
+ method, route = ctxt.route.split(AT_THE_RATE) if ctxt.route
85
+ if @exception[:stackTrace]
86
+ ::Digest::SHA256.hexdigest("#{@exception[:stackTrace].join(PIPE)}#{category}#{route}#{method}").to_s
87
+ else
88
+ ::Digest::SHA256.hexdigest("#{category}#{route}#{method}").to_s
89
+ end
90
+ end
91
+
92
+ end
93
+ end
94
+ end
95
+ end
@@ -21,6 +21,8 @@ module NewRelic::Security
21
21
  @jsonVersion = NewRelic::Security::Agent.config[:json_version]
22
22
  @timestamp = current_time_millis
23
23
  @applicationUUID = NewRelic::Security::Agent.config[:uuid]
24
+ @appAccountId = NewRelic::Security::Agent.config[:account_id]
25
+ @appEntityGuid = NewRelic::Security::Agent.config[:entity_guid]
24
26
  @framework = NewRelic::Security::Agent.config[:framework]
25
27
  @groupName = NewRelic::Security::Agent.config[:mode]
26
28
  @policyVersion = nil
@@ -19,6 +19,8 @@ module NewRelic::Security
19
19
  def collect(case_type, args, event_category = nil, **keyword_args)
20
20
  return unless NewRelic::Security::Agent.config[:enabled]
21
21
  return if NewRelic::Security::Agent::Control::HTTPContext.get_context.nil? && NewRelic::Security::Agent::Control::GRPCContext.get_context.nil?
22
+ return if check_and_exclude_from_iast_scan_for_api
23
+ return if check_and_exclude_from_iast_scan_for_detection_category(case_type)
22
24
  args.map! { |file| Pathname.new(file).relative? ? File.join(Dir.pwd, file) : file } if [FILE_OPERATION, FILE_INTEGRITY].include?(case_type)
23
25
 
24
26
  event = NewRelic::Security::Agent::Control::Event.new(case_type, args, event_category)
@@ -44,7 +46,7 @@ module NewRelic::Security
44
46
  # Hence, considering only frame absolute_path & lineno for apiId calculation.
45
47
  user_frame_index = get_user_frame_index(stk)
46
48
  event.apiId = "#{case_type}-#{calculate_api_id(stk[0..user_frame_index].map { |frame| "#{frame.absolute_path}:#{frame.lineno}" }, event.httpRequest[:method], route)}"
47
- stk.delete_if {|frame| frame.path.match(/newrelic_security/) || frame.path.match(/new_relic/)}
49
+ stk.delete_if { |frame| frame.path.match?(/newrelic_security/) || frame.path.match?(/new_relic/) }
48
50
  user_frame_index = get_user_frame_index(stk)
49
51
  return if case_type != REFLECTED_XSS && user_frame_index == -1 # TODO: Add log message here: "Filtered because User Stk frame NOT FOUND \r\n"
50
52
  if user_frame_index != -1
@@ -59,8 +61,8 @@ module NewRelic::Security
59
61
  end
60
62
  event.stacktrace = stk[0..user_frame_index].map(&:to_s)
61
63
  NewRelic::Security::Agent.agent.event_processor.send_event(event)
62
- if event.httpRequest[:headers].key?(NR_CSEC_FUZZ_REQUEST_ID) && event.apiId == event.httpRequest[:headers][NR_CSEC_FUZZ_REQUEST_ID].split(COLON_IAST_COLON)[0]
63
- NewRelic::Security::Agent.agent.iast_client.completed_requests[event.parentId] << event.id 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
@@ -32,7 +32,7 @@ module NewRelic::Security
32
32
  fuzz_request = NewRelic::Security::Agent::Control::FuzzRequest.new(message_object[:id])
33
33
  fuzz_request.request = prepare_fuzz_request(message_object)
34
34
  fuzz_request.case_type = message_object[:arguments][1]
35
- fuzz_request.reflected_metadata = message_object[:reflectedMetaData]
35
+ fuzz_request.reflected_metadata = message_object[:reflectedMetaData]
36
36
  NewRelic::Security::Agent.agent.iast_client.pending_request_ids << message_object[:id]
37
37
  NewRelic::Security::Agent.agent.iast_client.enqueue(fuzz_request)
38
38
  fuzz_request = nil
@@ -49,8 +49,6 @@ module NewRelic::Security
49
49
  message_object[:arguments].each { |processed_id| NewRelic::Security::Agent.agent.iast_client.completed_requests.delete(processed_id) }
50
50
  when 100
51
51
  NewRelic::Security::Agent.logger.debug "Control command : '100', #{message_object.to_json}"
52
- ::NewRelic::Agent.instance.events.notify(:security_policy_received, message_object[:data])
53
- # TODO: Update policy from file here, if enabled.
54
52
  when 101
55
53
 
56
54
  when 102
@@ -104,6 +102,7 @@ module NewRelic::Security
104
102
 
105
103
  def prepare_fuzz_request(message_object)
106
104
  message_object[:arguments][0].gsub!(NR_CSEC_VALIDATOR_HOME_TMP, NewRelic::Security::Agent.config[:fuzz_dir_path])
105
+ message_object[:arguments][0].gsub!(NR_CSEC_VALIDATOR_HOME_TMP_URL_ENCODED, NewRelic::Security::Agent.config[:fuzz_dir_path])
107
106
  message_object[:arguments][0].gsub!(NR_CSEC_VALIDATOR_FILE_SEPARATOR, ::File::SEPARATOR)
108
107
  prepared_fuzz_request = ::JSON.parse(message_object[:arguments][0])
109
108
  prepared_fuzz_request[HEADERS][NR_CSEC_PARENT_ID] = message_object[:id]
@@ -21,6 +21,8 @@ module NewRelic::Security
21
21
  @buildNumber = nil
22
22
  @jsonVersion = NewRelic::Security::Agent.config[:json_version]
23
23
  @applicationUUID = NewRelic::Security::Agent.config[:uuid]
24
+ @appAccountId = NewRelic::Security::Agent.config[:account_id]
25
+ @appEntityGuid = NewRelic::Security::Agent.config[:entity_guid]
24
26
  @linkingMetadata = add_linking_metadata
25
27
  @timestamp = current_time_millis
26
28
  @message = message
@@ -0,0 +1,74 @@
1
+ module NewRelic::Security
2
+ module Agent
3
+ module Control
4
+ class ErrorReporting
5
+
6
+ STATUS_CODES_5XX = {
7
+ 500 => "Internal Server Error",
8
+ 501 => "Not Implemented",
9
+ 502 => "Bad Gateway",
10
+ 503 => "Service Unavailable",
11
+ 504 => "Gateway Timeout",
12
+ 505 => "HTTP Version Not Supported",
13
+ 506 => "Variant Also Negotiates",
14
+ 507 => "Insufficient Storage",
15
+ 508 => "Loop Detected",
16
+ 509 => "Bandwidth Limit Exceeded",
17
+ 510 => "Not Extended",
18
+ 511 => "Network Authentication Required"
19
+ }.freeze
20
+
21
+ attr_accessor :exceptions_map
22
+
23
+ def initialize
24
+ @exceptions_map = {}
25
+ end
26
+
27
+ def generate_unhandled_exception(noticed_error, ctxt, response_code)
28
+ unhandled_exception = {}
29
+ category = nil
30
+ if noticed_error
31
+ unhandled_exception[:message] = noticed_error.message
32
+ unhandled_exception[:cause] = nil
33
+ unhandled_exception[:type] = noticed_error.exception_class_name
34
+ unhandled_exception[:stackTrace] = noticed_error.stack_trace
35
+ category = noticed_error.exception_class_name
36
+ end
37
+ category = STATUS_CODES_5XX[response_code] if response_code
38
+ application_runtime_error = NewRelic::Security::Agent::Control::ApplicationRuntimeError.new(unhandled_exception, ctxt, response_code, category)
39
+ key = if response_code
40
+ # TODO: when do refactoring of ctxt.route, use both route and method to generate key
41
+ ctxt.route&.+ response_code.to_s
42
+ else
43
+ application_runtime_error.exception[:type] + application_runtime_error.exception[:stackTrace][0]
44
+ end
45
+ application_runtime_error.counter = @exceptions_map[key].counter + 1 if @exceptions_map.key?(key)
46
+ @exceptions_map[key] = application_runtime_error
47
+ rescue Exception => exception
48
+ NewRelic::Security::Agent.logger.error "Exception in generating unhandled exception: #{exception.inspect} #{exception.backtrace}\n"
49
+ end
50
+
51
+ def extract_noticed_error(current_transaction, ctxt, response_code)
52
+ # TODO: Below operation is expensive, talk to APM to get optimized way to do this
53
+ current_transaction.exceptions.each do |_, span|
54
+ current_transaction.segments.each do |segment|
55
+ generate_unhandled_exception(segment.noticed_error, ctxt, response_code) if span[:span_id] == segment.guid
56
+ end
57
+ end
58
+ rescue Exception => exception
59
+ NewRelic::Security::Agent.logger.error "Exception in extract_noticed_error: #{exception.inspect} #{exception.backtrace}\n"
60
+ end
61
+
62
+ def report_unhandled_or_5xx_exceptions(current_transaction, ctxt, response_code = nil)
63
+ http_response_code = response_code || current_transaction&.http_response_code
64
+ if current_transaction.exceptions.empty? && http_response_code&.between?(500, 599)
65
+ generate_unhandled_exception(nil, ctxt, response_code)
66
+ else
67
+ extract_noticed_error(current_transaction, ctxt, response_code) unless current_transaction.exceptions.empty?
68
+ end
69
+ end
70
+
71
+ end
72
+ end
73
+ end
74
+ end
@@ -26,9 +26,24 @@ module NewRelic::Security
26
26
  @buildNumber = nil
27
27
  @jsonVersion = NewRelic::Security::Agent.config[:json_version]
28
28
  @applicationUUID = NewRelic::Security::Agent.config[:uuid]
29
+ @appAccountId = NewRelic::Security::Agent.config[:account_id]
30
+ @appEntityGuid = NewRelic::Security::Agent.config[:entity_guid]
29
31
  @httpRequest = Hash.new
30
32
  @httpResponse = Hash.new
31
- @metaData = { :reflectedMetaData => { :listen_port => NewRelic::Security::Agent.config[:listen_port].to_s } }
33
+ @metaData = {
34
+ :reflectedMetaData => {
35
+ :listen_port => NewRelic::Security::Agent.config[:listen_port].to_s
36
+ },
37
+ :appServerInfo => {
38
+ :applicationDirectory => NewRelic::Security::Agent.config[:app_root],
39
+ :serverBaseDirectory => NewRelic::Security::Agent.config[:app_root]
40
+ },
41
+ :skipScanParameters => {
42
+ :header => NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.http_request_parameters.header'],
43
+ :query => NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.http_request_parameters.query'],
44
+ :body => NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.http_request_parameters.body']
45
+ }
46
+ }
32
47
  @linkingMetadata = add_linking_metadata
33
48
  @pid = pid
34
49
  @parameters = args
@@ -113,7 +128,7 @@ module NewRelic::Security
113
128
  http_request[:contentType] = "TODO: "
114
129
  http_request[:isGrpc] = ctxt.is_grpc
115
130
  @httpRequest = http_request
116
- @metaData = ctxt.metadata
131
+ @metaData.merge!(ctxt.metadata)
117
132
  end
118
133
 
119
134
  private
@@ -128,13 +143,20 @@ module NewRelic::Security
128
143
  end
129
144
 
130
145
  def event_id
131
- "#{Process.pid}:#{::Thread.current.object_id}:#{thread_monotonic_ctr}"
146
+ "#{Process.pid}:#{current_transaction&.guid}:#{thread_monotonic_ctr}"
147
+ end
148
+
149
+ def current_transaction
150
+ ::NewRelic::Agent::Tracer.current_transaction if defined?(::NewRelic::Agent::Tracer)
132
151
  end
133
152
 
134
153
  def thread_monotonic_ctr
135
154
  ctxt = NewRelic::Security::Agent::Control::HTTPContext.get_context if NewRelic::Security::Agent::Control::HTTPContext.get_context
136
155
  ctxt = NewRelic::Security::Agent::Control::GRPCContext.get_context if NewRelic::Security::Agent::Control::GRPCContext.get_context
137
- ctxt.event_counter = ctxt.event_counter + 1 if ctxt
156
+ return unless ctxt
157
+ ctxt.mutex.synchronize do
158
+ ctxt.event_counter = ctxt.event_counter + 1
159
+ end
138
160
  end
139
161
 
140
162
  def add_linking_metadata