tbm 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,11 +1,12 @@
1
1
  # Tunnel Boring Machine
2
+ [![Build Status](https://travis-ci.org/geoffreywiseman/tunnel-boring-machine.png?branch=master)](https://travis-ci.org/geoffreywiseman/tunnel-boring-machine) [![Dependency Status](https://gemnasium.com/geoffreywiseman/tunnel-boring-machine.png)](https://gemnasium.com/geoffreywiseman/tunnel-boring-machine) [![Code Climate](https://codeclimate.com/github/geoffreywiseman/tunnel-boring-machine.png)](https://codeclimate.com/github/geoffreywiseman/tunnel-boring-machine)
2
3
 
3
4
  Tunnel Boring Machine is a ruby application to manage SSH tunnels, which you can use to achieve something a little like a VPN, wherein SSH access to a server can give you access to the network beyond that server.
4
5
 
5
6
  I use SSH tunnels on a regular basis to access resources at client sites that are not exposed directly to the internet as a whole. Managing those tunnels as a series of bash scripts or aliases became cumbersome. I wanted / needed something better, and the tunnel boring machine has evolved from that need.
6
7
 
7
8
  ## Current Status ##
8
- It's pretty early days. I'm using this myself, but I haven't gone out of my way to share it with anyone because so much is still in flux. I imagine it will be starting to stabilize soon into something I might call a 'beta' product. At the moment, it's probably more of an 'alpha'.
9
+ It's pretty early days. I'm using this myself, and I've shared it so other people can use it, but its advantages over a shell script are still fairly thin. There are some enhancements that I have planned that might make the case much stronger, but we'll see.
9
10
 
10
11
  ## Installing ##
11
12
  It is bundled as a ruby gem, so if you have Ruby and RubyGems installed, simply run:
@@ -17,12 +18,12 @@ If you prefer, you can certainly download it and build it yourself, or simply in
17
18
  ## Invocation ##
18
19
  For the time being, TBM is a simple command you invoke to open the tunnels you need, then you cancel with `^C` to close the tunnels that you had opened. Something like this:
19
20
 
20
- $ tbm dev-ngnix
21
+ $ tbm dev-nginx
21
22
 
22
23
  Eventually, I expect that TBM will become a little more interactive, allowing you to open additional tunnels without closing the ones you already opened, close a tunnel without closing all of them, and so forth. Whether it does this as an interactive program, a shell command that interacts with a running process is all TBD.
23
24
 
24
25
  ## Configuration ##
25
- You configure the tunnel boring machine by creating a configuration file in YAML form at `~/.tunnels`. At the moment, you can't have multiple configuration files, change the location of the configuration file or anything of that nature.
26
+ You configure the tunnel boring machine by creating a configuration file in YAML form at `~/.tbm`. At the moment, you can't have multiple configuration files, change the location of the configuration file or anything of that nature.
26
27
 
27
28
  An example configuration file follows:
28
29
 
@@ -35,14 +36,12 @@ An example configuration file follows:
35
36
  as400: [ 449, 8470, 8471, 8476 ]
36
37
  alias: [ ju, ussi ]
37
38
  qa:
38
- forward: 8080
39
- staging:
40
- alias: [ stage, st ]
41
- tunnel: 8080:80
39
+ tunnel: 8080
40
+ staging (stage, st): 8080:80
42
41
  5250: 8023:as400:23
43
42
  webfacing: as400:10905
44
43
 
45
- Although the above configuration format is still subject to flux, I feel like it's starting to stabilize, so I'm going to need to document it more thoroughly. For the time being, you might want to look at the [closed issue](https://github.com/geoffreywiseman/tunnel-boring-machine/issues/38) regarding the format change.
44
+ I have [documented the configuration file format](http://geoffreywiseman.github.com/tunnel-boring-machine/config.html) on the website.
46
45
 
47
46
  ## License ##
48
47
  I've put it under the UNLICENSE. Basically, I don't care if you use it, bundle it inside commercial software, or otherwise make use of it, and I don't offer any kind of warranty or support guarantees, nor do I guarantee that any of the projects dependencies are suited for whatever purpose you have in mind. That's all up to you. That said, if you want to talk about it, see the next section.
data/Rakefile CHANGED
@@ -21,19 +21,26 @@ RSpec::Core::RakeTask.new(:coverage) do |t|
21
21
  end
22
22
 
23
23
  spec = Gem::Specification.new do |spec|
24
+ # Basics
24
25
  spec.name = 'tbm'
25
26
  spec.version = TBM::VERSION
26
27
  spec.date = TBM::RELEASE_DATE
27
28
  spec.summary = 'Manages SSH Tunnels by creating an SSH connection and forwarding ports based on named targets defined in configuration.'
28
29
  spec.description = 'The "Tunnel Boring Machine" is meant to bore ssh tunnels through the internet to your desired destination simply and repeatedly, as often as you need them. This is a tool for someone who needs SSH tunnels frequently.'
29
- spec.author = 'Geoffrey Wiseman'
30
- spec.email = 'geoffrey.wiseman@codiform.com'
31
- spec.homepage = 'http://github.com/geoffreywiseman/tunnel-boring-machine'
32
- spec.executables << 'tbm'
30
+ spec.add_dependency( 'net-ssh', '>= 2.6.2' )
33
31
 
32
+ # Files
33
+ spec.executables << 'tbm'
34
34
  spec.files = Dir['{lib,spec}/**/*.rb', 'bin/*', 'Rakefile', 'README.md', 'UNLICENSE']
35
35
 
36
- spec.add_dependency( 'net-ssh', '>= 2.6.2' )
36
+ # Documentation
37
+ spec.has_rdoc = true
38
+
39
+ # About
40
+ spec.author = 'Geoffrey Wiseman'
41
+ spec.email = 'geoffrey.wiseman@codiform.com'
42
+ spec.homepage = 'http://github.com/geoffreywiseman/tunnel-boring-machine'
43
+ spec.license = 'UNLICENSE'
37
44
  end
38
45
 
39
46
  Gem::PackageTask.new( spec ) do |pkg|
@@ -41,7 +41,8 @@ module TBM
41
41
  machine.bore
42
42
  end
43
43
  else
44
- puts "Cannot parse configuration:\n\t#{config.errors.join('\n\t')}"
44
+ formatted_errors = config.errors.join( "\n\t" )
45
+ puts "Cannot parse configuration. Errors:\n\t#{formatted_errors}\n"
45
46
  end
46
47
  end
47
48
 
@@ -7,8 +7,31 @@ module TBM
7
7
 
8
8
  # The configuration file used for parsing config.
9
9
  CONFIG_FILE = File.expand_path( '~/.tbm' )
10
+
11
+ # Pattern for Gateway Server with Optional Username
10
12
  GATEWAY_PATTERN = /^([^@]+)(@([^@]+))?$/
11
13
 
14
+ # Pattern for a tunnel with a remote host followed by a port (example.com:3333)
15
+ HOSTPORT_PATTERN = /^([a-zA-Z0-9\.\-]+):(\d{1,5})$/
16
+
17
+ # Pattern for a tunnel with a remote host and local and remote ports (1234:example.com:4321)
18
+ PORTHOSTPORT_PATTERN = /^(\d{1,5}):([a-zA-Z0-9\.\-]+):(\d{1,5})$/
19
+
20
+ # Pattern for a tunnel with a local port and a gateway port (1234:4321)
21
+ PORTPORT_PATTERN = /^(\d{1,5}):(\d{1,5})$/
22
+
23
+ # Pattern for a tunnel with a single port to be used client/server to forward to the gateway (1234)
24
+ PORT_PATTERN = /^\d{1,5}$/
25
+
26
+ # Pattern for a target with aliases defined in the target name
27
+ TARGET_NAME_PATTERN = /^
28
+ ([A-Za-z0-9\.\-\[\]\#]+) # name
29
+ (\s*\(\s* # optional parenthesized aliases
30
+ ([A-Za-z0-9\.\-\[\]\#]+\s* # first alias
31
+ (,\s*[A-Za-z0-9\.\-\[\]\#]+\s*)*) # repeating alias pattern with commas
32
+ \))?\s*$/x # end of parenthesized aliases
33
+
34
+
12
35
  # Parses the tunnel boring machine configuration to get a list of targets which can
13
36
  # be invoked to bore tunnels.
14
37
  #
@@ -69,16 +92,33 @@ module TBM
69
92
 
70
93
  def self.parse_targets( gateway_host, gateway_username, targets, config )
71
94
  if Hash === targets
72
- targets.each_key do |target_name|
73
- target = Target.new( target_name.to_s, gateway_host, gateway_username )
74
- config.targets << target
75
- configure_target( target, targets[target_name], config )
95
+ targets.each_key do |target_name_string|
96
+ names = parse_target_names( target_name_string.to_s )
97
+ if names.empty?
98
+ config.errors << "Cannot parse target name: #{target_name_string}"
99
+ else
100
+ target = Target.new( names.shift, gateway_host, gateway_username )
101
+ config.targets << target
102
+ names.each { |aka| target.add_alias aka }
103
+ configure_target( target, targets[target_name_string], config )
104
+ end
76
105
  end
77
106
  else
78
107
  config.errors << "Cannot parse targets, expected Hash, received: #{targets.class}"
79
108
  end
80
109
  end
81
110
 
111
+ def self.parse_target_names( target_name_string )
112
+ names = []
113
+ if TARGET_NAME_PATTERN =~ target_name_string
114
+ names << $1
115
+ unless $3.nil?
116
+ names.concat $3.split(',').map { |x| x.strip }
117
+ end
118
+ end
119
+ return names
120
+ end
121
+
82
122
  def self.configure_target( target, target_config, config )
83
123
  case target_config
84
124
  when Fixnum, String
@@ -99,54 +139,45 @@ module TBM
99
139
  def self.tunnel( target, tunnel_config, config )
100
140
  case tunnel_config
101
141
  when Fixnum
102
- if valid_port?( tunnel_config )
103
- target.add_tunnel( Tunnel.new( tunnel_config ) )
104
- else
105
- config.errors << "Invalid port number: #{tunnel_config}"
106
- end
142
+ config.errors.concat validate_and_add( tunnel_config, tunnel_config, target )
107
143
  when String
108
144
  case tunnel_config
109
- when /^\d{1,5}$/
110
- port = tunnel_config.to_i
111
- if valid_port?( port )
112
- target.add_tunnel( Tunnel.new( port ) )
113
- else
114
- config.errors << "Invalid port number: #{tunnel_config}"
115
- end
116
- when /^(\d{1,5}):(\d{1,5})$/
117
- port = $1.to_i
118
- remote_port = $2.to_i
119
- if !valid_port?( port )
120
- config.errors << "Invalid local port number #{port} from #{tunnel_config}"
121
- elsif !valid_port?( remote_port )
122
- config.errors << "Invalid remote port number #{remote_port} from #{tunnel_config}"
123
- else
124
- target.add_tunnel( Tunnel.new( port, :remote_port => remote_port ) )
125
- end
126
- when /^(\d{1,5}):([a-zA-Z0-9\.\-]+):(\d{1,5})$/
127
- port = $1.to_i
128
- remote_host = $2
129
- remote_port = $3.to_i
130
- if !valid_port?( port )
131
- config.errors << "Invalid local port number #{port} from #{tunnel_config}"
132
- elsif !valid_port?( remote_port )
133
- config.errors << "Invalid remote port number #{remote_port} from #{tunnel_config}"
134
- else
135
- target.add_tunnel( Tunnel.new( port, :remote_host => remote_host, :remote_port => remote_port ) )
136
- end
137
- when /^([a-zA-Z0-9\.\-]+):(\d{1,5})$/
138
- port = $2.to_i
139
- remote_host = $1
140
- if !valid_port?( port )
141
- config.errors << "Invalid port number #{port} from #{tunnel_config}"
142
- else
143
- target.add_tunnel( Tunnel.new( port, :remote_host => remote_host ) )
144
- end
145
+ when PORT_PATTERN
146
+ config.errors.concat validate_and_add( tunnel_config.to_i, tunnel_config, target )
147
+ when PORTPORT_PATTERN
148
+ config.errors.concat validate_and_add( $1.to_i, tunnel_config, target, :remote_port => $2.to_i )
149
+ when PORTHOSTPORT_PATTERN
150
+ config.errors.concat validate_and_add( $1.to_i, tunnel_config, target, :remote_host => $2, :remote_port => $3.to_i )
151
+ when HOSTPORT_PATTERN
152
+ config.errors.concat validate_and_add( $2.to_i, tunnel_config, target, :remote_host => $1 )
145
153
  else
146
- config.errors << "Cannot parse tunnel: #{tunnel_config} (#{tunnel_config.class})"
154
+ config.errors.concat "Cannot parse tunnel: #{tunnel_config} (#{tunnel_config.class})"
147
155
  end
148
156
  else
149
- config.errors << "Cannot parse tunnel: #{tunnel_config} (#{tunnel_config.class})"
157
+ config.errors.concat "Cannot parse tunnel: #{tunnel_config} (#{tunnel_config.class})"
158
+ end
159
+ end
160
+
161
+ def self.validate_and_add( port, tunnel_config, target, options={} )
162
+ errors = []
163
+ if options.has_key?( :remote_port ) then
164
+ validate_port( port, 'local', tunnel_config, errors )
165
+ validate_port( options[:remote_port], 'remote', tunnel_config, errors )
166
+ else
167
+ validate_port( port, nil, tunnel_config, errors )
168
+ end
169
+ target.add_tunnel( Tunnel.new( port, options ) ) if errors.empty?
170
+ return errors
171
+ end
172
+
173
+ def self.validate_port( port, port_qualifier, tunnel_config, errors )
174
+ if valid_port?( port )
175
+ return true
176
+ else
177
+ qualified_port = ( port_qualifier.nil? ? "port" : "#{port_qualifier} port" )
178
+ port_source = tunnel_config.to_s.match PORT_PATTERN ? '' : " from ${tunnel_config}"
179
+ errors << "Invalid #{qualified_port} number #{port}#{port_source}"
180
+ return false
150
181
  end
151
182
  end
152
183
 
@@ -184,35 +215,18 @@ module TBM
184
215
  def self.remote_tunnel( target, remote_host, tunnel_config, config )
185
216
  case tunnel_config
186
217
  when Fixnum
187
- if valid_port?( tunnel_config )
188
- target.add_tunnel( Tunnel.new( tunnel_config, :remote_host => remote_host ) )
189
- else
190
- config.errors << "Invalid port number: #{tunnel_config}"
191
- end
218
+ config.errors.concat validate_and_add( tunnel_config, tunnel_config, target, :remote_host => remote_host )
192
219
  when String
193
220
  case tunnel_config
194
- when /^\d{1,5}$/
195
- port = tunnel_config.to_i
196
- if valid_port?( port )
197
- target.add_tunnel( Tunnel.new( port, :remote_host => remote_host ) )
198
- else
199
- config.errors << "Invalid port number: #{tunnel_config}"
200
- end
201
- when /^(\d{1,5}):(\d{1,5})$/
202
- port = $1.to_i
203
- remote_port = $2.to_i
204
- if !valid_port?( port )
205
- config.errors << "Invalid local port number #{port} from #{tunnel_config}"
206
- elsif !valid_port?( remote_port )
207
- config.errors << "Invalid remote port number #{remote_port} from #{tunnel_config}"
208
- else
209
- target.add_tunnel( Tunnel.new( port, :remote_port => remote_port, :remote_host => remote_host ) )
210
- end
221
+ when PORT_PATTERN
222
+ config.errors.concat validate_and_add( tunnel_config.to_i, tunnel_config, target, :remote_host => remote_host )
223
+ when PORTPORT_PATTERN
224
+ config.errors.concat validate_and_add( $1.to_i, tunnel_config, target, :remote_host => remote_host, :remote_port => $2.to_i )
211
225
  else
212
- config.errors << "Cannot parse tunnel: #{tunnel_config} (#{tunnel_config.class})"
226
+ config.errors.concat "Cannot parse tunnel: #{tunnel_config} (#{tunnel_config.class})"
213
227
  end
214
228
  else
215
- config.errors << "Cannot parse tunnel: #{tunnel_config} (#{tunnel_config.class})"
229
+ config.errors.concat "Cannot parse tunnel: #{tunnel_config} (#{tunnel_config.class})"
216
230
  end
217
231
  end
218
232
 
@@ -1,6 +1,6 @@
1
1
  # The namespace for this application/gem.
2
2
  module TBM
3
3
  APP_NAME = "Tunnel Boring Machine"
4
- VERSION = "0.2.0"
5
- RELEASE_DATE = '2013-01-22'
4
+ VERSION = "0.3.0"
5
+ RELEASE_DATE = '2013-03-07'
6
6
  end
@@ -85,6 +85,23 @@ describe ConfigParser do
85
85
  specify { subject.errors.should include_match(/No target config/) }
86
86
  end
87
87
 
88
+ context "with parenthesized alias" do
89
+ let(:targets) { { 'target-name (aka)' => 8080 } }
90
+ it "should accept alias as name" do
91
+ subject.should be_valid
92
+ subject.get_target( 'aka' ).should_not be_nil
93
+ end
94
+ end
95
+
96
+ context "with parenthesized aliases" do
97
+ let(:targets) { { 'target-name (pseudo,nym)' => 8080 } }
98
+ it "should accept alias as name" do
99
+ subject.should be_valid
100
+ subject.get_target( 'pseudo' ).should_not be_nil
101
+ subject.get_target( 'nym' ).should_not be_nil
102
+ end
103
+ end
104
+
88
105
  context "with target config of 8080" do
89
106
  let(:target) { 8080 }
90
107
  it "should forward port 8080" do
@@ -95,7 +112,7 @@ describe ConfigParser do
95
112
 
96
113
  context "with target config of 0" do
97
114
  let(:target) { 0 }
98
- specify { subject.errors.should include( "Invalid port number: 0" ) }
115
+ specify { subject.errors.should include( "Invalid port number 0" ) }
99
116
  end
100
117
 
101
118
  context "with target config of '8443'" do
@@ -108,7 +125,7 @@ describe ConfigParser do
108
125
 
109
126
  context "with target config of '77777'" do
110
127
  let(:target) { "77777" }
111
- specify { subject.errors.should include( "Invalid port number: 77777" ) }
128
+ specify { subject.errors.should include( "Invalid port number 77777" ) }
112
129
  end
113
130
 
114
131
  context "with target config of '8080:80'" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tbm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-22 00:00:00.000000000 Z
12
+ date: 2013-03-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: net-ssh
@@ -53,7 +53,8 @@ files:
53
53
  - README.md
54
54
  - UNLICENSE
55
55
  homepage: http://github.com/geoffreywiseman/tunnel-boring-machine
56
- licenses: []
56
+ licenses:
57
+ - UNLICENSE
57
58
  post_install_message:
58
59
  rdoc_options: []
59
60
  require_paths:
@@ -66,7 +67,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
66
67
  version: '0'
67
68
  segments:
68
69
  - 0
69
- hash: -1799474746480285819
70
+ hash: 450154189210157473
70
71
  required_rubygems_version: !ruby/object:Gem::Requirement
71
72
  none: false
72
73
  requirements: