Merge pull request #2754 from mitchellh/f-insert-private-key

Support password-based SSH, automatically insert insecure key
This commit is contained in:
Mitchell Hashimoto 2014-01-03 11:34:53 -08:00
commit 0f989ce554
12 changed files with 162 additions and 19 deletions

View File

@ -28,11 +28,15 @@ module Vagrant
# not yet ready for SSH, so we raise this exception.
raise Errors::SSHNotReady if info.nil?
if info[:private_key_path]
# Check SSH key permissions
info[:private_key_path].each do |path|
SSH.check_key_permissions(Pathname.new(path))
end
info[:private_key_path] ||= []
# Check SSH key permissions
info[:private_key_path].each do |path|
SSH.check_key_permissions(Pathname.new(path))
end
if info[:private_key_path].empty? && info[:password]
env[:ui].warn(I18n.t("vagrant.ssh_exec_password"))
end
# Exec!

View File

@ -26,11 +26,15 @@ module Vagrant
# not yet ready for SSH, so we raise this exception.
raise Errors::SSHNotReady if info.nil?
if info[:private_key_path]
# Check SSH key permissions
info[:private_key_path].each do |path|
SSH.check_key_permissions(Pathname.new(path))
end
info[:private_key_path] ||= []
# Check SSH key permissions
info[:private_key_path].each do |path|
SSH.check_key_permissions(Pathname.new(path))
end
if info[:private_key_path].empty?
raise Errors::SSHRunRequiresKeys
end
# Get the command and wrap it in a login shell

View File

@ -56,6 +56,10 @@ module Vagrant
return if env[:interrupted]
end
# Join so that they can raise exceptions if there were any
ready_thr.join if !ready_thr.alive?
states_thr.join if !states_thr.alive?
# If it went into a bad state, then raise an error
if !states_thr[:result]
raise Errors::VMBootBadState,

View File

@ -532,6 +532,10 @@ module Vagrant
error_key(:ssh_port_not_detected)
end
class SSHRunRequiresKeys < VagrantError
error_key(:ssh_run_requires_keys)
end
class SSHUnavailable < VagrantError
error_key(:ssh_unavailable)
end

View File

@ -280,6 +280,7 @@ module Vagrant
info[:host] = @config.ssh.host if @config.ssh.host
info[:port] = @config.ssh.port if @config.ssh.port
info[:username] = @config.ssh.username if @config.ssh.username
info[:password] = @config.ssh.password if @config.ssh.password
# We also set some fields that are purely controlled by Varant
info[:forward_agent] = @config.ssh.forward_agent
@ -291,7 +292,7 @@ module Vagrant
# Set the private key path. If a specific private key is given in
# the Vagrantfile we set that. Otherwise, we use the default (insecure)
# private key, but only if the provider didn't give us one.
if !info[:private_key_path]
if !info[:private_key_path] && !info[:password]
if @config.ssh.private_key_path
info[:private_key_path] = @config.ssh.private_key_path
else
@ -299,7 +300,14 @@ module Vagrant
end
end
# If we have a private key in our data dir, then use that
data_private_key = @data_dir.join("private_key")
if data_private_key.file?
info[:private_key_path] = [data_private_key.to_s]
end
# Setup the keys
info[:private_key_path] ||= []
if !info[:private_key_path].is_a?(Array)
info[:private_key_path] = [info[:private_key_path]]
end

View File

