Skip to content
This repository has been archived by the owner on Jun 19, 2020. It is now read-only.

Commit

Permalink
(FACT-2330) Add ssh fact for Windows OpenSSH feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Oana Tanasoiu committed Apr 2, 2020
1 parent e98a7c2 commit 865fdcf
Show file tree
Hide file tree
Showing 10 changed files with 338 additions and 48 deletions.
5 changes: 3 additions & 2 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# This configuration was generated by
# `rubocop --auto-gen-config --exclude-limit 1000`
# on 2020-04-02 14:42:50 +0300 using RuboCop version 0.74.0.
# on 2020-04-02 17:19:17 +0300 using RuboCop version 0.74.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.

# Offense count: 79
# Offense count: 80
# Configuration parameters: CustomTransform, IgnoreMethods.
RSpec/FilePath:
Exclude:
Expand Down Expand Up @@ -57,6 +57,7 @@ RSpec/FilePath:
- 'spec/facter/resolvers/system_profile_resolver_spec.rb'
- 'spec/facter/resolvers/utils/aix/odm_query_spec.rb'
- 'spec/facter/resolvers/utils/macosx/filesystem_helper_spec.rb'
- 'spec/facter/resolvers/utils/ssh_helper_spec.rb'
- 'spec/facter/resolvers/utils/windows/win32ole_spec.rb'
- 'spec/facter/resolvers/windows/dmi_bios_resolver_spec.rb'
- 'spec/facter/resolvers/windows/dmi_computersystem_resolver_spec.rb'
Expand Down
27 changes: 27 additions & 0 deletions lib/facts/windows/ssh.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module Facts
module Windows
class Ssh
FACT_NAME = 'ssh'

def call_the_resolver
privileged = Facter::Resolvers::Identity.resolve(:privileged)
result = Facter::Resolvers::Windows::Ssh.resolve(:ssh) if privileged
ssh_facts = {}
result&.each { |ssh| ssh_facts.merge!(create_ssh_fact(ssh)) }
Facter::ResolvedFact.new(FACT_NAME, ssh_facts.empty? ? nil : ssh_facts)
end

private

def create_ssh_fact(ssh)
{ ssh.name.to_sym =>
{ fingerprints: { sha1: ssh.fingerprint.sha1,
sha256: ssh.fingerprint.sha256 },
key: ssh.key,
type: ssh.type } }
end
end
end
end
1 change: 1 addition & 0 deletions lib/framework/core/file_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def load_lib_dirs(*dirs)
require "#{ROOT_DIR}/lib/framework/config/block_list"
require "#{ROOT_DIR}/lib/resolvers/utils/fingerprint.rb"
require "#{ROOT_DIR}/lib/resolvers/utils/ssh.rb"
require "#{ROOT_DIR}/lib/resolvers/utils/ssh_helper.rb"
require "#{ROOT_DIR}/lib/resolvers/utils/filesystem_helper.rb"

load_dir(['config'])
Expand Down
45 changes: 3 additions & 42 deletions lib/resolvers/ssh_resolver.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
# frozen_string_literal: true

require 'base64'
require 'digest/sha1'

module Facter
module Resolvers
class SshResolver < BaseResolver
Expand All @@ -24,52 +21,16 @@ def retrieve_info(fact_name)
next unless File.directory?(file_path)

@file_names.each do |file_name|
next unless FileTest.file?(File.join(file_path, file_name))
next unless File.readable?(File.join(file_path, file_name))

key_type, key = File.read(File.join(file_path, file_name)).split(' ')
key_name = determine_ssh_key_name(key_type)
ssh_list << create_ssh(key_name, key_type, key)
key_name = SshHelper.determine_ssh_key_name(key_type)
ssh_list << SshHelper.create_ssh(key_name, key_type, key)
end
end
@fact_list[:ssh] = ssh_list
@fact_list[fact_name]
end

def create_ssh(key_name, key_type, key)
decoded_key = Base64.decode64(key)
ssh_fa = determine_ssh_fingerprint(key_name)
sha1 = "SSHFP #{ssh_fa} 1 #{Digest::SHA1.new.update(decoded_key)}"
sha256 = "SSHFP #{ssh_fa} 2 #{Digest::SHA2.new.update(decoded_key)}"

fingerprint = FingerPrint.new(sha1, sha256)
Ssh.new(fingerprint, key_type, key, key_name)
end

