From e115322e78d3da464657b8cdb69ed691245581ae Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 3 Jan 2014 09:48:35 -0800 Subject: [PATCH 01/10] core: accept passwords in ssh_info --- lib/vagrant/machine.rb | 4 +++- plugins/kernel_v2/config/ssh_connect.rb | 3 +++ test/unit/vagrant/machine_test.rb | 11 ++++++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/vagrant/machine.rb b/lib/vagrant/machine.rb index 14c700197..3d46edde9 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 @@ -300,6 +301,7 @@ module Vagrant 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/kernel_v2/config/ssh_connect.rb b/plugins/kernel_v2/config/ssh_connect.rb index 06d9de10e..f3925f09c 100644 --- a/plugins/kernel_v2/config/ssh_connect.rb +++ b/plugins/kernel_v2/config/ssh_connect.rb @@ -5,12 +5,14 @@ module VagrantPlugins attr_accessor :port attr_accessor :private_key_path attr_accessor :username + attr_accessor :password def initialize @host = UNSET_VALUE @port = UNSET_VALUE @private_key_path = UNSET_VALUE @username = UNSET_VALUE + @password = UNSET_VALUE end def finalize! @@ -18,6 +20,7 @@ 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 if @private_key_path && !@private_key_path.is_a?(Array) @private_key_path = [@private_key_path] diff --git a/test/unit/vagrant/machine_test.rb b/test/unit/vagrant/machine_test.rb index 8eaa30810..29758246f 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,15 @@ 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 end end From 38e7166a216663eaeaa63ecc7dfb7fa17d90770c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 3 Jan 2014 09:55:34 -0800 Subject: [PATCH 02/10] communicators/ssh: support passwords --- plugins/communicators/ssh/communicator.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/communicators/ssh/communicator.rb b/plugins/communicators/ssh/communicator.rb index bb0ae252c..eac6fe618 100644 --- a/plugins/communicators/ssh/communicator.rb +++ b/plugins/communicators/ssh/communicator.rb @@ -170,6 +170,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 +223,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) From d1fdee7ae3769c2792fa366531c59aeb677a7dbf Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 3 Jan 2014 10:45:42 -0800 Subject: [PATCH 03/10] core: warn if password only on `vagrant ssh` --- lib/vagrant/action/builtin/ssh_exec.rb | 4 ++++ templates/locales/en.yml | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/lib/vagrant/action/builtin/ssh_exec.rb b/lib/vagrant/action/builtin/ssh_exec.rb index 0268f3c29..4e644e38d 100644 --- a/lib/vagrant/action/builtin/ssh_exec.rb +++ b/lib/vagrant/action/builtin/ssh_exec.rb @@ -35,6 +35,10 @@ module Vagrant end end + if info[:private_key_path].empty? && info[:password] + env[:ui].warn(I18n.t("vagrant.ssh_exec_password")) + end + # Exec! SSH.exec(info, env[:ssh_opts]) end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 545a50291..55d99a88c 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -77,6 +77,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: |- From 664aaa0088b97da0224d7637c9248a1dfe72a3c6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 3 Jan 2014 10:57:04 -0800 Subject: [PATCH 04/10] core: raise exceptions if they happen in WaitForCommunicator threads --- lib/vagrant/action/builtin/wait_for_communicator.rb | 4 ++++ 1 file changed, 4 insertions(+) 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, From b3a9e6a08836576b4039bdd1e790f7b66825109d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 3 Jan 2014 11:13:21 -0800 Subject: [PATCH 05/10] insert_public_key cap, and use that if it exists --- lib/vagrant/machine.rb | 6 +++ plugins/communicators/ssh/communicator.rb | 47 ++++++++++++++++--- plugins/guests/linux/cap/insert_public_key.rb | 17 +++++++ plugins/guests/linux/plugin.rb | 5 ++ plugins/kernel_v2/config/ssh_connect.rb | 3 ++ test/unit/vagrant/machine_test.rb | 14 ++++++ 6 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 plugins/guests/linux/cap/insert_public_key.rb diff --git a/lib/vagrant/machine.rb b/lib/vagrant/machine.rb index 3d46edde9..1f2329d91 100644 --- a/lib/vagrant/machine.rb +++ b/lib/vagrant/machine.rb @@ -300,6 +300,12 @@ 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) diff --git a/plugins/communicators/ssh/communicator.rb b/plugins/communicators/ssh/communicator.rb index eac6fe618..59cc65787 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,57 @@ 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 + @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.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 + + @logger.info("Disconecting SSH so we can reconnect with new SSH 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) 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 f3925f09c..7b2ad68e5 100644 --- a/plugins/kernel_v2/config/ssh_connect.rb +++ b/plugins/kernel_v2/config/ssh_connect.rb @@ -6,6 +6,7 @@ module VagrantPlugins attr_accessor :private_key_path attr_accessor :username attr_accessor :password + attr_accessor :insert_key def initialize @host = UNSET_VALUE @@ -13,6 +14,7 @@ module VagrantPlugins @private_key_path = UNSET_VALUE @username = UNSET_VALUE @password = UNSET_VALUE + @insert_key = UNSET_VALUE end def finalize! @@ -21,6 +23,7 @@ module VagrantPlugins @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/test/unit/vagrant/machine_test.rb b/test/unit/vagrant/machine_test.rb index 29758246f..9cb6fe6b8 100644 --- a/test/unit/vagrant/machine_test.rb +++ b/test/unit/vagrant/machine_test.rb @@ -382,6 +382,20 @@ describe Vagrant::Machine do 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 From 074bb2c7fbe31bce36c44fcf34a0ca951c13cd35 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 3 Jan 2014 11:19:17 -0800 Subject: [PATCH 06/10] core: fix potential exception case in SSHExec middleware --- lib/vagrant/action/builtin/ssh_exec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/vagrant/action/builtin/ssh_exec.rb b/lib/vagrant/action/builtin/ssh_exec.rb index 4e644e38d..bd9197b9d 100644 --- a/lib/vagrant/action/builtin/ssh_exec.rb +++ b/lib/vagrant/action/builtin/ssh_exec.rb @@ -28,11 +28,11 @@ 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] From 54e640b0c92ee465719ef7780df44a64cd3705d1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 3 Jan 2014 11:22:05 -0800 Subject: [PATCH 07/10] communicators/ssh: output UI when inserting key --- plugins/communicators/ssh/communicator.rb | 3 ++- templates/locales/en.yml | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/communicators/ssh/communicator.rb b/plugins/communicators/ssh/communicator.rb index 59cc65787..681ce7fde 100644 --- a/plugins/communicators/ssh/communicator.rb +++ b/plugins/communicators/ssh/communicator.rb @@ -60,6 +60,7 @@ module VagrantPlugins 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) @@ -70,7 +71,7 @@ module VagrantPlugins f.write(Vagrant.source_root.join("keys", "vagrant").read) end - @logger.info("Disconecting SSH so we can reconnect with new SSH key") + @machine.ui.info(I18n.t("vagrant.inserted_key")) @connection.close @connection = nil diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 55d99a88c..b3939491f 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 From 92413d03930528d8e2e85f3a7e0874ad7cfa7c70 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 3 Jan 2014 11:26:41 -0800 Subject: [PATCH 08/10] core: error if vagrant ssh -c with password --- lib/vagrant/action/builtin/ssh_run.rb | 14 +++++++++----- lib/vagrant/errors.rb | 4 ++++ templates/locales/en.yml | 9 +++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) 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/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/templates/locales/en.yml b/templates/locales/en.yml index b3939491f..154693760 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -650,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 From 4c6957b5cf14d9c9afe51e553c274aac7b4a84c6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 3 Jan 2014 11:29:44 -0800 Subject: [PATCH 09/10] communicators/ssh: if insert_key is false, don't insert a key --- plugins/communicators/ssh/communicator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/communicators/ssh/communicator.rb b/plugins/communicators/ssh/communicator.rb index 681ce7fde..0a9845436 100644 --- a/plugins/communicators/ssh/communicator.rb +++ b/plugins/communicators/ssh/communicator.rb @@ -52,7 +52,7 @@ module VagrantPlugins # 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 + return true if @inserted_key || !@machine.config.ssh.insert_key @inserted_key = true end From a6f4f56ba771a9fc2ea343b53911d3ced8519824 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 3 Jan 2014 11:31:33 -0800 Subject: [PATCH 10/10] website/docs: update docs for insert_key --- .../source/v2/vagrantfile/ssh_settings.html.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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