newrelic_security 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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