def determine_ssh_key_name(key)
case key
when 'ssh-dss'
'dsa'
when 'ecdsa-sha2-nistp256'
'ecdsa'
when 'ssh-ed25519'
'ed25519'
when 'ssh-rsa'
'rsa'
end
end

def determine_ssh_fingerprint(key_name)
case key_name
when 'rsa'
1
when 'dsa'
2
when 'ecdsa'
3
when 'ed25519'
4
end
end
end
end
end
Expand Down
28 changes: 28 additions & 0 deletions lib/resolvers/utils/ssh_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

require 'base64'
require 'digest/sha1'

module Facter
class SshHelper
class << self
SSH_NAME = { 'ssh-dss' => 'dsa', 'ecdsa-sha2-nistp256' => 'ecdsa',
'ssh-ed25519' => 'ed25519', 'ssh-rsa' => 'rsa' }.freeze
SSH_FINGERPRINT = { 'rsa' => 1, 'dsa' => 2, 'ecdsa' => 3, 'ed25519' => 4 }.freeze

def create_ssh(key_name, key_type, key)
decoded_key = Base64.decode64(key)
ssh_fp = SSH_FINGERPRINT[key_name]
sha1 = "SSHFP #{ssh_fp} 1 #{Digest::SHA1.new.update(decoded_key)}"
sha256 = "SSHFP #{ssh_fp} 2 #{Digest::SHA2.new.update(decoded_key)}"

fingerprint = FingerPrint.new(sha1, sha256)
Ssh.new(fingerprint, key_type, key, key_name)
end

def determine_ssh_key_name(key)
SSH_NAME[key]
end
end
end
end
47 changes: 47 additions & 0 deletions lib/resolvers/windows/ssh.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

module Facter
module Resolvers
module Windows
class Ssh < BaseResolver
@log = Facter::Log.new(self)
@semaphore = Mutex.new
@fact_list ||= {}
FILE_NAMES = %w[ssh_host_rsa_key.pub ssh_host_dsa_key.pub
ssh_host_ecdsa_key.pub ssh_host_ed25519_key.pub].freeze
class << self
private

def post_resolve(fact_name)
@fact_list.fetch(fact_name) { retrieve_info(fact_name) }
end

def retrieve_info(fact_name)
ssh_dir = determine_ssh_dir
return unless ssh_dir && File.directory?(ssh_dir)

ssh_list = []

FILE_NAMES.each do |file_name|
next unless File.readable?(File.join(ssh_dir, file_name))

key_type, key = File.read(File.join(ssh_dir, file_name)).split(' ')
key_name = SshHelper.determine_ssh_key_name(key_type)
ssh_list << SshHelper.create_ssh(key_name, key_type, key)
end
@fact_list[:ssh] = ssh_list.empty? ? nil : ssh_list
@fact_list[fact_name]
end

def determine_ssh_dir
progdata_dir = ENV['programdata']

return if !progdata_dir || progdata_dir.empty?

File.join(progdata_dir, 'ssh')
end
end
end
end
end
end
87 changes: 87 additions & 0 deletions spec/facter/facts/windows/ssh_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# frozen_string_literal: true

describe Facts::Windows::Ssh do
describe '#call_the_resolver' do
subject(:fact) { Facts::Windows::Ssh.new }

context 'when user is privileged' do
let(:ssh) do
[Facter::Ssh.new(Facter::FingerPrint.new('test', 'test'), 'ecdsa', 'test', 'ecdsa')]
end
let(:value) do
{ 'ecdsa' => { 'fingerprints' =>
{ 'sha1' => 'test', 'sha256' => 'test' },
'key' => 'test',
'type' => 'ecdsa' } }
end

before do
allow(Facter::Resolvers::Identity).to receive(:resolve).with(:privileged).and_return(true)
allow(Facter::Resolvers::Windows::Ssh).to receive(:resolve).with(:ssh).and_return(ssh)
end

it 'calls Facter::Resolvers::Windows::Ssh' do
fact.call_the_resolver
expect(Facter::Resolvers::Windows::Ssh).to have_received(:resolve).with(:ssh)
end

it 'calls Facter::Resolvers::Windows::Identity' do
fact.call_the_resolver
expect(Facter::Resolvers::Identity).to have_received(:resolve).with(:privileged)
end

