diff options
author | Jamis Buck <jamis@37signals.com> | 2005-08-13 18:36:02 +0000 |
---|---|---|
committer | Jamis Buck <jamis@37signals.com> | 2005-08-13 18:36:02 +0000 |
commit | f44dac89356d648a2b3a4249e232a76b82e6275b (patch) | |
tree | 342c9af79ae6c84616e2692967044876f54a0d87 /switchtower | |
parent | cbf709fc5c7725f64471980ca52f3f99d6bb568b (diff) | |
download | rails-f44dac89356d648a2b3a4249e232a76b82e6275b.tar.gz rails-f44dac89356d648a2b3a4249e232a76b82e6275b.tar.bz2 rails-f44dac89356d648a2b3a4249e232a76b82e6275b.zip |
Works with public keys now, for passwordless operation
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@2000 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'switchtower')
-rw-r--r-- | switchtower/CHANGELOG | 2 | ||||
-rw-r--r-- | switchtower/lib/switchtower/actor.rb | 9 | ||||
-rw-r--r-- | switchtower/lib/switchtower/gateway.rb | 11 | ||||
-rw-r--r-- | switchtower/lib/switchtower/ssh.rb | 30 | ||||
-rw-r--r-- | switchtower/test/scm/subversion_test.rb | 20 | ||||
-rw-r--r-- | switchtower/test/ssh_test.rb | 104 | ||||
-rw-r--r-- | switchtower/test/utils.rb | 36 |
7 files changed, 181 insertions, 31 deletions
diff --git a/switchtower/CHANGELOG b/switchtower/CHANGELOG index c645bff275..f40d897780 100644 --- a/switchtower/CHANGELOG +++ b/switchtower/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Works with public keys now, for passwordless deployment + * Subversion module recognizes the password prompt for HTTP authentication * Preserve +x on scripts when using darcs #1929 [Scott Barron] diff --git a/switchtower/lib/switchtower/actor.rb b/switchtower/lib/switchtower/actor.rb index db25ca99c2..012f4a1f55 100644 --- a/switchtower/lib/switchtower/actor.rb +++ b/switchtower/lib/switchtower/actor.rb @@ -1,7 +1,7 @@ require 'erb' -require 'net/ssh' require 'switchtower/command' require 'switchtower/gateway' +require 'switchtower/ssh' module SwitchTower @@ -12,7 +12,7 @@ module SwitchTower # new actor via Configuration#actor. class Actor - # An adaptor for making the Net::SSH interface look and act like that of the + # An adaptor for making the SSH interface look and act like that of the # Gateway class. class DefaultConnectionFactory #:nodoc: def initialize(config) @@ -20,8 +20,7 @@ module SwitchTower end def connect_to(server) - Net::SSH.start(server, :username => @config.user, - :password => @config.password) + SSH.connect(server, @config) end end @@ -40,7 +39,7 @@ module SwitchTower # instances of Actor::Task. attr_reader :tasks - # A hash of the Net::SSH sessions that are currently open and available. + # A hash of the SSH sessions that are currently open and available. # Because sessions are constructed lazily, this will only contain # connections to those servers that have been the targets of one or more # executed tasks. diff --git a/switchtower/lib/switchtower/gateway.rb b/switchtower/lib/switchtower/gateway.rb index 531c34ba5f..46f8361e9a 100644 --- a/switchtower/lib/switchtower/gateway.rb +++ b/switchtower/lib/switchtower/gateway.rb @@ -1,5 +1,5 @@ require 'thread' -require 'net/ssh' +require 'switchtower/ssh' Thread.abort_on_exception = true @@ -36,9 +36,7 @@ module SwitchTower @thread = Thread.new do @config.logger.trace "starting connection to gateway #{server}" - Net::SSH.start(server, :username => @config.user, - :password => @config.password - ) do |@session| + SSH.connect(server, @config) do |@session| @config.logger.trace "gateway connection established" @mutex.synchronize { waiter.signal } connection = @session.registry[:connection][:driver] @@ -93,9 +91,8 @@ module SwitchTower begin @session.forward.local(port, key, 22) - @pending_forward_requests[key] = - Net::SSH.start('127.0.0.1', :username => @config.user, - :password => @config.password, :port => port) + @pending_forward_requests[key] = SSH.connect('127.0.0.1', @config, + port) @config.logger.trace "connection to #{key} via gateway established" rescue Object @pending_forward_requests[key] = nil diff --git a/switchtower/lib/switchtower/ssh.rb b/switchtower/lib/switchtower/ssh.rb new file mode 100644 index 0000000000..b810f20573 --- /dev/null +++ b/switchtower/lib/switchtower/ssh.rb @@ -0,0 +1,30 @@ +require 'net/ssh' + +module SwitchTower + # A helper class for dealing with SSH connections. + class SSH + # An abstraction to make it possible to connect to the server via public key + # without prompting for the password. If the public key authentication fails + # this will fall back to password authentication. + # + # If a block is given, the new session is yielded to it, otherwise the new + # session is returned. + def self.connect(server, config, port=22, &block) + methods = [ %w(publickey hostbased), %w(password keyboard-interactive) ] + password_value = nil + + begin + Net::SSH.start(server, + :username => config.user, + :password => password_value, + :port => port, + :auth_methods => methods.shift, + &block) + rescue Net::SSH::AuthenticationFailed + raise if methods.empty? + password_value = config.password + retry + end + end + end +end diff --git a/switchtower/test/scm/subversion_test.rb b/switchtower/test/scm/subversion_test.rb index 47942d9b9c..fa65714b6b 100644 --- a/switchtower/test/scm/subversion_test.rb +++ b/switchtower/test/scm/subversion_test.rb @@ -1,28 +1,10 @@ $:.unshift File.dirname(__FILE__) + "/../../lib" +require File.dirname(__FILE__) + "/../utils" require 'test/unit' require 'switchtower/scm/subversion' class ScmSubversionTest < Test::Unit::TestCase - class MockLogger - def info(msg,pfx=nil) end - def debug(msg,pfx=nil) end - end - - class MockConfiguration < Hash - def logger - @logger ||= MockLogger.new - end - - def method_missing(sym, *args) - if args.length == 0 - self[sym] - else - super - end - end - end - class SubversionTest < SwitchTower::SCM::Subversion attr_accessor :story attr_reader :last_path diff --git a/switchtower/test/ssh_test.rb b/switchtower/test/ssh_test.rb new file mode 100644 index 0000000000..60e791beb3 --- /dev/null +++ b/switchtower/test/ssh_test.rb @@ -0,0 +1,104 @@ +$:.unshift File.dirname(__FILE__) + "/../lib" + +require File.dirname(__FILE__) + "/utils" +require 'test/unit' +require 'switchtower/ssh' + +class SSHTest < Test::Unit::TestCase + class MockSSH + AuthenticationFailed = Net::SSH::AuthenticationFailed + + class <<self + attr_accessor :story + attr_accessor :invocations + end + + def self.start(server, opts, &block) + @invocations << [server, opts, block] + err = story.shift + raise err if err + end + end + + def setup + @config = MockConfiguration.new + @config[:user] = 'demo' + @config[:password] = 'c0c0nutfr0st1ng' + MockSSH.story = [] + MockSSH.invocations = [] + end + + def test_publickey_auth_succeeds_default_port_no_block + Net.const_during(:SSH, MockSSH) do + SwitchTower::SSH.connect('demo.server.i', @config) + end + + assert_equal 1, MockSSH.invocations.length + assert_equal 'demo.server.i', MockSSH.invocations.first[0] + assert_equal 22, MockSSH.invocations.first[1][:port] + assert_equal 'demo', MockSSH.invocations.first[1][:username] + assert_nil MockSSH.invocations.first[1][:password] + assert_equal %w(publickey hostbased), + MockSSH.invocations.first[1][:auth_methods] + assert_nil MockSSH.invocations.first[2] + end + + def test_publickey_auth_succeeds_explicit_port_no_block + Net.const_during(:SSH, MockSSH) do + SwitchTower::SSH.connect('demo.server.i', @config, 23) + end + + assert_equal 1, MockSSH.invocations.length + assert_equal 23, MockSSH.invocations.first[1][:port] + assert_nil MockSSH.invocations.first[2] + end + + def test_publickey_auth_succeeds_with_block + Net.const_during(:SSH, MockSSH) do + SwitchTower::SSH.connect('demo.server.i', @config) do |session| + end + end + + assert_equal 1, MockSSH.invocations.length + assert_instance_of Proc, MockSSH.invocations.first[2] + end + + def test_publickey_auth_fails + MockSSH.story << Net::SSH::AuthenticationFailed + + Net.const_during(:SSH, MockSSH) do + SwitchTower::SSH.connect('demo.server.i', @config) + end + + assert_equal 2, MockSSH.invocations.length + + assert_nil MockSSH.invocations.first[1][:password] + assert_equal %w(publickey hostbased), + MockSSH.invocations.first[1][:auth_methods] + + assert_equal 'c0c0nutfr0st1ng', MockSSH.invocations.last[1][:password] + assert_equal %w(password keyboard-interactive), + MockSSH.invocations.last[1][:auth_methods] + end + + def test_password_auth_fails + MockSSH.story << Net::SSH::AuthenticationFailed + MockSSH.story << Net::SSH::AuthenticationFailed + + Net.const_during(:SSH, MockSSH) do + assert_raises(Net::SSH::AuthenticationFailed) do + SwitchTower::SSH.connect('demo.server.i', @config) + end + end + + assert_equal 2, MockSSH.invocations.length + + assert_nil MockSSH.invocations.first[1][:password] + assert_equal %w(publickey hostbased), + MockSSH.invocations.first[1][:auth_methods] + + assert_equal 'c0c0nutfr0st1ng', MockSSH.invocations.last[1][:password] + assert_equal %w(password keyboard-interactive), + MockSSH.invocations.last[1][:auth_methods] + end +end diff --git a/switchtower/test/utils.rb b/switchtower/test/utils.rb new file mode 100644 index 0000000000..b483f0c005 --- /dev/null +++ b/switchtower/test/utils.rb @@ -0,0 +1,36 @@ +class Module + def const_during(constant, value) + if const_defined?(constant) + overridden = true + saved = const_get(constant) + remove_const(constant) + end + + const_set(constant, value) + yield + ensure + if overridden + remove_const(constant) + const_set(constant, saved) + end + end +end + +class MockLogger + def info(msg,pfx=nil) end + def debug(msg,pfx=nil) end +end + +class MockConfiguration < Hash + def logger + @logger ||= MockLogger.new + end + + def method_missing(sym, *args) + if args.length == 0 + self[sym] + else + super + end + end +end |