Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inspect guest for supported key types #13334

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 45 additions & 12 deletions plugins/communicators/ssh/communicator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -838,26 +838,59 @@ def supports_key_type?(type)
end

def supported_key_types
return @supported_key_types if @supported_key_types

if @connection.nil?
raise Vagrant::Errors::SSHNotReady
end

server_data = @connection.
transport&.
algorithms&.
instance_variable_get(:@server_data)
if server_data.nil?
@logger.warn("No server data available for key type support check")
raise ServerDataError, "no data available"
list = ""
result = sudo("sshd -T | grep key", {error_check: false}) do |type, data|
list << data
end

# If the command failed, attempt to extract some supported
# key information from within net-ssh
if result != 0
server_data = @connection.
transport&.
algorithms&.
instance_variable_get(:@server_data)
if server_data.nil?
@logger.warn("No server data available for key type support check")
raise ServerDataError, "no data available"
end
if !server_data.is_a?(Hash)
@logger.warn("Server data is not expected type (expecting Hash, got #{server_data.class})")
raise ServerDataError, "unexpected type encountered (expecting Hash, got #{server_data.class})"
end

@logger.debug("server supported key type list (extracted from connection server info using host key): #{server_data[:host_key]}")
return @supported_key_types = server_data[:host_key]
end
if !server_data.is_a?(Hash)
@logger.warn("Server data is not expected type (expecting Hash, got #{server_data.class})")
raise ServerDataError, "unexpected type encountered (expecting Hash, got #{server_data.class})"

# Convert the options into a Hash for easy access
opts = Hash[*list.split("\n").map{|line| line.split(" ", 2)}.flatten]

# Define the option names to check for in preferred order
# NOTE: pubkeyacceptedkeytypes has been renamed to pubkeyacceptedalgorithms
# ref: https://github.com/openssh/openssh-portable/commit/ee9c0da8035b3168e8e57c1dedc2d1b0daf00eec
["pubkeyacceptedalgorithms", "pubkeyacceptedkeytypes", "hostkeyalgorithms"].each do |opt_name|
next if !opts.key?(opt_name)

@supported_key_types = opts[opt_name].split(",")
@logger.debug("server supported key type list (using #{opt_name}): #{@supported_key_types}")

return @supported_key_types
end

@logger.debug("server supported key type list: #{server_data[:host_key]}")
# Still here means unable to determine key types
# so log what information was returned and toss
# and error
@logger.warn("failed to determine supported key types from remote inspection")
@logger.debug("data returned for supported key types remote inspection: #{list.inspect}")

server_data[:host_key]
raise ServerDataError, "no data available"
end
end
end
Expand Down
159 changes: 142 additions & 17 deletions test/unit/plugins/communicators/ssh/communicator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@
let(:command_stderr_data) { '' }
# Mock for net-ssh scp
let(:scp) { double("scp") }

# Value returned from remote ssh supported key check
let(:sudo_supported_key_list) { "pubkeyacceptedalgorithms ssh-rsa" }

# Setup for commands using the net-ssh connection. This can be reused where needed
# by providing to `before`
Expand All @@ -93,13 +94,16 @@
and_yield(nil, exit_data)
# Return mocked net-ssh connection during setup
allow(communicator).to receive(:retryable).and_return(connection)
# Stub in a response for supported key types check
allow(communicator).to receive(:sudo).with("sshd -T | grep key", any_args).
and_yield(:stdout, sudo_supported_key_list).and_return(0)
end

before do
allow(host).to receive(:capability?).and_return(false)
end

describe ".wait_for_ready" do
describe "#wait_for_ready" do
before(&connection_setup)
context "with no static config (default scenario)" do
context "when ssh_info requires a multiple tries before it is ready" do
Expand Down Expand Up @@ -162,7 +166,7 @@
end
end

describe "reset!" do
describe "#reset!" do
let(:connection) { double("connection") }

before do
Expand All @@ -182,7 +186,7 @@
end
end

describe ".ready?" do
describe "#ready?" do
before(&connection_setup)
it "returns true if shell test is successful" do
expect(communicator.ready?).to be(true)
Expand Down Expand Up @@ -248,8 +252,6 @@
let(:path_joiner){ double("path_joiner") }
let(:algorithms) { double(:algorithms) }
let(:transport) { double(:transport, algorithms: algorithms) }
let(:valid_key_types) { [] }
let(:server_data) { { host_key: valid_key_types} }

before do
allow(Vagrant::Util::Keypair).to receive(:create).
Expand All @@ -264,7 +266,6 @@
allow(guest).to receive(:capability).with(:insert_public_key)
allow(guest).to receive(:capability).with(:remove_public_key)
allow(connection).to receive(:transport).and_return(transport)
allow(algorithms).to receive(:instance_variable_get).with(:@server_data).and_return(server_data)
allow(communicator).to receive(:supported_key_types).and_raise(described_class.const_get(:ServerDataError))
end

Expand Down Expand Up @@ -297,7 +298,7 @@

context "with server algorithm support data" do
before do
allow(communicator).to receive(:supported_key_types).and_call_original
allow(communicator).to receive(:supported_key_types).and_return(valid_key_types)
end

context "when rsa is the only match" do
Expand Down Expand Up @@ -371,8 +372,7 @@

context "when an error is encountered getting server data" do
before do
expect(communicator).to receive(:supported_key_types).and_call_original
expect(connection).to receive(:transport).and_raise(StandardError)
expect(communicator).to receive(:supported_key_types).and_raise(StandardError)
end

it "should default to rsa key" do
Expand All @@ -385,7 +385,7 @@
end
end

