em-wssh 0.6.0 → 0.7.0
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.
- 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
|
-
}
|