arachni-rpc-em 0.1.2 → 0.1.3dev1
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.
- data/CHANGELOG.md +9 -3
- data/README.md +9 -6
- data/Rakefile +4 -4
- data/examples/client.EM.run.rb +8 -45
- data/examples/client.rb +17 -36
- data/examples/server.rb +16 -12
- data/lib/arachni/rpc/em/client.rb +37 -50
- data/lib/arachni/rpc/em/em.rb +1 -1
- data/lib/arachni/rpc/em/protocol.rb +33 -4
- data/lib/arachni/rpc/em/server.rb +41 -49
- data/lib/arachni/rpc/em/ssl.rb +8 -11
- data/lib/arachni/rpc/em/version.rb +1 -1
- data/spec/arachni/rpc/em/client_spec.rb +42 -29
- data/spec/pems/cacert.pem +35 -37
- data/spec/pems/client/cert.pem +35 -37
- data/spec/pems/client/key.pem +49 -49
- data/spec/pems/server/cert.pem +35 -37
- data/spec/pems/server/key.pem +49 -49
- data/spec/servers/with_fallback.rb +13 -0
- data/spec/servers/with_ssl_primitives.rb +4 -7
- data/spec/spec_helper.rb +1 -0
- metadata +13 -11
data/lib/arachni/rpc/em/em.rb
CHANGED
@@ -88,7 +88,7 @@ module Protocol
|
|
88
88
|
# cut them out as soon as possible
|
89
89
|
#
|
90
90
|
# don't buffer any data from unverified peers if SSL peer
|
91
|
-
#
|
91
|
+
# verification has been enabled
|
92
92
|
#
|
93
93
|
if ssl_opts? && !verified_peer? && @role == :server
|
94
94
|
e = Arachni::RPC::Exceptions::SSLPeerVerificationFailed.new( 'Could not verify peer.' )
|
@@ -107,7 +107,7 @@ module Protocol
|
|
107
107
|
while @buf.size >= 4
|
108
108
|
if @buf.size >= 4 + ( size = @buf.unpack( 'N' ).first )
|
109
109
|
@buf.slice!( 0, 4 )
|
110
|
-
receive_object(
|
110
|
+
receive_object( unserialize( @buf.slice!( 0, size ) ) )
|
111
111
|
else
|
112
112
|
break
|
113
113
|
end
|
@@ -120,7 +120,7 @@ module Protocol
|
|
120
120
|
# Will split the object in chunks of MAX_CHUNK_SIZE and transmit one at a time.
|
121
121
|
#
|
122
122
|
def send_object( obj )
|
123
|
-
data =
|
123
|
+
data = serialize( obj )
|
124
124
|
packed = [data.bytesize, data].pack( 'Na*' )
|
125
125
|
|
126
126
|
while packed
|
@@ -134,7 +134,7 @@ module Protocol
|
|
134
134
|
end
|
135
135
|
|
136
136
|
#
|
137
|
-
# Returns the preferred
|
137
|
+
# Returns the preferred based on the 'serializer' option of the server.
|
138
138
|
#
|
139
139
|
# Defaults to <i>YAML</i>.
|
140
140
|
#
|
@@ -143,9 +143,38 @@ module Protocol
|
|
143
143
|
# @see http://eventmachine.rubyforge.org/EventMachine/Protocols/ObjectProtocol.html#M000369
|
144
144
|
#
|
145
145
|
def serializer
|
146
|
+
return @client_serializer if @client_serializer
|
147
|
+
|
146
148
|
@opts[:serializer] ? @opts[:serializer] : YAML
|
147
149
|
end
|
148
150
|
|
151
|
+
def fallback_serializer
|
152
|
+
@opts[:fallback_serializer] ? @opts[:serializer] : YAML
|
153
|
+
end
|
154
|
+
|
155
|
+
def serialize( obj )
|
156
|
+
serializer.dump obj
|
157
|
+
end
|
158
|
+
|
159
|
+
def unserialize( obj )
|
160
|
+
begin
|
161
|
+
r = serializer.load( obj )
|
162
|
+
|
163
|
+
if !r.is_a?( Hash ) && @opts[:fallback_serializer]
|
164
|
+
r = @opts[:fallback_serializer].load( obj )
|
165
|
+
@client_serializer = @opts[:fallback_serializer]
|
166
|
+
end
|
167
|
+
|
168
|
+
r
|
169
|
+
rescue Exception => e
|
170
|
+
raise if !@opts[:fallback_serializer]
|
171
|
+
|
172
|
+
@client_serializer = @opts[:fallback_serializer]
|
173
|
+
|
174
|
+
@opts[:fallback_serializer].load obj
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
149
178
|
end
|
150
179
|
|
151
180
|
end
|
@@ -82,53 +82,46 @@ class Server
|
|
82
82
|
|
83
83
|
# the method call may block a little so tell EventMachine to
|
84
84
|
# stick it in its own thread.
|
85
|
-
|
86
|
-
|
87
|
-
peer = peer_ip_addr
|
85
|
+
res = Response.new
|
86
|
+
peer = peer_ip_addr
|
88
87
|
|
89
|
-
|
90
|
-
|
91
|
-
|
88
|
+
begin
|
89
|
+
# token-based authentication
|
90
|
+
authenticate!
|
92
91
|
|
93
|
-
|
94
|
-
|
92
|
+
# grab the result of the method call
|
93
|
+
res.merge!( @server.call( self ) )
|
95
94
|
|
96
|
-
|
97
|
-
|
98
|
-
|
95
|
+
# handle exceptions and convert them to a simple hash,
|
96
|
+
# ready to be passed to the client.
|
97
|
+
rescue Exception => e
|
99
98
|
|
100
|
-
|
99
|
+
type = ''
|
101
100
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
end
|
101
|
+
# if it's an RPC exception pass the type along as is
|
102
|
+
if e.rpc_exception?
|
103
|
+
type = e.class.name.split( ':' )[-1]
|
104
|
+
# otherwise set it to a RemoteExeption
|
105
|
+
else
|
106
|
+
type = 'RemoteException'
|
107
|
+
end
|
110
108
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
109
|
+
res.obj = {
|
110
|
+
'exception' => e.to_s,
|
111
|
+
'backtrace' => e.backtrace,
|
112
|
+
'type' => type
|
113
|
+
}
|
116
114
|
|
117
|
-
|
118
|
-
|
119
|
-
|
115
|
+
msg = "#{e.to_s}\n#{e.backtrace.join( "\n" )}"
|
116
|
+
@server.logger.error( 'Exception' ){ msg + " [on behalf of #{peer}]" }
|
117
|
+
end
|
120
118
|
|
121
|
-
|
122
|
-
#
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
# along with the callback ID but *only* if it wan't async
|
128
|
-
# because server.call() will have already taken care of it
|
129
|
-
#
|
130
|
-
send_response( res ) if !res.async?
|
131
|
-
# })
|
119
|
+
#
|
120
|
+
# pass the result of the RPC call back to the client
|
121
|
+
# along with the callback ID but *only* if it wan't async
|
122
|
+
# because server.call() will have already taken care of it
|
123
|
+
#
|
124
|
+
send_response( res ) if !res.async?
|
132
125
|
end
|
133
126
|
|
134
127
|
#
|
@@ -136,9 +129,6 @@ class Server
|
|
136
129
|
#
|
137
130
|
# It will raise an exception if the token doesn't check-out.
|
138
131
|
#
|
139
|
-
# @param [String] peer IP address of the client
|
140
|
-
# @param [Hash] req request
|
141
|
-
#
|
142
132
|
def authenticate!
|
143
133
|
if !valid_token?( @request.token )
|
144
134
|
|
@@ -148,7 +138,7 @@ class Server
|
|
148
138
|
msg + " [on behalf of #{peer_ip_addr}]"
|
149
139
|
}
|
150
140
|
|
151
|
-
|
141
|
+
fail InvalidToken.new( msg )
|
152
142
|
end
|
153
143
|
end
|
154
144
|
|
@@ -187,6 +177,9 @@ class Server
|
|
187
177
|
# # http://eventmachine.rubyforge.org/EventMachine/Protocols/ObjectProtocol.html#M000369
|
188
178
|
# :serializer => Marshal,
|
189
179
|
#
|
180
|
+
# # serializer to use if the first choice fails
|
181
|
+
# :fallback_serializer => YAML,
|
182
|
+
#
|
190
183
|
# #
|
191
184
|
# # In order to enable peer verification one must first provide
|
192
185
|
# # the following:
|
@@ -232,15 +225,14 @@ class Server
|
|
232
225
|
# you can just decide dynamically based on the plethora of data which Ruby provides
|
233
226
|
# by its 'Method' class.
|
234
227
|
#
|
235
|
-
# server.add_async_check
|
236
|
-
# |method|
|
228
|
+
# server.add_async_check do |method|
|
237
229
|
# #
|
238
230
|
# # Must return 'true' for async and 'false' for sync.
|
239
231
|
# #
|
240
232
|
# # Very simple check here...
|
241
233
|
# #
|
242
234
|
# 'async' == method.name.to_s.split( '_' )[0]
|
243
|
-
#
|
235
|
+
# end
|
244
236
|
#
|
245
237
|
# @param [Proc] &block
|
246
238
|
#
|
@@ -310,13 +302,13 @@ class Server
|
|
310
302
|
if !object_exist?( obj_name )
|
311
303
|
msg = "Trying to access non-existent object '#{obj_name}'."
|
312
304
|
@logger.error( 'Call' ){ msg + " [on behalf of #{peer_ip_addr}]" }
|
313
|
-
raise
|
305
|
+
raise InvalidObject.new( msg )
|
314
306
|
end
|
315
307
|
|
316
308
|
if !public_method?( obj_name, meth_name )
|
317
309
|
msg = "Trying to access non-public method '#{meth_name}'."
|
318
310
|
@logger.error( 'Call' ){ msg + " [on behalf of #{peer_ip_addr}]" }
|
319
|
-
raise
|
311
|
+
raise InvalidMethod.new( msg )
|
320
312
|
end
|
321
313
|
|
322
314
|
# the proxy needs to know whether this is an async call because if it
|
@@ -352,7 +344,7 @@ class Server
|
|
352
344
|
@logger.info( 'System' ){ "Shutting down in #{wait_for} seconds..." }
|
353
345
|
|
354
346
|
# don't die before returning
|
355
|
-
|
347
|
+
::EM.add_timer( wait_for ) { ::EM.stop }
|
356
348
|
true
|
357
349
|
end
|
358
350
|
|
data/lib/arachni/rpc/em/ssl.rb
CHANGED
@@ -88,7 +88,7 @@ module SSL
|
|
88
88
|
#
|
89
89
|
def log( severity, progname, msg )
|
90
90
|
warn "#{progname}: #{msg}" if severity == :error
|
91
|
-
|
91
|
+
fail "#{progname}: #{msg}" if severity == :fatal
|
92
92
|
end
|
93
93
|
|
94
94
|
#
|
@@ -100,7 +100,7 @@ module SSL
|
|
100
100
|
@ca_store = OpenSSL::X509::Store.new
|
101
101
|
@ca_store.add_file( file )
|
102
102
|
else
|
103
|
-
|
103
|
+
fail "No CA certificate has been provided."
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
@@ -144,17 +144,14 @@ module SSL
|
|
144
144
|
# @see http://eventmachine.rubyforge.org/EventMachine/Connection.html#M000270
|
145
145
|
#
|
146
146
|
def ssl_handshake_completed
|
147
|
-
if are_we_a_client?
|
148
|
-
|
149
|
-
@opts[:host] )
|
147
|
+
return if !are_we_a_client? || !ssl_opts? ||
|
148
|
+
OpenSSL::SSL.verify_certificate_identity( @last_seen_cert, @opts[:host] )
|
150
149
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
)
|
150
|
+
log( :error, 'SSL',
|
151
|
+
"The hostname '#{@opts[:host]}' does not match the server certificate."
|
152
|
+
)
|
155
153
|
|
156
|
-
|
157
|
-
end
|
154
|
+
close_connection
|
158
155
|
end
|
159
156
|
|
160
157
|
def are_we_a_client?
|
@@ -8,6 +8,35 @@ describe Arachni::RPC::EM::Client do
|
|
8
8
|
]
|
9
9
|
end
|
10
10
|
|
11
|
+
it "should be able to retain stability and consistency under heavy load" do
|
12
|
+
client = start_client( rpc_opts )
|
13
|
+
|
14
|
+
n = 400
|
15
|
+
cnt = 0
|
16
|
+
|
17
|
+
mismatches = []
|
18
|
+
|
19
|
+
n.times do |i|
|
20
|
+
client.call( 'test.foo', i ) do |res|
|
21
|
+
cnt += 1
|
22
|
+
mismatches << [i, res] if i != res
|
23
|
+
::EM.stop if cnt == n || !mismatches.empty?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Arachni::RPC::EM.block
|
28
|
+
cnt.should > 0
|
29
|
+
mismatches.should be_empty
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should throw error when connecting to inexistent server" do
|
33
|
+
start_client( rpc_opts.merge( :port => 9999 ) ).call( 'test.foo', @arg ) do |res|
|
34
|
+
res.rpc_connection_error?.should be_true
|
35
|
+
::EM.stop
|
36
|
+
end
|
37
|
+
Arachni::RPC::EM.block
|
38
|
+
end
|
39
|
+
|
11
40
|
describe "#initialize" do
|
12
41
|
it "should be able to properly assign class options (including :role)" do
|
13
42
|
opts = rpc_opts.merge( :role => :client )
|
@@ -15,6 +44,19 @@ describe Arachni::RPC::EM::Client do
|
|
15
44
|
end
|
16
45
|
end
|
17
46
|
|
47
|
+
context 'when using a fallback serializer' do
|
48
|
+
context 'and the primary serializer fails' do
|
49
|
+
it 'should use the fallback' do
|
50
|
+
opts = rpc_opts.merge( port: 7333, serializer: YAML )
|
51
|
+
start_client( opts ).call( 'test.foo', @arg ).should == @arg
|
52
|
+
|
53
|
+
opts = rpc_opts.merge( port: 7333, serializer: Marshal )
|
54
|
+
start_client( opts ).call( 'test.foo', @arg ).should == @arg
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
18
60
|
describe "raw interface" do
|
19
61
|
|
20
62
|
context "when using Threads" do
|
@@ -131,35 +173,6 @@ describe Arachni::RPC::EM::Client do
|
|
131
173
|
end
|
132
174
|
end
|
133
175
|
|
134
|
-
it "should be able to retain stability and consistency under heavy load" do
|
135
|
-
client = start_client( rpc_opts )
|
136
|
-
|
137
|
-
n = 400
|
138
|
-
cnt = 0
|
139
|
-
|
140
|
-
mismatches = []
|
141
|
-
|
142
|
-
n.times do |i|
|
143
|
-
client.call( 'test.foo', i ) do |res|
|
144
|
-
cnt += 1
|
145
|
-
mismatches << [i, res] if i != res
|
146
|
-
::EM.stop if cnt == n || !mismatches.empty?
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
Arachni::RPC::EM.block
|
151
|
-
cnt.should > 0
|
152
|
-
mismatches.should be_empty
|
153
|
-
end
|
154
|
-
|
155
|
-
it "should throw error when connecting to inexistent server" do
|
156
|
-
start_client( rpc_opts.merge( :port => 9999 ) ).call( 'test.foo', @arg ) do |res|
|
157
|
-
res.rpc_connection_error?.should be_true
|
158
|
-
::EM.stop
|
159
|
-
end
|
160
|
-
Arachni::RPC::EM.block
|
161
|
-
end
|
162
|
-
|
163
176
|
context "when using valid SSL primitives" do
|
164
177
|
it "should be able to establish a connection" do
|
165
178
|
res = start_client( rpc_opts_with_ssl_primitives ).call( 'test.foo', @arg )
|
data/spec/pems/cacert.pem
CHANGED
@@ -1,39 +1,37 @@
|
|
1
1
|
-----BEGIN CERTIFICATE-----
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
gqwBJUTAmib8tIj/HEdRfAMRTsizQw2NJM8Vc35tUsfpmhzbxqOjrrdZTgtTXt4d
|
38
|
-
WC2Jc17gy6ZvkI3wzvqe/eSyq2N8zRzYQEgaotDhkrbL81N0klZ6RoKkxeI=
|
2
|
+
MIIGaDCCBFCgAwIBAgIJAPBYwqAog9f1MA0GCSqGSIb3DQEBBQUAMFMxCzAJBgNV
|
3
|
+
BAYTAkdSMRgwFgYDVQQDEw9BcmFjaG5pLXRlc3QtQ0ExKjAoBgkqhkiG9w0BCQEW
|
4
|
+
G2FyYWNobmlAYXJhY2huaS1zY2FubmVyLmNvbTAeFw0xMjExMDIxMDIyNTNaFw0y
|
5
|
+
MjEwMzExMDIyNTNaMFMxCzAJBgNVBAYTAkdSMRgwFgYDVQQDEw9BcmFjaG5pLXRl
|
6
|
+
c3QtQ0ExKjAoBgkqhkiG9w0BCQEWG2FyYWNobmlAYXJhY2huaS1zY2FubmVyLmNv
|
7
|
+
bTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANHbVRqyD7FT8KSCZLm1
|
8
|
+
YrOwMj78OXRjMb+ucPR+fncXR1TEuPlEFojLtzqtsaA5OJXpj+0toFP+2T4DeFlu
|
9
|
+
YT4TBrRQ3DxmeY2b5b6ZuurLMfMc1vU+dE92Tj4nuInBz3Z09aDQ8ZWXWzJ7uD3J
|
10
|
+
eNjOcj7Jc94AGFx05Tii9VriUmX+jXQALv5S5WGVtKt4p67mLm/2xD4JhS+a0+B8
|
11
|
+
s/Xo7l6EXaFeTsH5jgZDiY+f0Dpk6cM+pZ5AJVJiNonDJs8/nl9vdWPRH40GHsyN
|
12
|
+
H0/lo/wTxuth/TvX3DBB5hTi/9V5eYbLTLtE1oyXgBxNKrjDu8FVn/jUl8DAIdJL
|
13
|
+
n4vXwXI70wLyS6aF8uu9ApNnmSbTTc2scAKDmnlINLHqGGlpyleojqphDy3MfQYk
|
14
|
+
YT9QSXNKHlO4NMLFqrqM1F3hvM3MEteeM12gAeeJLY6YYYJafMMMh/e7kK/y5u+J
|
15
|
+
ype5t5N8GKskSRp0RvlRYfoH/lnyJd6FEyh9P0QHA4CKAadZBCfOcmwmTY/G0Kjn
|
16
|
+
3Y7r7BmJb4PIEcDqUjXwuyq6ZHjx7sawuGG6eXhIGln0JtSymy4j+h+xlh0S7O8B
|
17
|
+
Ti6dMVxg+DNTkEJz2O00IIBcyToqZ26XovFkN5ueRNOROB3YVpldmpbLyuOQae/D
|
18
|
+
4Gc21bEWoR7OAaY2PRl4r563AgMBAAGjggE9MIIBOTAdBgNVHQ4EFgQU8VOtNUbZ
|
19
|
+
rnbX0gRWIds5yaT+uCwwgYMGA1UdIwR8MHqAFPFTrTVG2a5219IEViHbOcmk/rgs
|
20
|
+
oVekVTBTMQswCQYDVQQGEwJHUjEYMBYGA1UEAxMPQXJhY2huaS10ZXN0LUNBMSow
|
21
|
+
KAYJKoZIhvcNAQkBFhthcmFjaG5pQGFyYWNobmktc2Nhbm5lci5jb22CCQDwWMKg
|
22
|
+
KIPX9TAPBgNVHRMBAf8EBTADAQH/MBEGCWCGSAGG+EIBAQQEAwIBBjAJBgNVHRIE
|
23
|
+
AjAAMCsGCWCGSAGG+EIBDQQeFhxUaW55Q0EgR2VuZXJhdGVkIENlcnRpZmljYXRl
|
24
|
+
MCYGA1UdEQQfMB2BG2FyYWNobmlAYXJhY2huaS1zY2FubmVyLmNvbTAOBgNVHQ8B
|
25
|
+
Af8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggIBAAAAebhmNydIUT+sZpvWM5JKNQdq
|
26
|
+
YtnghnpZyM37cxLmFIeDKKPTw/3lhDViRRqQaaFTI7ukcqgloPmhMuymQT4o11Fw
|
27
|
+
XLoQuIeSgS7Mg6eZF3CUKoEy1KnlluegDXtDI+WH/EQHlokBvoMaClltj6vsfqI/
|
28
|
+
K9K2MAXUKi8K7NRq6VYO1QPtrBfrX9VmLyndbYm8lSG5oDkGGh8NjVgHHZDgrQAQ
|
29
|
+
2EmsWbE9yMZ6yl+AaaE5XrbPWnBI8rK77WP93JYVAhmcaQiJzPfMw3sb2QojKdak
|
30
|
+
7fvJzAjBeXAoTP5Mu/E+BPPgELzB/DnRaWlrYsAQeRZBV+I3+k5CCVqdOAdJCk5Y
|
31
|
+
dFNTppHfwVaDj5qKOmodzdUDcDL6ynl15t6WHgj2yBwsDVpWsvbqyitZkemLFwrf
|
32
|
+
hAedR3dKr+IxrOynST1w4LorDorcGK/DqhD475bQ9EDel5nW18hotUeeeO+K3qc7
|
33
|
+
iGgj7zTKfmhzL/KMotir/SQYYxQbbtLkkhXDaNVlWiYkHotOzrNbpKAFM776CI47
|
34
|
+
KTXG88FydcycGHYU8SQLEbEDiowAikr2u+CUHKrJvz2Wr8jkWaMCSfgCyokcY6vR
|
35
|
+
IIqnrpYHhX2FmKf+tRB8o3KXM6uiOSUvaW23LHcs0OKqcJ0XHOkhTMDFZ82eDZzl
|
36
|
+
CXJQkVNhmc0Y9prF
|
39
37
|
-----END CERTIFICATE-----
|