Skip to content

Commit

Permalink
vagrant ssh will automatically fix permissions on the private key i…
Browse files Browse the repository at this point in the history
…f necessary
  • Loading branch information
mitchellh committed Mar 19, 2010
1 parent 260b099 commit a2a59b5
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 1 deletion.
27 changes: 27 additions & 0 deletions lib/vagrant/ssh.rb
Expand Up @@ -22,6 +22,7 @@ def connect(opts={})
options[param] = opts[param] || env.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

Expand Down Expand Up @@ -67,6 +68,32 @@ def up?
error_and_exit(:vm_ssh_auth_failed)
end

# Checks the file permissions for the private key, resetting them
# if needed, or on failure erroring.
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

# Returns the file permissions of a given file. This is fairly unix specific
# and probably doesn't belong in this class. Will be refactored out later.
def file_perms(path)
perms = sprintf("%o", File.stat(path).mode)
perms.reverse[0..2].reverse
end

# Returns the port which is either given in the options hash or taken from
# the config by finding it in the forwarded ports hash based on the
# `config.ssh.forwarded_port_key`
Expand Down
6 changes: 6 additions & 0 deletions templates/errors.yml
Expand Up @@ -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
Expand Down
77 changes: 76 additions & 1 deletion test/vagrant/ssh_test.rb
Expand Up @@ -16,6 +16,14 @@ def mock_ssh
context "connecting to external SSH" do
setup do
mock_ssh
@ssh.stubs(:check_key_permissions)
end

should "check key permissions prior to exec" do
exec_seq = sequence("exec_seq")
@ssh.expects(:check_key_permissions).with(@env.config.ssh.private_key_path).once.in_sequence(exec_seq)
Kernel.expects(:exec).in_sequence(exec_seq)
@ssh.connect
end

should "call exec with defaults when no options are supplied" do
Expand All @@ -38,7 +46,7 @@ def ssh_exec_expect(port, key_path, uname, host)
assert arg =~ /-p #{port}/
assert arg =~ /-i #{key_path}/
assert arg =~ /#{uname}@#{host}/
# TODO options not tested for as they may be removed, they may be removed
# TODO options not tested for as they may be removed
true
end
end
Expand Down Expand Up @@ -144,4 +152,71 @@ def ssh_exec_expect(port, key_path, uname, host)
assert_equal "47", @ssh.port({ :port => "47" })
end
end

context "checking key permissions" do
setup do
mock_ssh
@ssh.stubs(:file_perms)

@key_path = "foo"


@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
@ssh.check_key_permissions(@key_path)
end

should "do nothing if the file perms equal 600" do
@ssh.expects(:file_perms).with(@key_path).returns("600")
File.expects(:chmod).never
@ssh.check_key_permissions(@key_path)
end

should "chmod the file if the file perms aren't 600" do
perm_sequence = sequence("perm_seq")
@ssh.expects(:file_perms).returns("900").in_sequence(perm_sequence)
File.expects(:chmod).with(0600, @key_path).once.in_sequence(perm_sequence)
@ssh.expects(:file_perms).returns("600").in_sequence(perm_sequence)
@ssh.expects(:error_and_exit).never
@ssh.check_key_permissions(@key_path)
end

should "error and exit if the resulting chmod doesn't work" do
perm_sequence = sequence("perm_seq")
@ssh.expects(:file_perms).returns("900").in_sequence(perm_sequence)
File.expects(:chmod).with(0600, @key_path).once.in_sequence(perm_sequence)
@ssh.expects(:file_perms).returns("900").in_sequence(perm_sequence)
@ssh.expects(:error_and_exit).once.with(:ssh_bad_permissions, :key_path => @key_path).in_sequence(perm_sequence)
@ssh.check_key_permissions(@key_path)
end

should "error and exit if a bad file perm is raised" do
@ssh.expects(:file_perms).with(@key_path).returns("900")
File.expects(:chmod).raises(Errno::EPERM)
@ssh.expects(:error_and_exit).once.with(:ssh_bad_permissions, :key_path => @key_path)
@ssh.check_key_permissions(@key_path)
end
end

context "getting file permissions" do
setup do
mock_ssh
end

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)
@ssh.expects(:sprintf).with("%o", mode).returns(mode)
assert_equal path, @ssh.file_perms(path)
end
end
end

0 comments on commit a2a59b5

Please sign in to comment.