diff --git a/lib/facter/util/resolvers/fingerprint.rb b/lib/facter/util/resolvers/fingerprint.rb index ff84b7adb7..e0199bf602 100644 --- a/lib/facter/util/resolvers/fingerprint.rb +++ b/lib/facter/util/resolvers/fingerprint.rb @@ -3,13 +3,7 @@ module Facter module Util module Resolvers - class FingerPrint - attr_accessor :sha1, :sha256 - def initialize(sha1, sha256) - @sha1 = sha1 - @sha256 = sha256 - end - end + FingerPrint = Struct.new(:sha1, :sha256) end end end diff --git a/lib/facter/util/resolvers/ssh.rb b/lib/facter/util/resolvers/ssh.rb index 4e176ca81e..752e908425 100644 --- a/lib/facter/util/resolvers/ssh.rb +++ b/lib/facter/util/resolvers/ssh.rb @@ -3,15 +3,7 @@ module Facter module Util module Resolvers - class Ssh - attr_accessor :fingerprint, :type, :key, :name - def initialize(fingerprint, type, key, name) - @fingerprint = fingerprint - @type = type - @key = key - @name = name - end - end + Ssh = Struct.new(:fingerprint, :type, :key, :name) end end end diff --git a/lib/facter/util/resolvers/ssh_helper.rb b/lib/facter/util/resolvers/ssh_helper.rb index 8742badff6..62462d1e47 100644 --- a/lib/facter/util/resolvers/ssh_helper.rb +++ b/lib/facter/util/resolvers/ssh_helper.rb @@ -9,6 +9,7 @@ module Resolvers class SshHelper class << self SSH_NAME = { 'ssh-dss' => 'dsa', 'ecdsa-sha2-nistp256' => 'ecdsa', + 'ecdsa-sha2-nistp384' => 'ecdsa', 'ecdsa-sha2-nistp521' => 'ecdsa', 'ssh-ed25519' => 'ed25519', 'ssh-rsa' => 'rsa' }.freeze SSH_FINGERPRINT = { 'rsa' => 1, 'dsa' => 2, 'ecdsa' => 3, 'ed25519' => 4 }.freeze @@ -16,6 +17,7 @@ def create_ssh(key_type, key) key_name = SSH_NAME[key_type] return unless key_name + # decode64 ignores non-base64 characters including newlines decoded_key = Base64.decode64(key) ssh_fp = SSH_FINGERPRINT[key_name] sha1 = "SSHFP #{ssh_fp} 1 #{Digest::SHA1.new.update(decoded_key)}" diff --git a/spec/facter/resolvers/ssh_spec.rb b/spec/facter/resolvers/ssh_spec.rb index b8833be2d2..455cfea717 100644 --- a/spec/facter/resolvers/ssh_spec.rb +++ b/spec/facter/resolvers/ssh_spec.rb @@ -2,42 +2,6 @@ describe Facter::Resolvers::Ssh do describe '#folders' do - let(:ecdsa_content) { load_fixture('ecdsa').read.strip! } - let(:rsa_content) { load_fixture('rsa').read.strip! } - let(:ed25519_content) { load_fixture('ed25519').read.strip! } - - let(:ecdsa_fingerprint) do - Facter::Util::Resolvers::FingerPrint.new( - 'SSHFP 3 1 fd92cf867fac0042d491eb1067e4f3cabf54039a', - 'SSHFP 3 2 a51271a67987d7bbd685fa6d7cdd2823a30373ab01420b094480523fabff2a05' - ) - end - - let(:rsa_fingerprint) do - Facter::Util::Resolvers::FingerPrint.new( - 'SSHFP 1 1 90134f93fec6ab5e22bdd88fc4d7cd6e9dca4a07', - 'SSHFP 1 2 efaa26ff8169f5ffc372ebcad17aef886f4ccaa727169acdd0379b51c6c77e99' - ) - end - - let(:ed25519_fingerprint) do - Facter::Util::Resolvers::FingerPrint.new( - 'SSHFP 4 1 f5780634d4e34c6ef2411ac439b517bfdce43cf1', - 'SSHFP 4 2 c1257b3865df22f3349f9ebe19961c8a8edf5fbbe883113e728671b42d2c9723' - ) - end - - let(:ecdsa_result) do - Facter::Util::Resolvers::Ssh.new(ecdsa_fingerprint, 'ecdsa-sha2-nistp256', ecdsa_content, 'ecdsa') - end - - let(:rsa_result) do - Facter::Util::Resolvers::Ssh.new(rsa_fingerprint, 'ssh-rsa', rsa_content, 'rsa') - end - let(:ed25519_result) do - Facter::Util::Resolvers::Ssh.new(ed25519_fingerprint, 'ssh-ed22519', ed25519_content, 'ed25519') - end - let(:paths) { %w[/etc/ssh /usr/local/etc/ssh /etc /usr/local/etc /etc/opt/ssh] } let(:file_names) { %w[ssh_host_rsa_key.pub ssh_host_ecdsa_key.pub ssh_host_ed25519_key.pub] } @@ -46,43 +10,95 @@ allow(File).to receive(:directory?).with('/etc').and_return(true) allow(Facter::Util::FileHelper).to receive(:safe_read) - .with('/etc/ssh_host_ecdsa_key.pub', nil).and_return(ecdsa_content) - allow(Facter::Util::FileHelper).to receive(:safe_read) - .with('/etc/ssh_host_dsa_key.pub', nil).and_return(nil) - allow(Facter::Util::FileHelper).to receive(:safe_read) - .with('/etc/ssh_host_rsa_key.pub', nil).and_return(rsa_content) - allow(Facter::Util::FileHelper).to receive(:safe_read) - .with('/etc/ssh_host_ed25519_key.pub', nil).and_return(ed25519_content) - - allow(Facter::Util::Resolvers::SshHelper).to receive(:create_ssh) - .with('ssh-rsa', load_fixture('rsa_key').read.strip!) - .and_return(rsa_result) - allow(Facter::Util::Resolvers::SshHelper).to receive(:create_ssh) - .with('ecdsa-sha2-nistp256', load_fixture('ecdsa_key').read.strip!) - .and_return(ecdsa_result) - allow(Facter::Util::Resolvers::SshHelper).to receive(:create_ssh) - .with('ssh-ed25519', load_fixture('ed25519_key').read.strip!) - .and_return(ed25519_result) + .with(a_string_starting_with('/etc/ssh_host'), nil).and_return(nil) end after do Facter::Resolvers::Ssh.invalidate_cache end - context 'when ssh_host_dsa_key.pub file is not readable' do - it 'returns resolved ssh' do - expect(Facter::Resolvers::Ssh.resolve(:ssh)).to eq([rsa_result, ecdsa_result, ed25519_result]) + shared_examples 'an ssh key' do + it 'resolves the key' do + allow(Facter::Util::FileHelper).to receive(:safe_read) + .with(path, nil).and_return(content) + + expect(Facter::Resolvers::Ssh.resolve(:ssh)).to eq([result]) end end - context 'when ssh_host_ecdsa_key.pub file is also not readable' do - before do - allow(Facter::Util::FileHelper).to receive(:safe_read) - .with('/etc/ssh_host_ecdsa_key.pub', nil).and_return(nil) + context 'when rsa' do + let(:path) { '/etc/ssh_host_rsa_key.pub' } + let(:content) { load_fixture('rsa').read } + let(:result) do + fingerprint = Facter::Util::Resolvers::FingerPrint.new( + 'SSHFP 1 1 90134f93fec6ab5e22bdd88fc4d7cd6e9dca4a07', + 'SSHFP 1 2 efaa26ff8169f5ffc372ebcad17aef886f4ccaa727169acdd0379b51c6c77e99' + ) + Facter::Util::Resolvers::Ssh.new(fingerprint, *content.strip.split(' '), 'rsa') end - it 'returns resolved ssh' do - expect(Facter::Resolvers::Ssh.resolve(:ssh)).to eq([rsa_result, ed25519_result]) + include_examples 'an ssh key' + end + + context 'when ecdsa' do + let(:path) { '/etc/ssh_host_dsa_key.pub' } + let(:content) { load_fixture('ecdsa').read } + let(:result) do + fingerprint = Facter::Util::Resolvers::FingerPrint.new( + 'SSHFP 3 1 fd92cf867fac0042d491eb1067e4f3cabf54039a', + 'SSHFP 3 2 a51271a67987d7bbd685fa6d7cdd2823a30373ab01420b094480523fabff2a05' + ) + Facter::Util::Resolvers::Ssh.new(fingerprint, *content.strip.split(' '), 'ecdsa') + end + + include_examples 'an ssh key' + end + + context 'when ed25519' do + let(:path) { '/etc/ssh_host_ed25519_key.pub' } + let(:content) { load_fixture('ed25519').read } + let(:result) do + fingerprint = Facter::Util::Resolvers::FingerPrint.new( + 'SSHFP 4 1 1c02084d251368b98a3af97820d9fbf2b8dc9558', + 'SSHFP 4 2 656bd7aa3f8ad4703bd581888231f822cb8cd4a2a258584469551d2c2c9f6b62' + ) + Facter::Util::Resolvers::Ssh.new(fingerprint, *content.strip.split(' '), 'ed25519') + end + + include_examples 'an ssh key' + end + + context 'when ecdsa 384-bit' do + let(:path) { '/etc/ssh_host_ecdsa_key.pub' } + let(:content) { load_fixture('ecdsa384').read } + let(:result) do + fingerprint = Facter::Util::Resolvers::FingerPrint.new( + 'SSHFP 3 1 a3c1dc40a07cd76ea2ffe3f57e96aae146427174', + 'SSHFP 3 2 949d92d65c6bb3908727bef5cdafef5b546650d64a081a4f85e7dcaf6b7cb7ab' + ) + Facter::Util::Resolvers::Ssh.new(fingerprint, *content.strip.split(' '), 'ecdsa') + end + + include_examples 'an ssh key' + end + + context 'when ecdsa 521-bit' do + let(:path) { '/etc/ssh_host_ecdsa_key.pub' } + let(:content) { load_fixture('ecdsa521').read } + let(:result) do + fingerprint = Facter::Util::Resolvers::FingerPrint.new( + 'SSHFP 3 1 61046cb5f7b38df21fe4511a9280436ce89514ee', + 'SSHFP 3 2 b74da480da3411a79abf37d0bcfbbcaa8c1dbfc6a983365276b3c7f0c7a8de3e' + ) + Facter::Util::Resolvers::Ssh.new(fingerprint, *content.strip.split(' '), 'ecdsa') + end + + include_examples 'an ssh key' + end + + context 'when no files are readable' do + it 'returns an empty array' do + expect(Facter::Resolvers::Ssh.resolve(:ssh)).to eq([]) end end @@ -108,11 +124,7 @@ allow(Facter::Util::FileHelper).to receive(:safe_read) .with('/etc/ssh_host_ecdsa_key.pub', nil).and_return('invalid key') allow(Facter::Util::FileHelper).to receive(:safe_read) - .with('/etc/ssh_host_dsa_key.pub', nil).and_return(nil) - allow(Facter::Util::FileHelper).to receive(:safe_read) - .with('/etc/ssh_host_rsa_key.pub', nil).and_return(nil) - allow(Facter::Util::FileHelper).to receive(:safe_read) - .with('/etc/ssh_host_ed25519_key.pub', nil).and_return(nil) + .with(a_string_starting_with('/etc/ssh_host'), nil) end after do diff --git a/spec/facter/util/resolvers/ssh_helper_spec.rb b/spec/facter/util/resolvers/ssh_helper_spec.rb index be444bcc4d..8c16589de6 100644 --- a/spec/facter/util/resolvers/ssh_helper_spec.rb +++ b/spec/facter/util/resolvers/ssh_helper_spec.rb @@ -4,16 +4,32 @@ subject(:ssh_helper) { Facter::Util::Resolvers::SshHelper } describe '#create_ssh' do - let(:fingerprint) { instance_spy(Facter::Util::Resolvers::FingerPrint) } let(:key) { load_fixture('rsa_key').read.strip } - before do - allow(Facter::Util::Resolvers::FingerPrint).to receive(:new).and_return(fingerprint) + it 'returns an RSA ssh object' do + expect(ssh_helper.create_ssh('ssh-rsa', key)).to \ + be_an_instance_of(Facter::Util::Resolvers::Ssh).and \ + have_attributes(name: 'rsa', type: 'ssh-rsa') end - it 'returns a ssh object' do - expect(ssh_helper.create_ssh('ssh-rsa', key)).to be_an_instance_of(Facter::Util::Resolvers::Ssh).and \ - have_attributes(name: 'rsa', type: 'ssh-rsa', fingerprint: fingerprint) + it 'returns sha1 fingerprint' do + expect(ssh_helper.create_ssh('ssh-rsa', key).fingerprint.sha1).to \ + eq('SSHFP 1 1 90134f93fec6ab5e22bdd88fc4d7cd6e9dca4a07') + end + + it 'returns sha256 fingerprint' do + expect(ssh_helper.create_ssh('ssh-rsa', key).fingerprint.sha256).to \ + eq('SSHFP 1 2 efaa26ff8169f5ffc372ebcad17aef886f4ccaa727169acdd0379b51c6c77e99') + end + + it 'ignores non-base64 characters' do + nonbase64_key = "\x00\n-_#{key}" + expect(ssh_helper.create_ssh('ssh-rsa', nonbase64_key).fingerprint.sha1).to \ + eq('SSHFP 1 1 90134f93fec6ab5e22bdd88fc4d7cd6e9dca4a07') + end + + it 'implements value semantics' do + expect(ssh_helper.create_ssh('ssh-rsa', key)).to eq(ssh_helper.create_ssh('ssh-rsa', key)) end end end diff --git a/spec/fixtures/ecdsa384 b/spec/fixtures/ecdsa384 new file mode 100644 index 0000000000..26401b3c25 --- /dev/null +++ b/spec/fixtures/ecdsa384 @@ -0,0 +1 @@ +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBI+YmNHUdvMtZSEdCDJLruZjtUGsi59cf/TNkmRKFcVGgaWO54NUXT/PlTwjm7g9uS1FKbZY4+MKP0Q4KsgfGJAwn9MLsdSeUGY2UIrhQ0UM6KUUZCDot0G7Xm2pAdy/Qw== diff --git a/spec/fixtures/ecdsa384_key b/spec/fixtures/ecdsa384_key new file mode 100644 index 0000000000..e457a5aca6 --- /dev/null +++ b/spec/fixtures/ecdsa384_key @@ -0,0 +1 @@ +AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBI+YmNHUdvMtZSEdCDJLruZjtUGsi59cf/TNkmRKFcVGgaWO54NUXT/PlTwjm7g9uS1FKbZY4+MKP0Q4KsgfGJAwn9MLsdSeUGY2UIrhQ0UM6KUUZCDot0G7Xm2pAdy/Qw== diff --git a/spec/fixtures/ecdsa521 b/spec/fixtures/ecdsa521 new file mode 100644 index 0000000000..f50c1bec9f --- /dev/null +++ b/spec/fixtures/ecdsa521 @@ -0,0 +1 @@ +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHKr7fO2HGs84ihV9+Z4Dkk4rX+FqhtKV4vGEIwnwR3r0GUIER1aIk+shXzOhCEPNqTiik5CRdE9sDhXkYDJa35+QFIBvo1i2qCNEQ1EowBbYZYBAhk3CPAhIUIYe+Achz+PCqBhqkPC+vHhqHpECAzOI0qjFuoT17rbEb4stl3n8yHfQ== diff --git a/spec/fixtures/ecdsa521_key b/spec/fixtures/ecdsa521_key new file mode 100644 index 0000000000..e0de09e605 --- /dev/null +++ b/spec/fixtures/ecdsa521_key @@ -0,0 +1 @@ +AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHKr7fO2HGs84ihV9+Z4Dkk4rX+FqhtKV4vGEIwnwR3r0GUIER1aIk+shXzOhCEPNqTiik5CRdE9sDhXkYDJa35+QFIBvo1i2qCNEQ1EowBbYZYBAhk3CPAhIUIYe+Achz+PCqBhqkPC+vHhqHpECAzOI0qjFuoT17rbEb4stl3n8yHfQ==