Works with public keys now, for passwordless operation

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@2000 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
Jamis Buck 2005-08-13 18:36:02 +00:00
parent cbf709fc5c
commit f44dac8935
7 changed files with 181 additions and 31 deletions

View File

@ -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]

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

36
switchtower/test/utils.rb Normal file
View File

@ -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