it 'returns ssh fact' do
expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \
have_attributes(name: 'ssh', value: value)
end
end

context 'when user is privileged but no ssh key found' do
let(:value) { nil }

before do
allow(Facter::Resolvers::Identity).to receive(:resolve).with(:privileged).and_return(true)
allow(Facter::Resolvers::Windows::Ssh).to receive(:resolve).with(:ssh).and_return({})
end

it 'calls Facter::Resolvers::Windows::Ssh' do
fact.call_the_resolver
expect(Facter::Resolvers::Windows::Ssh).to have_received(:resolve).with(:ssh)
end

it 'calls Facter::Resolvers::Windows::Identity' do
fact.call_the_resolver
expect(Facter::Resolvers::Identity).to have_received(:resolve).with(:privileged)
end

it 'returns ssh fact' do
expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \
have_attributes(name: 'ssh', value: value)
end
end

context 'when user is not privileged' do
let(:value) { nil }

before do
allow(Facter::Resolvers::Identity).to receive(:resolve).with(:privileged).and_return(false)
allow(Facter::Resolvers::Windows::Ssh).to receive(:resolve).with(:ssh).and_return(value)
end

it "doesn't call Facter::Resolvers::Windows::Ssh" do
fact.call_the_resolver
expect(Facter::Resolvers::Windows::Ssh).not_to have_received(:resolve).with(:ssh)
end

it 'calls Facter::Resolvers::Windows::Identity' do
fact.call_the_resolver
expect(Facter::Resolvers::Identity).to have_received(:resolve).with(:privileged)
end

it 'returns ssh fact' do
expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \
have_attributes(name: 'ssh', value: value)
end
end
end
end
8 changes: 4 additions & 4 deletions spec/facter/resolvers/ssh_resolver_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@

file_names.each do |file_name|
unless file_name == 'ssh_host_rsa_key.pub'
allow(FileTest).to receive(:file?).with('/etc/' + file_name)
allow(File).to receive(:readable?).with('/etc/' + file_name)
.and_return(false)
end
end

allow(FileTest).to receive(:file?).with('/etc/ssh_host_ecdsa_key.pub').and_return(true)
allow(FileTest).to receive(:file?).with('/etc/ssh_host_rsa_key.pub').and_return(true)
allow(FileTest).to receive(:file?).with('/etc/ssh_host_ed25519_key.pub').and_return(true)
allow(File).to receive(:readable?).with('/etc/ssh_host_ecdsa_key.pub').and_return(true)
allow(File).to receive(:readable?).with('/etc/ssh_host_rsa_key.pub').and_return(true)
allow(File).to receive(:readable?).with('/etc/ssh_host_ed25519_key.pub').and_return(true)
allow(File).to receive(:read).with('/etc/ssh_host_ecdsa_key.pub').and_return(ecdsa_content)
allow(File).to receive(:read).with('/etc/ssh_host_rsa_key.pub').and_return(rsa_content)
allow(File).to receive(:read).with('/etc/ssh_host_ed25519_key.pub').and_return(ed25519_content)
Expand Down
34 changes: 34 additions & 0 deletions spec/facter/resolvers/utils/ssh_helper_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

describe Facter::SshHelper do
subject(:ssh_helper) { Facter::SshHelper }

describe '#determine_ssh_key_name' do
it 'returns ecdsa' do
expect(ssh_helper.determine_ssh_key_name('ecdsa-sha2-nistp256')).to eql('ecdsa')
end

it 'returns rsa' do
expect(ssh_helper.determine_ssh_key_name('ssh-rsa')).to eql('rsa')
end

it 'returns nil' do
expect(ssh_helper.determine_ssh_key_name('ssh-test')).to be(nil)
end
end

describe '#create_ssh' do
let(:fingerprint) { instance_spy(Facter::FingerPrint) }
let(:key) { load_fixture('rsa_key').read.strip }
let(:ssh_object) { Facter::Ssh.new(fingerprint, 'ssh-rsa', key, 'rsa') }

before do
allow(Facter::FingerPrint).to receive(:new).and_return(fingerprint)
allow(Facter::Ssh).to receive(:new).and_return(ssh_object)
end

it 'returns a ssh object' do
expect(ssh_helper.create_ssh('rsa', 'ssh-rsa', key)).to eql(ssh_object)
end
end
end
Loading

0 comments on commit 865fdcf

Please sign in to comment.