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,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