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 8, 2020
1 parent 43fc506 commit 8aad8c3
Show file tree
Hide file tree
Showing 10 changed files with 338 additions and 97 deletions.
8 changes: 4 additions & 4 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-07 19:23:35 +0300 using RuboCop version 0.74.0.
# on 2020-04-08 15:29:11 +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 Expand Up @@ -190,7 +191,7 @@ RSpec/SubjectStub:
- 'spec/custom_facts/util/fact_spec.rb'
- 'spec/custom_facts/util/resolution_spec.rb'

# Offense count: 193
# Offense count: 187
# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
RSpec/VerifiedDoubles:
Exclude:
Expand Down Expand Up @@ -233,7 +234,6 @@ RSpec/VerifiedDoubles:
- 'spec/facter/resolvers/solaris/zfs_resolver_spec.rb'
- 'spec/facter/resolvers/solaris/zone_resolver_spec.rb'
- 'spec/facter/resolvers/solaris/zpool_resolver_spec.rb'
- 'spec/facter/resolvers/ssh_resolver_spec.rb'
- 'spec/facter/resolvers/utils/windows/network_utils_spec.rb'
- 'spec/facter/resolvers/utils/windows/win32ole_spec.rb'
- 'spec/facter/resolvers/windows/dmi_bios_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)
ssh_info = Facter::Resolvers::Windows::Ssh.resolve(:ssh) if privileged
ssh_facts = {}
ssh_info&.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
44 changes: 2 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,15 @@ 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)
ssh_list << SshHelper.create_ssh(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
25 changes: 25 additions & 0 deletions lib/resolvers/utils/ssh_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 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_type, key)
key_name = SSH_NAME[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
end
end
end
46 changes: 46 additions & 0 deletions lib/resolvers/windows/ssh.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# 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(' ')
ssh_list << SshHelper.create_ssh(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
Loading

0 comments on commit 8aad8c3

Please sign in to comment.