arachni-rpc-em 0.1.2 → 0.1.3dev1
Sign up to get free protection for your applications and to get access to all the features.
- 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-----
|