@ -1,6 +1,7 @@
require 'logger'
require 'pathname'
require 'stringio'
require 'thread'
require 'timeout'
require 'log4r'
@ -27,25 +28,58 @@ module VagrantPlugins
end
def initialize(machine)
@lock = Mutex.new
@machine = machine
@logger = Log4r::Logger.new("vagrant::communication::ssh")
@connection = nil
@inserted_key = false
end
def ready?
@logger.debug("Checking whether SSH is ready...")
# Attempt to connect. This will raise an exception if it fails.
connect
begin
connect
@logger.info("SSH is ready!")
rescue Vagrant::Errors::VagrantError => e
# We catch a `VagrantError` which would signal that something went
# wrong expectedly in the `connect`, which means we didn't connect.
@logger.info("SSH not up: #{e.inspect}")
return false
end
# If we're already attempting to switch out the SSH key, then
# just return that we're ready (for Machine#guest).
@lock.synchronize do
return true if @inserted_key || !@machine.config.ssh.insert_key
@inserted_key = true
end
# If we used a password, then insert the insecure key
ssh_info = @machine.ssh_info
if ssh_info[:password] && ssh_info[:private_key_path].empty?
@logger.info("Inserting insecure key to avoid password")
@machine.ui.info(I18n.t("vagrant.inserting_insecure_key"))
@machine.guest.capability(
:insert_public_key,
Vagrant.source_root.join("keys", "vagrant.pub").read)
# Write out the private key in the data dir so that the
# machine automatically picks it up.
@machine.data_dir.join("private_key").open("w+") do |f|
f.write(Vagrant.source_root.join("keys", "vagrant").read)
end
@machine.ui.info(I18n.t("vagrant.inserted_key"))
@connection.close
@connection = nil
return ready?
end
# If we reached this point then we successfully connected
@logger.info("SSH is ready!")
true
rescue Vagrant::Errors::VagrantError => e
# We catch a `VagrantError` which would signal that something went
# wrong expectedly in the `connect`, which means we didn't connect.
@logger.info("SSH not up: #{e.inspect}")
return false
end
def execute(command, opts=nil, &block)
@ -170,6 +204,7 @@ module VagrantPlugins
:keys => ssh_info[:private_key_path],
:keys_only => true,
:paranoid => false,
:password => ssh_info[:password],
:port => ssh_info[:port],
:user_known_hosts_file => []
}
@ -222,6 +257,7 @@ module VagrantPlugins
@logger.info(" - Host: #{ssh_info[:host]}")
@logger.info(" - Port: #{ssh_info[:port]}")
@logger.info(" - Username: #{ssh_info[:username]}")
@logger.info(" - Password? #{!!ssh_info[:password]}")
@logger.info(" - Key Path: #{ssh_info[:private_key_path]}")
Net::SSH.start(ssh_info[:host], ssh_info[:username], connect_opts)

View File

@ -0,0 +1,17 @@
module VagrantPlugins
module GuestLinux
module Cap
class InsertPublicKey
def self.insert_public_key(machine, contents)
machine.communicate.tap do |comm|
comm.execute("echo #{contents} > /tmp/key.pub")
comm.execute("mkdir -p ~/.ssh")
comm.execute("chmod 0700 ~/.ssh")
comm.execute("cat /tmp/key.pub >> ~/.ssh/authorized_keys")
comm.execute("chmod 0600 ~/.ssh/authorized_keys")
end
end
end
end
end
end

View File

@ -16,6 +16,11 @@ module VagrantPlugins
Cap::Halt
end
guest_capability("linux", "insert_public_key") do
require_relative "cap/insert_public_key"
Cap::InsertPublicKey
end
guest_capability("linux", "shell_expand_guest_path") do
require_relative "cap/shell_expand_guest_path"
Cap::ShellExpandGuestPath

View File

@ -5,12 +5,16 @@ module VagrantPlugins
attr_accessor :port
attr_accessor :private_key_path
attr_accessor :username
attr_accessor :password
attr_accessor :insert_key
def initialize
@host = UNSET_VALUE
@port = UNSET_VALUE
@private_key_path = UNSET_VALUE
@username = UNSET_VALUE
@password = UNSET_VALUE
@insert_key = UNSET_VALUE
end
def finalize!
@ -18,6 +22,8 @@ module VagrantPlugins
@port = nil if @port == UNSET_VALUE
@private_key_path = nil if @private_key_path == UNSET_VALUE
@username = nil if @username == UNSET_VALUE
@password = nil if @password == UNSET_VALUE
@insert_key = true if @insert_key == UNSET_VALUE
if @private_key_path && !@private_key_path.is_a?(Array)
@private_key_path = [@private_key_path]

