Skip to content

Commit

Permalink
(SIMP-6366) Configure Bolt module ssh (#186)
Browse files Browse the repository at this point in the history
Add a `simplib__sshd_config` fact that collects relevant bits of the SSH configuration and can be easily extended over time to return additional configuration items.

SIMP-6366 #comment Create sshd_config fact for simp_bolt
  • Loading branch information
m-morrone authored and trevor-vaughan committed Apr 24, 2019
1 parent 662c6d0 commit 5e24a5d
Show file tree
Hide file tree
Showing 3 changed files with 328 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
* Fri Apr 12 2019 Michael Morrone <michael.morrone@onyxpoint.com> - 3.15.0-0
- Added `simplib__sshd_config` fact to check the contents of sshd_config file

* Fri Apr 05 2019 Joseph Sharkey <shark.bruhaha@gmail.com> - 3.15.0-0
- Re-enable simplib deprecation warnings by default

Expand Down
80 changes: 80 additions & 0 deletions lib/facter/simplib__sshd_config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# _Description_
#
# Return values from the /etc/ssh/sshd_conf file
#
Facter.add('simplib__sshd_config') do
confine { File.exist?('/etc/ssh/sshd_config') && File.readable?('/etc/ssh/sshd_config')}

setcode do

# Items that we wish to pull from the configuration
#
# Format:
# Key => Default Value
selected_settings = {
'AuthorizedKeysFile' => '.ssh/authorized_keys'
}

sshd = Facter::Util::Resolution.which('sshd')
if sshd
full_version = Facter::Core::Execution.execute("#{sshd} --help 2>&1", :on_fail => :failed)

unless full_version == :failed
sshd_config ||= {}

full_version = full_version.lines.grep(/^\s*OpenSSH_\d/).first

if full_version
sshd_config['version'] = full_version.split(/,|\s/).first.split('_').last
sshd_config['full_version'] = full_version
end
end
end

sshd_disk_config = File.read('/etc/ssh/sshd_config')

match_section = nil
sshd_disk_config.lines do |line|
line.strip!

next if line.empty?
next if line[0].chr == '#'

if config_parts = line.match(/^(?:(?<key>.+?))\s+(?<value>.+)\s*$/)
if config_parts[:key] == 'Match'
match_section = line
next
end

next unless selected_settings.keys.include?(config_parts[:key])

if match_section
sshd_config ||= {}
sshd_config[match_section] ||= {}

if sshd_config[match_section][config_parts[:key]]
sshd_config[match_section][config_parts[:key]] = Array(sshd_config[match_section][config_parts[:key]])
sshd_config[match_section][config_parts[:key]] << config_parts[:value]
else
sshd_config[match_section][config_parts[:key]] = config_parts[:value]
end
else
sshd_config ||= {}

if sshd_config[config_parts[:key]]
sshd_config[config_parts[:key]] = Array(sshd_config[config_parts[:key]])
sshd_config[config_parts[:key]] << config_parts[:value]
else
sshd_config[config_parts[:key]] = config_parts[:value]
end
end
end
end

if sshd_config
# Add in any defaults that we missed
# This should *not* be a deep_merge!
selected_settings.merge(sshd_config)
end
end
end
245 changes: 245 additions & 0 deletions spec/unit/facter/simplib__sshd_config_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
require 'spec_helper'

describe "simplib__sshd_config" do

before :each do
Facter.clear

Facter::Util::Resolution.expects(:which).with('sshd').returns('/usr/bin/sshd')
Facter::Core::Execution.expects(:execute).with('/usr/bin/sshd --help 2>&1', :on_fail => :failed).returns(openssh_version['full_version'])

File.expects(:exist?).with('/etc/ssh/sshd_config').returns(true)
File.expects(:readable?).with('/etc/ssh/sshd_config').returns(true)
File.expects(:read).with('/etc/ssh/sshd_config').returns(sshd_config_content)

# This resets the stubbing code in Mocha to ensure that the code does not
# try to catch any other calls to the stubbed methods above.
#
# This is not documented well and is almost always what you want in
# Puppet testing

File.stubs(:exist?).with(Not(equals('/etc/ssh/sshd_config')))
File.stubs(:readable?).with(Not(equals('/etc/ssh/sshd_config')))
File.stubs(:read).with(Not(equals('/etc/ssh/sshd_config')))
end

let(:openssh_version) {{
'full_version' => 'OpenSSH_7.9p1, OpenSSL 1.1.1a FIPS 20 Nov 2018',
'version' => '7.9p1'
}}

context 'with a simp /etc/ssh/sshd_config' do
let(:sshd_config_content) { <<-EOM
#Brief chunk of file
Port 22
ListenAddress 0.0.0.0
#ListenAddress ::
#PubkeyAuthentication yes
# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
# but this is overridden so installations will only check .ssh/authorized_keys
AuthorizedKeysFile /etc/ssh/local_keys/%u
#AuthorizedPrincipalsFile none
#AuthorizedKeysCommand none
AuthorizedKeysCommand /usr/bin/sss_ssh_authorizedkeys
#AuthorizedKeysCommandUser nobody
AuthorizedKeysCommandUser nobody
# Even inline comments!
# And indented comments
EOM
}
it {
expect(Facter.fact('simplib__sshd_config').value).to eq(openssh_version.merge({"AuthorizedKeysFile"=>"/etc/ssh/local_keys/%u"}))
}
end

context 'with a default /etc/ssh/sshd_config' do
let(:sshd_config_content) { <<-EOM
#Brief chunk of file
#Port 22
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::
#PubkeyAuthentication yes
# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
# but this is overridden so installations will only check .ssh/authorized_keys
AuthorizedKeysFile .ssh/authorized_keys
#AuthorizedPrincipalsFile none
#AuthorizedKeysCommand none
#AuthorizedKeysCommandUser nobody
# Even inline comments!
# And indented comments
EOM
}

it {
expect(Facter.fact(:simplib__sshd_config).value).to eq(openssh_version.merge({ 'AuthorizedKeysFile' => '.ssh/authorized_keys' }))
}

context 'when the SSH daemon does not return a version string' do
let(:openssh_version) {{
'full_version' => :failed,
'version' => nil
}}

it {
expect(Facter.fact(:simplib__sshd_config).value).to eq({ 'AuthorizedKeysFile' => '.ssh/authorized_keys' })
}
end

context 'when the SSH daemon does not return a valid version string' do
let(:openssh_version) {{
'full_version' => 'OpenSSH_is amazing',
'version' => nil
}}

it {
expect(Facter.fact(:simplib__sshd_config).value).to eq({ 'AuthorizedKeysFile' => '.ssh/authorized_keys' })
}
end
end

context 'with a commented values /etc/ssh/sshd_config' do
let(:sshd_config_content) { <<-EOM
#Brief chunk of file
#Port 22
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::
#PubkeyAuthentication yes
# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
# but this is overridden so installations will only check .ssh/authorized_keys
#AuthorizedKeysFile /etc/ssh/local_keys/%u
#AuthorizedPrincipalsFile none
#AuthorizedKeysCommand none
#AuthorizedKeysCommandUser nobody
EOM
}

it {
expect(Facter.fact(:simplib__sshd_config).value).to eq(openssh_version.merge({ 'AuthorizedKeysFile' => '.ssh/authorized_keys' }))
}
end

context 'with empty /etc/ssh/sshd_config' do
let(:sshd_config_content) {''}

it {
expect(Facter.fact(:simplib__sshd_config).value).to eq(openssh_version.merge({ 'AuthorizedKeysFile' => '.ssh/authorized_keys' }))
}
end

context 'with multiple matching entries' do
let(:sshd_config_content) { <<-EOM
#Brief chunk of file
#Port 22
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::
#PubkeyAuthentication yes
# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
# but this is overridden so installations will only check .ssh/authorized_keys
AuthorizedKeysFile /etc/ssh/local_keys/%u
AuthorizedKeysFile /foo/bar/baz
#AuthorizedPrincipalsFile none
#AuthorizedKeysCommand none
#AuthorizedKeysCommandUser nobody
EOM
}

it {
expect(Facter.fact(:simplib__sshd_config).value).to eq(openssh_version.merge(
{ 'AuthorizedKeysFile' => ['/etc/ssh/local_keys/%u', '/foo/bar/baz'] }
))
}
end

context 'with multiple entries in a Match block' do
let(:sshd_config_content) { <<-EOM
#Brief chunk of file
#Port 22
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::
#PubkeyAuthentication yes
# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
# but this is overridden so installations will only check .ssh/authorized_keys
Match user foo
AuthorizedKeysFile /etc/ssh/local_keys/%u
AuthorizedKeysFile /foo/bar/baz
#AuthorizedPrincipalsFile none
#AuthorizedKeysCommand none
#AuthorizedKeysCommandUser nobody
EOM
}

it {
expect(Facter.fact(:simplib__sshd_config).value).to eq(openssh_version.merge(
{
'AuthorizedKeysFile' => '.ssh/authorized_keys',
'Match user foo' => {
'AuthorizedKeysFile' => [ '/etc/ssh/local_keys/%u', '/foo/bar/baz']
}
}
))
}
end

context 'with global and Match block entries' do
let(:sshd_config_content) { <<-EOM
#Brief chunk of file
#Port 22
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::
#PubkeyAuthentication yes
AuthorizedKeysFile /global/time
# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
# but this is overridden so installations will only check .ssh/authorized_keys
Match user foo
AuthorizedKeysFile /etc/ssh/local_keys/%u
AuthorizedKeysFile /foo/bar/baz
#AuthorizedPrincipalsFile none
#AuthorizedKeysCommand none
#AuthorizedKeysCommandUser nobody
EOM
}

it {
expect(Facter.fact(:simplib__sshd_config).value).to eq(openssh_version.merge(
{
'AuthorizedKeysFile' => '/global/time',
'Match user foo' => {
'AuthorizedKeysFile' => [ '/etc/ssh/local_keys/%u', '/foo/bar/baz']
}
}
))
}
end
end

0 comments on commit 5e24a5d

Please sign in to comment.