diff --git a/lib/vagrant/ssh.rb b/lib/vagrant/ssh.rb index d22376794..1252422c4 100644 --- a/lib/vagrant/ssh.rb +++ b/lib/vagrant/ssh.rb @@ -9,6 +9,7 @@ module Vagrant options[param] = opts[param] || Vagrant.config.ssh.send(param) end + check_key_permissions(options[:private_key_path]) Kernel.exec "ssh -p #{port(opts)} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i #{options[:private_key_path]} #{options[:username]}@#{options[:host]}".strip end @@ -49,6 +50,28 @@ module Vagrant def port(opts={}) opts[:port] || Vagrant.config.vm.forwarded_ports[Vagrant.config.ssh.forwarded_port_key][:hostport] end + + def check_key_permissions(key_path) + # TODO: This only works on unix based systems for now. Windows + # systems will need to be investigated further. + stat = File.stat(key_path) + + if stat.owned? && file_perms(key_path) != "600" + logger.info "Permissions on private key incorrect, fixing..." + File.chmod(0600, key_path) + + error_and_exit(:ssh_bad_permissions, :key_path => key_path) if file_perms(key_path) != "600" + end + rescue Errno::EPERM + # This shouldn't happen since we verify we own the file, but just + # in case. + error_and_exit(:ssh_bad_permissions, :key_path => key_path) + end + + def file_perms(path) + perms = sprintf("%o", File.stat(path).mode) + perms.reverse[0..2].reverse + end end end end diff --git a/templates/errors.yml b/templates/errors.yml index 3039d0e3d..f01c48d0e 100644 --- a/templates/errors.yml +++ b/templates/errors.yml @@ -52,6 +52,12 @@ \nsince it describes the expected environment that vagrant is supposed \nto manage. Please create a `<%= Vagrant::Env::ROOTFILE_NAME %>` and place it in your project \nroot." +:ssh_bad_permissions: "The private key to connect to this box via SSH has invalid permissions + \nset on it. The permissions of the private key should be set to 0600, otherwise SSH will + \nignore the key. Vagrant tried to do this automatically for you but failed. Please set the + \npermissions on the following file to 0600 and then try running this command again: + + \n<%= key_path %>" :virtualbox_import_failure: "The VM import failed! Try running `VBoxManage import` on the box file manually for more verbose error output." :virtualbox_invalid_version: "Vagrant has detected that you have VirtualBox version <%= version %> installed! \nVagrant requires that you use at least VirtualBox version 3.1. Please install diff --git a/test/vagrant/ssh_test.rb b/test/vagrant/ssh_test.rb index fd2b400da..50251e896 100644 --- a/test/vagrant/ssh_test.rb +++ b/test/vagrant/ssh_test.rb @@ -6,7 +6,18 @@ class SshTest < Test::Unit::TestCase end context "connecting to SSH" do - test "should call exec with defaults when no options are supplied" do + setup do + Vagrant::SSH.stubs(:check_key_permissions) + end + + should "check key permissions prior to exec" do + exec_seq = sequence("exec_seq") + Vagrant::SSH.expects(:check_key_permissions).with(Vagrant.config.ssh.private_key_path).once.in_sequence(exec_seq) + Kernel.expects(:exec).in_sequence(exec_seq) + Vagrant::SSH.connect + end + + should "call exec with defaults when no options are supplied" do ssh = Vagrant.config.ssh ssh_exec_expect(Vagrant::SSH.port, Vagrant.config.ssh.private_key_path, @@ -15,7 +26,7 @@ class SshTest < Test::Unit::TestCase Vagrant::SSH.connect end - test "should call exec with supplied params" do + should "call exec with supplied params" do args = {:username => 'bar', :private_key_path => 'baz', :host => 'bak', :port => 'bag'} ssh_exec_expect(args[:port], args[:private_key_path], args[:username], args[:host]) Vagrant::SSH.connect(args) @@ -121,4 +132,65 @@ class SshTest < Test::Unit::TestCase assert_equal "47", Vagrant::SSH.port({ :port => "47" }) end end + + context "checking key permissions" do + setup do + @key_path = "foo" + + Vagrant::SSH.stubs(:file_perms) + + @stat = mock("stat") + @stat.stubs(:owned?).returns(true) + File.stubs(:stat).returns(@stat) + end + + should "do nothing if the user is not the owner" do + @stat.expects(:owned?).returns(false) + File.expects(:chmod).never + Vagrant::SSH.check_key_permissions(@key_path) + end + + should "do nothing if the file perms equal 600" do + Vagrant::SSH.expects(:file_perms).with(@key_path).returns("600") + File.expects(:chmod).never + Vagrant::SSH.check_key_permissions(@key_path) + end + + should "chmod the file if the file perms aren't 600" do + perm_sequence = sequence("perm_seq") + Vagrant::SSH.expects(:file_perms).returns("900").in_sequence(perm_sequence) + File.expects(:chmod).with(0600, @key_path).once.in_sequence(perm_sequence) + Vagrant::SSH.expects(:file_perms).returns("600").in_sequence(perm_sequence) + Vagrant::SSH.expects(:error_and_exit).never + Vagrant::SSH.check_key_permissions(@key_path) + end + + should "error and exit if the resulting chmod doesn't work" do + perm_sequence = sequence("perm_seq") + Vagrant::SSH.expects(:file_perms).returns("900").in_sequence(perm_sequence) + File.expects(:chmod).with(0600, @key_path).once.in_sequence(perm_sequence) + Vagrant::SSH.expects(:file_perms).returns("900").in_sequence(perm_sequence) + Vagrant::SSH.expects(:error_and_exit).once.with(:ssh_bad_permissions, :key_path => @key_path).in_sequence(perm_sequence) + Vagrant::SSH.check_key_permissions(@key_path) + end + + should "error and exit if a bad file perm is raised" do + Vagrant::SSH.expects(:file_perms).with(@key_path).returns("900") + File.expects(:chmod).raises(Errno::EPERM) + Vagrant::SSH.expects(:error_and_exit).once.with(:ssh_bad_permissions, :key_path => @key_path) + Vagrant::SSH.check_key_permissions(@key_path) + end + end + + context "getting file permissions" do + should "return the last 3 characters of the file mode" do + path = "foo" + mode = "10000foo" + stat = mock("stat") + File.expects(:stat).with(path).returns(stat) + stat.expects(:mode).returns(mode) + Vagrant::SSH.expects(:sprintf).with("%o", mode).returns(mode) + assert_equal path, Vagrant::SSH.file_perms(path) + end + end end