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,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ class Error < RuntimeError
5
+ class Frame < NewRelic::Security::WebSocket::Error
6
+ class ControlFramePayloadTooLong < NewRelic::Security::WebSocket::Error::Frame
7
+ def message
8
+ :control_frame_payload_too_long
9
+ end
10
+ end
11
+
12
+ class DataFrameInsteadContinuation < NewRelic::Security::WebSocket::Error::Frame
13
+ def message
14
+ :data_frame_instead_continuation
15
+ end
16
+ end
17
+
18
+ class FragmentedControlFrame < NewRelic::Security::WebSocket::Error::Frame
19
+ def message
20
+ :fragmented_control_frame
21
+ end
22
+ end
23
+
24
+ class Invalid < NewRelic::Security::WebSocket::Error::Frame
25
+ def message
26
+ :invalid_frame
27
+ end
28
+ end
29
+
30
+ class InvalidPayloadEncoding < NewRelic::Security::WebSocket::Error::Frame
31
+ def message
32
+ :invalid_payload_encoding
33
+ end
34
+ end
35
+
36
+ class MaskTooShort < NewRelic::Security::WebSocket::Error::Frame
37
+ def message
38
+ :mask_is_too_short
39
+ end
40
+ end
41
+
42
+ class ReservedBitUsed < NewRelic::Security::WebSocket::Error::Frame
43
+ def message
44
+ :reserved_bit_used
45
+ end
46
+ end
47
+
48
+ class TooLong < NewRelic::Security::WebSocket::Error::Frame
49
+ def message
50
+ :frame_too_long
51
+ end
52
+ end
53
+
54
+ class UnexpectedContinuationFrame < NewRelic::Security::WebSocket::Error::Frame
55
+ def message
56
+ :unexpected_continuation_frame
57
+ end
58
+ end
59
+
60
+ class UnknownFrameType < NewRelic::Security::WebSocket::Error::Frame
61
+ def message
62
+ :unknown_frame_type
63
+ end
64
+ end
65
+
66
+ class UnknownOpcode < NewRelic::Security::WebSocket::Error::Frame
67
+ def message
68
+ :unknown_opcode
69
+ end
70
+ end
71
+
72
+ class UnknownCloseCode < NewRelic::Security::WebSocket::Error::Frame
73
+ def message
74
+ :unknown_close_code
75
+ end
76
+ end
77
+
78
+ class UnknownVersion < NewRelic::Security::WebSocket::Error::Frame
79
+ def message
80
+ :unknown_protocol_version
81
+ end
82
+ end
83
+ end
84
+
85
+ class Handshake < NewRelic::Security::WebSocket::Error
86
+ class GetRequestRequired < NewRelic::Security::WebSocket::Error::Handshake
87
+ def message
88
+ :get_request_required
89
+ end
90
+ end
91
+
92
+ class InvalidAuthentication < NewRelic::Security::WebSocket::Error::Handshake
93
+ def message
94
+ :invalid_handshake_authentication
95
+ end
96
+ end
97
+
98
+ class InvalidHeader < NewRelic::Security::WebSocket::Error::Handshake
99
+ def message
100
+ :invalid_header
101
+ end
102
+ end
103
+
104
+ class UnsupportedProtocol < NewRelic::Security::WebSocket::Error::Handshake
105
+ def message
106
+ :unsupported_protocol
107
+ end
108
+ end
109
+
110
+ class InvalidStatusCode < NewRelic::Security::WebSocket::Error::Handshake
111
+ def message
112
+ :invalid_status_code
113
+ end
114
+ end
115
+
116
+ class NoHostProvided < NewRelic::Security::WebSocket::Error::Handshake
117
+ def message
118
+ :no_host_provided
119
+ end
120
+ end
121
+
122
+ class UnknownVersion < NewRelic::Security::WebSocket::Error::Handshake
123
+ def message
124
+ :unknown_protocol_version
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module ExceptionHandler
5
+ attr_accessor :error
6
+
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+ # Rescue from NewRelic::Security::WebSocket::Error errors.
13
+ #
14
+ # @param [String] method_name Name of method that should be wrapped and rescued
15
+ # @param [Hash] options Options for rescue
16
+ #
17
+ # @option options [Any] :return Value that should be returned instead of raised error
18
+ def rescue_method(method_name, options = {})
19
+ define_method "#{method_name}_with_rescue" do |*args|
20
+ begin
21
+ send("#{method_name}_without_rescue", *args)
22
+ rescue NewRelic::Security::WebSocket::Error => e
23
+ self.error = e.message.to_sym
24
+ NewRelic::Security::WebSocket.should_raise ? raise : options[:return]
25
+ end
26
+ end
27
+ alias_method "#{method_name}_without_rescue", method_name
28
+ alias_method method_name, "#{method_name}_with_rescue"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Frame
5
+ # @abstract Subclass and override to implement custom frames
6
+ class Base
7
+ include ExceptionHandler
8
+ include NiceInspect
9
+
10
+ attr_reader :type, :version, :error
11
+ attr_accessor :data, :code
12
+
13
+ # Initialize frame
14
+ # @param args [Hash] Arguments for frame
15
+ # @option args [String] :data default data for frame
16
+ # @option args [String] :type Type of frame - available types are "text", "binary", "ping", "pong" and "close"(support depends on draft version)
17
+ # @option args [Integer] :code Code for close frame. Supported by drafts > 05.
18
+ # @option args [Integer] :version Version of draft. Currently supported version are 75, 76 and 00-13.
19
+ def initialize(args = {})
20
+ @type = args[:type].to_sym if args[:type]
21
+ @code = args[:code]
22
+ @data = Data.new(args[:data].to_s)
23
+ @version = args[:version] || DEFAULT_VERSION
24
+ @handler = nil
25
+ include_version
26
+ end
27
+ rescue_method :initialize
28
+
29
+ # Check if some errors occured
30
+ # @return [Boolean] True if error is set
31
+ def error?
32
+ !@error.nil?
33
+ end
34
+
35
+ # Is selected type supported for selected handler?
36
+ def support_type?
37
+ @handler.supported_frames.include?(@type)
38
+ end
39
+
40
+ # Implement in submodules
41
+ def supported_frames
42
+ raise NotImplementedError
43
+ end
44
+
45
+ private
46
+
47
+ # Include set of methods for selected protocol version
48
+ # @return [Boolean] false if protocol number is unknown, otherwise true
49
+ def include_version
50
+ @handler = case @version
51
+ when 75..76 then Handler::Handler75.new(self)
52
+ when 0..2 then Handler::Handler75.new(self)
53
+ when 3 then Handler::Handler03.new(self)
54
+ when 4 then Handler::Handler04.new(self)
55
+ when 5..6 then Handler::Handler05.new(self)
56
+ when 7..13 then Handler::Handler07.new(self)
57
+ else raise NewRelic::Security::WebSocket::Error::Frame::UnknownVersion
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Frame
5
+ class Data < String
6
+ def initialize(*args)
7
+ super(*convert_args(args))
8
+ @masking_key = nil
9
+ end
10
+
11
+ def <<(*args)
12
+ super(*convert_args(args))
13
+ end
14
+
15
+ # Convert all arguments to ASCII-8BIT for easier traversing
16
+ def convert_args(args)
17
+ args.collect { |arg| arg.dup.force_encoding('ASCII-8BIT') }
18
+ end
19
+
20
+ # Extract mask from 4 first bytes according to spec
21
+ def set_mask
22
+ raise NewRelic::Security::WebSocket::Error::Frame::MaskTooShort if bytesize < 4
23
+ @masking_key = self[0..3].bytes.to_a
24
+ end
25
+
26
+ # Remove mask flag - it will still be present in payload
27
+ def unset_mask
28
+ @masking_key = nil
29
+ end
30
+
31
+ # Extract `count` bytes starting from `start_index` and unmask it if needed.
32
+ def getbytes(start_index, count)
33
+ data = self[start_index, count]
34
+ data = mask(data.bytes.to_a, @masking_key).pack('C*') if @masking_key
35
+ data
36
+ end
37
+
38
+ # Mask whole payload using mask key
39
+ def mask(payload, mask)
40
+ return mask_native(payload, mask) if respond_to?(:mask_native)
41
+ result = []
42
+ payload.each_with_index do |byte, i|
43
+ result[i] = byte ^ mask[i % 4]
44
+ end
45
+ result
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Frame
5
+ module Handler
6
+ class Base
7
+ def initialize(frame)
8
+ @frame = frame
9
+ end
10
+
11
+ # Convert data to raw frame ready to send to client
12
+ # @return [String] Encoded frame
13
+ def encode_frame
14
+ raise NotImplementedError
15
+ end
16
+
17
+ # Convert raw data to decoded frame
18
+ # @return [NewRelic::Security::WebSocket::Frame::Incoming] Frame if found, nil otherwise
19
+ def decode_frame
20
+ raise NotImplementedError
21
+ end
22
+
23
+ private
24
+
25
+ # Check if frame is one of control frames
26
+ # @param [Symbol] frame_type Frame type
27
+ # @return [Boolean] True if given frame type is control frame
28
+ def control_frame?(frame_type)
29
+ !%i[text binary continuation].include?(frame_type)
30
+ end
31
+
32
+ # Check if frame is one of data frames
33
+ # @param [Symbol] frame_type Frame type
34
+ # @return [Boolean] True if given frame type is data frame
35
+ def data_frame?(frame_type)
36
+ %i[text binary].include?(frame_type)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,224 @@
1
+ # encoding: binary
2
+ # frozen_string_literal: true
3
+
4
+ require 'securerandom'
5
+
6
+ module NewRelic::Security::WebSocket
7
+ module Frame
8
+ module Handler
9
+ class Handler03 < Base
10
+ # Hash of frame names and it's opcodes
11
+ FRAME_TYPES = {
12
+ continuation: 0,
13
+ close: 1,
14
+ ping: 2,
15
+ pong: 3,
16
+ text: 4,
17
+ binary: 5
18
+ }.freeze
19
+
20
+ # Hash of frame opcodes and it's names
21
+ FRAME_TYPES_INVERSE = FRAME_TYPES.invert.freeze
22
+
23
+ def initialize(frame)
24
+ super
25
+ @application_data_buffer = nil
26
+ end
27
+
28
+ # @see NewRelic::Security::WebSocket::Frame::Base#supported_frames
29
+ def supported_frames
30
+ %i[text binary close ping pong]
31
+ end
32
+
33
+ # @see NewRelic::Security::WebSocket::Frame::Handler::Base#encode_frame
34
+ def encode_frame
35
+ frame = if @frame.outgoing_masking?
36
+ masking_key = SecureRandom.random_bytes(4)
37
+ tmp_data = Data.new(masking_key + @frame.data)
38
+ tmp_data.set_mask
39
+ masking_key + tmp_data.getbytes(4, tmp_data.size)
40
+ else
41
+ @frame.data
42
+ end
43
+
44
+ encode_header + frame
45
+ end
46
+
47
+ # @see NewRelic::Security::WebSocket::Frame::Handler::Base#decode_frame
48
+ def decode_frame
49
+ while @frame.data.size > 1
50
+ valid_header, more, frame_type, mask, payload_length = decode_header
51
+ return unless valid_header
52
+
53
+ application_data = decode_payload(payload_length, mask)
54
+
55
+ if more
56
+ decode_continuation_frame(application_data, frame_type)
57
+ elsif frame_type == :continuation
58
+ return decode_finish_continuation_frame(application_data)
59
+ else
60
+ raise(NewRelic::Security::WebSocket::Error::Frame::InvalidPayloadEncoding) if frame_type == :text && !application_data.valid_encoding?
61
+ return @frame.class.new(version: @frame.version, type: frame_type, data: application_data, decoded: true)
62
+ end
63
+ end
64
+ nil
65
+ end
66
+
67
+ # Allow turning on or off masking
68
+ def masking?
69
+ false
70
+ end
71
+
72
+ private
73
+
74
+ # This allows flipping the more bit to fin for draft 04
75
+ def fin
76
+ false
77
+ end
78
+
79
+ # Convert frame type name to opcode
80
+ # @param [Symbol] frame_type Frame type name
81
+ # @return [Integer] opcode or nil
82
+ # @raise [NewRelic::Security::WebSocket::Error] if frame opcode is not known
83
+ def type_to_opcode(frame_type)
84
+ FRAME_TYPES[frame_type] || raise(NewRelic::Security::WebSocket::Error::Frame::UnknownFrameType)
85
+ end
86
+
87
+ # Convert frame opcode to type name
88
+ # @param [Integer] opcode Opcode
89
+ # @return [Symbol] Frame type name or nil
90
+ # @raise [NewRelic::Security::WebSocket::Error] if frame type name is not known
91
+ def opcode_to_type(opcode)
92
+ FRAME_TYPES_INVERSE[opcode] || raise(NewRelic::Security::WebSocket::Error::Frame::UnknownOpcode)
93
+ end
94
+
95
+ def encode_header
96
+ mask = @frame.outgoing_masking? ? 0b10000000 : 0b00000000
97
+
98
+ output = String.new('')
99
+ output << (type_to_opcode(@frame.type) | (fin ? 0b10000000 : 0b00000000)) # since more, rsv1-3 are 0 and 0x80 for Draft 4
100
+ output << encode_payload_length(@frame.data.size, mask)
101
+ output
102
+ end
103
+
104
+ def encode_payload_length(length, mask)
105
+ output = String.new('')
106
+ if length <= 125
107
+ output << (length | mask) # since rsv4 is 0
108
+ elsif length < 65_536 # write 2 byte length
109
+ output << (126 | mask)
110
+ output << [length].pack('n')
111
+ else # write 8 byte length
112
+ output << (127 | mask)
113
+ output << [length >> 32, length & 0xFFFFFFFF].pack('NN')
114
+ end
115
+ output
116
+ end
117
+
118
+ def decode_header
119
+ more, frame_type = decode_first_byte
120
+ header_length, payload_length, mask = decode_second_byte(frame_type)
121
+ return unless header_length
122
+
123
+ # Compute the expected frame length
124
+ frame_length = header_length + payload_length
125
+ frame_length += 4 if mask
126
+
127
+ raise(NewRelic::Security::WebSocket::Error::Frame::TooLong) if frame_length > NewRelic::Security::WebSocket.max_frame_size
128
+
129
+ # Check buffer size
130
+ return unless buffer_exists?(frame_length) # Buffer incomplete
131
+
132
+ # Remove frame header
133
+ @frame.data.slice!(0...header_length)
134
+
135
+ [true, more, frame_type, mask, payload_length]
136
+ end
137
+
138
+ def buffer_exists?(buffer_number)
139
+ !@frame.data.getbyte(buffer_number - 1).nil?
140
+ end
141
+
142
+ def decode_first_byte
143
+ first_byte = @frame.data.getbyte(0)
144
+
145
+ raise(NewRelic::Security::WebSocket::Error::Frame::ReservedBitUsed) if first_byte & 0b01110000 != 0b00000000
146
+
147
+ more = ((first_byte & 0b10000000) == 0b10000000) ^ fin
148
+ frame_type = opcode_to_type first_byte & 0b00001111
149
+
150
+ raise(NewRelic::Security::WebSocket::Error::Frame::FragmentedControlFrame) if more && control_frame?(frame_type)
151
+ raise(NewRelic::Security::WebSocket::Error::Frame::DataFrameInsteadContinuation) if data_frame?(frame_type) && !@application_data_buffer.nil?
152
+
153
+ [more, frame_type]
154
+ end
155
+
156
+ def decode_second_byte(frame_type)
157
+ second_byte = @frame.data.getbyte(1)
158
+
159
+ mask = @frame.incoming_masking? && (second_byte & 0b10000000) == 0b10000000
160
+ length = second_byte & 0b01111111
161
+
162
+ raise(NewRelic::Security::WebSocket::Error::Frame::ControlFramePayloadTooLong) if length > 125 && control_frame?(frame_type)
163
+
164
+ header_length, payload_length = decode_payload_length(length)
165
+
166
+ [header_length, payload_length, mask]
167
+ end
168
+
169
+ def decode_payload_length(length)
170
+ case length
171
+ when 127 # Length defined by 8 bytes
172
+ # Check buffer size
173
+ return unless buffer_exists?(10) # Buffer incomplete
174
+
175
+ # Only using the last 4 bytes for now, till I work out how to
176
+ # unpack 8 bytes. I'm sure 4GB frames will do for now :)
177
+ [10, @frame.data.getbytes(6, 4).unpack('N').first]
178
+ when 126 # Length defined by 2 bytes
179
+ # Check buffer size
180
+ return unless buffer_exists?(4) # Buffer incomplete
181
+
182
+ [4, @frame.data.getbytes(2, 2).unpack('n').first]
183
+ else
184
+ [2, length]
185
+ end
186
+ end
187
+
188
+ def decode_payload(payload_length, mask)
189
+ pointer = 0
190
+
191
+ # Read application data (unmasked if required)
192
+ @frame.data.set_mask if mask
193
+ pointer += 4 if mask
194
+ payload = @frame.data.getbytes(pointer, payload_length)
195
+ payload.force_encoding('UTF-8')
196
+ pointer += payload_length
197
+ @frame.data.unset_mask if mask
198
+
199
+ # Throw away data up to pointer
200
+ @frame.data.slice!(0...pointer)
201
+
202
+ payload
203
+ end
204
+
205
+ def decode_continuation_frame(application_data, frame_type)
206
+ @application_data_buffer ||= String.new('')
207
+ @application_data_buffer << application_data
208
+ @frame_type ||= frame_type
209
+ end
210
+
211
+ def decode_finish_continuation_frame(application_data)
212
+ raise(NewRelic::Security::WebSocket::Error::Frame::UnexpectedContinuationFrame) unless @frame_type
213
+ @application_data_buffer << application_data
214
+ # Test valid UTF-8 encoding
215
+ raise(NewRelic::Security::WebSocket::Error::Frame::InvalidPayloadEncoding) if @frame_type == :text && !@application_data_buffer.valid_encoding?
216
+ message = @frame.class.new(version: @frame.version, type: @frame_type, data: @application_data_buffer, decoded: true)
217
+ @application_data_buffer = nil
218
+ @frame_type = nil
219
+ message
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: binary
2
+ # frozen_string_literal: true
3
+
4
+ module NewRelic::Security::WebSocket
5
+ module Frame
6
+ module Handler
7
+ class Handler04 < Handler03
8
+ private
9
+
10
+ # The only difference between draft 03 framing and draft 04 framing is
11
+ # that the MORE bit has been changed to a FIN bit
12
+ def fin
13
+ true
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: binary
2
+ # frozen_string_literal: true
3
+
4
+ module NewRelic::Security::WebSocket
5
+ module Frame
6
+ module Handler
7
+ class Handler05 < Handler04
8
+ # Since handler 5 masking should be enabled by default
9
+ def masking?
10
+ true
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -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 Handler07 < Handler05
8
+ # Hash of frame names and it's opcodes
9
+ FRAME_TYPES = {
10
+ continuation: 0,
11
+ text: 1,
12
+ binary: 2,
13
+ close: 8,
14
+ ping: 9,
15
+ pong: 10
16
+ }.freeze
17
+
18
+ # Hash of frame opcodes and it's names
19
+ FRAME_TYPES_INVERSE = FRAME_TYPES.invert.freeze
20
+
21
+ def encode_frame
22
+ if @frame.type == :close
23
+ code = @frame.code || 1000
24
+ raise NewRelic::Security::WebSocket::Error::Frame::UnknownCloseCode unless valid_code?(code)
25
+ @frame.data = Data.new([code].pack('n') + @frame.data.to_s)
26
+ @frame.code = nil
27
+ end
28
+ super
29
+ end
30
+
31
+ def decode_frame
32
+ result = super
33
+ if close_code?(result)
34
+ code = result.data.slice!(0..1)
35
+ result.code = code.unpack('n').first
36
+ raise NewRelic::Security::WebSocket::Error::Frame::UnknownCloseCode unless valid_code?(result.code)
37
+ raise NewRelic::Security::WebSocket::Error::Frame::InvalidPayloadEncoding unless valid_encoding?(result.data)
38
+ end
39
+ result
40
+ end
41
+
42
+ private
43
+
44
+ def valid_code?(code)
45
+ [1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011].include?(code) || (3000..4999).cover?(code)
46
+ end
47
+
48
+ def valid_encoding?(data)
49
+ return true if data.nil?
50
+ data.encode('UTF-8')
51
+ true
52
+ rescue StandardError
53
+ false
54
+ end
55
+
56
+ def close_code?(frame)
57
+ frame && frame.type == :close && !frame.data.empty?
58
+ end
59
+
60
+ # Convert frame type name to opcode
61
+ # @param [Symbol] frame_type Frame type name
62
+ # @return [Integer] opcode or nil
63
+ # @raise [NewRelic::Security::WebSocket::Error] if frame opcode is not known
64
+ def type_to_opcode(frame_type)
65
+ FRAME_TYPES[frame_type] || raise(NewRelic::Security::WebSocket::Error::Frame::UnknownFrameType)
66
+ end
67
+
68
+ # Convert frame opcode to type name
69
+ # @param [Integer] opcode Opcode
70
+ # @return [Symbol] Frame type name or nil
71
+ # @raise [NewRelic::Security::WebSocket::Error] if frame type name is not known
72
+ def opcode_to_type(opcode)
73
+ FRAME_TYPES_INVERSE[opcode] || raise(NewRelic::Security::WebSocket::Error::Frame::UnknownOpcode)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end