em-wssh 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +22 -0
- data/em-wssh.gemspec +1 -0
- data/lib/em/wssh.rb +1 -1
- data/lib/em/wssh/connect.rb +2 -0
- data/lib/em/wssh/ephemeral.rb +0 -19
- data/lib/em/wssh/service.rb +1 -1
- data/lib/em/wssh/tls.rb +110 -0
- data/lib/em/wssh/uri.rb +20 -0
- metadata +18 -3
- data/lib/em/wssh/index.js +0 -212
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bd71c6b444c01d0a53369ee3a19b4e249117fa87
|
4
|
+
data.tar.gz: a0e23c5ad1d09084ae419ff505358ca87094aad1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3e807d7f77ffaed65ce0e065f2266735fe3290e2e01bef37e83ebcdaaa81c99bfb3ce599ab12f7751d11f726c3693b4023210b7ec78621e4f825e90b4013b6f5
|
7
|
+
data.tar.gz: a4576f6827cd4722b4a7fb64f54558386a4c4bab8ece93db875239ce4150ea086e9c23b3287b3b6171a078c57d58c5b57218773d561de51f5617b94a9a964085
|
data/README.md
CHANGED
@@ -34,6 +34,23 @@ Single command `wssh` is exported. Sometimes it should be `bundle exec wssh`.
|
|
34
34
|
|
35
35
|
To run WSSH server say `wssh server`.
|
36
36
|
|
37
|
+
You can set some options for server, ie:
|
38
|
+
|
39
|
+
Most useful option is `--base=path` (or `-b`). It set path, where server files stored.
|
40
|
+
By default this path is where gem installed. To use current directory say `wssh server -b.`
|
41
|
+
|
42
|
+
This `base` path is used to locate `hosts.yml` file, which maps host requested by user to real servers.
|
43
|
+
See [sample](hosts.yml). One can define direct map or regexp-style mapping (denoted by // with optional //i).
|
44
|
+
If multiple regexps match user host, the last one wins.
|
45
|
+
To disable connecting to host (or all hosts for regexp) map it to null or false.
|
46
|
+
|
47
|
+
The `base` also is root for log file and pid file, created by server.
|
48
|
+
|
49
|
+
Parameters `--listen=port` (`-l`) and `--all` (`-a`) define on what address server will listen.
|
50
|
+
By default it is `localhost:4567`.
|
51
|
+
|
52
|
+
Parameter `--daemon` will force server to go in background.
|
53
|
+
|
37
54
|
### nginx
|
38
55
|
|
39
56
|
Directly exposing WSSH server to Internet is not a good idea.
|
@@ -77,6 +94,11 @@ x=Net::SSH.start 'sshd.local', 'root',
|
|
77
94
|
puts x.exec! 'hostname'
|
78
95
|
```
|
79
96
|
|
97
|
+
Proxy allows the same command line parameters as server.
|
98
|
+
For proxy parameter `--ping` may be useful,
|
99
|
+
it forces proxy to periodically send Websocket ping packets to server
|
100
|
+
for nginx to not drop connection on timeout.
|
101
|
+
|
80
102
|
## API
|
81
103
|
|
82
104
|
WSSH server, client or proxy can be start programmaticaly:
|
data/em-wssh.gemspec
CHANGED
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_dependency "em-websocket" # server side
|
22
22
|
spec.add_dependency "faye-websocket" # client side
|
23
|
+
spec.add_dependency "openssl-win-root" if Gem.win_platform?
|
23
24
|
|
24
25
|
spec.add_development_dependency "bundler", "~> 1.3"
|
25
26
|
spec.add_development_dependency "rake"
|
data/lib/em/wssh.rb
CHANGED
data/lib/em/wssh/connect.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require_relative 'service'
|
2
|
+
require_relative 'uri'
|
2
3
|
|
3
4
|
module EventMachine::Wssh
|
4
5
|
module Connect
|
@@ -167,6 +168,7 @@ Simple HTTP CONNECT proxy to WSSH daemon
|
|
167
168
|
end
|
168
169
|
|
169
170
|
def self.listen!
|
171
|
+
options[:uri]=TLS.wrap options[:uri]
|
170
172
|
conn=EM.start_server options[:host], options[:port], Http
|
171
173
|
options[:onlisten].call Socket.unpack_sockaddr_in(EM.get_sockname conn)[0] if options[:onlisten]
|
172
174
|
end
|
data/lib/em/wssh/ephemeral.rb
CHANGED
@@ -5,11 +5,9 @@ class Ephemeral
|
|
5
5
|
extend Service
|
6
6
|
|
7
7
|
@options={
|
8
|
-
tlswrap: true,
|
9
8
|
base: '.',
|
10
9
|
pid: 'tmp/pids/ephemeral.pid',
|
11
10
|
log: 'log/ephemeral.log',
|
12
|
-
tls: 'log/tls.log',
|
13
11
|
}
|
14
12
|
|
15
13
|
%i(log options mkdir).each do |meth|
|
@@ -33,24 +31,7 @@ class Ephemeral
|
|
33
31
|
sock.gets.to_i
|
34
32
|
end
|
35
33
|
|
36
|
-
def tlswrap uri
|
37
|
-
return uri unless options[:tlswrap] and Gem.win_platform?
|
38
|
-
require 'uri'
|
39
|
-
z = URI uri
|
40
|
-
return uri unless %w(wss https).include? z.scheme
|
41
|
-
log "Running TLS Wrapper..."
|
42
|
-
spawn 'node', '.', myport.to_s, z.host,
|
43
|
-
chdir: __dir__,
|
44
|
-
%i(out err)=>File.open(mkdir(:tls), 'a')
|
45
|
-
z.scheme='ws'
|
46
|
-
z.host='localhost'
|
47
|
-
z.port=rport
|
48
|
-
z.to_s
|
49
|
-
end
|
50
|
-
|
51
34
|
def allocate uri
|
52
|
-
uri = tlswrap uri
|
53
|
-
|
54
35
|
log "Running WSSH proxy..."
|
55
36
|
spawn *%w(bundle exec wssh ephemeral), myport.to_s, uri,
|
56
37
|
%i(out err)=>File.open(mkdir(:log), 'a')
|
data/lib/em/wssh/service.rb
CHANGED
data/lib/em/wssh/tls.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'openssl'
|
3
|
+
require 'openssl/win/root' if Gem.win_platform?
|
4
|
+
|
5
|
+
require_relative 'service'
|
6
|
+
|
7
|
+
module EventMachine::Wssh
|
8
|
+
class TLS
|
9
|
+
extend Service
|
10
|
+
|
11
|
+
Chunk=0x10000
|
12
|
+
|
13
|
+
def self.run! host
|
14
|
+
@@host=host
|
15
|
+
s=TCPServer.new '127.0.0.1', 0
|
16
|
+
log "WSTunnel on #{s.addr} -> #{host}"
|
17
|
+
Thread.new do
|
18
|
+
new s.accept while true
|
19
|
+
end
|
20
|
+
s.addr[1]
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.count
|
24
|
+
@n||=0
|
25
|
+
@n+=1
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize client
|
29
|
+
@count=self.class.count
|
30
|
+
@client=client
|
31
|
+
@t1=Thread.new{cloop!}
|
32
|
+
end
|
33
|
+
|
34
|
+
def log *msg
|
35
|
+
self.class.log "<&#{@count}>", *msg
|
36
|
+
end
|
37
|
+
|
38
|
+
def cloop!
|
39
|
+
begin
|
40
|
+
log "Connected from", @client.peeraddr
|
41
|
+
cloop
|
42
|
+
rescue=>e
|
43
|
+
log "Client error", e
|
44
|
+
ensure
|
45
|
+
log "Client disconnected"
|
46
|
+
@client.close
|
47
|
+
@t2.exit if @t2
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def headerz
|
52
|
+
r=[]
|
53
|
+
until @client.eof
|
54
|
+
s=@client.gets.strip
|
55
|
+
break if 0==s.length
|
56
|
+
r << s
|
57
|
+
end
|
58
|
+
r
|
59
|
+
end
|
60
|
+
|
61
|
+
def headerz! headers
|
62
|
+
return headers if headers.length<1
|
63
|
+
verb=headers.shift
|
64
|
+
[verb]+
|
65
|
+
%w(Host Origin).map{|h| "#{h}: #{@@host}"}+
|
66
|
+
headers.reject{|h| /^(?:host|origin):/i.match h}
|
67
|
+
end
|
68
|
+
|
69
|
+
def connect!
|
70
|
+
srv=Socket.tcp @@host, 443
|
71
|
+
ctx=OpenSSL::SSL::SSLContext.new
|
72
|
+
ctx.set_params verify_mode: OpenSSL::SSL::VERIFY_PEER
|
73
|
+
srv=OpenSSL::SSL::SSLSocket.new srv, ctx
|
74
|
+
srv.hostname=@@host if srv.respond_to? :hostname=
|
75
|
+
srv.connect
|
76
|
+
srv
|
77
|
+
end
|
78
|
+
|
79
|
+
def cloop
|
80
|
+
h=headerz! headerz
|
81
|
+
if h.length<1
|
82
|
+
@client.write "HTTP/1.0 500 Invalid request\r\n"
|
83
|
+
return
|
84
|
+
end
|
85
|
+
@headers=h
|
86
|
+
@server=connect!
|
87
|
+
@t2=Thread.new{sloop!}
|
88
|
+
@server.write @client.readpartial Chunk until @client.eof
|
89
|
+
end
|
90
|
+
|
91
|
+
def sloop!
|
92
|
+
begin
|
93
|
+
log "Connected to server;", "Verify=#{@server.verify_result}"
|
94
|
+
sloop
|
95
|
+
rescue=>e
|
96
|
+
log "Server error", e
|
97
|
+
ensure
|
98
|
+
log "Server disconnected"
|
99
|
+
@server.close
|
100
|
+
@t1.exit
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def sloop
|
105
|
+
@server.write @headers*"\r\n"+"\r\n"*2
|
106
|
+
@headers=nil
|
107
|
+
@client.write @server.readpartial Chunk until @server.eof
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/lib/em/wssh/uri.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
require_relative '../wssh'
|
4
|
+
|
5
|
+
module EventMachine::Wssh
|
6
|
+
class TLS
|
7
|
+
|
8
|
+
def self.wrap uri
|
9
|
+
return uri unless Gem.win_platform?
|
10
|
+
z = URI uri
|
11
|
+
return uri unless %w(wss https).include? z.scheme
|
12
|
+
require_relative 'tls'
|
13
|
+
z.port=TLS.run! z.host
|
14
|
+
z.scheme='ws'
|
15
|
+
z.host='localhost'
|
16
|
+
z.to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: em-wssh
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stas Ukolov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-03-
|
11
|
+
date: 2015-03-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: em-websocket
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - '>='
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: openssl-win-root
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: bundler
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -90,9 +104,10 @@ files:
|
|
90
104
|
- lib/em/wssh/ephemeral.rb
|
91
105
|
- lib/em/wssh/exe.rb
|
92
106
|
- lib/em/wssh/help.rb
|
93
|
-
- lib/em/wssh/index.js
|
94
107
|
- lib/em/wssh/server.rb
|
95
108
|
- lib/em/wssh/service.rb
|
109
|
+
- lib/em/wssh/tls.rb
|
110
|
+
- lib/em/wssh/uri.rb
|
96
111
|
- lib/em/wssh/version.rb
|
97
112
|
- nginx/ssh
|
98
113
|
homepage: https://github.com/ukoloff/em-wssh
|
data/lib/em/wssh/index.js
DELETED
@@ -1,212 +0,0 @@
|
|
1
|
-
//
|
2
|
-
// Simple stunnel for Websocket
|
3
|
-
//
|
4
|
-
net = require('net')
|
5
|
-
ssl = require('tls')
|
6
|
-
|
7
|
-
if(4!=process.argv.length) help()
|
8
|
-
|
9
|
-
var Host = process.argv[3]
|
10
|
-
|
11
|
-
net.createServer(Req)
|
12
|
-
.listen(0, On)
|
13
|
-
|
14
|
-
var
|
15
|
-
myport,
|
16
|
-
Count=0,
|
17
|
-
_log=log
|
18
|
-
|
19
|
-
function help()
|
20
|
-
{
|
21
|
-
path = require('path')
|
22
|
-
log('Usage:', path.basename(__filename), 'port host')
|
23
|
-
process.exit()
|
24
|
-
}
|
25
|
-
|
26
|
-
function On()
|
27
|
-
{
|
28
|
-
myport = this.address().port;
|
29
|
-
log("Listening", this.address())
|
30
|
-
net.connect(process.argv[2], 'localhost')
|
31
|
-
.on('connect', pConnect)
|
32
|
-
.on('end', pEnd)
|
33
|
-
.on('error', pError)
|
34
|
-
}
|
35
|
-
|
36
|
-
function pConnect()
|
37
|
-
{
|
38
|
-
log('Connected to parent')
|
39
|
-
this.write(''+myport+'\n')
|
40
|
-
}
|
41
|
-
|
42
|
-
function pEnd()
|
43
|
-
{
|
44
|
-
log('Parent closed')
|
45
|
-
process.exit()
|
46
|
-
}
|
47
|
-
|
48
|
-
function pError(err)
|
49
|
-
{
|
50
|
-
log('Parent error', err)
|
51
|
-
process.exit()
|
52
|
-
}
|
53
|
-
|
54
|
-
function Req(conn)
|
55
|
-
{
|
56
|
-
var
|
57
|
-
No=++Count, hs=[], prolog, body, tls
|
58
|
-
|
59
|
-
function log()
|
60
|
-
{
|
61
|
-
_log.apply(this, ['<'+No+'>'].concat([].slice.call(arguments)))
|
62
|
-
}
|
63
|
-
|
64
|
-
log("Client connected from", conn.remoteAddress+':'+conn.remotePort)
|
65
|
-
|
66
|
-
conn
|
67
|
-
.on('readable', cRead)
|
68
|
-
.on('end', cClose)
|
69
|
-
.on('error', cError)
|
70
|
-
|
71
|
-
function cRead()
|
72
|
-
{
|
73
|
-
var x
|
74
|
-
while(null!=(x=this.read()))
|
75
|
-
body ? queue(x) : headers(x)
|
76
|
-
}
|
77
|
-
|
78
|
-
function cClose()
|
79
|
-
{
|
80
|
-
log('Client disconnected')
|
81
|
-
bye()
|
82
|
-
}
|
83
|
-
|
84
|
-
function cError(e)
|
85
|
-
{
|
86
|
-
log('Client error', e)
|
87
|
-
bye()
|
88
|
-
}
|
89
|
-
|
90
|
-
function bye()
|
91
|
-
{
|
92
|
-
conn.end()
|
93
|
-
if(tls) tls.end()
|
94
|
-
}
|
95
|
-
|
96
|
-
function queue(data)
|
97
|
-
{
|
98
|
-
if(Array.isArray(body))
|
99
|
-
body.push(data)
|
100
|
-
else
|
101
|
-
send(data)
|
102
|
-
}
|
103
|
-
|
104
|
-
function headers(data)
|
105
|
-
{
|
106
|
-
prolog = prolog ? Buffer.concat([prolog, data]) : data
|
107
|
-
while(splitHdrs()){}
|
108
|
-
}
|
109
|
-
|
110
|
-
function splitHdrs()
|
111
|
-
{
|
112
|
-
for(var cr, L=prolog.length, i=0; i<L; i++)
|
113
|
-
if(10==prolog[i])
|
114
|
-
{
|
115
|
-
L = prolog
|
116
|
-
prolog = prolog.slice(i+1)
|
117
|
-
header(L.slice(0, i-(cr ? 1 : 0)).toString())
|
118
|
-
return true
|
119
|
-
}
|
120
|
-
else
|
121
|
-
cr = 13==prolog[i]
|
122
|
-
}
|
123
|
-
|
124
|
-
function header(line)
|
125
|
-
{
|
126
|
-
if(!line.length)
|
127
|
-
tlsConnect()
|
128
|
-
else
|
129
|
-
hs.push(line)
|
130
|
-
}
|
131
|
-
|
132
|
-
function tlsConnect()
|
133
|
-
{
|
134
|
-
body=[prolog]
|
135
|
-
prolog=new Buffer(0)
|
136
|
-
patchHeaders()
|
137
|
-
tls = ssl.connect({
|
138
|
-
servername: Host,
|
139
|
-
host: Host,
|
140
|
-
port: 443
|
141
|
-
})
|
142
|
-
tls
|
143
|
-
.on('secureConnect', tConnect)
|
144
|
-
.on('readable', tData)
|
145
|
-
.on('end', tEnd)
|
146
|
-
.on('error', tError)
|
147
|
-
}
|
148
|
-
|
149
|
-
function patchHeaders()
|
150
|
-
{
|
151
|
-
if(!hs.length) return
|
152
|
-
var verb = hs.shift()
|
153
|
-
hs=['Host', 'Origin']
|
154
|
-
.map(hostHeader)
|
155
|
-
.concat(hs.filter(filterHeader))
|
156
|
-
hs.unshift(verb)
|
157
|
-
}
|
158
|
-
|
159
|
-
function send(data)
|
160
|
-
{
|
161
|
-
if(data.length)
|
162
|
-
tls.write(data)
|
163
|
-
}
|
164
|
-
|
165
|
-
function tConnect()
|
166
|
-
{
|
167
|
-
log('TLS connected', 'Auth='+this.authorized,'CN='+this.getPeerCertificate().subject.CN)
|
168
|
-
send(hs.join('\r\n')+'\r\n\r\n')
|
169
|
-
body.forEach(send)
|
170
|
-
body = true
|
171
|
-
}
|
172
|
-
|
173
|
-
function tData()
|
174
|
-
{
|
175
|
-
var x
|
176
|
-
while(null!=(x=this.read()))
|
177
|
-
if(x.length) conn.write(x)
|
178
|
-
}
|
179
|
-
|
180
|
-
function tError(err)
|
181
|
-
{
|
182
|
-
log('TLS error', err)
|
183
|
-
bye()
|
184
|
-
}
|
185
|
-
|
186
|
-
function tEnd()
|
187
|
-
{
|
188
|
-
log('TLS closed')
|
189
|
-
bye()
|
190
|
-
}
|
191
|
-
}
|
192
|
-
|
193
|
-
function hostHeader(s)
|
194
|
-
{
|
195
|
-
return s+': '+Host
|
196
|
-
}
|
197
|
-
|
198
|
-
function filterHeader(s)
|
199
|
-
{
|
200
|
-
return !/^(?:host|origin):/i.test(s)
|
201
|
-
}
|
202
|
-
|
203
|
-
function log()
|
204
|
-
{
|
205
|
-
var
|
206
|
-
d = new Date()
|
207
|
-
console.info.apply(
|
208
|
-
console,
|
209
|
-
['['+d.toISOString().replace('T', ' ').replace(/Z$/, '')
|
210
|
-
+d.toTimeString().split(/\s+/)[1].replace(/^\w+/, ' ')+']']
|
211
|
-
.concat([].slice.call(arguments)))
|
212
|
-
}
|