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,78 @@
1
+ # encoding: binary
2
+ # frozen_string_literal: true
3
+
4
+ module NewRelic::Security::WebSocket
5
+ module Frame
6
+ module Handler
7
+ class Handler75 < Base
8
+ # @see NewRelic::Security::WebSocket::Frame::Base#supported_frames
9
+ def supported_frames
10
+ %i[text close]
11
+ end
12
+
13
+ # @see NewRelic::Security::WebSocket::Frame::Handler::Base#encode_frame
14
+ def encode_frame
15
+ case @frame.type
16
+ when :close then "\xff\x00"
17
+ when :text then
18
+ ary = ["\x00", @frame.data, "\xff"]
19
+ ary.map { |s| s.encode('UTF-8', 'UTF-8', invalid: :replace) }
20
+ ary.join
21
+ else raise NewRelic::Security::WebSocket::Error::Frame::UnknownFrameType
22
+ end
23
+ end
24
+
25
+ # @see NewRelic::Security::WebSocket::Frame::Handler::Base#decode_frame
26
+ def decode_frame
27
+ return if @frame.data.size.zero?
28
+
29
+ pointer = 0
30
+ frame_type = @frame.data.getbyte(pointer)
31
+ pointer += 1
32
+
33
+ if (frame_type & 0x80) == 0x80
34
+ # If the high-order bit of the /frame type/ byte is set
35
+ length = 0
36
+
37
+ loop do
38
+ return unless @frame.data.getbyte(pointer)
39
+ b = @frame.data.getbyte(pointer)
40
+ pointer += 1
41
+ b_v = b & 0x7F
42
+ length = length * 128 + b_v
43
+ break unless (b & 0x80) == 0x80
44
+ end
45
+
46
+ raise NewRelic::Security::WebSocket::Error::Frame::TooLong if length > ::NewRelic::Security::WebSocket.max_frame_size
47
+
48
+ unless @frame.data.getbyte(pointer + length - 1).nil?
49
+ # Straight from spec - I'm sure this isn't crazy...
50
+ # 6. Read /length/ bytes.
51
+ # 7. Discard the read bytes.
52
+ @frame.instance_variable_set '@data', @frame.data[(pointer + length)..-1]
53
+
54
+ # If the /frame type/ is 0xFF and the /length/ was 0, then close
55
+ if length.zero?
56
+ @frame.class.new(version: @frame.version, type: :close, decoded: true)
57
+ end
58
+ end
59
+ else
60
+ # If the high-order bit of the /frame type/ byte is _not_ set
61
+
62
+ raise NewRelic::Security::WebSocket::Error::Frame::Invalid if @frame.data.getbyte(0) != 0x00
63
+
64
+ # Addition to the spec to protect against malicious requests
65
+ raise NewRelic::Security::WebSocket::Error::Frame::TooLong if @frame.data.size > ::NewRelic::Security::WebSocket.max_frame_size
66
+
67
+ msg = @frame.data.slice!(/\A\x00[^\xff]*\xff/)
68
+ if msg
69
+ msg.gsub!(/\A\x00|\xff\z/, '')
70
+ msg.force_encoding('UTF-8')
71
+ @frame.class.new(version: @frame.version, type: :text, data: msg, decoded: true)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Frame
5
+ module Handler
6
+ autoload :Base, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/handler/base"
7
+
8
+ autoload :Handler03, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/handler/handler03"
9
+ autoload :Handler04, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/handler/handler04"
10
+ autoload :Handler05, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/handler/handler05"
11
+ autoload :Handler07, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/handler/handler07"
12
+ autoload :Handler75, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/handler/handler75"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Frame
5
+ class Incoming
6
+ class Client < Incoming
7
+ def incoming_masking?
8
+ false
9
+ end
10
+
11
+ def outgoing_masking?
12
+ @handler.masking?
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Frame
5
+ class Incoming
6
+ class Server < Incoming
7
+ def incoming_masking?
8
+ @handler.masking?
9
+ end
10
+
11
+ def outgoing_masking?
12
+ false
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Frame
5
+ # Construct or parse incoming WebSocket Frame.
6
+ # @note You should NEVER use this class directly - use Client or Server subclasses instead, as they contain additional frame options(i.e. Client-side masking in draft 04)
7
+ #
8
+ # @example
9
+ # frame = NewRelic::Security::WebSocket::Frame::Incoming::Server.new(version: @handshake.version)
10
+ # frame << "\x81\x05\x48\x65\x6c\x6c\x6f\x81\x06\x77\x6f\x72\x6c\x64\x21"
11
+ # frame.next # "Hello"
12
+ # frame.next # "world!""
13
+ class Incoming < Base
14
+ autoload :Client, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/incoming/client"
15
+ autoload :Server, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/incoming/server"
16
+
17
+ def initialize(args = {})
18
+ @decoded = args[:decoded] || false
19
+ super
20
+ end
21
+
22
+ # If data is still encoded after receiving then this is false. After calling "next" you will receive
23
+ # another instance of incoming frame, but with data decoded - this function will return true and
24
+ # to_s will return frame content instead of raw data.
25
+ # @return [Boolean] If frame already decoded?
26
+ def decoded?
27
+ @decoded
28
+ end
29
+
30
+ # Add provided string as raw incoming frame.
31
+ # @param data [String] Raw frame
32
+ def <<(data)
33
+ @data << data
34
+ end
35
+
36
+ # Return next complete frame.
37
+ # This function will merge together splitted frames and return as combined content.
38
+ # Check #error if nil received to check for eventual parsing errors
39
+ # @return [NewRelic::Security::WebSocket::Frame::Incoming] Single incoming frame or nil if no complete frame is available.
40
+ def next
41
+ @handler.decode_frame unless decoded?
42
+ end
43
+ rescue_method :next
44
+
45
+ # If decoded then this will return frame content. Otherwise it will return raw frame.
46
+ # @return [String] Data of frame
47
+ def to_s
48
+ @data
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Frame
5
+ class Outgoing
6
+ class Client < Outgoing
7
+ def incoming_masking?
8
+ false
9
+ end
10
+
11
+ def outgoing_masking?
12
+ @handler.masking?
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Frame
5
+ class Outgoing
6
+ class Server < Outgoing
7
+ def incoming_masking?
8
+ @handler.masking?
9
+ end
10
+
11
+ def outgoing_masking?
12
+ false
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Frame
5
+ # Construct or parse outgoing WebSocket Frame.
6
+ # @note You should NEVER use this class directly - use Client or Server subclasses instead, as they contain additional frame options(i.e. Client-side masking in draft 04)
7
+ #
8
+ # @example
9
+ # frame = NewRelic::Security::WebSocket::Frame::Outgoing::Server.new(version: @handshake.version, data: "Hello", type: :text)
10
+ # frame.to_s # "\x81\x05\x48\x65\x6c\x6c\x6f"
11
+ class Outgoing < Base
12
+ autoload :Client, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/outgoing/client"
13
+ autoload :Server, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/outgoing/server"
14
+
15
+ # Is selected type supported by current draft version?
16
+ # @return [Boolean] true if frame type is supported
17
+ def supported?
18
+ support_type?
19
+ end
20
+
21
+ # Should current frame be sent? Exclude empty frames etc.
22
+ # @return [Boolean] true if frame should be sent
23
+ def require_sending?
24
+ !error?
25
+ end
26
+
27
+ # Return raw frame formatted for sending.
28
+ def to_s
29
+ raise NewRelic::Security::WebSocket::Error::Frame::UnknownFrameType unless supported?
30
+ @handler.encode_frame
31
+ end
32
+ rescue_method :to_s
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Frame
5
+ autoload :Base, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/base"
6
+ autoload :Data, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/data"
7
+ autoload :Handler, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/handler"
8
+ autoload :Incoming, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/incoming"
9
+ autoload :Outgoing, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/outgoing"
10
+ end
11
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Handshake
5
+ # @abstract Subclass and override to implement custom handshakes
6
+ class Base
7
+ include ExceptionHandler
8
+ include NiceInspect
9
+
10
+ attr_reader :host, :path, :query,
11
+ :state, :version, :secure,
12
+ :headers, :protocols
13
+
14
+ # Initialize new WebSocket Handshake and set it's state to :new
15
+ def initialize(args = {})
16
+ args.each do |k, v|
17
+ value = begin
18
+ v.dup
19
+ rescue TypeError
20
+ v
21
+ end
22
+ instance_variable_set("@#{k}", value)
23
+ end
24
+
25
+ @state = :new
26
+ @handler = nil
27
+
28
+ @data = String.new('')
29
+ @headers ||= {}
30
+ @protocols ||= []
31
+ end
32
+
33
+ # @abstract Add data to handshake
34
+ def <<(data)
35
+ @data << data
36
+ end
37
+
38
+ # Return textual representation of handshake request or response
39
+ # @return [String] text of response
40
+ def to_s
41
+ @handler ? @handler.to_s : ''
42
+ end
43
+ rescue_method :to_s, return: ''
44
+
45
+ # Is parsing of data finished?
46
+ # @return [Boolena] True if request was completely parsed or error occured. False otherwise
47
+ def finished?
48
+ @state == :finished || @state == :error
49
+ end
50
+
51
+ # Is parsed data valid?
52
+ # @return [Boolean] False if some errors occured. Reason for error could be found in error method
53
+ def valid?
54
+ finished? && @error.nil? && @handler && @handler.valid?
55
+ end
56
+ rescue_method :valid?, return: false
57
+
58
+ # @abstract Should send data after parsing is finished?
59
+ def should_respond?
60
+ raise NotImplementedError
61
+ end
62
+
63
+ # Data left from parsing. Sometimes data that doesn't belong to handshake are added - use this method to retrieve them.
64
+ # @return [String] String if some data are available. Nil otherwise
65
+ def leftovers
66
+ (@leftovers.to_s.split("\n", reserved_leftover_lines + 1)[reserved_leftover_lines] || '').strip
67
+ end
68
+
69
+ # Return default port for protocol (80 for ws, 443 for wss)
70
+ def default_port
71
+ secure ? 443 : 80
72
+ end
73
+
74
+ # Check if provided port is a default one
75
+ def default_port?
76
+ port == default_port
77
+ end
78
+
79
+ def port
80
+ @port || default_port
81
+ end
82
+
83
+ # URI of request.
84
+ # @return [String] Full URI with protocol
85
+ # @example
86
+ # @handshake.uri #=> "ws://example.com/path?query=true"
87
+ def uri
88
+ uri = String.new(secure ? 'wss://' : 'ws://')
89
+ uri << host
90
+ uri << ":#{port}" unless default_port?
91
+ uri << path
92
+ uri << "?#{query}" if query
93
+ uri
94
+ end
95
+
96
+ private
97
+
98
+ # Number of lines after header that should be handled as belonging to handshake. Any data after those lines will be handled as leftovers.
99
+ # @return [Integer] Number of lines
100
+ def reserved_leftover_lines
101
+ 0
102
+ end
103
+
104
+ # Changes state to error and sets error message
105
+ # @param [String] message Error message to set
106
+ def error=(message)
107
+ @state = :error
108
+ super
109
+ end
110
+
111
+ HEADER = /^([^:]+):\s*(.+)$/
112
+
113
+ # Parse data imported to handshake and sets state to finished if necessary.
114
+ # @return [Boolean] True if finished parsing. False if not all data received yet.
115
+ def parse_data
116
+ header, @leftovers = @data.split("\r\n\r\n", 2)
117
+ return false unless @leftovers # The whole header has not been received yet.
118
+
119
+ lines = header.split("\r\n")
120
+
121
+ first_line = lines.shift
122
+ parse_first_line(first_line)
123
+
124
+ lines.each do |line|
125
+ h = HEADER.match(line)
126
+ next unless h # Skip any invalid headers
127
+ key = h[1].strip.downcase
128
+ val = h[2].strip
129
+ # If the header is already set and refers to the websocket protocol, append the new value
130
+ if @headers.key?(key) && key =~ /^(sec-)?websocket-protocol$/
131
+ @headers[key] << ", #{val}"
132
+ else
133
+ @headers[key] = val
134
+ end
135
+ end
136
+
137
+ @state = :finished
138
+ true
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+
5
+ module NewRelic::Security::WebSocket
6
+ module Handshake
7
+ # Construct or parse a client WebSocket handshake.
8
+ #
9
+ # @example
10
+ # @handshake = NewRelic::Security::WebSocket::Handshake::Client.new(url: 'ws://example.com')
11
+ #
12
+ # # Create request
13
+ # @handshake.to_s # GET /demo HTTP/1.1
14
+ # # Upgrade: websocket
15
+ # # Connection: Upgrade
16
+ # # Host: example.com
17
+ # # Origin: http://example.com
18
+ # # Sec-WebSocket-Version: 13
19
+ # # Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
20
+ #
21
+ # # Parse server response
22
+ # @handshake << <<EOF
23
+ # HTTP/1.1 101 Switching Protocols\r
24
+ # Upgrade: websocket\r
25
+ # Connection: Upgrade\r
26
+ # Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r
27
+ # \r
28
+ # EOF
29
+ #
30
+ # # All data received?
31
+ # @handshake.finished?
32
+ #
33
+ # # No parsing errors?
34
+ # @handshake.valid?
35
+ #
36
+ class Client < Base
37
+ attr_reader :origin, :headers
38
+
39
+ # Initialize new WebSocket Client
40
+ #
41
+ # @param [Hash] args Arguments for client
42
+ #
43
+ # @option args [String] :host Host of request. Required if no :url param was provided.
44
+ # @option args [String] :origin Origin of request. Optional, should be used mostly by browsers. Default: nil
45
+ # @option args [String] :path Path of request. Should start with '/'. Default: '/'
46
+ # @option args [Integer] :port Port of request. Default: nil
47
+ # @option args [String] :query. Query for request. Should be in format "aaa=bbb&ccc=ddd"
48
+ # @option args [Boolean] :secure Defines protocol to use. If true then wss://, otherwise ws://. This option will not change default port - it should be handled by programmer.
49
+ # @option args [String] :url URL of request. Must by in format like ws://example.com/path?query=true. Every part of this url will be overriden by more specific arguments.
50
+ # @option args [String] :uri Alias to :url
51
+ # @option args [Array<String>] :protocols An array of supported sub-protocols
52
+ # @option args [Integer] :version Version of WebSocket to use. Default: 13 (this is version from RFC)
53
+ # @option args [Hash] :headers HTTP headers to use in the handshake
54
+ #
55
+ # @example
56
+ # Websocket::Handshake::Client.new(url: "ws://example.com/path?query=true")
57
+ def initialize(args = {})
58
+ super
59
+
60
+ if @url || @uri
61
+ uri = URI.parse(@url || @uri)
62
+ @secure ||= (uri.scheme == 'wss')
63
+ @host ||= uri.host
64
+ @port ||= uri.port || default_port
65
+ @path ||= uri.path
66
+ @query ||= uri.query
67
+ end
68
+
69
+ @path = '/' if @path.nil? || @path.empty?
70
+ @version ||= DEFAULT_VERSION
71
+
72
+ raise NewRelic::Security::WebSocket::Error::Handshake::NoHostProvided unless @host
73
+
74
+ include_version
75
+ end
76
+ rescue_method :initialize
77
+
78
+ # Add text of response from Server. This method will parse content immediately and update state and error(if neccessary)
79
+ #
80
+ # @param [String] data Data to add
81
+ #
82
+ # @example
83
+ # @handshake << <<EOF
84
+ # HTTP/1.1 101 Switching Protocols
85
+ # Upgrade: websocket
86
+ # Connection: Upgrade
87
+ # Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
88
+ #
89
+ # EOF
90
+ def <<(data)
91
+ super
92
+ parse_data
93
+ end
94
+ rescue_method :<<
95
+
96
+ # Should send content to server after finished parsing?
97
+ # @return [Boolean] false
98
+ def should_respond?
99
+ false
100
+ end
101
+
102
+ private
103
+
104
+ # Include set of methods for selected protocol version
105
+ # @return [Boolean] false if protocol number is unknown, otherwise true
106
+ def include_version
107
+ @handler = case @version
108
+ when 75 then Handler::Client75.new(self)
109
+ when 76, 0 then Handler::Client76.new(self)
110
+ when 1..3 then Handler::Client01.new(self)
111
+ when 4..10 then Handler::Client04.new(self)
112
+ when 11..17 then Handler::Client11.new(self)
113
+ else raise NewRelic::Security::WebSocket::Error::Handshake::UnknownVersion
114
+ end
115
+ end
116
+
117
+ FIRST_LINE = %r{^HTTP\/1\.1 (\d{3})[\w\s]*$}
118
+
119
+ # Parse first line of Server response.
120
+ # @param [String] line Line to parse
121
+ # @return [Boolean] True if parsed correctly. False otherwise
122
+ def parse_first_line(line)
123
+ line_parts = line.match(FIRST_LINE)
124
+ raise NewRelic::Security::WebSocket::Error::Handshake::InvalidHeader unless line_parts
125
+ status = line_parts[1]
126
+ raise NewRelic::Security::WebSocket::Error::Handshake::InvalidStatusCode unless status == '101'
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Handshake
5
+ module Handler
6
+ # This class and it's descendants are included in client or server handshake in order to extend basic functionality
7
+ class Base
8
+ def initialize(handshake)
9
+ @handshake = handshake
10
+ end
11
+
12
+ # @see NewRelic::Security::WebSocket::Handshake::Base#to_s
13
+ def to_s
14
+ result = [header_line]
15
+ handshake_keys.each do |key|
16
+ result << key.join(': ')
17
+ end
18
+ result << ''
19
+ result << finishing_line
20
+ result.join("\r\n")
21
+ end
22
+
23
+ def valid?
24
+ true
25
+ end
26
+
27
+ private
28
+
29
+ # Set first line of text representation according to specification.
30
+ # @return [String] First line of HTTP header
31
+ def header_line
32
+ ''
33
+ end
34
+
35
+ # Set handshake headers. Provided as array because some protocol version require specific order of fields.
36
+ # @return [Array] List of headers as arrays [key, value]
37
+ def handshake_keys
38
+ []
39
+ end
40
+
41
+ # Set data to send after headers. In most cases it will be blank data.
42
+ # @return [String] data
43
+ def finishing_line
44
+ ''
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Handshake
5
+ module Handler
6
+ class Client < Base
7
+ private
8
+
9
+ # @see NewRelic::Security::WebSocket::Handshake::Handler::Base#header_line
10
+ def header_line
11
+ path = @handshake.path
12
+ path += '?' + @handshake.query if @handshake.query
13
+ "GET #{path} HTTP/1.1"
14
+ end
15
+
16
+ # @see NewRelic::Security::WebSocket::Handshake::Handler::Base#header_handshake_keys
17
+ def handshake_keys
18
+ super + @handshake.headers.to_a
19
+ end
20
+
21
+ # Verify if received header matches with one of the sent ones
22
+ # @return [Boolean] True if matching. False otherwise(appropriate error is set)
23
+ def verify_protocol
24
+ return true if supported_protocols.empty?
25
+ protos = provided_protocols & supported_protocols
26
+ raise NewRelic::Security::WebSocket::Error::Handshake::UnsupportedProtocol if protos.empty?
27
+ true
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest/md5'
4
+
5
+ module NewRelic::Security::WebSocket
6
+ module Handshake
7
+ module Handler
8
+ class Client01 < Client76
9
+ private
10
+
11
+ # @see NewRelic::Security::WebSocket::Handshake::Handler::Base#handshake_keys
12
+ def handshake_keys
13
+ keys = super
14
+ keys << ['Sec-WebSocket-Draft', @handshake.version]
15
+ keys
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end