tunneler 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ /log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+ ruby "1.9.3"
3
+
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Nels Broberg
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,54 @@
1
+ Tunneler
2
+ =========
3
+
4
+ Tunneler is a Ruby command line interface for multi-hop SSH tunneling.
5
+
6
+ - SCP files through a bastion host
7
+ - Execute remote commands through a bastion host
8
+ - SSH through a bastion host using native terminal
9
+
10
+ Version
11
+ ----
12
+
13
+ 0.0.1
14
+
15
+ Installation
16
+ --------------
17
+
18
+ ```sh
19
+ gem install tunneler
20
+ ```
21
+
22
+ Example Command-Line Usage
23
+ --------------
24
+
25
+ ```sh
26
+ tunneler --bastion-host 150.1.2.3 --destination-host 151.2.3.4 ssh
27
+ tunneler --bastion-host 150.1.2.3 --destination-host 151.2.3.4 scp local_file destination_file
28
+ tunneler --bastion-host 150.1.2.3 -d 151.2.3.4 execute 'whoami'
29
+ ```
30
+
31
+ Example Gem Usage
32
+ --------------
33
+
34
+ ```ruby
35
+ require "tunneler"
36
+
37
+ # Create SSH tunnel
38
+ tunnel = SshTunnel.new(bastion_user, bastion_host, {:keys => [bastion_key]})
39
+
40
+ # Establish remote connection
41
+ destination_host_connection = tunnel.remote(destination_user, destination_host, {:keys => [destination_key]})
42
+
43
+ # Upload file to destination host via tunnel
44
+ destination_host_connection.scp(local_file_path, destination_file_path)
45
+
46
+ # Execute common on destination host via tunnel
47
+ response = destination_host_connection.ssh(command)
48
+ ```
49
+
50
+
51
+ License
52
+ ----
53
+
54
+ MIT
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
8
+ task :console do
9
+ exec 'irb -I lib -r provisioner.rb'
10
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require "tunneler"
5
+
6
+ tunneler_cli = Tunneler::CommandLine.new
7
+ tunneler_cli.run
@@ -0,0 +1,26 @@
1
+ require "net/scp"
2
+ require "net/ssh/gateway"
3
+ require "trollop"
4
+ require "singleton"
5
+ require "time"
6
+
7
+ module Tunneler
8
+
9
+ APPLICATION_ROOT = File.expand_path("..",File.dirname(__FILE__))
10
+
11
+ def self.debug
12
+ Config.instance.debug
13
+ end
14
+
15
+ def self.debug=(boolean)
16
+ require "debugger" if boolean
17
+ Config.instance.debug = boolean
18
+ end
19
+
20
+ def self.log(message, level=:info)
21
+ Logger.log(message, level)
22
+ end
23
+
24
+ Gem.find_files("tunneler/**/*.rb").each { |path| require path }
25
+
26
+ end
@@ -0,0 +1,123 @@
1
+ module Tunneler
2
+ class CommandLine
3
+
4
+ def run
5
+ setup_options
6
+ subcommand = process_sub_command
7
+ if @strict_host_checking
8
+ @strict_host_checking_options = ""
9
+ else
10
+ @strict_host_checking_options = "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
11
+ end
12
+
13
+ Tunneler.debug = @debug
14
+
15
+ if subcommand == "ssh"
16
+ ssh
17
+ elsif subcommand == "scp"
18
+ local_file_path = ARGV.shift
19
+ destination_file_path = ARGV.shift
20
+ if local_file_path && destination_file_path
21
+ scp(local_file_path, destination_file_path)
22
+ end
23
+ elsif subcommand == "execute"
24
+ if command = ARGV.shift
25
+ execute(command)
26
+ end
27
+ end
28
+ end
29
+
30
+ def execute(command)
31
+ tunnel = SshTunnel.new(@bastion_user, @bastion_host, {:keys => [@bastion_key]})
32
+ destination_host_connection = tunnel.remote(@destination_user, @destination_host, {:keys => [@destination_key]})
33
+ Tunneler.log "Executing remote command '#{command}' on '#{@destination_host}' via '#{@bastion_host}'..."
34
+ Tunneler.log destination_host_connection.ssh(command)
35
+ end
36
+
37
+ def scp(local_file_path, destination_file_path)
38
+ tunnel = SshTunnel.new(@bastion_user, @bastion_host, {:keys => [@bastion_key]})
39
+ destination_host_connection = tunnel.remote(@destination_user, @destination_host, {:keys => [@destination_key]})
40
+ Tunneler.log "Uploading '#{local_file_path}' to '#{destination_file_path}' on '#{@destination_host}' via '#{@bastion_host}'..."
41
+ destination_host_connection.scp(local_file_path, destination_file_path)
42
+ end
43
+
44
+ def ssh
45
+ destination_key_filename = Pathname.new(@destination_key).basename
46
+ commands = [
47
+ "scp -i #{@bastion_key} #{@destination_key} #{@bastion_user}@#{@bastion_host}:",
48
+ "ssh -i #{@bastion_key} #{@strict_host_checking_options} -A -t -l #{@bastion_user} #{@bastion_host} -L #{DEFAULT_LOCAL_TUNNEL_PORT}:localhost:#{DEFAULT_LOCAL_TUNNEL_PORT} ssh #{@strict_host_checking_options} -A -t -i /home/#{@bastion_user}/#{destination_key_filename} -l #{@destination_user} #{@destination_host} -L #{DEFAULT_LOCAL_TUNNEL_PORT}:localhost:#{DEFAULT_LOCAL_TUNNEL_PORT}",
49
+ ]
50
+ print "Opening ssh connection to '#{@destination_host}' via '#{@bastion_host}'"
51
+ open_terminal(commands.join(" ; "))
52
+ # remove the key as soon as authentication has occurred
53
+ # system "ssh -i #{@bastion_key} #{@strict_host_checking_options} -A -t -l #{@bastion_user} #{@bastion_host} -e 'rm /home/#{@bastion_user}/#{destination_private_key_filename}'"
54
+ end
55
+
56
+ def open_terminal(command)
57
+ os = RbConfig::CONFIG['host_os']
58
+ case os
59
+ when /darwin|mac os/
60
+ terminal = ENV['TERM_PROGRAM']
61
+ if terminal =~ /iTerm/
62
+ local_command = "osascript -e 'tell application \"System Events\" to keystroke \"t\" using command down' -e 'tell application \"iTerm\" to tell session -1 of current terminal to write text \"#{command}\"'"
63
+ elsif terminal =~ /Terminal/
64
+ local_command = "osascript -e 'tell application \"Terminal\" to activate' -e 'tell application \"System Events\" to tell process \"Terminal\" to keystroke \"t\" using command down' -e 'tell application \"Terminal\" to do script \"#{command}\" in selected tab of the front window'"
65
+ else
66
+ Tunneler.log "Terminal '#{terminal}' not supported", :cli
67
+ exit
68
+ end
69
+ when /linux/
70
+ local_command = "gnome-terminal -e '#{command}'"
71
+ else
72
+ Tunneler.log "SSH option not available in #{os.inspect}", :warn
73
+ return
74
+ end
75
+ system(local_command)
76
+ end
77
+
78
+ def setup_options
79
+ help = self.class.help + "\nParameters:"
80
+ sub_commands = self.class.sub_commands
81
+ options = Trollop::options(ARGV) do
82
+ banner help
83
+ opt :bastion_host, "Tunneler host", :type => :string, :required => true
84
+ opt :bastion_user, "Tunneler SSH user", :type => :string, :default => DEFAULT_SSH_USER
85
+ opt :bastion_key, "Tunneler SSH key path", :type => :string, :default => DEFAULT_SSH_KEY_PATH
86
+ opt :destination_host, "Destination host", :type => :string, :required => true
87
+ opt :destination_user, "Destination SSH user", :type => :string, :default => DEFAULT_SSH_USER
88
+ opt :destination_key, "Destination SSH key path", :type => :string, :default => DEFAULT_SSH_KEY_PATH
89
+ opt :strict_host_checking, "Enable SSH Strict Host Checking", :default => false
90
+ opt :debug, "Enable debug mode"
91
+ stop_on sub_commands
92
+ end
93
+ options.each { |name, value| instance_variable_set("@#{name}", value) }
94
+ end
95
+
96
+ def self.help
97
+ "Tunneler CLI - ssh or scp through bastion host tunnel\n\n" +
98
+ "Available subcommands: #{self.sub_commands.join(', ')}\n\n" +
99
+ "Command syntax: tunneler <parameters> <subcommand>\n" +
100
+ "Examples: tunneler -b 120.1.2.3 -d 121.2.3.4 ssh\n" +
101
+ " tunneler -b 120.1.2.3 -d 121.2.3.4 scp local_file destination_file\n" +
102
+ " tunneler -b 120.1.2.3 -d 121.2.3.4 execute whoami\n"
103
+ end
104
+
105
+ def self.sub_commands
106
+ ["ssh", "scp", "execute"]
107
+ end
108
+
109
+ def process_sub_command
110
+ sub_command = ARGV.shift
111
+ unless self.class.sub_commands.include?(sub_command)
112
+ if sub_command
113
+ Tunneler.log "Invalid subcommand '#{sub_command}'", :cli
114
+ else
115
+ Tunneler.log "Please specify --help for command information", :cli
116
+ end
117
+ exit
118
+ end
119
+ sub_command
120
+ end
121
+
122
+ end
123
+ end
@@ -0,0 +1,21 @@
1
+ module Tunneler
2
+
3
+ DEFAULT_SSH_USER = ENV['USER']
4
+
5
+ DEFAULT_SSH_KEY_PATH = "#{ENV['HOME']}/.ssh/id_rsa"
6
+
7
+ DEFAULT_LOCAL_TUNNEL_PORT = 9997
8
+
9
+ class Config
10
+
11
+ include Singleton
12
+
13
+ attr_accessor :debug
14
+
15
+ def initialize
16
+ @debug = false
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,43 @@
1
+ module Tunneler
2
+ class Logger
3
+ include Singleton
4
+
5
+ @@session = ""
6
+
7
+ LOG_FILE = "#{APPLICATION_ROOT}/log"
8
+
9
+ def self.create_log_file
10
+ unless File.exist?(LOG_FILE)
11
+ File.open(LOG_FILE, "w") {}
12
+ end
13
+ end
14
+
15
+ def self.log(message, level)
16
+ message = message.to_s
17
+ self.log_to_file(message, level) unless level == :cli
18
+ self.log_to_session(message)
19
+ puts message unless level == :debug && !Tunneler.debug
20
+ end
21
+
22
+ def self.log_to_file(message, level)
23
+ self.create_log_file
24
+ @@log ||= File.open(LOG_FILE, "a")
25
+ log_entry = [Time.now.utc.iso8601, level.upcase, message.strip.strip]
26
+ @@log.puts(log_entry.join("\t"))
27
+ end
28
+
29
+ def self.log_to_session(message)
30
+ @@session << message + "\n"
31
+ end
32
+
33
+ def self.session
34
+ @@session
35
+ end
36
+
37
+ def self.truncate_session
38
+ session = @@session
39
+ @@session = ""
40
+ session
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,85 @@
1
+ module Tunneler
2
+ class Ssh
3
+
4
+ SSH_READY_TIMEOUT = 70
5
+ SSH_CONNECTIVITY_TIMEOUT = 15
6
+ ACCEPTABLE_CONNECTIVITY_ERRORS = [Errno::ECONNREFUSED]
7
+
8
+ SSH_COMMAND_TIMEOUT = 300
9
+
10
+ SCP_OPTIONS = { :recursive => true, :compression => "zlib" }
11
+
12
+ def initialize(user, host, options={})
13
+ @user = user
14
+ @host = host
15
+ @options = self.class.merge_options(options)
16
+ end
17
+
18
+ def self.default_options
19
+ {
20
+ :paranoid => false,
21
+ :keys => [DEFAULT_SSH_KEY_PATH],
22
+ :timeout => self::SSH_COMMAND_TIMEOUT,
23
+ }
24
+ end
25
+
26
+ def self.merge_options(options={})
27
+ if options.empty?
28
+ Ssh.default_options
29
+ else
30
+ Ssh.default_options.merge(options)
31
+ end
32
+ end
33
+
34
+ def wait_for_ssh_access
35
+ Timer.timeout("#{@host}: SSH test",self.class::SSH_READY_TIMEOUT) do
36
+ until sshable? do
37
+ Tunneler.log "Waiting between connection attempts", :debug
38
+ sleep 5
39
+ end
40
+ end
41
+ end
42
+
43
+ def sshable?
44
+ begin
45
+ return test_connectivity
46
+ rescue Exception => e
47
+ if ACCEPTABLE_CONNECTIVITY_ERRORS.include?(e.class)
48
+ Tunneler.log "Connectivity error: #{e.class} - #{e.message}", :debug
49
+ return false
50
+ else
51
+ raise e
52
+ end
53
+ end
54
+ end
55
+
56
+ def test_connectivity
57
+ options_override = @options.clone
58
+ options_override[:timeout] = self.class::SSH_CONNECTIVITY_TIMEOUT
59
+ options_override[:verbose] = :debug if Tunneler.debug
60
+ if ssh("whoami", options_override).strip == @user
61
+ return true
62
+ end
63
+ end
64
+
65
+ def ssh(command, options_override={})
66
+ if options_override.empty?
67
+ options = @options
68
+ else
69
+ options = options_override
70
+ end
71
+ output = ""
72
+ Net::SSH.start(@host, @user, @options) do |ssh|
73
+ output = ssh.exec!(command)
74
+ end
75
+ output
76
+ end
77
+
78
+ def scp(local_path, remote_path)
79
+ Net::SSH.start(@host, @user, @options) do |ssh|
80
+ ssh.scp.upload!(local_path, remote_path, SCP_OPTIONS)
81
+ end
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,103 @@
1
+ module Tunneler
2
+ class SshTunnel
3
+
4
+ attr_reader :options
5
+
6
+ def initialize(user, host, options={})
7
+ @user = user
8
+ @host = host
9
+ @options = Ssh.merge_options(options)
10
+
11
+ connect
12
+ end
13
+
14
+ def connect
15
+ @gateway = Gateway.connect(@host, @user, @options)
16
+ end
17
+
18
+ def remote(user, host, options={})
19
+ Remote.new(@gateway, user, host, Ssh.merge_options(options))
20
+ end
21
+
22
+ def terminate
23
+ @gateway.shutdown!
24
+ end
25
+
26
+ class Gateway < Net::SSH::Gateway
27
+
28
+ def initialize(host, user, options={})
29
+ super
30
+ @host = host
31
+ @user = user
32
+ @options = options
33
+ end
34
+
35
+ def self.connect(host, user, options={})
36
+ gateway = self.new(host, user, options)
37
+ gateway.open("127.0.0.1", self.random_open_port)
38
+ gateway
39
+ end
40
+
41
+ def self.random_open_port
42
+ socket = Socket.new(:INET, :STREAM, 0)
43
+ socket.bind(Addrinfo.tcp("127.0.0.1", 0))
44
+ socket.local_address.ip_port
45
+ end
46
+
47
+ def reconnect
48
+ shutdown!
49
+ self.class.connect(@host,@user,@options)
50
+ end
51
+
52
+ end
53
+
54
+ class Remote < Ssh
55
+
56
+ SSH_READY_TIMEOUT = 100
57
+ ACCEPTABLE_CONNECTIVITY_ERRORS = [Net::SSH::Disconnect, IOError]
58
+
59
+ def initialize(gateway, user, host, options={})
60
+ @gateway = gateway
61
+ super(user, host, options)
62
+ end
63
+
64
+ def sshable?
65
+ begin
66
+ gateway_reconnect
67
+ return test_connectivity
68
+ rescue Exception => e
69
+ if ACCEPTABLE_CONNECTIVITY_ERRORS.include?(e.class)
70
+ Tunneler.log "Connectivity error: #{e.class} - #{e.message}", :debug
71
+ return false
72
+ else
73
+ raise e
74
+ end
75
+ end
76
+ end
77
+
78
+ def gateway_reconnect
79
+ @gateway = @gateway.reconnect
80
+ end
81
+
82
+ def ssh(command, options_override={})
83
+ if options_override.empty?
84
+ options = @options
85
+ else
86
+ options = options_override
87
+ end
88
+ output = ""
89
+ @gateway.ssh(@host, @user, @options) do |ssh|
90
+ output = ssh.exec!(command)
91
+ end
92
+ output
93
+ end
94
+
95
+ def scp(local_path, remote_path)
96
+ @gateway.ssh(@host, @user, @options) do |ssh|
97
+ ssh.scp.upload!(local_path, remote_path, SCP_OPTIONS)
98
+ end
99
+ end
100
+ end
101
+
102
+ end
103
+ end
@@ -0,0 +1,3 @@
1
+ module Tunneler
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tunneler/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "tunneler"
8
+ spec.version = Tunneler::VERSION
9
+ spec.authors = ["Nels Broberg"]
10
+ spec.email = ["zifridorio@hotmail.com"]
11
+ spec.description = %q{Command line tool and ruby gem for SSH tunneling}
12
+ spec.summary = %q{SSH to server via tunneler host}
13
+ spec.homepage = "https://github.com/nbroberg/tunneler"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "net-scp"
22
+ spec.add_dependency "net-ssh-gateway"
23
+ spec.add_dependency "trollop"
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.3"
26
+ spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "rspec"
28
+ spec.add_development_dependency "debugger"
29
+ end
metadata ADDED
@@ -0,0 +1,174 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tunneler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Nels Broberg
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-10-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: net-scp
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: net-ssh-gateway
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: trollop
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: bundler
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '1.3'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '1.3'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rake
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rspec
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: debugger
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: Command line tool and ruby gem for SSH tunneling
127
+ email:
128
+ - zifridorio@hotmail.com
129
+ executables:
130
+ - tunneler
131
+ extensions: []
132
+ extra_rdoc_files: []
133
+ files:
134
+ - .gitignore
135
+ - Gemfile
136
+ - Gemfile.lock
137
+ - LICENSE
138
+ - README.md
139
+ - Rakefile
140
+ - bin/tunneler
141
+ - lib/tunneler.rb
142
+ - lib/tunneler/cli.rb
143
+ - lib/tunneler/config.rb
144
+ - lib/tunneler/logger.rb
145
+ - lib/tunneler/ssh.rb
146
+ - lib/tunneler/ssh_tunnel.rb
147
+ - lib/tunneler/version.rb
148
+ - tunneler.gemspec
149
+ homepage: https://github.com/nbroberg/tunneler
150
+ licenses:
151
+ - MIT
152
+ post_install_message:
153
+ rdoc_options: []
154
+ require_paths:
155
+ - lib
156
+ required_ruby_version: !ruby/object:Gem::Requirement
157
+ none: false
158
+ requirements:
159
+ - - ! '>='
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ required_rubygems_version: !ruby/object:Gem::Requirement
163
+ none: false
164
+ requirements:
165
+ - - ! '>='
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ requirements: []
169
+ rubyforge_project:
170
+ rubygems_version: 1.8.23
171
+ signing_key:
172
+ specification_version: 3
173
+ summary: SSH to server via tunneler host
174
+ test_files: []