newrelic_security 0.1.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.
Files changed (205) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  3. data/.github/ISSUE_TEMPLATE/enhancement.md +27 -0
  4. data/.github/actions/simplecov-report/LICENSE +22 -0
  5. data/.github/actions/simplecov-report/README.md +36 -0
  6. data/.github/actions/simplecov-report/__tests__/.keep +0 -0
  7. data/.github/actions/simplecov-report/__tests__/main.test.ts +3 -0
  8. data/.github/actions/simplecov-report/action.yml +25 -0
  9. data/.github/actions/simplecov-report/dist/index.js +10238 -0
  10. data/.github/actions/simplecov-report/dummy_coverage/.last_run.json +5 -0
  11. data/.github/actions/simplecov-report/jest.config.js +11 -0
  12. data/.github/actions/simplecov-report/package.json +51 -0
  13. data/.github/actions/simplecov-report/src/main.ts +54 -0
  14. data/.github/actions/simplecov-report/src/report.ts +28 -0
  15. data/.github/actions/simplecov-report/tsconfig.json +12 -0
  16. data/.github/workflows/pr_ci.yml +77 -0
  17. data/.github/workflows/release.yml +51 -0
  18. data/.github/workflows/repolinter.yml +31 -0
  19. data/.github/workflows/rubocop.yml +17 -0
  20. data/.github/workflows/scripts/rubygems-authenticate.py +13 -0
  21. data/.github/workflows/scripts/rubygems-publish.rb +33 -0
  22. data/.gitignore +72 -0
  23. data/.rubocop.yml +9 -0
  24. data/.rubocop_todo.yml +1414 -0
  25. data/.simplecov +16 -0
  26. data/CHANGELOG.md +69 -0
  27. data/CONTRIBUTING.md +22 -0
  28. data/Gemfile +6 -0
  29. data/Gemfile_test +58 -0
  30. data/LICENSE +43 -0
  31. data/README.md +133 -0
  32. data/README_agent.md +44 -0
  33. data/Rakefile +28 -0
  34. data/THIRD_PARTY_NOTICES.md +36 -0
  35. data/lib/newrelic_security/agent/agent.rb +109 -0
  36. data/lib/newrelic_security/agent/configuration/default_source.rb +8 -0
  37. data/lib/newrelic_security/agent/configuration/environment_source.rb +8 -0
  38. data/lib/newrelic_security/agent/configuration/manager.rb +178 -0
  39. data/lib/newrelic_security/agent/configuration/manual_source.rb +8 -0
  40. data/lib/newrelic_security/agent/configuration/server_source.rb +8 -0
  41. data/lib/newrelic_security/agent/configuration/yaml_source.rb +8 -0
  42. data/lib/newrelic_security/agent/control/app_info.rb +132 -0
  43. data/lib/newrelic_security/agent/control/application_url_mappings.rb +66 -0
  44. data/lib/newrelic_security/agent/control/collector.rb +117 -0
  45. data/lib/newrelic_security/agent/control/control_command.rb +117 -0
  46. data/lib/newrelic_security/agent/control/critical_message.rb +58 -0
  47. data/lib/newrelic_security/agent/control/event.rb +149 -0
  48. data/lib/newrelic_security/agent/control/event_counter.rb +28 -0
  49. data/lib/newrelic_security/agent/control/event_processor.rb +134 -0
  50. data/lib/newrelic_security/agent/control/event_stats.rb +26 -0
  51. data/lib/newrelic_security/agent/control/event_subscriber.rb +28 -0
  52. data/lib/newrelic_security/agent/control/exit_event.rb +38 -0
  53. data/lib/newrelic_security/agent/control/fuzz_request.rb +18 -0
  54. data/lib/newrelic_security/agent/control/grpc_context.rb +57 -0
  55. data/lib/newrelic_security/agent/control/health_check.rb +136 -0
  56. data/lib/newrelic_security/agent/control/http_context.rb +73 -0
  57. data/lib/newrelic_security/agent/control/iast_client.rb +151 -0
  58. data/lib/newrelic_security/agent/control/iast_data_transfer_request.rb +32 -0
  59. data/lib/newrelic_security/agent/control/reflected_xss.rb +258 -0
  60. data/lib/newrelic_security/agent/control/websocket_client.rb +131 -0
  61. data/lib/newrelic_security/agent/logging/init_logger.rb +91 -0
  62. data/lib/newrelic_security/agent/logging/logger.rb +92 -0
  63. data/lib/newrelic_security/agent/logging/null_logger.rb +21 -0
  64. data/lib/newrelic_security/agent/resources/cert.pem +50 -0
  65. data/lib/newrelic_security/agent/utils/agent_utils.rb +219 -0
  66. data/lib/newrelic_security/agent.rb +57 -0
  67. data/lib/newrelic_security/constants.rb +67 -0
  68. data/lib/newrelic_security/instrumentation-security/active_record/mysql2_adapter/chain.rb +70 -0
  69. data/lib/newrelic_security/instrumentation-security/active_record/mysql2_adapter/instrumentation.rb +187 -0
  70. data/lib/newrelic_security/instrumentation-security/active_record/mysql2_adapter/prepend.rb +54 -0
  71. data/lib/newrelic_security/instrumentation-security/active_record/postgresql_adapter/chain.rb +60 -0
  72. data/lib/newrelic_security/instrumentation-security/active_record/postgresql_adapter/instrumentation.rb +143 -0
  73. data/lib/newrelic_security/instrumentation-security/active_record/postgresql_adapter/prepend.rb +48 -0
  74. data/lib/newrelic_security/instrumentation-security/active_record/sqlite3_adapter/chain.rb +72 -0
  75. data/lib/newrelic_security/instrumentation-security/active_record/sqlite3_adapter/instrumentation.rb +187 -0
  76. data/lib/newrelic_security/instrumentation-security/active_record/sqlite3_adapter/prepend.rb +54 -0
  77. data/lib/newrelic_security/instrumentation-security/async-http/chain.rb +21 -0
  78. data/lib/newrelic_security/instrumentation-security/async-http/instrumentation.rb +46 -0
  79. data/lib/newrelic_security/instrumentation-security/async-http/prepend.rb +16 -0
  80. data/lib/newrelic_security/instrumentation-security/curb/chain.rb +26 -0
  81. data/lib/newrelic_security/instrumentation-security/curb/instrumentation.rb +52 -0
  82. data/lib/newrelic_security/instrumentation-security/curb/prepend.rb +18 -0
  83. data/lib/newrelic_security/instrumentation-security/dir/chain.rb +42 -0
  84. data/lib/newrelic_security/instrumentation-security/dir/instrumentation.rb +102 -0
  85. data/lib/newrelic_security/instrumentation-security/dir/prepend.rb +28 -0
  86. data/lib/newrelic_security/instrumentation-security/ethon/chain.rb +53 -0
  87. data/lib/newrelic_security/instrumentation-security/ethon/instrumentation.rb +122 -0
  88. data/lib/newrelic_security/instrumentation-security/ethon/prepend.rb +39 -0
  89. data/lib/newrelic_security/instrumentation-security/excon/chain.rb +23 -0
  90. data/lib/newrelic_security/instrumentation-security/excon/instrumentation.rb +44 -0
  91. data/lib/newrelic_security/instrumentation-security/excon/prepend.rb +17 -0
  92. data/lib/newrelic_security/instrumentation-security/file/chain.rb +34 -0
  93. data/lib/newrelic_security/instrumentation-security/file/instrumentation.rb +62 -0
  94. data/lib/newrelic_security/instrumentation-security/file/prepend.rb +22 -0
  95. data/lib/newrelic_security/instrumentation-security/grape/chain.rb +42 -0
  96. data/lib/newrelic_security/instrumentation-security/grape/instrumentation.rb +56 -0
  97. data/lib/newrelic_security/instrumentation-security/grape/prepend.rb +30 -0
  98. data/lib/newrelic_security/instrumentation-security/grpc/client/chain.rb +47 -0
  99. data/lib/newrelic_security/instrumentation-security/grpc/client/instrumentation.rb +37 -0
  100. data/lib/newrelic_security/instrumentation-security/grpc/client/prepend.rb +36 -0
  101. data/lib/newrelic_security/instrumentation-security/grpc/server/chain.rb +62 -0
  102. data/lib/newrelic_security/instrumentation-security/grpc/server/instrumentation.rb +65 -0
  103. data/lib/newrelic_security/instrumentation-security/grpc/server/prepend.rb +46 -0
  104. data/lib/newrelic_security/instrumentation-security/httpclient/chain.rb +30 -0
  105. data/lib/newrelic_security/instrumentation-security/httpclient/instrumentation.rb +82 -0
  106. data/lib/newrelic_security/instrumentation-security/httpclient/prepend.rb +22 -0
  107. data/lib/newrelic_security/instrumentation-security/httprb/chain.rb +21 -0
  108. data/lib/newrelic_security/instrumentation-security/httprb/instrumentation.rb +44 -0
  109. data/lib/newrelic_security/instrumentation-security/httprb/prepend.rb +16 -0
  110. data/lib/newrelic_security/instrumentation-security/httpx/chain.rb +23 -0
  111. data/lib/newrelic_security/instrumentation-security/httpx/instrumentation.rb +51 -0
  112. data/lib/newrelic_security/instrumentation-security/httpx/prepend.rb +18 -0
  113. data/lib/newrelic_security/instrumentation-security/instrumentation_loader.rb +50 -0
  114. data/lib/newrelic_security/instrumentation-security/instrumentation_utils.rb +165 -0
  115. data/lib/newrelic_security/instrumentation-security/io/chain.rb +113 -0
  116. data/lib/newrelic_security/instrumentation-security/io/instrumentation.rb +300 -0
  117. data/lib/newrelic_security/instrumentation-security/io/prepend.rb +86 -0
  118. data/lib/newrelic_security/instrumentation-security/kernel/chain.rb +65 -0
  119. data/lib/newrelic_security/instrumentation-security/kernel/instrumentation.rb +167 -0
  120. data/lib/newrelic_security/instrumentation-security/kernel/prepend.rb +50 -0
  121. data/lib/newrelic_security/instrumentation-security/mongo/chain.rb +106 -0
  122. data/lib/newrelic_security/instrumentation-security/mongo/instrumentation.rb +273 -0
  123. data/lib/newrelic_security/instrumentation-security/mongo/prepend.rb +77 -0
  124. data/lib/newrelic_security/instrumentation-security/mysql2/chain.rb +53 -0
  125. data/lib/newrelic_security/instrumentation-security/mysql2/instrumentation.rb +84 -0
  126. data/lib/newrelic_security/instrumentation-security/mysql2/prepend.rb +37 -0
  127. data/lib/newrelic_security/instrumentation-security/net_http/chain.rb +21 -0
  128. data/lib/newrelic_security/instrumentation-security/net_http/instrumentation.rb +60 -0
  129. data/lib/newrelic_security/instrumentation-security/net_http/prepend.rb +16 -0
  130. data/lib/newrelic_security/instrumentation-security/net_ldap/chain.rb +21 -0
  131. data/lib/newrelic_security/instrumentation-security/net_ldap/instrumentation.rb +42 -0
  132. data/lib/newrelic_security/instrumentation-security/net_ldap/prepend.rb +16 -0
  133. data/lib/newrelic_security/instrumentation-security/nokogiri/chain.rb +46 -0
  134. data/lib/newrelic_security/instrumentation-security/nokogiri/instrumentation.rb +36 -0
  135. data/lib/newrelic_security/instrumentation-security/nokogiri/prepend.rb +31 -0
  136. data/lib/newrelic_security/instrumentation-security/padrino/chain.rb +26 -0
  137. data/lib/newrelic_security/instrumentation-security/padrino/instrumentation.rb +42 -0
  138. data/lib/newrelic_security/instrumentation-security/padrino/prepend.rb +20 -0
  139. data/lib/newrelic_security/instrumentation-security/patron/chain.rb +23 -0
  140. data/lib/newrelic_security/instrumentation-security/patron/instrumentation.rb +50 -0
  141. data/lib/newrelic_security/instrumentation-security/patron/prepend.rb +18 -0
  142. data/lib/newrelic_security/instrumentation-security/pg/chain.rb +49 -0
  143. data/lib/newrelic_security/instrumentation-security/pg/instrumentation.rb +102 -0
  144. data/lib/newrelic_security/instrumentation-security/pg/prepend.rb +36 -0
  145. data/lib/newrelic_security/instrumentation-security/pty/chain.rb +31 -0
  146. data/lib/newrelic_security/instrumentation-security/pty/instrumentation.rb +52 -0
  147. data/lib/newrelic_security/instrumentation-security/pty/prepend.rb +22 -0
  148. data/lib/newrelic_security/instrumentation-security/rails/chain.rb +46 -0
  149. data/lib/newrelic_security/instrumentation-security/rails/instrumentation.rb +67 -0
  150. data/lib/newrelic_security/instrumentation-security/rails/prepend.rb +33 -0
  151. data/lib/newrelic_security/instrumentation-security/roda/chain.rb +22 -0
  152. data/lib/newrelic_security/instrumentation-security/roda/instrumentation.rb +41 -0
  153. data/lib/newrelic_security/instrumentation-security/roda/prepend.rb +16 -0
  154. data/lib/newrelic_security/instrumentation-security/sinatra/chain.rb +29 -0
  155. data/lib/newrelic_security/instrumentation-security/sinatra/instrumentation.rb +49 -0
  156. data/lib/newrelic_security/instrumentation-security/sinatra/prepend.rb +21 -0
  157. data/lib/newrelic_security/instrumentation-security/sqlite3/chain.rb +79 -0
  158. data/lib/newrelic_security/instrumentation-security/sqlite3/instrumentation.rb +164 -0
  159. data/lib/newrelic_security/instrumentation-security/sqlite3/prepend.rb +56 -0
  160. data/lib/newrelic_security/newrelic-security-api/api.rb +72 -0
  161. data/lib/newrelic_security/version.rb +5 -0
  162. data/lib/newrelic_security/websocket-client-simple/client.rb +128 -0
  163. data/lib/newrelic_security/websocket-client-simple/event_emitter.rb +72 -0
  164. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/error.rb +129 -0
  165. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/exception_handler.rb +32 -0
  166. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/base.rb +62 -0
  167. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/data.rb +49 -0
  168. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler/base.rb +41 -0
  169. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler/handler03.rb +224 -0
  170. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler/handler04.rb +18 -0
  171. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler/handler05.rb +15 -0
  172. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler/handler07.rb +78 -0
  173. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler/handler75.rb +78 -0
  174. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler.rb +15 -0
  175. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/incoming/client.rb +17 -0
  176. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/incoming/server.rb +17 -0
  177. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/incoming.rb +52 -0
  178. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/outgoing/client.rb +17 -0
  179. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/outgoing/server.rb +17 -0
  180. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/outgoing.rb +35 -0
  181. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame.rb +11 -0
  182. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/base.rb +142 -0
  183. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/client.rb +130 -0
  184. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/base.rb +49 -0
  185. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/client.rb +32 -0
  186. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/client01.rb +20 -0
  187. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/client04.rb +63 -0
  188. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/client11.rb +22 -0
  189. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/client75.rb +39 -0
  190. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/client76.rb +105 -0
  191. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/server.rb +10 -0
  192. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/server04.rb +56 -0
  193. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/server75.rb +40 -0
  194. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/server76.rb +75 -0
  195. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler.rb +21 -0
  196. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/server.rb +179 -0
  197. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake.rb +10 -0
  198. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/nice_inspect.rb +12 -0
  199. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/version.rb +5 -0
  200. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket.rb +50 -0
  201. data/lib/newrelic_security.rb +6 -0
  202. data/lib/tasks/all.rb +8 -0
  203. data/lib/tasks/coverage_report.rake +27 -0
  204. data/newrelic_security.gemspec +51 -0
  205. metadata +342 -0
