tunneler 0.0.1

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.
@@ -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: []