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:
commit
3b6789ab56
@ -3,6 +3,9 @@ fixtures:
|
||||
stdlib:
|
||||
repo: 'https://github.com/puppetlabs/puppetlabs-stdlib.git'
|
||||
ref: '4.6.0'
|
||||
concat:
|
||||
repo: 'https://github.com/puppetlabs/puppetlabs-concat.git'
|
||||
ref: '2.2.1'
|
||||
common:
|
||||
repo: 'https://github.com/ghoneycutt/puppet-module-common.git'
|
||||
ref: 'v1.4.1'
|
||||
|
53
README.md
53
README.md
@ -8,6 +8,9 @@ ssh_key_ensure and purge_keys.
|
||||
|
||||
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
|
||||
@ -54,8 +57,9 @@ A value of `'USE_DEFAULTS'` will use the defaults specified by the module.
|
||||
|
||||
hiera_merge
|
||||
-----------
|
||||
Boolean to merges all found instances of ssh::keys in Hiera. This is useful for specifying
|
||||
SSH keys at different levels of the hierarchy and having them all included in the catalog.
|
||||
Boolean to merges all found instances of ssh::keys and ssh::config_entries in Hiera.
|
||||
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.
|
||||
|
||||
@ -616,6 +620,24 @@ See `sshd_config(5)` for more details
|
||||
|
||||
- *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
|
||||
----
|
||||
Hash of keys for user's ~/.ssh/authorized_keys
|
||||
@ -852,3 +874,30 @@ ssh::keys:
|
||||
ensure: absent
|
||||
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
36
manifests/config_entry.pp
Normal 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",
|
||||
}
|
||||
}
|
@ -109,6 +109,7 @@ class ssh (
|
||||
$ssh_config_global_known_hosts_group = 'root',
|
||||
$ssh_config_global_known_hosts_mode = '0644',
|
||||
$ssh_config_user_known_hosts_file = undef,
|
||||
$config_entries = {},
|
||||
$keys = undef,
|
||||
$manage_root_ssh_config = false,
|
||||
$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']
|
||||
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 {
|
||||
$sshd_config_allowgroups_real = hiera_array('ssh::sshd_config_allowgroups',[])
|
||||
$sshd_config_allowusers_real = hiera_array('ssh::sshd_config_allowusers',[])
|
||||
$sshd_config_denygroups_real = hiera_array('ssh::sshd_config_denygroups',[])
|
||||
$sshd_config_denyusers_real = hiera_array('ssh::sshd_config_denyusers',[])
|
||||
$config_entries_real = hiera_hash('ssh::config_entries',{})
|
||||
} else {
|
||||
$sshd_config_allowgroups_real = $sshd_config_allowgroups
|
||||
$sshd_config_allowusers_real = $sshd_config_allowusers
|
||||
$sshd_config_denygroups_real = $sshd_config_denygroups
|
||||
$sshd_config_denyusers_real = $sshd_config_denyusers
|
||||
$config_entries_real = $config_entries
|
||||
}
|
||||
validate_hash($config_entries_real)
|
||||
|
||||
if $sshd_config_denyusers_real != [] {
|
||||
validate_array($sshd_config_denyusers_real)
|
||||
@ -973,6 +977,9 @@ class ssh (
|
||||
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
|
||||
if $keys != undef {
|
||||
if $hiera_merge_real == true {
|
||||
|
@ -88,6 +88,7 @@
|
||||
"description": "Manage SSH",
|
||||
"dependencies": [
|
||||
{"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":"puppetlabs/firewall","version_requirement":">= 1.9.0 < 2.0.0"}
|
||||
]
|
||||
|
@ -295,6 +295,8 @@ describe 'ssh' do
|
||||
'purge' => 'true',
|
||||
})
|
||||
}
|
||||
|
||||
it { should have_ssh__config_entry_resource_count(0) }
|
||||
end
|
||||
end
|
||||
|
||||
@ -1345,6 +1347,71 @@ describe 'sshd_config_print_last_log param' do
|
||||
}
|
||||
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
|
||||
let(:params) { { :keys => {
|
||||
'root_for_userX' => {
|
||||
@ -2514,14 +2581,15 @@ describe 'sshd_config_print_last_log param' do
|
||||
end
|
||||
|
||||
describe 'variable type and content validations' do
|
||||
# set needed custom facts and variables
|
||||
let(:mandatory_params) do
|
||||
{
|
||||
#:param => 'value',
|
||||
}
|
||||
end
|
||||
mandatory_params = {} if mandatory_params.nil?
|
||||
|
||||
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)' => {
|
||||
:name => %w(ssh_config_use_roaming),
|
||||
:valid => ['yes', 'no', 'unset'],
|
||||
@ -2543,9 +2611,7 @@ describe 'sshd_config_print_last_log param' do
|
||||
var[:invalid].each do |invalid|
|
||||
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) }
|
||||
it 'should fail' do
|
||||
expect { should contain_class(subject) }.to raise_error(Puppet::Error, /#{var[:message]}/)
|
||||
end
|
||||
it { is_expected.to compile.and_raise_error(/#{var[:message]}/) }
|
||||
end
|
||||
end
|
||||
end # var[:name].each
|
||||
|
83
spec/defines/config_entry_spec.rb
Normal file
83
spec/defines/config_entry_spec.rb
Normal 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
|
@ -7,3 +7,9 @@ ssh::sshd_config_denygroups:
|
||||
- denygroup_from_fqdn
|
||||
ssh::sshd_config_denyusers:
|
||||
- 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'
|
||||
|
@ -7,3 +7,9 @@ ssh::sshd_config_denygroups:
|
||||
- denygroup_from_fact
|
||||
ssh::sshd_config_denyusers:
|
||||
- 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'
|
||||
|
Loading…
x
Reference in New Issue
Block a user