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,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest/sha1'
4
+ require 'base64'
5
+
6
+ module NewRelic::Security::WebSocket
7
+ module Handshake
8
+ module Handler
9
+ class Client04 < Client
10
+ # @see NewRelic::Security::WebSocket::Handshake::Base#valid?
11
+ def valid?
12
+ super && verify_accept && verify_protocol
13
+ end
14
+
15
+ private
16
+
17
+ # @see NewRelic::Security::WebSocket::Handshake::Handler::Base#handshake_keys
18
+ def handshake_keys
19
+ keys = [
20
+ %w[Upgrade websocket],
21
+ %w[Connection Upgrade]
22
+ ]
23
+ host = @handshake.host
24
+ host += ":#{@handshake.port}" unless @handshake.default_port?
25
+ keys << ['Host', host]
26
+ keys += super
27
+ keys << ['Sec-WebSocket-Origin', @handshake.origin] if @handshake.origin
28
+ keys << ['Sec-WebSocket-Version', @handshake.version]
29
+ keys << ['Sec-WebSocket-Key', key]
30
+ keys << ['Sec-WebSocket-Protocol', @handshake.protocols.join(', ')] if @handshake.protocols.any?
31
+ keys
32
+ end
33
+
34
+ # Sec-WebSocket-Key value
35
+ # @return [String] key
36
+ def key
37
+ @key ||= Base64.encode64((1..16).map { rand(255).chr } * '').strip
38
+ end
39
+
40
+ # Value of Sec-WebSocket-Accept that should be delivered back by server
41
+ # @return [Sering] accept
42
+ def accept
43
+ @accept ||= Base64.encode64(Digest::SHA1.digest(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')).strip
44
+ end
45
+
46
+ # Verify if received header Sec-WebSocket-Accept matches generated one.
47
+ # @return [Boolean] True if accept is matching. False otherwise(appropriate error is set)
48
+ def verify_accept
49
+ raise NewRelic::Security::WebSocket::Error::Handshake::InvalidAuthentication unless @handshake.headers['sec-websocket-accept'] == accept
50
+ true
51
+ end
52
+
53
+ def supported_protocols
54
+ @handshake.protocols
55
+ end
56
+
57
+ def provided_protocols
58
+ @handshake.headers['sec-websocket-protocol'].to_s.split(/ *, */)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Handshake
5
+ module Handler
6
+ class Client11 < Client04
7
+ private
8
+
9
+ # @see NewRelic::Security::WebSocket::Handshake::Handler::Base#handshake_keys
10
+ def handshake_keys
11
+ super.collect do |key_pair|
12
+ if key_pair[0] == 'Sec-WebSocket-Origin'
13
+ ['Origin', key_pair[1]]
14
+ else
15
+ key_pair
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Handshake
5
+ module Handler
6
+ class Client75 < Client
7
+ # @see NewRelic::Security::WebSocket::Handshake::Base#valid?
8
+ def valid?
9
+ super && verify_protocol
10
+ end
11
+
12
+ private
13
+
14
+ # @see NewRelic::Security::WebSocket::Handshake::Handler::Base#handshake_keys
15
+ def handshake_keys
16
+ keys = [
17
+ %w[Upgrade WebSocket],
18
+ %w[Connection Upgrade]
19
+ ]
20
+ host = @handshake.host
21
+ host += ":#{@handshake.port}" unless @handshake.default_port?
22
+ keys << ['Host', host]
23
+ keys << ['Origin', @handshake.origin] if @handshake.origin
24
+ keys << ['WebSocket-Protocol', @handshake.protocols.first] if @handshake.protocols.any?
25
+ keys += super
26
+ keys
27
+ end
28
+
29
+ def supported_protocols
30
+ Array(@handshake.protocols.first)
31
+ end
32
+
33
+ def provided_protocols
34
+ Array(@handshake.headers['websocket-protocol'].to_s.strip)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest/md5'
4
+
5
+ module NewRelic::Security::WebSocket
6
+ module Handshake
7
+ module Handler
8
+ class Client76 < Client75
9
+ # @see NewRelic::Security::WebSocket::Handshake::Base#valid?
10
+ def valid?
11
+ super && verify_challenge && verify_protocol
12
+ end
13
+
14
+ private
15
+
16
+ # @see NewRelic::Security::WebSocket::Handshake::Base#reserved_leftover_lines
17
+ def reserved_leftover_lines
18
+ 1
19
+ end
20
+
21
+ # @see NewRelic::Security::WebSocket::Handshake::Handler::Base#handshake_keys
22
+ def handshake_keys
23
+ keys = super
24
+ keys << ['Sec-WebSocket-Key1', key1]
25
+ keys << ['Sec-WebSocket-Key2', key2]
26
+ keys
27
+ end
28
+
29
+ # @see NewRelic::Security::WebSocket::Handshake::Handler::Base#finishing_line
30
+ def finishing_line
31
+ key3
32
+ end
33
+
34
+ # Sec-WebSocket-Key1 value
35
+ # @return [String] key
36
+ def key1
37
+ @key1 ||= generate_key(:key1)
38
+ end
39
+
40
+ # Sec-WebSocket-Key2 value
41
+ # @return [String] key
42
+ def key2
43
+ @key2 ||= generate_key(:key2)
44
+ end
45
+
46
+ # Value of third key, sent in body
47
+ # @return [String] key
48
+ def key3
49
+ @key3 ||= generate_key3
50
+ end
51
+
52
+ # Expected challenge that should be sent by server
53
+ # @return [String] challenge
54
+ def challenge
55
+ return @challenge if defined?(@challenge)
56
+ key1 && key2
57
+ sum = [@key1_number].pack('N*') +
58
+ [@key2_number].pack('N*') +
59
+ key3
60
+
61
+ @challenge = Digest::MD5.digest(sum).strip
62
+ end
63
+
64
+ # Verify if challenge sent by server match generated one
65
+ # @return [Boolena] True if challenge matches, false otherwise(sets appropriate error)
66
+ def verify_challenge
67
+ raise NewRelic::Security::WebSocket::Error::Handshake::InvalidAuthentication unless @handshake.leftovers == challenge
68
+ true
69
+ end
70
+
71
+ NOISE_CHARS = ("\x21".."\x2f").to_a + ("\x3a".."\x7e").to_a
72
+
73
+ # Generate Sec-WebSocket-Key1 and Sec-WebSocket-Key2
74
+ # @param key [String] name of key. Will be used to set number variable needed later. Valid values: key1, key2
75
+ # @return [String] generated key
76
+ def generate_key(key)
77
+ spaces = rand(1..12)
78
+ max = 0xffffffff / spaces
79
+ number = rand(max + 1)
80
+ instance_variable_set("@#{key}_number", number)
81
+ key = (number * spaces).to_s
82
+ rand(1..12).times do
83
+ char = NOISE_CHARS[rand(NOISE_CHARS.size)]
84
+ pos = rand(key.size + 1)
85
+ key[pos...pos] = char
86
+ end
87
+ spaces.times do
88
+ pos = 1 + rand(key.size - 1)
89
+ key[pos...pos] = ' '
90
+ end
91
+ key
92
+ end
93
+
94
+ # Generate third key
95
+ def generate_key3
96
+ [rand(0x100000000)].pack('N') + [rand(0x100000000)].pack('N')
97
+ end
98
+
99
+ def provided_protocols
100
+ Array(@handshake.headers['sec-websocket-protocol'].to_s.strip)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Handshake
5
+ module Handler
6
+ class Server < Base
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest/sha1'
4
+ require 'base64'
5
+
6
+ module NewRelic::Security::WebSocket
7
+ module Handshake
8
+ module Handler
9
+ class Server04 < Server
10
+ # @see NewRelic::Security::WebSocket::Handshake::Base#valid?
11
+ def valid?
12
+ super && verify_key
13
+ end
14
+
15
+ private
16
+
17
+ # @see NewRelic::Security::WebSocket::Handshake::Handler::Base#header_line
18
+ def header_line
19
+ 'HTTP/1.1 101 Switching Protocols'
20
+ end
21
+
22
+ # @see NewRelic::Security::WebSocket::Handshake::Handler::Base#handshake_keys
23
+ def handshake_keys
24
+ [
25
+ %w[Upgrade websocket],
26
+ %w[Connection Upgrade],
27
+ ['Sec-WebSocket-Accept', signature]
28
+ ] + protocol
29
+ end
30
+
31
+ # Signature of response, created from client request Sec-WebSocket-Key
32
+ # @return [String] signature
33
+ def signature
34
+ return unless key
35
+ string_to_sign = "#{key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
36
+ Base64.encode64(Digest::SHA1.digest(string_to_sign)).chomp
37
+ end
38
+
39
+ def verify_key
40
+ raise NewRelic::Security::WebSocket::Error::Handshake::InvalidAuthentication unless key
41
+ true
42
+ end
43
+
44
+ def key
45
+ @handshake.headers['sec-websocket-key']
46
+ end
47
+
48
+ def protocol
49
+ return [] unless @handshake.headers.key?('sec-websocket-protocol')
50
+ protos = @handshake.headers['sec-websocket-protocol'].split(/ *, */) & @handshake.protocols
51
+ [['Sec-WebSocket-Protocol', protos.first]]
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Handshake
5
+ module Handler
6
+ class Server75 < Server
7
+ private
8
+
9
+ def headers
10
+ {
11
+ origin: 'WebSocket-Origin',
12
+ location: 'WebSocket-Location',
13
+ protocol: 'WebSocket-Protocol'
14
+ }.freeze
15
+ end
16
+
17
+ # @see NewRelic::Security::WebSocket::Handshake::Handler::Base#header_line
18
+ def header_line
19
+ 'HTTP/1.1 101 Web Socket Protocol Handshake'
20
+ end
21
+
22
+ # @see NewRelic::Security::WebSocket::Handshake::Handler::Base#handshake_keys
23
+ def handshake_keys
24
+ [
25
+ %w[Upgrade WebSocket],
26
+ %w[Connection Upgrade],
27
+ [headers[:origin], @handshake.headers['origin']],
28
+ [headers[:location], @handshake.uri]
29
+ ] + protocol
30
+ end
31
+
32
+ def protocol
33
+ return [] unless @handshake.headers.key?(headers[:protocol].downcase)
34
+ proto = @handshake.headers[headers[:protocol].downcase]
35
+ [[headers[:protocol], @handshake.protocols.include?(proto) ? proto : nil]]
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest/md5'
4
+
5
+ module NewRelic::Security::WebSocket
6
+ module Handshake
7
+ module Handler
8
+ class Server76 < Server75
9
+ # @see NewRelic::Security::WebSocket::Handshake::Base#valid?
10
+ def valid?
11
+ super && !finishing_line.nil?
12
+ end
13
+
14
+ private
15
+
16
+ def headers
17
+ {
18
+ origin: 'Sec-WebSocket-Origin',
19
+ location: 'Sec-WebSocket-Location',
20
+ protocol: 'Sec-WebSocket-Protocol'
21
+ }.freeze
22
+ end
23
+
24
+ # @see NewRelic::Security::WebSocket::Handshake::Base#reserved_leftover_lines
25
+ def reserved_leftover_lines
26
+ 1
27
+ end
28
+
29
+ # @see NewRelic::Security::WebSocket::Handshake::Handler::Base#header_line
30
+ def header_line
31
+ 'HTTP/1.1 101 WebSocket Protocol Handshake'
32
+ end
33
+
34
+ # @see NewRelic::Security::WebSocket::Handshake::Handler::Base#finishing_line
35
+ def finishing_line
36
+ @finishing_line ||= challenge_response
37
+ end
38
+
39
+ # Response to client challenge from request Sec-WebSocket-Key1, Sec-WebSocket-Key2 and leftovers
40
+ # @return [String] Challenge response or nil if error occured
41
+ def challenge_response
42
+ # Refer to 5.2 4-9 of the draft 76
43
+ first = numbers_over_spaces(@handshake.headers['sec-websocket-key1'].to_s)
44
+ second = numbers_over_spaces(@handshake.headers['sec-websocket-key2'].to_s)
45
+ third = @handshake.leftovers
46
+
47
+ sum = [first].pack('N*') +
48
+ [second].pack('N*') +
49
+ third
50
+ Digest::MD5.digest(sum)
51
+ end
52
+
53
+ # Calculate numbers over spaces, according to spec 5.2
54
+ # @param [String] string Key to parse
55
+ # @return [Integer] Result of calculations or nil if error occured
56
+ def numbers_over_spaces(string)
57
+ numbers = string.scan(/[0-9]/).join.to_i
58
+
59
+ spaces = string.scan(/ /).size
60
+ # As per 5.2.5, abort the connection if spaces are zero.
61
+ raise NewRelic::Security::WebSocket::Error::Handshake::InvalidAuthentication if spaces.zero?
62
+
63
+ # As per 5.2.6, abort if numbers is not an integral multiple of spaces
64
+ raise NewRelic::Security::WebSocket::Error::Handshake::InvalidAuthentication if numbers % spaces != 0
65
+
66
+ quotient = numbers / spaces
67
+
68
+ raise NewRelic::Security::WebSocket::Error::Handshake::InvalidAuthentication if quotient > 2**32 - 1
69
+
70
+ quotient
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Handshake
5
+ module Handler
6
+ autoload :Base, "#{NewRelic::Security::WebSocket::ROOT}/websocket/handshake/handler/base"
7
+
8
+ autoload :Client, "#{NewRelic::Security::WebSocket::ROOT}/websocket/handshake/handler/client"
9
+ autoload :Client01, "#{NewRelic::Security::WebSocket::ROOT}/websocket/handshake/handler/client01"
10
+ autoload :Client04, "#{NewRelic::Security::WebSocket::ROOT}/websocket/handshake/handler/client04"
11
+ autoload :Client11, "#{NewRelic::Security::WebSocket::ROOT}/websocket/handshake/handler/client11"
12
+ autoload :Client75, "#{NewRelic::Security::WebSocket::ROOT}/websocket/handshake/handler/client75"
13
+ autoload :Client76, "#{NewRelic::Security::WebSocket::ROOT}/websocket/handshake/handler/client76"
14
+
15
+ autoload :Server, "#{NewRelic::Security::WebSocket::ROOT}/websocket/handshake/handler/server"
16
+ autoload :Server04, "#{NewRelic::Security::WebSocket::ROOT}/websocket/handshake/handler/server04"
17
+ autoload :Server75, "#{NewRelic::Security::WebSocket::ROOT}/websocket/handshake/handler/server75"
18
+ autoload :Server76, "#{NewRelic::Security::WebSocket::ROOT}/websocket/handshake/handler/server76"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Handshake
5
+ # Construct or parse a server WebSocket handshake.
6
+ #
7
+ # @example
8
+ # handshake = NewRelic::Security::WebSocket::Handshake::Server.new
9
+ #
10
+ # # Parse client request
11
+ # @handshake << <<EOF
12
+ # GET /demo HTTP/1.1\r
13
+ # Upgrade: websocket\r
14
+ # Connection: Upgrade\r
15
+ # Host: example.com\r
16
+ # Origin: http://example.com\r
17
+ # Sec-WebSocket-Version: 13\r
18
+ # Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r
19
+ # \r
20
+ # EOF
21
+ #
22
+ # # All data received?
23
+ # @handshake.finished?
24
+ #
25
+ # # No parsing errors?
26
+ # @handshake.valid?
27
+ #
28
+ # # Create response
29
+ # @handshake.to_s # HTTP/1.1 101 Switching Protocols
30
+ # # Upgrade: websocket
31
+ # # Connection: Upgrade
32
+ # # Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
33
+ #
34
+ class Server < Base
35
+ # Initialize new WebSocket Server
36
+ #
37
+ # @param [Hash] args Arguments for server
38
+ #
39
+ # @option args [Boolean] :secure If true then server will use wss:// protocol
40
+ # @option args [Array<String>] :protocols an array of supported sub-protocols
41
+ #
42
+ # @example
43
+ # Websocket::Handshake::Server.new(secure: true)
44
+ def initialize(args = {})
45
+ super
46
+ @secure ||= false
47
+ end
48
+
49
+ # Add text of request from Client. This method will parse content immediately and update version, state and error(if neccessary)
50
+ #
51
+ # @param [String] data Data to add
52
+ #
53
+ # @example
54
+ # @handshake << <<EOF
55
+ # GET /demo HTTP/1.1
56
+ # Upgrade: websocket
57
+ # Connection: Upgrade
58
+ # Host: example.com
59
+ # Origin: http://example.com
60
+ # Sec-WebSocket-Version: 13
61
+ # Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
62
+ #
63
+ # EOF
64
+ def <<(data)
65
+ super
66
+ set_version if parse_data
67
+ end
68
+ rescue_method :<<
69
+
70
+ # Parse the request from a rack environment
71
+ # @param env Rack Environment
72
+ #
73
+ # @example
74
+ # @handshake.from_rack(env)
75
+ def from_rack(env)
76
+ @headers = env.select { |key, _value| key.to_s.start_with? 'HTTP_' }.each_with_object({}) do |tuple, memo|
77
+ key, value = tuple
78
+ memo[key.gsub(/\AHTTP_/, '').tr('_', '-').downcase] = value
79
+ end
80
+
81
+ @path = env['REQUEST_PATH']
82
+ @query = env['QUERY_STRING']
83
+
84
+ set_version
85
+
86
+ # Passenger is blocking on read
87
+ # Unicorn doesn't support readpartial
88
+ # Maybe someone is providing even plain string?
89
+ # Better safe than sorry...
90
+ if @version == 76
91
+ input = env['rack.input']
92
+ @leftovers = if input.respond_to?(:readpartial)
93
+ input.readpartial
94
+ elsif input.respond_to?(:read)
95
+ input.read
96
+ else
97
+ input.to_s
98
+ end
99
+ end
100
+
101
+ @state = :finished
102
+ end
103
+
104
+ # Parse the request from hash
105
+ # @param hash Hash to import data
106
+ # @option hash [Hash] :headers HTTP headers of request, downcased
107
+ # @option hash [String] :path Path for request(without host and query string)
108
+ # @option hash [String] :query Query string for request
109
+ # @option hash [String] :body Body of request(if exists)
110
+ #
111
+ # @example
112
+ # @handshake.from_hash(hash)
113
+ def from_hash(hash)
114
+ @headers = hash[:headers] || {}
115
+ @path = hash[:path] || '/'
116
+ @query = hash[:query] || ''
117
+ @leftovers = hash[:body]
118
+
119
+ set_version
120
+ @state = :finished
121
+ end
122
+
123
+ # Should send content to client after finished parsing?
124
+ # @return [Boolean] true
125
+ def should_respond?
126
+ true
127
+ end
128
+
129
+ # Host of server according to client header
130
+ # @return [String] host
131
+ def host
132
+ @host || @headers['host'].to_s.split(':')[0].to_s
133
+ end
134
+
135
+ # Port of server according to client header
136
+ # @return [Integer] port
137
+ def port
138
+ (@port || @headers['host'].to_s.split(':')[1] || default_port).to_i
139
+ end
140
+
141
+ private
142
+
143
+ # Set version of protocol basing on client requets. AFter cotting method calls include_version.
144
+ def set_version
145
+ @version = @headers['sec-websocket-version'].to_i if @headers['sec-websocket-version']
146
+ @version ||= @headers['sec-websocket-draft'].to_i if @headers['sec-websocket-draft']
147
+ @version ||= 76 if @headers['sec-websocket-key1']
148
+ @version ||= 75
149
+ include_version
150
+ end
151
+
152
+ # Include set of methods for selected protocol version
153
+ # @return [Boolean] false if protocol number is unknown, otherwise true
154
+ def include_version
155
+ @handler = case @version
156
+ when 75 then Handler::Server75.new(self)
157
+ when 76, 0..3 then Handler::Server76.new(self)
158
+ when 4..17 then Handler::Server04.new(self)
159
+ else raise NewRelic::Security::WebSocket::Error::Handshake::UnknownVersion
160
+ end
161
+ end
162
+
163
+ PATH = %r{^(\w+) (\/[^\s]*) HTTP\/1\.1$}
164
+
165
+ # Parse first line of Client response.
166
+ # @param [String] line Line to parse
167
+ # @return [Boolean] True if parsed correctly. False otherwise
168
+ def parse_first_line(line)
169
+ line_parts = line.match(PATH)
170
+ raise NewRelic::Security::WebSocket::Error::Handshake::InvalidHeader unless line_parts
171
+ method = line_parts[1].strip
172
+ raise NewRelic::Security::WebSocket::Error::Handshake::GetRequestRequired unless method == 'GET'
173
+
174
+ resource_name = line_parts[2].strip
175
+ @path, @query = resource_name.split('?', 2)
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Handshake
5
+ autoload :Base, "#{NewRelic::Security::WebSocket::ROOT}/websocket/handshake/base"
6
+ autoload :Client, "#{NewRelic::Security::WebSocket::ROOT}/websocket/handshake/client"
7
+ autoload :Handler, "#{NewRelic::Security::WebSocket::ROOT}/websocket/handshake/handler"
8
+ autoload :Server, "#{NewRelic::Security::WebSocket::ROOT}/websocket/handshake/server"
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module NiceInspect
5
+ # Recreate inspect as #to_s will be overwritten
6
+ def inspect
7
+ vars = instance_variables.map { |v| "#{v}=#{instance_variable_get(v).inspect}" }.join(', ')
8
+ insp = Kernel.format("#{self.class}:0x%08x", __id__)
9
+ "<#{insp} #{vars}>"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebSocket
4
+ VERSION = '1.2.9'.freeze
5
+ end