newrelic_security 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- data/.github/ISSUE_TEMPLATE/enhancement.md +27 -0
- data/.github/actions/simplecov-report/LICENSE +22 -0
- data/.github/actions/simplecov-report/README.md +36 -0
- data/.github/actions/simplecov-report/__tests__/.keep +0 -0
- data/.github/actions/simplecov-report/__tests__/main.test.ts +3 -0
- data/.github/actions/simplecov-report/action.yml +25 -0
- data/.github/actions/simplecov-report/dist/index.js +10238 -0
- data/.github/actions/simplecov-report/dummy_coverage/.last_run.json +5 -0
- data/.github/actions/simplecov-report/jest.config.js +11 -0
- data/.github/actions/simplecov-report/package.json +51 -0
- data/.github/actions/simplecov-report/src/main.ts +54 -0
- data/.github/actions/simplecov-report/src/report.ts +28 -0
- data/.github/actions/simplecov-report/tsconfig.json +12 -0
- data/.github/workflows/pr_ci.yml +77 -0
- data/.github/workflows/release.yml +51 -0
- data/.github/workflows/repolinter.yml +31 -0
- data/.github/workflows/rubocop.yml +17 -0
- data/.github/workflows/scripts/rubygems-authenticate.py +13 -0
- data/.github/workflows/scripts/rubygems-publish.rb +33 -0
- data/.gitignore +72 -0
- data/.rubocop.yml +9 -0
- data/.rubocop_todo.yml +1414 -0
- data/.simplecov +16 -0
- data/CHANGELOG.md +69 -0
- data/CONTRIBUTING.md +22 -0
- data/Gemfile +6 -0
- data/Gemfile_test +58 -0
- data/LICENSE +43 -0
- data/README.md +133 -0
- data/README_agent.md +44 -0
- data/Rakefile +28 -0
- data/THIRD_PARTY_NOTICES.md +36 -0
- data/lib/newrelic_security/agent/agent.rb +109 -0
- data/lib/newrelic_security/agent/configuration/default_source.rb +8 -0
- data/lib/newrelic_security/agent/configuration/environment_source.rb +8 -0
- data/lib/newrelic_security/agent/configuration/manager.rb +178 -0
- data/lib/newrelic_security/agent/configuration/manual_source.rb +8 -0
- data/lib/newrelic_security/agent/configuration/server_source.rb +8 -0
- data/lib/newrelic_security/agent/configuration/yaml_source.rb +8 -0
- data/lib/newrelic_security/agent/control/app_info.rb +132 -0
- data/lib/newrelic_security/agent/control/application_url_mappings.rb +66 -0
- data/lib/newrelic_security/agent/control/collector.rb +117 -0
- data/lib/newrelic_security/agent/control/control_command.rb +117 -0
- data/lib/newrelic_security/agent/control/critical_message.rb +58 -0
- data/lib/newrelic_security/agent/control/event.rb +149 -0
- data/lib/newrelic_security/agent/control/event_counter.rb +28 -0
- data/lib/newrelic_security/agent/control/event_processor.rb +134 -0
- data/lib/newrelic_security/agent/control/event_stats.rb +26 -0
- data/lib/newrelic_security/agent/control/event_subscriber.rb +28 -0
- data/lib/newrelic_security/agent/control/exit_event.rb +38 -0
- data/lib/newrelic_security/agent/control/fuzz_request.rb +18 -0
- data/lib/newrelic_security/agent/control/grpc_context.rb +57 -0
- data/lib/newrelic_security/agent/control/health_check.rb +136 -0
- data/lib/newrelic_security/agent/control/http_context.rb +73 -0
- data/lib/newrelic_security/agent/control/iast_client.rb +151 -0
- data/lib/newrelic_security/agent/control/iast_data_transfer_request.rb +32 -0
- data/lib/newrelic_security/agent/control/reflected_xss.rb +258 -0
- data/lib/newrelic_security/agent/control/websocket_client.rb +131 -0
- data/lib/newrelic_security/agent/logging/init_logger.rb +91 -0
- data/lib/newrelic_security/agent/logging/logger.rb +92 -0
- data/lib/newrelic_security/agent/logging/null_logger.rb +21 -0
- data/lib/newrelic_security/agent/resources/cert.pem +50 -0
- data/lib/newrelic_security/agent/utils/agent_utils.rb +219 -0
- data/lib/newrelic_security/agent.rb +57 -0
- data/lib/newrelic_security/constants.rb +67 -0
- data/lib/newrelic_security/instrumentation-security/active_record/mysql2_adapter/chain.rb +70 -0
- data/lib/newrelic_security/instrumentation-security/active_record/mysql2_adapter/instrumentation.rb +187 -0
- data/lib/newrelic_security/instrumentation-security/active_record/mysql2_adapter/prepend.rb +54 -0
- data/lib/newrelic_security/instrumentation-security/active_record/postgresql_adapter/chain.rb +60 -0
- data/lib/newrelic_security/instrumentation-security/active_record/postgresql_adapter/instrumentation.rb +143 -0
- data/lib/newrelic_security/instrumentation-security/active_record/postgresql_adapter/prepend.rb +48 -0
- data/lib/newrelic_security/instrumentation-security/active_record/sqlite3_adapter/chain.rb +72 -0
- data/lib/newrelic_security/instrumentation-security/active_record/sqlite3_adapter/instrumentation.rb +187 -0
- data/lib/newrelic_security/instrumentation-security/active_record/sqlite3_adapter/prepend.rb +54 -0
- data/lib/newrelic_security/instrumentation-security/async-http/chain.rb +21 -0
- data/lib/newrelic_security/instrumentation-security/async-http/instrumentation.rb +46 -0
- data/lib/newrelic_security/instrumentation-security/async-http/prepend.rb +16 -0
- data/lib/newrelic_security/instrumentation-security/curb/chain.rb +26 -0
- data/lib/newrelic_security/instrumentation-security/curb/instrumentation.rb +52 -0
- data/lib/newrelic_security/instrumentation-security/curb/prepend.rb +18 -0
- data/lib/newrelic_security/instrumentation-security/dir/chain.rb +42 -0
- data/lib/newrelic_security/instrumentation-security/dir/instrumentation.rb +102 -0
- data/lib/newrelic_security/instrumentation-security/dir/prepend.rb +28 -0
- data/lib/newrelic_security/instrumentation-security/ethon/chain.rb +53 -0
- data/lib/newrelic_security/instrumentation-security/ethon/instrumentation.rb +122 -0
- data/lib/newrelic_security/instrumentation-security/ethon/prepend.rb +39 -0
- data/lib/newrelic_security/instrumentation-security/excon/chain.rb +23 -0
- data/lib/newrelic_security/instrumentation-security/excon/instrumentation.rb +44 -0
- data/lib/newrelic_security/instrumentation-security/excon/prepend.rb +17 -0
- data/lib/newrelic_security/instrumentation-security/file/chain.rb +34 -0
- data/lib/newrelic_security/instrumentation-security/file/instrumentation.rb +62 -0
- data/lib/newrelic_security/instrumentation-security/file/prepend.rb +22 -0
- data/lib/newrelic_security/instrumentation-security/grape/chain.rb +42 -0
- data/lib/newrelic_security/instrumentation-security/grape/instrumentation.rb +56 -0
- data/lib/newrelic_security/instrumentation-security/grape/prepend.rb +30 -0
- data/lib/newrelic_security/instrumentation-security/grpc/client/chain.rb +47 -0
- data/lib/newrelic_security/instrumentation-security/grpc/client/instrumentation.rb +37 -0
- data/lib/newrelic_security/instrumentation-security/grpc/client/prepend.rb +36 -0
- data/lib/newrelic_security/instrumentation-security/grpc/server/chain.rb +62 -0
- data/lib/newrelic_security/instrumentation-security/grpc/server/instrumentation.rb +65 -0
- data/lib/newrelic_security/instrumentation-security/grpc/server/prepend.rb +46 -0
- data/lib/newrelic_security/instrumentation-security/httpclient/chain.rb +30 -0
- data/lib/newrelic_security/instrumentation-security/httpclient/instrumentation.rb +82 -0
- data/lib/newrelic_security/instrumentation-security/httpclient/prepend.rb +22 -0
- data/lib/newrelic_security/instrumentation-security/httprb/chain.rb +21 -0
- data/lib/newrelic_security/instrumentation-security/httprb/instrumentation.rb +44 -0
- data/lib/newrelic_security/instrumentation-security/httprb/prepend.rb +16 -0
- data/lib/newrelic_security/instrumentation-security/httpx/chain.rb +23 -0
- data/lib/newrelic_security/instrumentation-security/httpx/instrumentation.rb +51 -0
- data/lib/newrelic_security/instrumentation-security/httpx/prepend.rb +18 -0
- data/lib/newrelic_security/instrumentation-security/instrumentation_loader.rb +50 -0
- data/lib/newrelic_security/instrumentation-security/instrumentation_utils.rb +165 -0
- data/lib/newrelic_security/instrumentation-security/io/chain.rb +113 -0
- data/lib/newrelic_security/instrumentation-security/io/instrumentation.rb +300 -0
- data/lib/newrelic_security/instrumentation-security/io/prepend.rb +86 -0
- data/lib/newrelic_security/instrumentation-security/kernel/chain.rb +65 -0
- data/lib/newrelic_security/instrumentation-security/kernel/instrumentation.rb +167 -0
- data/lib/newrelic_security/instrumentation-security/kernel/prepend.rb +50 -0
- data/lib/newrelic_security/instrumentation-security/mongo/chain.rb +106 -0
- data/lib/newrelic_security/instrumentation-security/mongo/instrumentation.rb +273 -0
- data/lib/newrelic_security/instrumentation-security/mongo/prepend.rb +77 -0
- data/lib/newrelic_security/instrumentation-security/mysql2/chain.rb +53 -0
- data/lib/newrelic_security/instrumentation-security/mysql2/instrumentation.rb +84 -0
- data/lib/newrelic_security/instrumentation-security/mysql2/prepend.rb +37 -0
- data/lib/newrelic_security/instrumentation-security/net_http/chain.rb +21 -0
- data/lib/newrelic_security/instrumentation-security/net_http/instrumentation.rb +60 -0
- data/lib/newrelic_security/instrumentation-security/net_http/prepend.rb +16 -0
- data/lib/newrelic_security/instrumentation-security/net_ldap/chain.rb +21 -0
- data/lib/newrelic_security/instrumentation-security/net_ldap/instrumentation.rb +42 -0
- data/lib/newrelic_security/instrumentation-security/net_ldap/prepend.rb +16 -0
- data/lib/newrelic_security/instrumentation-security/nokogiri/chain.rb +46 -0
- data/lib/newrelic_security/instrumentation-security/nokogiri/instrumentation.rb +36 -0
- data/lib/newrelic_security/instrumentation-security/nokogiri/prepend.rb +31 -0
- data/lib/newrelic_security/instrumentation-security/padrino/chain.rb +26 -0
- data/lib/newrelic_security/instrumentation-security/padrino/instrumentation.rb +42 -0
- data/lib/newrelic_security/instrumentation-security/padrino/prepend.rb +20 -0
- data/lib/newrelic_security/instrumentation-security/patron/chain.rb +23 -0
- data/lib/newrelic_security/instrumentation-security/patron/instrumentation.rb +50 -0
- data/lib/newrelic_security/instrumentation-security/patron/prepend.rb +18 -0
- data/lib/newrelic_security/instrumentation-security/pg/chain.rb +49 -0
- data/lib/newrelic_security/instrumentation-security/pg/instrumentation.rb +102 -0
- data/lib/newrelic_security/instrumentation-security/pg/prepend.rb +36 -0
- data/lib/newrelic_security/instrumentation-security/pty/chain.rb +31 -0
- data/lib/newrelic_security/instrumentation-security/pty/instrumentation.rb +52 -0
- data/lib/newrelic_security/instrumentation-security/pty/prepend.rb +22 -0
- data/lib/newrelic_security/instrumentation-security/rails/chain.rb +46 -0
- data/lib/newrelic_security/instrumentation-security/rails/instrumentation.rb +67 -0
- data/lib/newrelic_security/instrumentation-security/rails/prepend.rb +33 -0
- data/lib/newrelic_security/instrumentation-security/roda/chain.rb +22 -0
- data/lib/newrelic_security/instrumentation-security/roda/instrumentation.rb +41 -0
- data/lib/newrelic_security/instrumentation-security/roda/prepend.rb +16 -0
- data/lib/newrelic_security/instrumentation-security/sinatra/chain.rb +29 -0
- data/lib/newrelic_security/instrumentation-security/sinatra/instrumentation.rb +49 -0
- data/lib/newrelic_security/instrumentation-security/sinatra/prepend.rb +21 -0
- data/lib/newrelic_security/instrumentation-security/sqlite3/chain.rb +79 -0
- data/lib/newrelic_security/instrumentation-security/sqlite3/instrumentation.rb +164 -0
- data/lib/newrelic_security/instrumentation-security/sqlite3/prepend.rb +56 -0
- data/lib/newrelic_security/newrelic-security-api/api.rb +72 -0
- data/lib/newrelic_security/version.rb +5 -0
- data/lib/newrelic_security/websocket-client-simple/client.rb +128 -0
- data/lib/newrelic_security/websocket-client-simple/event_emitter.rb +72 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/error.rb +129 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/exception_handler.rb +32 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/base.rb +62 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/data.rb +49 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler/base.rb +41 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler/handler03.rb +224 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler/handler04.rb +18 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler/handler05.rb +15 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler/handler07.rb +78 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler/handler75.rb +78 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler.rb +15 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/incoming/client.rb +17 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/incoming/server.rb +17 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/incoming.rb +52 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/outgoing/client.rb +17 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/outgoing/server.rb +17 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/outgoing.rb +35 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame.rb +11 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/base.rb +142 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/client.rb +130 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/base.rb +49 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/client.rb +32 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/client01.rb +20 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/client04.rb +63 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/client11.rb +22 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/client75.rb +39 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/client76.rb +105 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/server.rb +10 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/server04.rb +56 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/server75.rb +40 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/server76.rb +75 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler.rb +21 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/server.rb +179 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake.rb +10 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/nice_inspect.rb +12 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/version.rb +5 -0
- data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket.rb +50 -0
- data/lib/newrelic_security.rb +6 -0
- data/lib/tasks/all.rb +8 -0
- data/lib/tasks/coverage_report.rake +27 -0
- data/newrelic_security.gemspec +51 -0
- 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
|
data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/exception_handler.rb
ADDED
@@ -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
|