describe ".execute" do
describe "#execute" do
before(&connection_setup)
it "runs valid command and returns successful status code" do
expect(command_channel).to receive(:send_data).with(/ls \/\n/)
Expand Down Expand Up @@ -579,7 +579,7 @@
end
end

describe ".test" do
describe "#test" do
before(&connection_setup)
context "with exit code as zero" do
it "returns true" do
Expand All @@ -598,7 +598,7 @@
end
end

describe ".upload" do
describe "#upload" do
before do
expect(communicator).to receive(:scp_connect).and_yield(scp)
allow(communicator).to receive(:create_remote_directory)
Expand Down Expand Up @@ -704,7 +704,7 @@
end
end

describe ".download" do
describe "#download" do
before do
expect(communicator).to receive(:scp_connect).and_yield(scp)
end
Expand All @@ -715,7 +715,7 @@
end
end

describe ".connect" do
describe "#connect" do

it "cannot be called directly" do
expect{ communicator.connect }.to raise_error(NoMethodError)
Expand Down Expand Up @@ -1030,7 +1030,7 @@
end
end

describe ".insecure_key?" do
describe "#insecure_key?" do
let(:key_data) { "" }
let(:key_file) {
if !@key_file
Expand Down Expand Up @@ -1069,7 +1069,7 @@
end
end

describe ".generate_environment_export" do
describe "#generate_environment_export" do
it "should generate bourne shell compatible export" do
expect(communicator.send(:generate_environment_export, "TEST", "value")).to eq("export TEST=\"value\"\n")
end
Expand All @@ -1082,4 +1082,129 @@
end
end
end

describe "#supported_key_types" do
let(:sudo_result) { 0 }
let(:sudo_data) { "" }
let(:server_data_error) { VagrantPlugins::CommunicatorSSH::Communicator::ServerDataError }
let(:transport) { double("transport", algorithms: algorithms) }
let(:algorithms) { double("algorithms") }

before do
allow(communicator).to receive(:ready?).and_return(true)
expect(communicator).to receive(:sudo).
with("sshd -T | grep key", any_args).
and_yield(:stdout, sudo_data).
and_return(sudo_result)
# The @connection value is checked to determine if supported key types
# can be checked. To facilitate this, set it to a non-nil value
communicator.instance_variable_set(:@connection, connection)
allow(connection).to receive(:transport).and_return(transport)
end

it "should raise an error when no data is returned" do
expect { communicator.send(:supported_key_types) }.to raise_error(server_data_error)
end

context "when sudo command is unsuccessful" do
let(:sudo_result) { 1 }

it "should inspect the net-ssh connection" do
expect(algorithms).to receive(:instance_variable_get).
with(:@server_data).and_return({})
communicator.send(:supported_key_types)
end
end

context "when data includes pubkeyacceptedalgorithms" do
let(:sudo_data) do
"pubkeyauthentication yes
gssapikeyexchange no
gssapistorecredentialsonrekey no
trustedusercakeys none
revokedkeys none
authorizedkeyscommand none
authorizedkeyscommanduser none
hostkeyagent none
hostbasedacceptedkeytypes ecdsa-sha2-nistp521,ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa
hostkeyalgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa
pubkeyacceptedalgorithms rsa-sha2-512,rsa-sha2-256,ssh-rsa
authorizedkeysfile .ssh/authorized_keys
hostkey /etc/ssh/ssh_host_rsa_key
rekeylimit 0 0"
end

it "should return expected values" do
expect(communicator.send(:supported_key_types)).to eq(["rsa-sha2-512", "rsa-sha2-256", "ssh-rsa"])
end
end

context "when data includes pubkeyacceptedkeytypes" do
let(:sudo_data) do
"pubkeyauthentication yes
gssapikeyexchange no
gssapistorecredentialsonrekey no
trustedusercakeys none
revokedkeys none
authorizedkeyscommand none
authorizedkeyscommanduser none
hostkeyagent none
hostbasedacceptedkeytypes ecdsa-sha2-nistp521,ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa
hostkeyalgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa
pubkeyacceptedkeytypes rsa-sha2-512,rsa-sha2-256,ssh-rsa
authorizedkeysfile .ssh/authorized_keys
hostkey /etc/ssh/ssh_host_rsa_key
rekeylimit 0 0"
end

it "should return expected values" do
expect(communicator.send(:supported_key_types)).
to eq(["rsa-sha2-512", "rsa-sha2-256", "ssh-rsa"])
end
end

context "when data does not include pubkeyacceptedalgorithms or pubkeyacceptedkeytypes" do
let(:sudo_data) do
"pubkeyauthentication yes
gssapikeyexchange no
gssapistorecredentialsonrekey no
trustedusercakeys none
revokedkeys none
authorizedkeyscommand none
authorizedkeyscommanduser none
hostkeyagent none
hostbasedacceptedkeytypes ecdsa-sha2-nistp521,ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa
hostkeyalgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa
authorizedkeysfile .ssh/authorized_keys
hostkey /etc/ssh/ssh_host_rsa_key
rekeylimit 0 0"
end

it "should use hostkeyalgorithms" do
expect(communicator.send(:supported_key_types)).
to eq(["ssh-ed25519", "rsa-sha2-512", "rsa-sha2-256", "ssh-rsa"])
end
end

context "when data does not include defined config options" do
let(:sudo_data) do
"pubkeyauthentication yes
gssapikeyexchange no
gssapistorecredentialsonrekey no
trustedusercakeys none
revokedkeys none
authorizedkeyscommand none
authorizedkeyscommanduser none
hostkeyagent none
authorizedkeysfile .ssh/authorized_keys
hostkey /etc/ssh/ssh_host_rsa_key
rekeylimit 0 0"
end

it "should raise error" do
expect { communicator.send(:supported_key_types) }.
to raise_error(server_data_error)
end
end
end
end