Merge pull request #246 from Phil-Friderici/ssh_config

Add ssh::config_entry defined type (with ssh::config_entries and tests)
This commit is contained in:
Garrett Honeycutt 2017-09-26 11:35:38 -04:00 committed by GitHub
commit 3b6789ab56
9 changed files with 269 additions and 12 deletions

View File

@ -3,6 +3,9 @@ fixtures:
stdlib: stdlib:
repo: 'https://github.com/puppetlabs/puppetlabs-stdlib.git' repo: 'https://github.com/puppetlabs/puppetlabs-stdlib.git'
ref: '4.6.0' ref: '4.6.0'
concat:
repo: 'https://github.com/puppetlabs/puppetlabs-concat.git'
ref: '2.2.1'
common: common:
repo: 'https://github.com/ghoneycutt/puppet-module-common.git' repo: 'https://github.com/ghoneycutt/puppet-module-common.git'
ref: 'v1.4.1' ref: 'v1.4.1'

View File

@ -8,6 +8,9 @@ ssh_key_ensure and purge_keys.
This module may be used with a simple `include ::ssh` This module may be used with a simple `include ::ssh`
The `ssh::config_entry` defined type may be used directly and is used to manage
Host entries in a personal `~/.ssh/config` file.
=== ===
### Table of Contents ### Table of Contents
@ -54,8 +57,9 @@ A value of `'USE_DEFAULTS'` will use the defaults specified by the module.
hiera_merge hiera_merge
----------- -----------
Boolean to merges all found instances of ssh::keys in Hiera. This is useful for specifying Boolean to merges all found instances of ssh::keys and ssh::config_entries in Hiera.
SSH keys at different levels of the hierarchy and having them all included in the catalog. This is useful for specifying SSH keys at different levels of the hierarchy and having
them all included in the catalog.
This will default to 'true' in future versions. This will default to 'true' in future versions.
@ -616,6 +620,24 @@ See `sshd_config(5)` for more details
- *Default*: undefined - *Default*: undefined
config_entries
--------------
Hash of config entries for a specific user's ~/.ssh/config. Please check the docs for ssd::config_entry for a list and details of the parameters usable here.
Setting hiera_merge to true will activate merging entries through all levels of hiera.
- *Hiera example*:
``` yaml
ssh::config_entries:
'root':
owner: 'root'
group: 'root'
path: '/root/.ssh/config'
host: 'host.example.local'
```
- *Default*: {}
keys keys
---- ----
Hash of keys for user's ~/.ssh/authorized_keys Hash of keys for user's ~/.ssh/authorized_keys
@ -852,3 +874,30 @@ ssh::keys:
ensure: absent ensure: absent
user: root user: root
``` ```
Manage config entries in a personal ssh/config file.
```
Ssh::Config_entry {
ensure => present,
path => '/home/jenkins/.ssh/config',
owner => 'jenkins',
group => 'jenkins',
}
ssh::config_entry { 'jenkins *':
host => '*',
lines => [
' ForwardX11 no',
' StrictHostKeyChecking no',
],
order => '10',
}
ssh::config_entry { 'jenkins github.com':
host => 'github.com',
lines => [" IdentityFile /home/jenkins/.ssh/jenkins-gihub.key"],
order => '20',
}
```

36
manifests/config_entry.pp Normal file
View File

@ -0,0 +1,36 @@
# == Define: ssh::config_entry
#
# Manage an entry in ~/.ssh/config for a particular user. Lines model the lines
# in each Host block.
define ssh::config_entry (
$owner,
$group,
$path,
$host,
$order = '10',
$ensure = 'present',
$lines = [],
) {
# All lines including the host line. This will be joined with "\n " for
# indentation.
$entry = concat(["Host ${host}"], $lines)
$content = join($entry, "\n")
if ! defined(Concat[$path]) {
concat { $path:
ensure => present,
owner => $owner,
group => $group,
mode => '0644',
ensure_newline => true,
}
}
concat::fragment { "${path} Host ${host}":
target => $path,
content => $content,
order => $order,
tag => "${owner}_ssh_config",
}
}

