diff --git a/lib/vagrant/action/builtin/ssh_exec.rb b/lib/vagrant/action/builtin/ssh_exec.rb index 0268f3c29..bd9197b9d 100644 --- a/lib/vagrant/action/builtin/ssh_exec.rb +++ b/lib/vagrant/action/builtin/ssh_exec.rb @@ -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! diff --git a/lib/vagrant/action/builtin/ssh_run.rb b/lib/vagrant/action/builtin/ssh_run.rb index 3636f1ab8..59b16f325 100644 --- a/lib/vagrant/action/builtin/ssh_run.rb +++ b/lib/vagrant/action/builtin/ssh_run.rb @@ -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 diff --git a/lib/vagrant/action/builtin/wait_for_communicator.rb b/lib/vagrant/action/builtin/wait_for_communicator.rb index fe1e59818..c30127659 100644 --- a/lib/vagrant/action/builtin/wait_for_communicator.rb +++ b/lib/vagrant/action/builtin/wait_for_communicator.rb @@ -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, diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index cfaad43c2..a4fd6090b 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -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 diff --git a/lib/vagrant/machine.rb b/lib/vagrant/machine.rb index 14c700197..1f2329d91 100644 --- a/lib/vagrant/machine.rb +++ b/lib/vagrant/machine.rb @@ -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 diff --git a/plugins/communicators/ssh/communicator.rb b/plugins/communicators/ssh/communicator.rb index bb0ae252c..0a9845436 100644 --- a/plugins/communicators/ssh/communicator.rb +++ b/plugins/communicators/ssh/communicator.rb @@ -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) diff --git a/plugins/guests/linux/cap/insert_public_key.rb b/plugins/guests/linux/cap/insert_public_key.rb new file mode 100644 index 000000000..69a526541 --- /dev/null +++ b/plugins/guests/linux/cap/insert_public_key.rb @@ -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 diff --git a/plugins/guests/linux/plugin.rb b/plugins/guests/linux/plugin.rb index c3529faad..c47a9d674 100644 --- a/plugins/guests/linux/plugin.rb +++ b/plugins/guests/linux/plugin.rb @@ -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 diff --git a/plugins/kernel_v2/config/ssh_connect.rb b/plugins/kernel_v2/config/ssh_connect.rb index 06d9de10e..7b2ad68e5 100644 --- a/plugins/kernel_v2/config/ssh_connect.rb +++ b/plugins/kernel_v2/config/ssh_connect.rb @@ -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] diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 545a50291..154693760 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -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 diff --git a/test/unit/vagrant/machine_test.rb b/test/unit/vagrant/machine_test.rb index 8eaa30810..9cb6fe6b8 100644 --- a/test/unit/vagrant/machine_test.rb +++ b/test/unit/vagrant/machine_test.rb @@ -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 diff --git a/website/docs/source/v2/vagrantfile/ssh_settings.html.md b/website/docs/source/v2/vagrantfile/ssh_settings.html.md index 9737f7ce0..7d6e92c77 100644 --- a/website/docs/source/v2/vagrantfile/ssh_settings.html.md +++ b/website/docs/source/v2/vagrantfile/ssh_settings.html.md @@ -20,6 +20,14 @@ public boxes are made as.
+`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. + +
+ `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.
+`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. + +
+ `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