@@ -0,0 +1,178 @@
1
+ require 'securerandom'
2
+ require 'socket'
3
+ require 'openssl'
4
+ require 'newrelic_security/agent/configuration/default_source'
5
+ require 'newrelic_security/agent/configuration/environment_source'
6
+ require 'newrelic_security/agent/configuration/manual_source'
7
+ require 'newrelic_security/agent/configuration/server_source'
8
+ require 'newrelic_security/agent/configuration/yaml_source'
9
+
10
+ module NewRelic::Security
11
+ module Agent
12
+ module Configuration
13
+ class Manager
14
+ def initialize
15
+ @cache = Hash.new
16
+ @cache[:agent_run_id] = ::NewRelic::Agent.agent.service.agent_id
17
+ @cache[:linking_metadata] = ::NewRelic::Agent.linking_metadata
18
+ @cache[:app_name] = ::NewRelic::Agent.config[:app_name][0]
19
+ @cache[:entity_guid] = ::NewRelic::Agent.config[:entity_guid]
20
+ @cache[:license_key] = ::NewRelic::Agent.config[:license_key]
21
+ @cache[:policy] = Hash.new
22
+ @cache[:account_id] = nil
23
+ @cache[:application_id] = nil
24
+ @cache[:primary_application_id] = nil
25
+ @cache[:log_file_path] = ::NewRelic::Agent.config[:log_file_path]
26
+ @cache[:fuzz_dir_path] = ::File.join(::File.absolute_path(::NewRelic::Agent.config[:log_file_path]), SEC_HOME_PATH, TMP_DIR)
27
+ @cache[:log_level] = ::NewRelic::Agent.config[:log_level]
28
+ @cache[:high_security] = ::NewRelic::Agent.config[:high_security]
29
+ @cache[:'agent.enabled'] = ::NewRelic::Agent.config[:'security.agent.enabled']
30
+ @cache[:enabled] = ::NewRelic::Agent.config[:'security.enabled']
31
+ @cache[:mode] = ::NewRelic::Agent.config[:'security.mode']
32
+ @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']
36
+ @cache[:framework] = detect_framework
37
+ @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
+ @cache[:listen_port] = nil
40
+ @cache[:app_root] = NewRelic::Security::Agent::Utils.app_root
41
+ @cache[:jruby_objectspace_enabled] = false
42
+ @cache[:json_version] = :'1.2.0'
43
+
44
+ @environment_source = NewRelic::Security::Agent::Configuration::EnvironmentSource.new
45
+ @server_source = NewRelic::Security::Agent::Configuration::ServerSource.new
46
+ @manual_source = NewRelic::Security::Agent::Configuration::ManualSource.new
47
+ @yaml_source = NewRelic::Security::Agent::Configuration::YamlSource.new
48
+ @default_source = NewRelic::Security::Agent::Configuration::DefaultSource.new
49
+ rescue Exception => exception
50
+ # TODO: remove this puts once agent stablizes
51
+ puts "Exception in Configuration::Manager.initialize : #{exception.inspect} #{exception.backtrace}"
52
+ end
53
+
54
+ def [](key)
55
+ @cache[key]
56
+ end
57
+
58
+ def has_key?(key)
59
+ @cache.has_key?(key)
60
+ end
61
+
62
+ def keys
63
+ @cache.keys
64
+ end
65
+
66
+ def cache
67
+ @cache
68
+ end
69
+
70
+ def refresh
71
+ NewRelic::Security::Agent.logger.debug "refreshing agent config"
72
+ NewRelic::Security::Agent.config = NewRelic::Security::Agent::Configuration::Manager.new
73
+ # TODO: add validator received config also after the new, else collector#40 throws error
74
+ end
75
+
76
+ def save_uuid
77
+ @cache[:uuid] = generate_uuid
78
+ end
79
+
80
+ def update_server_config
81
+ @cache[:agent_run_id] = ::NewRelic::Agent.agent.service.agent_id
82
+ @cache[:linking_metadata] = ::NewRelic::Agent.linking_metadata
83
+ server_source = ::NewRelic::Agent.config.instance_variable_get(:@server_source) if defined?(::NewRelic::Agent)
84
+ @cache[:account_id] = server_source[:account_id]
85
+ @cache[:application_id] = server_source[:application_id]
86
+ @cache[:entity_guid] = server_source[:entity_guid]
87
+ @cache[:primary_application_id] = server_source[:primary_application_id]
88
+ @cache[:extraction_key] = generate_key(@cache[:entity_guid])
89
+ rescue Exception => exception
90
+ NewRelic::Security::Agent.logger.error "Exception in update_server_config : #{exception.inspect} #{exception.backtrace}"
91
+ end
92
+
93
+ def update_port=(listen_port)
94
+ @cache[:listen_port] = listen_port
95
+ end
96
+
97
+ def app_server=(app_server)
98
+ @cache[:app_server] = app_server
99
+ end
100
+
101
+ def jruby_objectspace_enabled=(jruby_objectspace_enabled)
102
+ @cache[:jruby_objectspace_enabled] = jruby_objectspace_enabled
103
+ end
104
+
105
+ def disable_security
106
+ @cache[:enabled] = false
107
+ NewRelic::Security::Agent.logger.info "Security Agent is now INACTIVE for #{NewRelic::Security::Agent.config[:uuid]}\n"
108
+ NewRelic::Security::Agent.init_logger.info "Security Agent is now INACTIVE for #{NewRelic::Security::Agent.config[:uuid]}\n"
109
+ end
110
+
111
+ def enable_security
112
+ @cache[:enabled] = true
113
+ NewRelic::Security::Agent.logger.info "Security Agent is now ACTIVE for #{NewRelic::Security::Agent.config[:uuid]}\n"
114
+ NewRelic::Security::Agent.init_logger.info "Security Agent is now ACTIVE for #{NewRelic::Security::Agent.config[:uuid]}\n"
115
+ NewRelic::Security::Agent.agent.event_processor.send_critical_message("Security Agent is now ACTIVE for #{NewRelic::Security::Agent.config[:uuid]}", "INFO", caller_locations[0].to_s, Thread.current.name, nil)
116
+ end
117
+
118
+ private
119
+
120
+ def detect_framework
121
+ return :rails if defined?(::Rails)
122
+ return :padrino if defined?(::Padrino)
123
+ return :sinatra if defined?(::Sinatra)
124
+ return :roda if defined?(::Roda)
125
+ return :grape if defined?(::Grape)
126
+ end
127
+
128
+ def generate_uuid
129
+ if defined?(::Puma::Cluster)
130
+ ObjectSpace.each_object(::Puma::Cluster) { |z| return fetch_or_create_uuid if !z.preload? && z.instance_variable_get(:@options)[:workers] >= 1 }
131
+ end
132
+ if defined?(::Unicorn::HttpServer)
133
+ ObjectSpace.each_object(::Unicorn::HttpServer) { |z| return fetch_or_create_uuid if !z.preload_app && z.worker_processes >= 1 }
134
+ end
135
+ if defined?(::PhusionPassenger::App) && ::PhusionPassenger::App.options[SPAWN_METHOD].match?(/#{DIRECT}/i)
136
+ return create_uuid
137
+ end
138
+ ::SecureRandom.uuid
139
+ rescue Exception => exception
140
+ NewRelic::Security::Agent.logger.error "Exception in generate_uuid : #{exception.inspect} #{exception.backtrace}"
141
+ end
142
+
143
+ def create_uuid
144
+ hostname = ::Socket.gethostname
145
+ ip_addr = Socket.ip_address_list.detect{|intf| intf.ipv4_private?}.ip_address.to_s
146
+ process_identity = ::Gem.win_platform? ? ::Process.pid : ::Process.getpgrp
147
+ [hostname, ip_addr, process_identity].join(HYPHEN)
148
+ end
149
+
150
+ def fetch_or_create_uuid
151
+ process_identity = ::Gem.win_platform? ? ::Process.pid : ::Process.getpgrp
152
+ tmp_dir = ::File.join(@cache[:log_file_path], SEC_HOME_PATH, TMP_DIR)
153
+ if ::File.directory?(tmp_dir)
154
+ uuid_file_name = ::File.join(@cache[:log_file_path], SEC_HOME_PATH, TMP_DIR, process_identity.to_s)
155
+ else
156
+ ::FileUtils.mkdir_p(TMP_DIR) unless ::File.directory?(TMP_DIR)
157
+ uuid_file_name = ::File.join(TMP_DIR, process_identity.to_s)
158
+ end
159
+ if ::File.exist?(uuid_file_name)
160
+ sleep 0.01
161
+ return ::File.read(uuid_file_name)
162
+ end
163
+ File.open(uuid_file_name, 'w', 0644) {|f|
164
+ ret = f.flock(File::LOCK_EX|File::LOCK_NB)
165
+ f.write(::SecureRandom.uuid) if ret == 0
166
+ }
167
+ ::File.read(uuid_file_name)
168
+ rescue Exception => exception
169
+ NewRelic::Security::Agent.logger.error "Exception in uuid file creation : #{exception.inspect} #{exception.backtrace}"
170
+ end
171
+
172
+ def generate_key(entity_guid)
173
+ ::OpenSSL::PKCS5.pbkdf2_hmac(entity_guid, entity_guid[0..15], 1024, 32, SHA1)
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,8 @@
1
+ module NewRelic::Security
2
+ module Agent
3
+ module Configuration
4
+ class ManualSource
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module NewRelic::Security
2
+ module Agent
3
+ module Configuration
4
+ class ServerSource
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module NewRelic::Security
2
+ module Agent
3
+ module Configuration
4
+ class YamlSource
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+ require 'digest'
3
+ require 'json'
4
+
5
+ module NewRelic::Security
6
+ module Agent
7
+ module Control
8
+
9
+ PROC_SELF_EXE = '/proc/self/exe'
10
+ PROC_SELF_CMDLINE = '/proc/self/cmdline'
11
+ STATIC = 'STATIC'
12
+ COLON = ':'
13
+ RUBYLIB = 'RUBYLIB'
14
+ BACKSLASH000 = '\000'
15
+ KIND = 'kind'
16
+
17
+ class AppInfo
18
+ attr_reader :jsonName
19
+
20
+ def initialize
21
+ @collectorType = RUBY
22
+ @language = Ruby
23
+ @jsonName = :applicationinfo
24
+ @collectorVersion = NewRelic::Security::VERSION
25
+ @buildNumber = nil
26
+ @jsonVersion = NewRelic::Security::Agent.config[:json_version]
27
+ @startTime = current_time_millis
28
+ @applicationUUID = NewRelic::Security::Agent.config[:uuid]
29
+ @framework = NewRelic::Security::Agent.config[:framework]
30
+ @groupName = NewRelic::Security::Agent.config[:mode]
31
+ @userProvidedApplicationInfo = Hash.new
32
+ @policyVersion = nil
33
+ @userDir = nil
34
+ @libraryPath = library_path
35
+ @bootLibraryPath = EMPTY_STRING
36
+ @binaryName = binary_name
37
+ @binaryVersion = binary_version
38
+ @pid = pid
39
+ @cpid = cpid
40
+ @binaryPath = binary_path
41
+ @agentAttachmentType = STATIC
42
+ @sha256 = sha_256
43
+ @runCommand = run_command
44
+ @cmdline = [run_command]
45
+ @procStartTime = current_time_millis
46
+ @osArch = os_arch
47
+ @osName = os_name
48
+ @osVersion = os_version
49
+ @serverInfo = Hash.new # TODO: Fill this
50
+ @identifier = Hash.new # TODO: Fill this
51
+ @linkingMetadata = add_linking_metadata
52
+ end
53
+
54
+ def as_json
55
+ instance_variables.map! do |ivar|
56
+ [ivar[1..-1].to_sym, instance_variable_get(ivar)]
57
+ end.to_h
58
+ end
59
+
60
+ def to_json
61
+ as_json.to_json
62
+ end
63
+
64
+ def update_app_info
65
+ @identifier[KIND] = 'HOST' # TODO: Added other identifier details
66
+ end
67
+
68
+ private
69
+
70
+ def current_time_millis
71
+ (Time.now.to_f * 1000).to_i
72
+ end
73
+
74
+ def library_path
75
+ ENV[RUBYLIB].split(COLON)
76
+ end
77
+
78
+ def binary_name
79
+ RUBY_ENGINE
80
+ end
81
+
82
+ def binary_version
83
+ RUBY_VERSION
84
+ end
85
+
86
+ def pid
87
+ return ::Process.pid if ::Gem.win_platform?
88
+ ::Process.getpgrp
89
+ end
90
+
91
+ def cpid
92
+ Process.pid
93
+ end
94
+
95
+ def binary_path
96
+ return ::File.realpath(PROC_SELF_EXE) if ::File.exist?(PROC_SELF_EXE)
97
+ nil
98
+ end
99
+
100
+ def sha_256
101
+ return ::Digest::SHA256.file(binary_path).hexdigest if binary_path
102
+ nil
103
+ end
104
+
105
+ def run_command
106
+ return ::File.read(PROC_SELF_CMDLINE).delete(BACKSLASH000) if File.exist?(PROC_SELF_CMDLINE)
107
+ $PROGRAM_NAME
108
+ end
109
+
110
+ def os_arch
111
+ ::Gem::Platform.local.cpu
112
+ end
113
+
114
+ def os_name
115
+ ::Gem::Platform.local.os
116
+ end
117
+
118
+ def os_version
119
+ ::Gem::Platform.local.version
120
+ end
121
+
122
+ def add_linking_metadata
123
+ linking_metadata = Hash.new
124
+ linking_metadata[:agentRunId] = NewRelic::Security::Agent.config[:agent_run_id]
125
+ linking_metadata.merge!(NewRelic::Security::Agent.config[:linking_metadata])
126
+ # TODO: add other fields as well in linking metadata, for event and heathcheck as well
127
+ end
128
+
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require 'json'
5
+
6
+ module NewRelic::Security
7
+ module Agent
8
+ module Control
9
+
10
+
11
+ class ApplicationURLMappings
12
+ attr_reader :jsonName
13
+
14
+ def initialize
15
+ @collectorType = RUBY
16
+ @language = Ruby
17
+ @jsonName = :'sec-application-url-mapping'
18
+ @eventType = :'sec-application-url-mapping'
19
+ @collectorVersion = NewRelic::Security::VERSION
20
+ @buildNumber = nil
21
+ @jsonVersion = NewRelic::Security::Agent.config[:json_version]
22
+ @timestamp = current_time_millis
23
+ @applicationUUID = NewRelic::Security::Agent.config[:uuid]
24
+ @framework = NewRelic::Security::Agent.config[:framework]
25
+ @groupName = NewRelic::Security::Agent.config[:mode]
26
+ @policyVersion = nil
27
+ @linkingMetadata = add_linking_metadata
28
+ @mappings = []
29
+ end
30
+
31
+ def as_json
32
+ instance_variables.map! do |ivar|
33
+ [ivar[1..-1].to_sym, instance_variable_get(ivar)]
34
+ end.to_h
35
+ end
36
+
37
+ def to_json
38
+ as_json.to_json
39
+ end
40
+
41
+ def update_application_url_mappings
42
+ maps = ::Set.new
43
+ NewRelic::Security::Agent.agent.route_map.each do |mapping|
44
+ method, path = mapping.split('@')
45
+ maps << { :method => method, :path => path }
46
+ end
47
+ @mappings = maps.to_a
48
+ end
49
+
50
+ private
51
+
52
+ def current_time_millis
53
+ (Time.now.to_f * 1000).to_i
54
+ end
55
+
56
+ def add_linking_metadata
57
+ linking_metadata = {}
58
+ linking_metadata[:agentRunId] = NewRelic::Security::Agent.config[:agent_run_id]
59
+ linking_metadata.merge!(NewRelic::Security::Agent.config[:linking_metadata])
60
+ # TODO: add other fields as well in linking metadata, for event and heathcheck as well
61
+ end
62
+
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+ require 'digest'
3
+ require 'pathname'
4
+
5
+ module NewRelic::Security
6
+ module Agent
7
+ module Control
8
+ module Collector
9
+
10
+ COVERAGE = 101
11
+ MARSHAL_RB = 'marshal.rb'
12
+ LOAD = 'load'
13
+ PSYCH_RB = 'psych.rb'
14
+ MARSHAL_LOAD = 'marshal_load'
15
+ EVAL = 'eval'
16
+
17
+ extend self
18
+
19
+ def collect(case_type, args, event_category = nil, **keyword_args)
20
+ return unless NewRelic::Security::Agent.config[:enabled]
21
+ return if NewRelic::Security::Agent::Control::HTTPContext.get_context.nil? && NewRelic::Security::Agent::Control::GRPCContext.get_context.nil?
22
+ args.map! { |file| Pathname.new(file).relative? ? File.join(Dir.pwd, file) : file } if [FILE_OPERATION, FILE_INTEGRITY].include?(case_type)
23
+
24
+ event = NewRelic::Security::Agent::Control::Event.new(case_type, args, event_category)
25
+ stk = caller_locations[1..COVERAGE]
26
+ event.sourceMethod = stk[0].label
27
+
28
+ event.copy_http_info(NewRelic::Security::Agent::Control::HTTPContext.get_context) if NewRelic::Security::Agent::Control::HTTPContext.get_context
29
+ event.copy_grpc_info(NewRelic::Security::Agent::Control::GRPCContext.get_context) if NewRelic::Security::Agent::Control::GRPCContext.get_context
30
+ event.isIASTEnable = true if NewRelic::Security::Agent::Utils.is_IAST?
31
+ event.isIASTRequest = true if NewRelic::Security::Agent::Utils.is_IAST_request?(event.httpRequest[:headers])
32
+ event.parentId = event.httpRequest[:headers][NR_CSEC_PARENT_ID] if event.httpRequest[:headers].key?(NR_CSEC_PARENT_ID)
33
+ find_deserialisation(event, stk) if case_type != REFLECTED_XSS && NewRelic::Security::Agent.config[:'security.detection.deserialization.enabled']
34
+ find_rci(event, stk) if case_type != REFLECTED_XSS && NewRelic::Security::Agent.config[:'security.detection.rci.enabled']
35
+ route = nil
36
+ if case_type == REFLECTED_XSS
37
+ event.httpResponse[:contentType] = keyword_args[:response_header]
38
+ route = NewRelic::Security::Agent::Control::HTTPContext.get_context.route
39
+ if route && NewRelic::Security::Agent.agent.route_map.include?(route)
40
+ event.stacktrace << route
41
+ end
42
+ end
43
+ # In rails 5 method name keeps chaning for same api call (ex: _app_views_sqli_sqlinjectionattackcase_html_erb__1999281606898621405_2624809100).
44
+ # Hence, considering only frame absolute_path & lineno for apiId calculation.
45
+ user_frame_index = get_user_frame_index(stk)
46
+ 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/)}
48
+ user_frame_index = get_user_frame_index(stk)
49
+ 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
+ if user_frame_index != -1
51
+ event.userMethodName = stk[user_frame_index].label.to_s
52
+ event.userFileName = stk[user_frame_index].path
53
+ event.lineNumber = stk[user_frame_index].lineno
54
+ else
55
+ event.sourceMethod = stk[0].label.to_s
56
+ event.userMethodName = stk[0].label.to_s
57
+ event.userFileName = stk[0].path
58
+ event.lineNumber = stk[0].lineno
59
+ end
60
+ event.stacktrace = stk[0..user_frame_index].map(&:to_s)
61
+ 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
+ end
65
+ event
66
+ rescue Exception => exception
67
+ NewRelic::Security::Agent.logger.error "Exception in event collector: #{exception.inspect} #{exception.backtrace}"
68
+ NewRelic::Security::Agent.agent.event_processor.send_critical_message(exception.message, "SEVERE", caller_locations[0].to_s, Thread.current.name, exception)
69
+ if NewRelic::Security::Agent::Utils.is_IAST_request?(event.httpRequest[:headers])
70
+ NewRelic::Security::Agent.agent.iast_event_stats.error_count.increment
71
+ else
72
+ NewRelic::Security::Agent.agent.rasp_event_stats.error_count.increment
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def get_user_frame_index(stk)
79
+ return -1 if NewRelic::Security::Agent.config[:app_root].nil?
80
+ stk.each_with_index do |val, index|
81
+ return index if val.path.start_with?(NewRelic::Security::Agent.config[:app_root])
82
+ end
83
+ return -1
84
+ end
85
+
86
+ def calculate_api_id(stk, method, route)
87
+ stk << route if route
88
+ ::Digest::SHA256.hexdigest("#{stk.join(PIPE)}|#{method}").to_s
89
+ rescue Exception => e
90
+ NewRelic::Security::Agent.logger.error "Exception in calculate_api_id : #{e} #{e.backtrace}"
91
+ nil
92
+ end
93
+
94
+ def find_deserialisation(event, stk)
95
+ stk.each_with_index { |val, index|
96
+ if (val.path.end_with?(MARSHAL_RB) && val.label == LOAD) || (val.path.end_with?(PSYCH_RB) && val.label == LOAD) || (val.label == MARSHAL_LOAD)
97
+ event.metaData[:triggerViaDeserialisation] = true
98
+ event.metaData[:rciMethodsCalls] = stk[0..index].collect { |frame| frame.label }
99
+ return
100
+ end
101
+ }
102
+ end
103
+
104
+ def find_rci(event, stk)
105
+ stk.each_with_index { |val, index|
106
+ if val.label == EVAL
107
+ event.metaData[:triggerViaRCI] = true
108
+ event.metaData[:rciMethodsCalls] = stk[0..index].collect { |frame| frame.label }
109
+ return
110
+ end
111
+ }
112
+ end
113
+
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,117 @@
1
+ require 'json'
2
+
3
+ module NewRelic::Security
4
+ module Agent
5
+ module Control
6
+ module ControlCommand
7
+
8
+ extend self
9
+
10
+ def handle_ic_command(message)
11
+ message_json = parse_message(message)
12
+ define_transform_keys unless message_json.respond_to?(:transform_keys)
13
+ message_object = message_json.transform_keys(&:to_sym)
14
+ return if message_object.nil?
15
+
16
+ if message_object.has_key?(:controlCommand)
17
+ case message_object[:controlCommand]
18
+ when 4
19
+
20
+ when 5
21
+ NewRelic::Security::Agent.logger.debug "Control command : '5', #{message_object}"
22
+ when 6
23
+
24
+ when 7
25
+ NewRelic::Security::Agent.logger.debug "Control command : '7', #{message_object}"
26
+ when 10
27
+ NewRelic::Security::Agent.logger.debug "Control command : '10', #{message_object}"
28
+ when 11
29
+ NewRelic::Security::Agent.logger.debug "Control command : '11', #{message_object.to_json}"
30
+ NewRelic::Security::Agent.config.update_port = message_object[:reflectedMetaData][LISTEN_PORT].to_i unless NewRelic::Security::Agent.config[:listen_port]
31
+ NewRelic::Security::Agent.agent.iast_client.last_fuzz_cc_timestamp = current_time_millis
32
+ fuzz_request = NewRelic::Security::Agent::Control::FuzzRequest.new(message_object[:id])
33
+ fuzz_request.request = prepare_fuzz_request(message_object)
34
+ fuzz_request.case_type = message_object[:arguments][1]
35
+ fuzz_request.reflected_metadata = message_object[:reflectedMetaData]
36
+ NewRelic::Security::Agent.agent.iast_client.pending_request_ids << message_object[:id]
37
+ NewRelic::Security::Agent.agent.iast_client.enqueue(fuzz_request)
38
+ fuzz_request = nil
39
+ when 12
40
+ NewRelic::Security::Agent.logger.info "Validator asked to reconnect(CC#12), calling reconnect_at_will"
41
+ reconnect_at_will
42
+ when 13
43
+ NewRelic::Security::Agent.logger.debug "Control command : '13', #{message_object}"
44
+ NewRelic::Security::Agent.logger.debug "Received IAST cooldown. Waiting for next : #{message_object[:data]} Seconds"
45
+ NewRelic::Security::Agent.agent.iast_client.cooldown_till_timestamp = current_time_millis + (message_object[:data] * 1000)
46
+ when 14
47
+ NewRelic::Security::Agent.logger.debug "Control command : '14', #{message_object}"
48
+ NewRelic::Security::Agent.logger.debug "Purging confirmed IAST processed records count : #{message_object[:arguments].size}"
49
+ message_object[:arguments].each { |processed_id| NewRelic::Security::Agent.agent.iast_client.completed_requests.delete(processed_id) }
50
+ when 100
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
+ when 101
55
+
56
+ when 102
57
+ NewRelic::Security::Agent.logger.error "Update policy failed at validator with error : #{message_object}"
58
+ # TODO: Apply initial policy here
59
+ when 1006
60
+ # TODO: abnormal closure in which case LC anyway have to reconnect
61
+ when 1013
62
+ # TODO: ndicates that the service is experiencing overload. A client should only connect to a different IP (when there are multiple for the target) or reconnect to the same IP upon user action.
63
+ else
64
+ NewRelic::Security::Agent.logger.error "Unrecognized control command : #{message_object}"
65
+ end
66
+ else
67
+ NewRelic::Security::Agent.logger.error "Control command is missing in IC message : #{message_object}"
68
+ end
69
+ end
70
+
71
+ def define_transform_keys
72
+ ::Hash.class_eval do
73
+ def transform_keys
74
+ result = {}
75
+ each_key do |key|
76
+ result[yield(key)] = self[key]
77
+ end
78
+ result
79
+ end
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ def parse_message(message)
86
+ JSON.parse(message)
87
+ rescue JSON::ParserError => error
88
+ NewRelic::Security::Agent.logger.error "Error in parsing IC message : #{error.inspect}"
89
+ NewRelic::Security::Agent.agent.event_processor.send_critical_message(exception.message, "SEVERE", caller_locations[0].to_s, Thread.current.name, exception)
90
+ nil
91
+ end
92
+
93
+ def reconnect_at_will
94
+ NewRelic::Security::Agent.agent.iast_client.fuzzQ.clear if NewRelic::Security::Agent.agent.iast_client
95
+ NewRelic::Security::Agent.agent.iast_client.completed_requests.clear if NewRelic::Security::Agent.agent.iast_client
96
+ NewRelic::Security::Agent.agent.iast_client.pending_request_ids.clear if NewRelic::Security::Agent.agent.iast_client
97
+ NewRelic::Security::Agent.config.disable_security
98
+ Thread.new { NewRelic::Security::Agent.agent.reconnect(0) }
99
+ end
100
+
101
+ def current_time_millis
102
+ (Time.now.to_f * 1000).to_i
103
+ end
104
+
105
+ def prepare_fuzz_request(message_object)
106
+ message_object[:arguments][0].gsub!(NR_CSEC_VALIDATOR_HOME_TMP, NewRelic::Security::Agent.config[:fuzz_dir_path])
107
+ message_object[:arguments][0].gsub!(NR_CSEC_VALIDATOR_FILE_SEPARATOR, ::File::SEPARATOR)
108
+ prepared_fuzz_request = ::JSON.parse(message_object[:arguments][0])
109
+ prepared_fuzz_request[HEADERS][NR_CSEC_PARENT_ID] = message_object[:id]
110
+ prepared_fuzz_request
111
+ rescue Exception => exception # rubocop:disable Lint/RescueException
112
+ NewRelic::Security::Agent.logger.error "Exception in preparing fuzz request : #{exception.inspect} #{exception.backtrace}"
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end