socket_duplex 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/README.md +15 -0
- data/lib/socket_duplex.rb +138 -0
- data/lib/websocket-client-simple/client.rb +91 -0
- data/lib/websocket-client-simple/version.rb +7 -0
- data/lib/websocket-client-simple.rb +15 -0
- data/socket_duplex.gemspec +24 -0
- metadata +147 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cf86bc3bcc2e7134ebbb05833da75421b521d2f3
|
4
|
+
data.tar.gz: 716448a796d76653a9c27e376871428c041d7f17
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b5f3fc1fda60758d11e2200e31fc737c40be0e02940cf5a9940e8bfe341183a79fb39189f0bb65f4fef2ed0b3fc74e4107a0a9e62885301e3a2be2f2c964a425
|
7
|
+
data.tar.gz: 6cf09a8a93af86ca0d38d153213c5ed1c5f8abb4b9ff7efcade7f26316b50b309c5155af58a0b6311f0c32057b79f6e5c3dd16abc58d88d7bc5652252efde873
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# ruby-agent
|
2
|
+
Secure Ruby on Rails with SECful.
|
3
|
+
|
4
|
+
Installation
|
5
|
+
------------
|
6
|
+
1. Add the following to your Gemfile:
|
7
|
+
gem 'socket_duplex', :git => 'git@github.com:SECful/ruby-agent.git'
|
8
|
+
|
9
|
+
2. Add the following to your config.ru:
|
10
|
+
require 'socket_duplex'
|
11
|
+
use Rack::SocketDuplex, 'wss://localhost:7000', 'token'
|
12
|
+
|
13
|
+
For a non SSL websocket use:
|
14
|
+
use Rack::SocketDuplex, 'ws://localhost:7000', 'token', OpenSSL::SSL::VERIFY_NONE
|
15
|
+
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'socket'
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
require_relative 'websocket-client-simple'
|
6
|
+
|
7
|
+
module Rack
|
8
|
+
class SocketDuplex
|
9
|
+
MAX_QUEUE_SIZE = 50
|
10
|
+
NUM_OF_THREADS = 5
|
11
|
+
|
12
|
+
def initialize(app, socket_path, secful_key, verify_mode=OpenSSL::SSL::VERIFY_PEER)
|
13
|
+
@app, @socket_path, @verify_mode = app, socket_path, verify_mode
|
14
|
+
begin
|
15
|
+
@secful_key = secful_key
|
16
|
+
@agent_identifier = SecureRandom.hex
|
17
|
+
@machine_ip = Socket.ip_address_list.detect(&:ipv4_private?).try(:ip_address)
|
18
|
+
@queue = SizedQueue.new(MAX_QUEUE_SIZE)
|
19
|
+
@threads_to_sockets = {}
|
20
|
+
Thread.new { activate_workers() }
|
21
|
+
rescue Exception
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def call(env)
|
26
|
+
begin
|
27
|
+
dup._call(env)
|
28
|
+
rescue Exception
|
29
|
+
@app.call(env)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def _call(env)
|
36
|
+
status, headers, body = @app.call(env)
|
37
|
+
if @queue.length < @queue.max
|
38
|
+
@queue << env
|
39
|
+
end
|
40
|
+
return [status, headers, body]
|
41
|
+
end
|
42
|
+
|
43
|
+
def activate_workers
|
44
|
+
NUM_OF_THREADS.times do
|
45
|
+
Thread.new {worker()}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def worker
|
50
|
+
loop do
|
51
|
+
env = @queue.pop
|
52
|
+
if env
|
53
|
+
connect_to_ws(Thread.current)
|
54
|
+
handle_request(env)
|
55
|
+
end rescue nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def connect_to_ws(thr)
|
60
|
+
begin
|
61
|
+
ws = @threads_to_sockets[thr]
|
62
|
+
if !ws
|
63
|
+
headers = { 'Agent-Type' => 'Ruby',
|
64
|
+
'Agent-Version' => '1.0',
|
65
|
+
'Agent-Identifier' => @agent_identifier,
|
66
|
+
'Authorization' => 'Bearer ' + @secful_key }
|
67
|
+
ws = WebSocket::Client::Simple.connect @socket_path, verify_mode: @verify_mode, headers: headers
|
68
|
+
sleep(3)
|
69
|
+
end
|
70
|
+
if !ws.open?
|
71
|
+
sleep(60)
|
72
|
+
end
|
73
|
+
if !ws.open?
|
74
|
+
ws.close()
|
75
|
+
ws = nil
|
76
|
+
end
|
77
|
+
rescue nil
|
78
|
+
end
|
79
|
+
@threads_to_sockets[thr] = ws
|
80
|
+
end
|
81
|
+
|
82
|
+
def handle_request(env)
|
83
|
+
request_hash = {}
|
84
|
+
if env['rack.url_scheme'] == 'http'
|
85
|
+
write_env(request_hash, env)
|
86
|
+
ws = @threads_to_sockets[Thread.current]
|
87
|
+
begin
|
88
|
+
ws.send request_hash.to_json
|
89
|
+
rescue Exception
|
90
|
+
if ws
|
91
|
+
ws.close()
|
92
|
+
end rescue nil
|
93
|
+
@threads_to_sockets[Thread.current] = nil
|
94
|
+
end
|
95
|
+
end rescue nil
|
96
|
+
end
|
97
|
+
|
98
|
+
def write_env(request_hash, env)
|
99
|
+
write_request_line request_hash, env
|
100
|
+
write_headers request_hash, env
|
101
|
+
write_post_body request_hash, env
|
102
|
+
end
|
103
|
+
|
104
|
+
def write_request_line(request_hash, env)
|
105
|
+
path_with_query_string = env['PATH_INFO']
|
106
|
+
path_with_query_string << "?#{env['QUERY_STRING']}" if env['QUERY_STRING'].length > 0
|
107
|
+
request_hash[:request] = {path: path_with_query_string,
|
108
|
+
version: env['HTTP_VERSION'],
|
109
|
+
method: env['REQUEST_METHOD']}
|
110
|
+
request_hash[:userSrcIp] = env['REMOTE_ADDR']
|
111
|
+
request_hash[:companyLocalIps] = [@machine_ip || env['SERVER_NAME']]
|
112
|
+
end
|
113
|
+
|
114
|
+
def write_headers(request_hash, env)
|
115
|
+
headers = env.select { |k, v| k =~ /^HTTP_/ }
|
116
|
+
headers = Hash[headers.map { |k, v| [k[5..-1].gsub('_', '-'), v] }]
|
117
|
+
headers.delete('VERSION')
|
118
|
+
if env['CONTENT_TYPE']
|
119
|
+
headers[:'CONTENT-TYPE'] = env['CONTENT_TYPE']
|
120
|
+
end
|
121
|
+
if env['CONTENT_LENGTH']
|
122
|
+
headers[:'CONTENT-LENGTH'] = env['CONTENT_LENGTH']
|
123
|
+
end
|
124
|
+
|
125
|
+
request_hash[:request][:headers] = headers_arr = []
|
126
|
+
headers.each do |k,v|
|
127
|
+
headers_arr << {key: k, value: v}
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def write_post_body(request_hash, env)
|
132
|
+
if env['CONTENT_LENGTH'] && (content_length = env['CONTENT_LENGTH'].to_i) > 0
|
133
|
+
request_hash[:request][:payload] = env['rack.input'].read
|
134
|
+
env['rack.input'].rewind
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module WebSocket
|
2
|
+
module Client
|
3
|
+
module Simple
|
4
|
+
|
5
|
+
def self.connect(url, options={})
|
6
|
+
::WebSocket::Client::Simple::Client.new(url, options)
|
7
|
+
end
|
8
|
+
|
9
|
+
class Client
|
10
|
+
include EventEmitter
|
11
|
+
attr_reader :url, :handshake
|
12
|
+
|
13
|
+
def initialize(url, options={})
|
14
|
+
@url = url
|
15
|
+
uri = URI.parse url
|
16
|
+
@socket = TCPSocket.new(uri.host,
|
17
|
+
uri.port || (uri.scheme == 'wss' ? 443 : 80))
|
18
|
+
if ['https', 'wss'].include? uri.scheme
|
19
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
20
|
+
ctx.ssl_version = options[:ssl_version] || 'SSLv23'
|
21
|
+
ctx.verify_mode = options[:verify_mode] || OpenSSL::SSL::VERIFY_NONE #use VERIFY_PEER for verification
|
22
|
+
cert_store = OpenSSL::X509::Store.new
|
23
|
+
cert_store.set_default_paths
|
24
|
+
ctx.cert_store = cert_store
|
25
|
+
@socket = ::OpenSSL::SSL::SSLSocket.new(@socket, ctx)
|
26
|
+
@socket.connect
|
27
|
+
end
|
28
|
+
@handshake = ::WebSocket::Handshake::Client.new :url => url, :headers => options[:headers]
|
29
|
+
@handshaked = false
|
30
|
+
frame = ::WebSocket::Frame::Incoming::Client.new
|
31
|
+
@closed = false
|
32
|
+
once :__close do |err|
|
33
|
+
close
|
34
|
+
emit :close, err
|
35
|
+
end
|
36
|
+
|
37
|
+
@thread = Thread.new do
|
38
|
+
while !@closed do
|
39
|
+
begin
|
40
|
+
unless recv_data = @socket.getc
|
41
|
+
sleep 1
|
42
|
+
next
|
43
|
+
end
|
44
|
+
unless @handshaked
|
45
|
+
@handshake << recv_data
|
46
|
+
if @handshake.finished?
|
47
|
+
@handshaked = true
|
48
|
+
emit :open
|
49
|
+
end
|
50
|
+
else
|
51
|
+
frame << recv_data
|
52
|
+
while msg = frame.next
|
53
|
+
emit :message, msg
|
54
|
+
end
|
55
|
+
end
|
56
|
+
rescue => e
|
57
|
+
emit :error, e
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
@socket.write @handshake.to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
def send(data, opt={:type => :text})
|
66
|
+
return if !@handshaked or @closed
|
67
|
+
type = opt[:type]
|
68
|
+
frame = ::WebSocket::Frame::Outgoing::Client.new(:data => data, :type => type, :version => @handshake.version)
|
69
|
+
#begin
|
70
|
+
@socket.write frame.to_s
|
71
|
+
#rescue Errno::EPIPE => e
|
72
|
+
# emit :__close, e
|
73
|
+
#end
|
74
|
+
end
|
75
|
+
|
76
|
+
def close
|
77
|
+
@closed = true
|
78
|
+
@socket.close if @socket
|
79
|
+
@socket = nil
|
80
|
+
Thread.kill @thread if @thread
|
81
|
+
end
|
82
|
+
|
83
|
+
def open?
|
84
|
+
@handshake.finished? and !@closed
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'event_emitter'
|
2
|
+
require 'websocket'
|
3
|
+
require 'socket'
|
4
|
+
require 'openssl'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
require 'websocket-client-simple/version'
|
8
|
+
require 'websocket-client-simple/client'
|
9
|
+
|
10
|
+
module WebSocket
|
11
|
+
module Client
|
12
|
+
module Simple
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = "socket_duplex"
|
6
|
+
spec.version = '1.0.0'
|
7
|
+
spec.authors = ["Secful"]
|
8
|
+
spec.description = %q{Rack middleware that duplexes HTTP traffic}
|
9
|
+
spec.summary = spec.description
|
10
|
+
|
11
|
+
spec.files = `git ls-files`.split($/).reject{|f| f == "Gemfile.lock" }
|
12
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
13
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
14
|
+
spec.require_paths = ["lib"]
|
15
|
+
|
16
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
17
|
+
spec.add_development_dependency "rake"
|
18
|
+
spec.add_development_dependency "minitest"
|
19
|
+
spec.add_development_dependency "websocket-eventmachine-server"
|
20
|
+
spec.add_development_dependency "eventmachine"
|
21
|
+
|
22
|
+
spec.add_runtime_dependency "websocket"
|
23
|
+
spec.add_runtime_dependency "event_emitter"
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: socket_duplex
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Secful
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-02-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: websocket-eventmachine-server
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: eventmachine
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: websocket
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: event_emitter
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: Rack middleware that duplexes HTTP traffic
|
112
|
+
email:
|
113
|
+
executables: []
|
114
|
+
extensions: []
|
115
|
+
extra_rdoc_files: []
|
116
|
+
files:
|
117
|
+
- Gemfile
|
118
|
+
- README.md
|
119
|
+
- lib/socket_duplex.rb
|
120
|
+
- lib/websocket-client-simple.rb
|
121
|
+
- lib/websocket-client-simple/client.rb
|
122
|
+
- lib/websocket-client-simple/version.rb
|
123
|
+
- socket_duplex.gemspec
|
124
|
+
homepage:
|
125
|
+
licenses: []
|
126
|
+
metadata: {}
|
127
|
+
post_install_message:
|
128
|
+
rdoc_options: []
|
129
|
+
require_paths:
|
130
|
+
- lib
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
requirements: []
|
142
|
+
rubyforge_project:
|
143
|
+
rubygems_version: 2.4.5.1
|
144
|
+
signing_key:
|
145
|
+
specification_version: 4
|
146
|
+
summary: Rack middleware that duplexes HTTP traffic
|
147
|
+
test_files: []
|