View File

@ -58,6 +58,10 @@ en:
docker_install_with_version_not_supported: |-
Vagrant is not capable of installing an specific version of Docker
onto the guest machine and the latest version will be installed.
inserted_key: |-
Key inserted! Disconnecting and reconnecting using new SSH key...
inserting_insecure_key: |-
Inserting Vagrant public key within guest...
plugin_needs_reinstall: |-
The following plugins were installed with a version of Vagrant
that had different versions of underlying components. Because
@ -77,6 +81,11 @@ en:
%{names}
provisioner_cleanup: |-
Running cleanup tasks for '%{name}' provisioner...
ssh_exec_password: |-
The machine you're attempting to SSH into is configured to use
password-based authentication. Vagrant can't script entering the
password for you. If you're prompted for a password, please enter
the same password you have configured in the Vagrantfile.
cfengine_config:
classes_array: |-
@ -641,6 +650,15 @@ en:
Please make sure that you have a forwarded port that goes to the configured
guest port value, or specify an explicit SSH port with `config.ssh.port`.
ssh_run_requires_keys: |-
Using `vagrant ssh -c` requires key-based SSH authentication, but your
Vagrant environmet is configured to use only password-based authentication.
Please configure your Vagrantfile with a private key to use this
feature.
Note that Vagrant can automatically insert a keypair and use that
keypair for you. Just set `config.ssh.insert_key = true` in your
Vagrantfile.
ssh_unavailable: "`ssh` binary could not be found. Is an SSH client installed?"
ssh_unavailable_windows: |-
`ssh` executable not found in any directories in the %PATH% variable. Is an

View File

@ -279,7 +279,7 @@ describe Vagrant::Machine do
let(:provider_ssh_info) { {} }
before(:each) do
provider.should_receive(:ssh_info).and_return(provider_ssh_info)
provider.stub(:ssh_info).and_return(provider_ssh_info)
end
[:host, :port, :username].each do |type|
@ -373,6 +373,29 @@ describe Vagrant::Machine do
instance.ssh_info[:private_key_path].should ==
[instance.env.default_private_key_path.to_s]
end
it "should not set any default private keys if a password is specified" do
provider_ssh_info[:private_key_path] = nil
instance.config.ssh.private_key_path = nil
instance.config.ssh.password = ""
expect(instance.ssh_info[:private_key_path]).to be_empty
expect(instance.ssh_info[:password]).to eql("")
end
it "should return the private key in the data dir above all else" do
provider_ssh_info[:private_key_path] = nil
instance.config.ssh.private_key_path = nil
instance.config.ssh.password = ""
instance.data_dir.join("private_key").open("w+") do |f|
f.write("hey")
end
expect(instance.ssh_info[:private_key_path]).to eql(
[instance.data_dir.join("private_key").to_s])
expect(instance.ssh_info[:password]).to eql("")
end
end
end

View File

@ -20,6 +20,14 @@ public boxes are made as.
<hr>
`config.ssh.password` - This sets a password that Vagrant will use to
authenticate the SSH user. Note that Vagrant recommends you use key-based
authentiation rather than a password (see `private_key_path`) below. If
you use a password, Vagrant will automatically insert a keypair if
`insert_key` is true.
<hr>
`config.ssh.host` - The hostname or IP to SSH into. By default this is
empty, because the provider usually figures this out for you.
@ -59,6 +67,12 @@ is enabled. Defaults to false.
<hr>
`config.ssh.insert_key` - If `true`, Vagrant will automatically insert
an insecure keypair to use for SSH. By default, this is true. This only
has an effect if you don't already use private keys for authentication.
<hr>
`config.ssh.shell` - The shell to use when executing SSH commands from
Vagrant. By default this is `bash -l`. Note that this has no effect on
the shell you get when you run `vagrant ssh`. This configuration option