View File

@ -109,6 +109,7 @@ class ssh (
$ssh_config_global_known_hosts_group = 'root', $ssh_config_global_known_hosts_group = 'root',
$ssh_config_global_known_hosts_mode = '0644', $ssh_config_global_known_hosts_mode = '0644',
$ssh_config_user_known_hosts_file = undef, $ssh_config_user_known_hosts_file = undef,
$config_entries = {},
$keys = undef, $keys = undef,
$manage_root_ssh_config = false, $manage_root_ssh_config = false,
$root_ssh_config_content = "# This file is being maintained by Puppet.\n# DO NOT EDIT\n", $root_ssh_config_content = "# This file is being maintained by Puppet.\n# DO NOT EDIT\n",
@ -802,18 +803,21 @@ class ssh (
$supported_loglevel_vals=['QUIET', 'FATAL', 'ERROR', 'INFO', 'VERBOSE'] $supported_loglevel_vals=['QUIET', 'FATAL', 'ERROR', 'INFO', 'VERBOSE']
validate_re($sshd_config_loglevel, $supported_loglevel_vals) validate_re($sshd_config_loglevel, $supported_loglevel_vals)
#enable hiera merging for groups and users #enable hiera merging for groups, users, and config_entries
if $hiera_merge_real == true { if $hiera_merge_real == true {
$sshd_config_allowgroups_real = hiera_array('ssh::sshd_config_allowgroups',[]) $sshd_config_allowgroups_real = hiera_array('ssh::sshd_config_allowgroups',[])
$sshd_config_allowusers_real = hiera_array('ssh::sshd_config_allowusers',[]) $sshd_config_allowusers_real = hiera_array('ssh::sshd_config_allowusers',[])
$sshd_config_denygroups_real = hiera_array('ssh::sshd_config_denygroups',[]) $sshd_config_denygroups_real = hiera_array('ssh::sshd_config_denygroups',[])
$sshd_config_denyusers_real = hiera_array('ssh::sshd_config_denyusers',[]) $sshd_config_denyusers_real = hiera_array('ssh::sshd_config_denyusers',[])
$config_entries_real = hiera_hash('ssh::config_entries',{})
} else { } else {
$sshd_config_allowgroups_real = $sshd_config_allowgroups $sshd_config_allowgroups_real = $sshd_config_allowgroups
$sshd_config_allowusers_real = $sshd_config_allowusers $sshd_config_allowusers_real = $sshd_config_allowusers
$sshd_config_denygroups_real = $sshd_config_denygroups $sshd_config_denygroups_real = $sshd_config_denygroups
$sshd_config_denyusers_real = $sshd_config_denyusers $sshd_config_denyusers_real = $sshd_config_denyusers
$config_entries_real = $config_entries
} }
validate_hash($config_entries_real)
if $sshd_config_denyusers_real != [] { if $sshd_config_denyusers_real != [] {
validate_array($sshd_config_denyusers_real) validate_array($sshd_config_denyusers_real)
@ -973,6 +977,9 @@ class ssh (
purge => $purge_keys_real, purge => $purge_keys_real,
} }
# manage users' ssh config entries if present
create_resources('ssh::config_entry',$config_entries_real)
# manage users' ssh authorized keys if present # manage users' ssh authorized keys if present
if $keys != undef { if $keys != undef {
if $hiera_merge_real == true { if $hiera_merge_real == true {

View File

@ -88,6 +88,7 @@
"description": "Manage SSH", "description": "Manage SSH",
"dependencies": [ "dependencies": [
{"name":"puppetlabs/stdlib","version_requirement":">= 4.6.0 < 6.0.0"}, {"name":"puppetlabs/stdlib","version_requirement":">= 4.6.0 < 6.0.0"},
{"name":"puppetlabs/concat","version_requirement":">= 2.0.0 < 3.0.0"},
{"name":"ghoneycutt/common","version_requirement":">= 1.4.1 < 2.0.0"}, {"name":"ghoneycutt/common","version_requirement":">= 1.4.1 < 2.0.0"},
{"name":"puppetlabs/firewall","version_requirement":">= 1.9.0 < 2.0.0"} {"name":"puppetlabs/firewall","version_requirement":">= 1.9.0 < 2.0.0"}
] ]

View File

@ -295,6 +295,8 @@ describe 'ssh' do
'purge' => 'true', 'purge' => 'true',
}) })
} }
it { should have_ssh__config_entry_resource_count(0) }
end end
end end
@ -1345,6 +1347,71 @@ describe 'sshd_config_print_last_log param' do
} }
end end
context 'with config_entries defined on valid osfamily' do
let(:params) do
{
:config_entries => {
'root' => {
'owner' => 'root',
'group' => 'root',
'path' => '/root/.ssh/config',
'host' => 'test_host1',
},
'user' => {
'owner' => 'user',
'group' => 'group',
'path' => '/home/user/.ssh/config',
'host' => 'test_host2',
'order' => '242',
'lines' => [ 'ForwardX11 no', 'StrictHostKeyChecking no' ],
},
}
}
end
it { should compile.with_all_deps }
it { should have_ssh__config_entry_resource_count(2) }
it do
should contain_ssh__config_entry('root').with({
'owner' => 'root',
'group' => 'root',
'path' => '/root/.ssh/config',
'host' => 'test_host1',
})
end
it do
should contain_ssh__config_entry('user').with({
'owner' => 'user',
'group' => 'group',
'path' => '/home/user/.ssh/config',
'host' => 'test_host2',
'order' => '242',
'lines' => [ 'ForwardX11 no', 'StrictHostKeyChecking no' ],
})
end
end
describe 'with hiera providing data from multiple levels' do
let(:facts) do
default_facts.merge({
:fqdn => 'hieramerge.example.com',
:specific => 'test_hiera_merge',
})
end
context 'with defaults for all parameters' do
it { should have_ssh__config_entry_resource_count(1) }
it { should contain_ssh__config_entry('user_from_fqdn') }
end
context 'with hiera_merge set to valid <true>' do
let(:params) { { :hiera_merge => true } }
it { should have_ssh__config_entry_resource_count(2) }
it { should contain_ssh__config_entry('user_from_fqdn') }
it { should contain_ssh__config_entry('user_from_fact') }
end
end
context 'with keys defined on valid osfamily' do context 'with keys defined on valid osfamily' do
let(:params) { { :keys => { let(:params) { { :keys => {
'root_for_userX' => { 'root_for_userX' => {
@ -2514,14 +2581,15 @@ describe 'sshd_config_print_last_log param' do
end end
describe 'variable type and content validations' do describe 'variable type and content validations' do
# set needed custom facts and variables mandatory_params = {} if mandatory_params.nil?
let(:mandatory_params) do
{
#:param => 'value',
}
end
validations = { validations = {
'hash' => {
:name => %w[config_entries],
:valid => [], # valid hashes are to complex to block test them here. types::mount should have its own spec tests anyway.
:invalid => ['string', %w[array], 3, 2.42, true],
:message => 'is not a Hash',
},
'regex (yes|no|unset)' => { 'regex (yes|no|unset)' => {
:name => %w(ssh_config_use_roaming), :name => %w(ssh_config_use_roaming),
:valid => ['yes', 'no', 'unset'], :valid => ['yes', 'no', 'unset'],
@ -2543,9 +2611,7 @@ describe 'sshd_config_print_last_log param' do
var[:invalid].each do |invalid| var[:invalid].each do |invalid|
context "when #{var_name} (#{type}) is set to invalid #{invalid} (as #{invalid.class})" do context "when #{var_name} (#{type}) is set to invalid #{invalid} (as #{invalid.class})" do
let(:params) { [mandatory_params, var[:params], { :"#{var_name}" => invalid, }].reduce(:merge) } let(:params) { [mandatory_params, var[:params], { :"#{var_name}" => invalid, }].reduce(:merge) }
it 'should fail' do it { is_expected.to compile.and_raise_error(/#{var[:message]}/) }
expect { should contain_class(subject) }.to raise_error(Puppet::Error, /#{var[:message]}/)
end
end end
end end
end # var[:name].each end # var[:name].each

View File

@ -0,0 +1,83 @@
require 'spec_helper'
describe 'ssh::config_entry' do
mandatory_params = {
:owner => 'test_owner',
:group => 'test_group',
:path => '/test/path',
:host => 'test_host',
}
let(:title) { 'example' }
let(:params) { mandatory_params }
context 'with no paramater is provided' do
let(:params) { {} }
it 'should fail' do
expect do
should contain_define(subject)
end.to raise_error(Puppet::Error, /(Must pass|expects a value for parameter)/) # Puppet4/5
end
end
context 'with mandatory params set' do
let(:params) { mandatory_params }
it { should compile.with_all_deps }
it do
should contain_concat('/test/path').with({
'ensure' => 'present',
'owner' => 'test_owner',
'group' => 'test_group',
'mode' => '0644',
'ensure_newline' => true,
})
end
it do
should contain_concat__fragment('/test/path Host test_host').with({
'target' => '/test/path',
'content' => 'Host test_host',
'order' => '10',
'tag' => 'test_owner_ssh_config',
})
end
end
context 'with owner set to valid string <other_owner>' do
let(:params) { mandatory_params.merge({ :owner => 'other_owner' }) }
it { should contain_concat('/test/path').with_owner('other_owner') }
it { should contain_concat__fragment('/test/path Host test_host').with_tag('other_owner_ssh_config') }
end
context 'with group set to valid string <other_group>' do
let(:params) { mandatory_params.merge({ :group => 'other_group' }) }
it { should contain_concat('/test/path').with_group('other_group') }
end
context 'with path set to valid string </other/path>' do
let(:params) { mandatory_params.merge({ :path => '/other/path' }) }
it { should contain_concat('/other/path') }
it { should contain_concat__fragment('/other/path Host test_host') }
end
context 'with host set to valid string <other_host>' do
let(:params) { mandatory_params.merge({ :host => 'other_host' }) }
it { should contain_concat__fragment('/test/path Host other_host').with_content('Host other_host') }
end
context 'with order set to valid string <242>' do
let(:params) { mandatory_params.merge({ :order => '242' }) }
it { should contain_concat__fragment('/test/path Host test_host').with_order('242') }
end
# /!\ no functionality for $ensure implemented yet
# context 'with ensure set to valid string <absent>' do
# let(:params) { mandatory_params.merge({ :ensure => 'absent' }) }
# it { should contain_concat('/test/path').with_ensure('absent') }
# end
context 'with lines set to valid array [ <ForwardX11 no>, <StrictHostKeyChecking no> ]' do
let(:params) { mandatory_params.merge({ :lines => ['ForwardX11 no', 'StrictHostKeyChecking no'] }) }
it { should contain_concat__fragment('/test/path Host test_host').with_content("Host test_host\nForwardX11 no\nStrictHostKeyChecking no") }
end
end

View File

@ -7,3 +7,9 @@ ssh::sshd_config_denygroups:
- denygroup_from_fqdn - denygroup_from_fqdn
ssh::sshd_config_denyusers: ssh::sshd_config_denyusers:
- denyuser_from_fqdn - denyuser_from_fqdn
ssh::config_entries:
'user_from_fqdn':
owner: 'fqdn_user'
group: 'fqdn_user'
path: '/home/fqdn_user/.ssh/config'
host: 'fqdn_host.example.local'

View File

@ -7,3 +7,9 @@ ssh::sshd_config_denygroups:
- denygroup_from_fact - denygroup_from_fact
ssh::sshd_config_denyusers: ssh::sshd_config_denyusers:
- denyuser_from_fact - denyuser_from_fact
ssh::config_entries:
'user_from_fact':
owner: 'fact_user'
group: 'fact_user'
path: '/home/fact_user/.ssh/config'
host: 'fact_host.example.local'