Merge pull request #13327 from chrisroberts/ssh-ecdsa
Add support for ecdsa ssh keys
This commit is contained in:
commit
588d7ecd6e
@ -5,6 +5,7 @@ require "log4r"
|
||||
|
||||
# Add patches to log4r to support trace level
|
||||
require "vagrant/patches/log4r"
|
||||
require "vagrant/patches/net-ssh"
|
||||
# Set our log levels and include trace
|
||||
require 'log4r/configurator'
|
||||
Log4r::Configurator.custom_levels(*(["TRACE"] + Log4r::Log4rConfig::LogLevels))
|
||||
|
||||
@ -863,6 +863,10 @@ module Vagrant
|
||||
error_key(:ssh_key_type_not_supported)
|
||||
end
|
||||
|
||||
class SSHKeyTypeNotSupportedByServer < VagrantError
|
||||
error_key(:ssh_key_type_not_supported_by_server)
|
||||
end
|
||||
|
||||
class SSHNoExitStatus < VagrantError
|
||||
error_key(:ssh_no_exit_status)
|
||||
end
|
||||
|
||||
76
lib/vagrant/patches/net-ssh.rb
Normal file
76
lib/vagrant/patches/net-ssh.rb
Normal file
@ -0,0 +1,76 @@
|
||||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
require "net/ssh"
|
||||
require "net/ssh/buffer"
|
||||
|
||||
# Set the version requirement for when net-ssh should be patched
|
||||
NET_SSH_PATCH_REQUIREMENT = Gem::Requirement.new(">= 7.0.0", "< 7.2.2")
|
||||
|
||||
# This patch provides support for properly loading ECDSA private keys
|
||||
if NET_SSH_PATCH_REQUIREMENT.satisfied_by?(Gem::Version.new(Net::SSH::Version::STRING))
|
||||
Net::SSH::Buffer.class_eval do
|
||||
def vagrant_read_private_keyblob(type)
|
||||
case type
|
||||
when /^ecdsa\-sha2\-(\w*)$/
|
||||
curve_name_in_type = $1
|
||||
curve_name_in_key = read_string
|
||||
|
||||
unless curve_name_in_type == curve_name_in_key
|
||||
raise Net::SSH::Exception, "curve name mismatched (`#{curve_name_in_key}' with `#{curve_name_in_type}')"
|
||||
end
|
||||
|
||||
public_key_oct = read_string
|
||||
priv_key_bignum = read_bignum
|
||||
begin
|
||||
curvename = OpenSSL::PKey::EC::CurveNameAlias[curve_name_in_key]
|
||||
group = OpenSSL::PKey::EC::Group.new(curvename)
|
||||
point = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(public_key_oct, 2))
|
||||
priv_bn = OpenSSL::BN.new(priv_key_bignum, 2)
|
||||
asn1 = OpenSSL::ASN1::Sequence(
|
||||
[
|
||||
OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(0)),
|
||||
OpenSSL::ASN1::Sequence.new(
|
||||
[
|
||||
OpenSSL::ASN1::ObjectId("id-ecPublicKey"),
|
||||
OpenSSL::ASN1::ObjectId(curvename)
|
||||
]
|
||||
),
|
||||
OpenSSL::ASN1::OctetString.new(
|
||||
OpenSSL::ASN1::Sequence.new(
|
||||
[
|
||||
OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(1)),
|
||||
OpenSSL::ASN1::OctetString.new(priv_bn.to_s(2)),
|
||||
OpenSSL::ASN1::ASN1Data.new(
|
||||
[
|
||||
OpenSSL::ASN1::BitString.new(point.to_octet_string(:uncompressed)),
|
||||
], 1, :CONTEXT_SPECIFIC,
|
||||
)
|
||||
]
|
||||
).to_der
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
key = OpenSSL::PKey::EC.new(asn1.to_der)
|
||||
|
||||
return key
|
||||
rescue OpenSSL::PKey::ECError
|
||||
raise NotImplementedError, "unsupported key type `#{type}'"
|
||||
end
|
||||
else
|
||||
netssh_read_private_keyblob(type)
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :netssh_read_private_keyblob, :read_private_keyblob
|
||||
alias_method :read_private_keyblob, :vagrant_read_private_keyblob
|
||||
end
|
||||
|
||||
OpenSSL::PKey::EC::Point.class_eval do
|
||||
include Net::SSH::Authentication::PubKeyFingerprint
|
||||
def to_pem
|
||||
"#{ssh_type} #{self.to_bn.to_s(2)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -36,7 +36,7 @@ module Vagrant
|
||||
autoload :IO, 'vagrant/util/io'
|
||||
autoload :IPV4Interfaces, 'vagrant/util/ipv4_interfaces'
|
||||
autoload :IsPortOpen, 'vagrant/util/is_port_open'
|
||||
autoload :KeyPair, 'vagrant/util/key_pair'
|
||||
autoload :Keypair, 'vagrant/util/keypair'
|
||||
autoload :LineBuffer, 'vagrant/util/line_buffer'
|
||||
autoload :LineEndingHelpers, 'vagrant/util/line_ending_helpers'
|
||||
autoload :LoggingFormatter, 'vagrant/util/logging_formatter'
|
||||
|
||||
@ -10,15 +10,43 @@ require "vagrant/util/retryable"
|
||||
module Vagrant
|
||||
module Util
|
||||
class Keypair
|
||||
# Magic string header
|
||||
AUTH_MAGIC = "openssh-key-v1".freeze
|
||||
# Header of private key file content
|
||||
PRIVATE_KEY_START = "-----BEGIN OPENSSH PRIVATE KEY-----\n".freeze
|
||||
# Footer of private key file content
|
||||
PRIVATE_KEY_END = "-----END OPENSSH PRIVATE KEY-----\n".freeze
|
||||
|
||||
# Check if provided key is a supported key type
|
||||
#
|
||||
# @param [Symbol] key Key type to check
|
||||
# @return [Boolean] key type is supported
|
||||
def self.valid_type?(key)
|
||||
VALID_TYPES.keys.include?(key)
|
||||
end
|
||||
|
||||
# @return [Array<Symbol>] list of supported key types
|
||||
def self.available_types
|
||||
PREFER_KEY_TYPES.values
|
||||
end
|
||||
|
||||
# Create a new keypair
|
||||
#
|
||||
# @param [String] password Password for the key or nil for no password (only supported for rsa type)
|
||||
# @param [Symbol] type Key type to generate
|
||||
# @return [Array<String, String, String>] Public key, openssh private key, openssh public key with comment
|
||||
def self.create(password=nil, type: :rsa)
|
||||
if !VALID_TYPES.key?(type)
|
||||
raise ArgumentError,
|
||||
"Invalid key type requested (supported types: #{available_types.map(&:inspect).join(", ")})"
|
||||
end
|
||||
|
||||
VALID_TYPES[type].create(password)
|
||||
end
|
||||
|
||||
class Ed25519
|
||||
# Magic string header
|
||||
AUTH_MAGIC = "openssh-key-v1".freeze
|
||||
# Key type identifier
|
||||
KEY_TYPE = "ssh-ed25519".freeze
|
||||
# Header of private key file content
|
||||
PRIVATE_KEY_START = "-----BEGIN OPENSSH PRIVATE KEY-----\n".freeze
|
||||
# Footer of private key file content
|
||||
PRIVATE_KEY_END = "-----END OPENSSH PRIVATE KEY-----\n".freeze
|
||||
|
||||
# Encodes given string
|
||||
#
|
||||
@ -95,6 +123,9 @@ module Vagrant
|
||||
class Rsa
|
||||
extend Retryable
|
||||
|
||||
# Key type identifier
|
||||
KEY_TYPE = "ssh-rsa"
|
||||
|
||||
# Creates an SSH keypair and returns it.
|
||||
#
|
||||
# @param [String] password Password for the key, or nil for no password.
|
||||
@ -140,19 +171,123 @@ module Vagrant
|
||||
end
|
||||
end
|
||||
|
||||
# Supported key types.
|
||||
VALID_TYPES = {ed25519: Ed25519, rsa: Rsa}.freeze
|
||||
# Ordered mapping of openssh key type name to lookup name
|
||||
PREFER_KEY_TYPES = {"ssh-ed25519".freeze => :ed25519, "ssh-rsa".freeze => :rsa}.freeze
|
||||
|
||||
def self.create(password=nil, type: :rsa)
|
||||
if !VALID_TYPES.key?(type)
|
||||
raise ArgumentError,
|
||||
"Invalid key type requested (supported types: #{VALID_TYPES.keys.map(&:inspect)})"
|
||||
# Base class for Ecdsa type keys to subclass
|
||||
class Ecdsa
|
||||
# Encodes given string
|
||||
#
|
||||
# @param [String] s String to encode
|
||||
# @return [String]
|
||||
def self.string(s)
|
||||
[s.length].pack("N") + s
|
||||
end
|
||||
|
||||
VALID_TYPES[type].create(password)
|
||||
# Encodes given string with padding to block size
|
||||
#
|
||||
# @param [String] s String to encode
|
||||
# @param [Integer] blocksize Defined block size
|
||||
# @return [String]
|
||||
def self.padded_string(s, blocksize)
|
||||
pad = blocksize - (s.length % blocksize)
|
||||
string(s + Array(1..pad).pack("c*"))
|
||||
end
|
||||
|
||||
# Creates an ed25519 SSH key pair
|
||||
# @return [Array<String, String, String>] Public key, openssh private key, openssh public key with comment
|
||||
# @note Password support was not included as it's not actively used anywhere. If it ends up being
|
||||
# something that's needed, it can be revisited
|
||||
def self.create(password=nil)
|
||||
if password
|
||||
raise NotImplementedError,
|
||||
"Ecdsa key pair generation does not support passwords"
|
||||
end
|
||||
|
||||
# Generate the key
|
||||
base_key = OpenSSL::PKey::EC.generate(self.const_get(:OPENSSL_CURVE))
|
||||
# Define the comment used for the key
|
||||
comment = "vagrant"
|
||||
|
||||
# Grab the raw public key
|
||||
public_key = base_key.public_key.to_bn.to_s(2)
|
||||
# Encode the public key for use building the openssh private key
|
||||
encoded_public_key = string(self.const_get(:KEY_TYPE)) + string(self.const_get(:OPENSSH_CURVE)) + string(public_key)
|
||||
# Format the public key into the openssh public key format for writing
|
||||
openssh_public_key = "#{self.const_get(:KEY_TYPE)} #{Base64.encode64(encoded_public_key).gsub("\n", "")} #{comment}"
|
||||
|
||||
pk_value = base_key.private_key.to_s(2)
|
||||
# Pad the start of the key if required
|
||||
if pk_value.length % 8 == 0
|
||||
pk_value = "\0#{pk_value}"
|
||||
end
|
||||
|
||||
# Agent encoded private key is used when building the openssh private key
|
||||
# (https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#section-4.2.3)
|
||||
# (https://dnaeon.github.io/openssh-private-key-binary-format/)
|
||||
agent_private_key = [
|
||||
([SecureRandom.random_number((2**32)-1)] * 2).pack("NN"), # checkint, random uint32 value, twice (used for encryption verification)
|
||||
encoded_public_key, # includes the key type and public key
|
||||
string(pk_value), # private key
|
||||
string(comment), # comment for the key
|
||||
].join
|
||||
|
||||
# Build openssh private key data (https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key)
|
||||
private_key = [
|
||||
AUTH_MAGIC + "\0", # Magic string
|
||||
string("none"), # cipher name, no encryption, so none
|
||||
string("none"), # kdf name, no encryption, so none
|
||||
string(""), # kdf options/data, no encryption, so empty string
|
||||
[1].pack("N"), # Number of keys (just one)
|
||||
string(encoded_public_key), # The public key
|
||||
padded_string(agent_private_key, 8) # Private key encoded with agent rules, padded for 8 byte block size
|
||||
].join
|
||||
|
||||
# Create the openssh private key content
|
||||
openssh_private_key = [
|
||||
PRIVATE_KEY_START,
|
||||
Base64.encode64(private_key),
|
||||
PRIVATE_KEY_END,
|
||||
].join
|
||||
|
||||
return [public_key, openssh_private_key, openssh_public_key]
|
||||
end
|
||||
end
|
||||
|
||||
class Ecdsa256 < Ecdsa
|
||||
KEY_TYPE = "ecdsa-sha2-nistp256".freeze
|
||||
OPENSSH_CURVE = "nistp256".freeze
|
||||
OPENSSL_CURVE = "prime256v1".freeze
|
||||
end
|
||||
|
||||
class Ecdsa384 < Ecdsa
|
||||
KEY_TYPE = "ecdsa-sha2-nistp384".freeze
|
||||
OPENSSH_CURVE = "nistp384".freeze
|
||||
OPENSSL_CURVE = "secp384r1".freeze
|
||||
end
|
||||
|
||||
class Ecdsa521 < Ecdsa
|
||||
KEY_TYPE = "ecdsa-sha2-nistp521".freeze
|
||||
OPENSSH_CURVE = "nistp521".freeze
|
||||
OPENSSL_CURVE = "secp521r1".freeze
|
||||
end
|
||||
|
||||
# Supported key types.
|
||||
VALID_TYPES = {
|
||||
ecdsa256: Ecdsa256,
|
||||
ecdsa384: Ecdsa384,
|
||||
ecdsa521: Ecdsa521,
|
||||
ed25519: Ed25519,
|
||||
rsa: Rsa
|
||||
}.freeze
|
||||
|
||||
# Ordered mapping of openssh key type name to lookup name. The
|
||||
# order defined here is based on preference. Note that ecdsa
|
||||
# ordering is based on performance
|
||||
PREFER_KEY_TYPES = {
|
||||
Ed25519::KEY_TYPE => :ed25519,
|
||||
Ecdsa256::KEY_TYPE => :ecdsa256,
|
||||
Ecdsa521::KEY_TYPE => :ecdsa521,
|
||||
Ecdsa384::KEY_TYPE => :ecdsa384,
|
||||
Rsa::KEY_TYPE => :rsa,
|
||||
}.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -113,6 +113,8 @@ module VagrantPlugins
|
||||
raise
|
||||
rescue Vagrant::Errors::SSHKeyTypeNotSupported
|
||||
raise
|
||||
rescue Vagrant::Errors::SSHKeyTypeNotSupportedByServer
|
||||
raise
|
||||
rescue Vagrant::Errors::SSHKeyBadOwner
|
||||
raise
|
||||
rescue Vagrant::Errors::SSHKeyBadPermissions
|
||||
@ -188,25 +190,56 @@ module VagrantPlugins
|
||||
@machine.guest.capability?(:remove_public_key)
|
||||
raise Vagrant::Errors::SSHInsertKeyUnsupported if !cap
|
||||
|
||||
# Check for supported key type
|
||||
key_type = catch(:key_type) do
|
||||
begin
|
||||
Vagrant::Util::Keypair::PREFER_KEY_TYPES.each do |type_name, type|
|
||||
throw :key_type, type if supports_key_type?(type_name)
|
||||
key_type = machine_config_ssh.key_type
|
||||
|
||||
begin
|
||||
# If the key type is set to `:auto` check for supported type. Otherwise
|
||||
# ensure that the key type is supported by the guest
|
||||
if key_type == :auto
|
||||
key_type = catch(:key_type) do
|
||||
begin
|
||||
Vagrant::Util::Keypair::PREFER_KEY_TYPES.each do |type_name, type|
|
||||
throw :key_type, type if supports_key_type?(type_name)
|
||||
end
|
||||
nil
|
||||
rescue => err
|
||||
@logger.warn("Failed to check key types server supports: #{err}")
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@logger.debug("Detected key type for new private key: #{key_type}")
|
||||
|
||||
# If no key type was discovered, default to rsa
|
||||
if key_type.nil?
|
||||
@logger.debug("Failed to detect supported key type in: #{supported_key_types.join(", ")}")
|
||||
available_types = supported_key_types.map { |t|
|
||||
next if !Vagrant::Util::Keypair::PREFER_KEY_TYPES.key?(t)
|
||||
"#{t} (#{Vagrant::Util::Keypair::PREFER_KEY_TYPES[t]})"
|
||||
}.compact.join(", ")
|
||||
|
||||
raise Vagrant::Errors::SSHKeyTypeNotSupportedByServer,
|
||||
requested_key_type: ":auto",
|
||||
available_key_types: available_types
|
||||
end
|
||||
else
|
||||
type_name = Vagrant::Util::Keypair::PREFER_KEY_TYPES.key(key_type)
|
||||
if !supports_key_type?(type_name)
|
||||
available_types = supported_key_types.map { |t|
|
||||
next if !Vagrant::Util::Keypair::PREFER_KEY_TYPES.key?(t)
|
||||
"#{t} (#{Vagrant::Util::Keypair::PREFER_KEY_TYPES[t]})"
|
||||
}.compact.join(", ")
|
||||
raise Vagrant::Errors::SSHKeyTypeNotSupportedByServer,
|
||||
requested_key_type: "#{type_name} (#{key_type})",
|
||||
available_key_types: available_types
|
||||
end
|
||||
nil
|
||||
rescue => err
|
||||
@logger.warn("Failed to check key types server supports: #{err}")
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@logger.debug("Detected key type for new private key: #{key_type}")
|
||||
|
||||
# If no key type was discovered, default to rsa
|
||||
if key_type.nil?
|
||||
@logger.debug("Failed to detect supported key type, defaulting to rsa")
|
||||
key_type = :rsa
|
||||
rescue ServerDataError
|
||||
@logger.warn("failed to load server data for key type check")
|
||||
if key_type.nil? || key_type == :auto
|
||||
@logger.warn("defaulting key type to :rsa due to failed server data loading")
|
||||
key_type = :rsa
|
||||
end
|
||||
end
|
||||
|
||||
@logger.info("Creating new ssh keypair (type: #{key_type.inspect})")
|
||||
@ -788,6 +821,8 @@ module VagrantPlugins
|
||||
|
||||
protected
|
||||
|
||||
class ServerDataError < StandardError; end
|
||||
|
||||
# Check if server supports given key type
|
||||
#
|
||||
# @param [String, Symbol] type Key type
|
||||
@ -798,21 +833,31 @@ module VagrantPlugins
|
||||
if @connection.nil?
|
||||
raise Vagrant::Errors::SSHNotReady
|
||||
end
|
||||
|
||||
supported_key_types.include?(type.to_s)
|
||||
end
|
||||
|
||||
def 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")
|
||||
return false
|
||||
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})")
|
||||
return false
|
||||
raise ServerDataError, "unexpected type encountered (expecting Hash, got #{server_data.class})"
|
||||
end
|
||||
|
||||
@logger.debug("server data used for host key support check: #{server_data.inspect}")
|
||||
server_data[:host_key].include?(type.to_s)
|
||||
@logger.debug("server supported key type list: #{server_data[:host_key]}")
|
||||
|
||||
server_data[:host_key]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -15,6 +15,7 @@ module VagrantPlugins
|
||||
attr_accessor :password
|
||||
attr_accessor :insert_key
|
||||
attr_accessor :keys_only
|
||||
attr_accessor :key_type
|
||||
attr_accessor :paranoid
|
||||
attr_accessor :verify_host_key
|
||||
attr_accessor :compression
|
||||
@ -33,6 +34,7 @@ module VagrantPlugins
|
||||
@password = UNSET_VALUE
|
||||
@insert_key = UNSET_VALUE
|
||||
@keys_only = UNSET_VALUE
|
||||
@key_type = UNSET_VALUE
|
||||
@paranoid = UNSET_VALUE
|
||||
@verify_host_key = UNSET_VALUE
|
||||
@compression = UNSET_VALUE
|
||||
@ -50,6 +52,7 @@ module VagrantPlugins
|
||||
@password = nil if @password == UNSET_VALUE
|
||||
@insert_key = true if @insert_key == UNSET_VALUE
|
||||
@keys_only = true if @keys_only == UNSET_VALUE
|
||||
@key_type = :auto if @key_type == UNSET_VALUE
|
||||
@paranoid = false if @paranoid == UNSET_VALUE
|
||||
@verify_host_key = :never if @verify_host_key == UNSET_VALUE
|
||||
@compression = true if @compression == UNSET_VALUE
|
||||
@ -96,6 +99,10 @@ module VagrantPlugins
|
||||
rescue
|
||||
# ignore
|
||||
end
|
||||
|
||||
if @key_type
|
||||
@key_type = @key_type.to_sym
|
||||
end
|
||||
end
|
||||
|
||||
# NOTE: This is _not_ a valid config validation method, since it
|
||||
@ -140,6 +147,14 @@ module VagrantPlugins
|
||||
given: @connect_timeout.to_s)
|
||||
end
|
||||
|
||||
if @key_type != :auto && !Vagrant::Util::Keypair.valid_type?(@key_type)
|
||||
errors << I18n.t(
|
||||
"vagrant.config.ssh.connect_invalid_key_type",
|
||||
given: @key_type.to_s,
|
||||
supported: Vagrant::Util::Keypair.available_types.join(", ")
|
||||
)
|
||||
end
|
||||
|
||||
errors
|
||||
end
|
||||
end
|
||||
|
||||
@ -1621,6 +1621,14 @@ en:
|
||||
sometimes keys in your ssh-agent can interfere with this as well,
|
||||
so verify the keys are valid there in addition to standard
|
||||
file paths.
|
||||
ssh_key_type_not_supported_by_server: |-
|
||||
The private key you are attempting to generate is not supported by
|
||||
the guest SSH server. Please use one of the available key types defined
|
||||
below that is supported by the guest SSH server.
|
||||
|
||||
Requested: %{requested_key_type}
|
||||
Available: %{available_key_types}
|
||||
|
||||
ssh_not_ready: |-
|
||||
The provider for this Vagrant-managed machine is reporting that it
|
||||
is not yet ready for SSH. Depending on your provider this can carry
|
||||
@ -2065,6 +2073,8 @@ en:
|
||||
`%{given}` type which cannot be converted to an Integer type.
|
||||
connect_timeout_invalid_value: |-
|
||||
The `connect_timeout` key only accepts values greater than 1 (received `%{given}`)
|
||||
connect_invalid_key_type: |-
|
||||
Invalid SSH key type set ('%{given}'). Supported types: %{supported}
|
||||
triggers:
|
||||
bad_trigger_type: |-
|
||||
The type '%{type}' defined for trigger '%{trigger}' is not valid. Must be one of the following types: '%{types}'
|
||||
|
||||
@ -13,6 +13,7 @@ describe VagrantPlugins::CommunicatorSSH::Communicator do
|
||||
# SSH configuration information mock
|
||||
let(:ssh) do
|
||||
double("ssh",
|
||||
key_type: :auto,
|
||||
timeout: 1,
|
||||
host: nil,
|
||||
port: 5986,
|
||||
@ -264,46 +265,48 @@ describe VagrantPlugins::CommunicatorSSH::Communicator do
|
||||
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
|
||||
|
||||
after{ communicator.ready? }
|
||||
|
||||
it "should create a new key pair" do
|
||||
expect(Vagrant::Util::Keypair).to receive(:create).
|
||||
and_return([new_public_key, new_private_key, openssh])
|
||||
communicator.ready?
|
||||
end
|
||||
|
||||
it "should call the insert_public_key guest capability" do
|
||||
expect(guest).to receive(:capability).with(:insert_public_key, openssh)
|
||||
communicator.ready?
|
||||
end
|
||||
|
||||
it "should write the new private key" do
|
||||
expect(private_key_file).to receive(:write).with(new_private_key)
|
||||
communicator.ready?
|
||||
end
|
||||
|
||||
it "should call the set_ssh_key_permissions host capability" do
|
||||
expect(host).to receive(:capability?).with(:set_ssh_key_permissions).and_return(true)
|
||||
expect(host).to receive(:capability).with(:set_ssh_key_permissions, private_key_file)
|
||||
communicator.ready?
|
||||
end
|
||||
|
||||
it "should remove the default public key" do
|
||||
expect(guest).to receive(:capability).with(:remove_public_key, any_args)
|
||||
communicator.ready?
|
||||
end
|
||||
|
||||
context "with server algorithm support data" do
|
||||
context "when no key type matches are found" do
|
||||
it "should default to rsa type" do
|
||||
expect(Vagrant::Util::Keypair).to receive(:create).
|
||||
with(type: :rsa).and_call_original
|
||||
end
|
||||
before do
|
||||
allow(communicator).to receive(:supported_key_types).and_call_original
|
||||
end
|
||||
|
||||
context "when rsa is the only match" do
|
||||
let(:valid_key_types) { ["ssh-edsca", "ssh-rsa"] }
|
||||
let(:valid_key_types) { ["ssh-ecdsa", "ssh-rsa"] }
|
||||
|
||||
it "should use rsa type" do
|
||||
expect(Vagrant::Util::Keypair).to receive(:create).
|
||||
with(type: :rsa).and_call_original
|
||||
communicator.ready?
|
||||
end
|
||||
end
|
||||
|
||||
@ -313,27 +316,69 @@ describe VagrantPlugins::CommunicatorSSH::Communicator do
|
||||
it "should use ed25519 type" do
|
||||
expect(Vagrant::Util::Keypair).to receive(:create).
|
||||
with(type: :ed25519).and_call_original
|
||||
communicator.ready?
|
||||
end
|
||||
end
|
||||
|
||||
context "when ed25519 is the only match" do
|
||||
let(:valid_key_types) { ["ssh-edsca", "ssh-ed25519"] }
|
||||
let(:valid_key_types) { ["ssh-ecdsa", "ssh-ed25519"] }
|
||||
|
||||
it "should use ed25519 type" do
|
||||
expect(Vagrant::Util::Keypair).to receive(:create).
|
||||
with(type: :ed25519).and_call_original
|
||||
communicator.ready?
|
||||
end
|
||||
end
|
||||
|
||||
context "with key_type set as :auto in configuration" do
|
||||
let(:valid_key_types) { ["ssh-ed25519", "ssh-rsa"] }
|
||||
before { allow(ssh).to receive(:key_type).and_return(:auto) }
|
||||
|
||||
it "should use the preferred ed25519 key type" do
|
||||
expect(Vagrant::Util::Keypair).to receive(:create).
|
||||
with(type: :ed25519).and_call_original
|
||||
communicator.ready?
|
||||
end
|
||||
|
||||
context "when no supported key type is detected" do
|
||||
let(:valid_key_types) { ["fake-type", "other-fake-type"] }
|
||||
|
||||
it "should raise an error" do
|
||||
expect { communicator.ready? }.to raise_error(Vagrant::Errors::SSHKeyTypeNotSupportedByServer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with key_type set as :ecdsa521 in configuration" do
|
||||
let(:valid_key_types) { ["ssh-ed25519", "ssh-rsa", "ecdsa-sha2-nistp521", "ecdsa-sha2-nistp256"] }
|
||||
before { allow(ssh).to receive(:key_type).and_return(:ecdsa521) }
|
||||
|
||||
it "should use the requested key type" do
|
||||
expect(Vagrant::Util::Keypair).to receive(:create).
|
||||
with(type: :ecdsa521).and_call_original
|
||||
communicator.ready?
|
||||
end
|
||||
|
||||
context "when requested key type is not supported" do
|
||||
let(:valid_key_types) { ["ssh-ed25519", "ssh-rsa", "ecdsa-sha2-nistp256"] }
|
||||
|
||||
it "should raise an error" do
|
||||
expect { communicator.ready? }.to raise_error(Vagrant::Errors::SSHKeyTypeNotSupportedByServer)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
it "should default to rsa key" do
|
||||
expect(Vagrant::Util::Keypair).to receive(:create).
|
||||
with(type: :rsa).and_call_original
|
||||
communicator.ready?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -44,6 +44,33 @@ describe VagrantPlugins::Kernel_V2::SSHConnectConfig do
|
||||
end
|
||||
end
|
||||
|
||||
describe "#key_type" do
|
||||
it "defaults to :auto" do
|
||||
subject.finalize!
|
||||
expect(subject.key_type).to eq(:auto)
|
||||
end
|
||||
|
||||
it "should allow supported key type" do
|
||||
subject.key_type = :ed25519
|
||||
subject.finalize!
|
||||
errors = subject.validate(machine)
|
||||
expect(errors).to be_empty
|
||||
end
|
||||
|
||||
it "should not allow unsupported key type" do
|
||||
subject.key_type = :unknown_type
|
||||
subject.finalize!
|
||||
errors = subject.validate(machine)
|
||||
expect(errors).not_to be_empty
|
||||
end
|
||||
|
||||
it "should convert string values to symbol" do
|
||||
subject.key_type = "ecdsa521"
|
||||
subject.finalize!
|
||||
expect(subject.key_type).to eq(:ecdsa521)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#config" do
|
||||
let(:config_file) { "/path/to/config" }
|
||||
|
||||
|
||||
@ -90,6 +90,12 @@ defaults are typically fine, but you can fine tune whatever you would like.
|
||||
- `config.ssh.keys_only` (boolean) - Only use Vagrant-provided SSH private keys (do not use
|
||||
any keys stored in ssh-agent). The default value is `true`.
|
||||
|
||||
- `config.ssh.key_type` (string, symbol) - The SSH key type that should be used when generating
|
||||
a new key to replace the default insecure key. Supported values are: `:ed25519`, `:ecdsa256`,
|
||||
`:ecdsa384`, `:ecdsa521`, `:rsa`, and `:auto`. When the value is set to `:auto`, Vagrant will
|
||||
automatically pick a type based on what is supported by the guest SSH server. The default
|
||||
value is `:auto`.
|
||||
|
||||
- `config.ssh.paranoid` (boolean) - Perform strict host-key verification. The default value is
|
||||
`false`.
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user