diff --git a/manifests/client.pp b/manifests/client.pp index 5c7c2bd..f700999 100644 --- a/manifests/client.pp +++ b/manifests/client.pp @@ -1,42 +1,64 @@ +# @summary +# This class add ssh client management +# +# @example Puppet usage +# class { 'ssh::client': +# ensure => present, +# storeconfigs_enabled => true, +# use_augeas => false, +# } +# +# @param ensure +# Ensurable param to ssh client +# +# @param storeconfigs_enabled +# Collected host keys from servers will be written to known_hosts unless storeconfigs_enabled is false +# +# @param options +# Dynamic hash for openssh client options +# +# @param options_absent +# Remove options (with augeas style) +# class ssh::client( String $ensure = present, Boolean $storeconfigs_enabled = true, Hash $options = {}, Boolean $use_augeas = false, Array $options_absent = [], ) inherits ssh::params { # Merge hashes from multiple layer of hierarchy in hiera $hiera_options = lookup("${module_name}::client::options", Optional[Hash], 'deep', {}) $fin_options = deep_merge($hiera_options, $options) if $use_augeas { $merged_options = sshclient_options_to_augeas_ssh_config($fin_options, $options_absent, { 'target' => $::ssh::params::ssh_config }) } else { $merged_options = merge($fin_options, delete($ssh::params::ssh_default_options, keys($fin_options))) } include ::ssh::client::install include ::ssh::client::config anchor { 'ssh::client::start': } anchor { 'ssh::client::end': } # Provide option to *not* use storeconfigs/puppetdb, which means not managing # hostkeys and knownhosts if ($storeconfigs_enabled) { include ::ssh::knownhosts Anchor['ssh::client::start'] -> Class['ssh::client::install'] -> Class['ssh::client::config'] -> Class['ssh::knownhosts'] -> Anchor['ssh::client::end'] } else { Anchor['ssh::client::start'] -> Class['ssh::client::install'] -> Class['ssh::client::config'] -> Anchor['ssh::client::end'] } } diff --git a/manifests/hostkeys.pp b/manifests/hostkeys.pp index 5271681..ba35c0c 100644 --- a/manifests/hostkeys.pp +++ b/manifests/hostkeys.pp @@ -1,45 +1,49 @@ -# Class ssh::hostkeys +# @summary +# This class manged hostkeys +# +# @api private +# class ssh::hostkeys( Boolean $export_ipaddresses = true, Optional[String] $storeconfigs_group = undef, Array $extra_aliases = [], Array $exclude_interfaces = [], Array $exclude_ipaddresses = [], ) { if $export_ipaddresses == true { $ipaddresses = ssh::ipaddresses($exclude_interfaces) $ipaddresses_real = $ipaddresses - $exclude_ipaddresses $host_aliases = unique(flatten([ $::fqdn, $::hostname, $extra_aliases, $ipaddresses_real ])) } else { $host_aliases = unique(flatten([ $::fqdn, $::hostname, $extra_aliases])) } if $storeconfigs_group { tag 'hostkey_all', "hostkey_${storeconfigs_group}" } ['dsa', 'rsa', 'ecdsa', 'ed25519'].each |String $key_type| { # can be removed as soon as we drop support for puppet 4 # see https://tickets.puppetlabs.com/browse/FACT-1377?jql=project%20%3D%20FACT%20AND%20fixVersion%20%3D%20%22FACT%203.12.0%22 if $key_type == 'ecdsa' { $key_type_real = 'ecdsa-sha2-nistp256' } else { $key_type_real = $key_type } if $key_type in $facts['ssh'] { @@sshkey { "${::fqdn}_${key_type}": ensure => present, host_aliases => $host_aliases, type => $key_type_real, key => $facts['ssh'][$key_type]['key'], } } else { @@sshkey { "${::fqdn}_${key_type}": ensure => absent, type => $key_type_real, } } } } diff --git a/manifests/init.pp b/manifests/init.pp index 4f3130f..392dddf 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -1,55 +1,153 @@ -# Main file for puppet-ssh +# @summary +# This class managed ssh, client and server +# +# @example Puppet usage +# class { 'ssh': +# storeconfigs_enabled => false, +# server_options => { +# 'Match User www-data' => { +# 'ChrootDirectory' => '%h', +# 'ForceCommand' => 'internal-sftp', +# 'PasswordAuthentication' => 'yes', +# 'AllowTcpForwarding' => 'no', +# 'X11Forwarding' => 'no', +# }, +# 'Port' => [22, 2222, 2288], +# }, +# client_options => { +# 'Host *.amazonaws.com' => { +# 'User' => 'ec2-user', +# }, +# }, +# users_client_options => { +# 'bob' => { +# options => { +# 'Host *.alice.fr' => { +# 'User' => 'alice', +# }, +# }, +# }, +# }, +# } +# +# @example hiera usage +# ssh::storeconfigs_enabled: true +# +# ssh::server_options: +# Protocol: '2' +# ListenAddress: +# - '127.0.0.0' +# - '%{::hostname}' +# PasswordAuthentication: 'yes' +# SyslogFacility: 'AUTHPRIV' +# UsePAM: 'yes' +# X11Forwarding: 'yes' +# +# ssh::server::match_block: +# filetransfer: +# type: group +# options: +# ChrootDirectory: /home/sftp +# ForceCommand: internal-sftp +# +# ssh::client_options: +# 'Host *': +# SendEnv: 'LANG LC_*' +# ForwardX11Trusted: 'yes' +# ServerAliveInterval: '10' +# +# ssh::users_client_options: +# 'bob': +# 'options': +# 'Host *.alice.fr': +# 'User': 'alice' +# 'PasswordAuthentication': 'no' +# +# +# @param server_options +# Add dynamic options for ssh server config +# +# @param server_match_block +# Add match block for ssh server config +# +# @param client_options +# Add dynamic options for ssh client config +# +# @param users_client_options +# Add users options for ssh client config +# +# @param version +# Define package version (pacakge ressource) +# +# @param storeconfigs_enabled +# Default value for storeconfigs_enabled (client and server) +# +# @param validate_sshd_file +# Default value for validate_sshd_file (server) +# +# @param use_augeas +# Default value to use augeas (client and server) +# +# @param server_options_absent +# List of options to remove for server config (augeas only) +# +# @param client_options_absent +# List of options to remove for client config (augeas only) +# +# @param use_issue_net +# Use issue_net header +# class ssh ( Hash $server_options = {}, Hash $server_match_block = {}, Hash $client_options = {}, Hash $users_client_options = {}, String $version = 'present', Boolean $storeconfigs_enabled = true, Boolean $validate_sshd_file = $::ssh::params::validate_sshd_file, Boolean $use_augeas = false, Array $server_options_absent = [], Array $client_options_absent = [], Boolean $use_issue_net = false, Boolean $purge_unmanaged_sshkeys = true, ) inherits ssh::params { # Merge hashes from multiple layer of hierarchy in hiera $hiera_server_options = lookup("${module_name}::server_options", Optional[Hash], 'deep', {}) $hiera_server_match_block = lookup("${module_name}::server_match_block", Optional[Hash], 'deep', {}) $hiera_client_options = lookup("${module_name}::client_options", Optional[Hash], 'deep', {}) $hiera_users_client_options = lookup("${module_name}::users_client_options", Optional[Hash], 'deep', {}) $fin_server_options = deep_merge($hiera_server_options, $server_options) $fin_server_match_block = deep_merge($hiera_server_match_block, $server_match_block) $fin_client_options = deep_merge($hiera_client_options, $client_options) $fin_users_client_options = deep_merge($hiera_users_client_options, $users_client_options) class { '::ssh::server': ensure => $version, storeconfigs_enabled => $storeconfigs_enabled, options => $fin_server_options, validate_sshd_file => $validate_sshd_file, use_augeas => $use_augeas, options_absent => $server_options_absent, use_issue_net => $use_issue_net, } class { '::ssh::client': ensure => $version, storeconfigs_enabled => $storeconfigs_enabled, options => $fin_client_options, use_augeas => $use_augeas, options_absent => $client_options_absent, } # If host keys are being managed, optionally purge unmanaged ones as well. if ($storeconfigs_enabled and $purge_unmanaged_sshkeys) { resources { 'sshkey': purge => true, } } create_resources('::ssh::client::config::user', $fin_users_client_options) create_resources('::ssh::server::match_block', $fin_server_match_block) } diff --git a/manifests/knownhosts.pp b/manifests/knownhosts.pp index 19c9213..cd7cf58 100644 --- a/manifests/knownhosts.pp +++ b/manifests/knownhosts.pp @@ -1,12 +1,21 @@ +# @summary +# This class manged knownhosts if collect is enable +# +# @param collect_enabled +# Enabled collect +# +# @param storeconfigs_group +# Define the hostkeys group storage +# class ssh::knownhosts( Boolean $collect_enabled = $ssh::params::collect_enabled, Optional[String] $storeconfigs_group = undef, ) inherits ssh::params { if ($collect_enabled) { if $storeconfigs_group { Sshkey <<| tag == "hostkey_${storeconfigs_group}" |>> } else { Sshkey <<| |>> } } } diff --git a/manifests/params.pp b/manifests/params.pp index b8d00d9..d6af325 100644 --- a/manifests/params.pp +++ b/manifests/params.pp @@ -1,233 +1,238 @@ +# @summary +# Params class +# +# @api private +# class ssh::params { case $::osfamily { 'Debian': { $server_package_name = 'openssh-server' $client_package_name = 'openssh-client' $sshd_dir = '/etc/ssh' $sshd_config = '/etc/ssh/sshd_config' $ssh_config = '/etc/ssh/ssh_config' $ssh_known_hosts = '/etc/ssh/ssh_known_hosts' $service_name = 'ssh' $sftp_server_path = '/usr/lib/openssh/sftp-server' $host_priv_key_group = 'root' } 'RedHat': { $server_package_name = 'openssh-server' $client_package_name = 'openssh-clients' $sshd_dir = '/etc/ssh' $sshd_config = '/etc/ssh/sshd_config' $ssh_config = '/etc/ssh/ssh_config' $ssh_known_hosts = '/etc/ssh/ssh_known_hosts' $service_name = 'sshd' $sftp_server_path = '/usr/libexec/openssh/sftp-server' if versioncmp($::operatingsystemmajrelease, '7') >= 0 { $host_priv_key_group = 'ssh_keys' } else { $host_priv_key_group = 'root' } } 'FreeBSD', 'DragonFly': { $server_package_name = undef $client_package_name = undef $sshd_dir = '/etc/ssh' $sshd_config = '/etc/ssh/sshd_config' $ssh_config = '/etc/ssh/ssh_config' $ssh_known_hosts = '/etc/ssh/ssh_known_hosts' $service_name = 'sshd' $sftp_server_path = '/usr/libexec/sftp-server' $host_priv_key_group = 'root' } 'OpenBSD': { $server_package_name = undef $client_package_name = undef $sshd_dir = '/etc/ssh' $sshd_config = '/etc/ssh/sshd_config' $ssh_config = '/etc/ssh/ssh_config' $ssh_known_hosts = '/etc/ssh/ssh_known_hosts' $service_name = 'sshd' $sftp_server_path = '/usr/libexec/sftp-server' $host_priv_key_group = 'root' } 'Darwin': { $server_package_name = undef $client_package_name = undef $sshd_dir = '/etc/ssh' $sshd_config = '/etc/ssh/sshd_config' $ssh_config = '/etc/ssh/ssh_config' $ssh_known_hosts = '/etc/ssh/ssh_known_hosts' $service_name = 'com.openssh.sshd' $sftp_server_path = '/usr/libexec/sftp-server' $host_priv_key_group = 'root' } 'ArchLinux': { $server_package_name = 'openssh' $client_package_name = 'openssh' $sshd_dir = '/etc/ssh' $sshd_config = '/etc/ssh/sshd_config' $ssh_config = '/etc/ssh/ssh_config' $ssh_known_hosts = '/etc/ssh/ssh_known_hosts' $service_name = 'sshd.service' $sftp_server_path = '/usr/lib/ssh/sftp-server' $host_priv_key_group = 'root' } 'Suse': { $server_package_name = 'openssh' $client_package_name = 'openssh' $sshd_dir = '/etc/ssh' $sshd_config = '/etc/ssh/sshd_config' $ssh_config = '/etc/ssh/ssh_config' $ssh_known_hosts = '/etc/ssh/ssh_known_hosts' $host_priv_key_group = 'root' case $::operatingsystem { 'SLES': { $service_name = 'sshd' # $::operatingsystemmajrelease isn't available on e.g. SLES 10 case $::operatingsystemrelease { /^10\./, /^11\./: { if ($::architecture == 'x86_64') { $sftp_server_path = '/usr/lib64/ssh/sftp-server' } else { $sftp_server_path = '/usr/lib/ssh/sftp-server' } } default: { $sftp_server_path = '/usr/lib/ssh/sftp-server' } } } 'OpenSuse': { $service_name = 'sshd' $sftp_server_path = '/usr/lib/ssh/sftp-server' } default: { fail("Unsupported platform: ${::osfamily}/${::operatingsystem}") } } } 'Solaris': { case $::operatingsystem { 'SmartOS': { $server_package_name = undef $client_package_name = undef $sshd_dir = '/etc/ssh' $sshd_config = '/etc/ssh/sshd_config' $ssh_config = '/etc/ssh/ssh_config' $ssh_known_hosts = '/etc/ssh/ssh_known_hosts' $service_name = 'svc:/network/ssh:default' $sftp_server_path = 'internal-sftp' $host_priv_key_group = 'root' } default: { $sshd_dir = '/etc/ssh' $sshd_config = '/etc/ssh/sshd_config' $ssh_config = '/etc/ssh/ssh_config' $ssh_known_hosts = '/etc/ssh/ssh_known_hosts' $service_name = 'svc:/network/ssh:default' $sftp_server_path = 'internal-sftp' $host_priv_key_group = 'root' case versioncmp($::kernelrelease, '5.10') { 1: { # Solaris 11 and later $server_package_name = '/service/network/ssh' $client_package_name = '/network/ssh' } 0: { # Solaris 10 $server_package_name = 'SUNWsshdu' $client_package_name = 'SUNWsshu' } default: { # Solaris 9 and earlier not supported fail("Unsupported platform: ${::osfamily}/${::kernelrelease}") } } } } } default: { case $::operatingsystem { 'Gentoo': { $server_package_name = 'openssh' $client_package_name = 'openssh' $sshd_dir = '/etc/ssh' $sshd_config = '/etc/ssh/sshd_config' $ssh_config = '/etc/ssh/ssh_config' $ssh_known_hosts = '/etc/ssh/ssh_known_hosts' $service_name = 'sshd' $sftp_server_path = '/usr/lib/misc/sftp-server' $host_priv_key_group = 'root' } 'Amazon': { $server_package_name = 'openssh-server' $client_package_name = 'openssh-clients' $sshd_dir = '/etc/ssh' $sshd_config = '/etc/ssh/sshd_config' $ssh_config = '/etc/ssh/ssh_config' $ssh_known_hosts = '/etc/ssh/ssh_known_hosts' $service_name = 'sshd' $sftp_server_path = '/usr/libexec/openssh/sftp-server' $host_priv_key_group = 'root' } default: { fail("Unsupported platform: ${::osfamily}/${::operatingsystem}") } } } } # ssh & sshd default options: # - OpenBSD doesn't know about UsePAM # - Sun_SSH doesn't know about UsePAM & AcceptEnv; SendEnv & HashKnownHosts case $::osfamily { 'OpenBSD': { $sshd_default_options = { 'ChallengeResponseAuthentication' => 'no', 'X11Forwarding' => 'yes', 'PrintMotd' => 'no', 'AcceptEnv' => 'LANG LC_*', 'Subsystem' => "sftp ${sftp_server_path}", } $ssh_default_options = { 'Host *' => { 'SendEnv' => 'LANG LC_*', 'HashKnownHosts' => 'yes', }, } } 'Solaris': { $sshd_default_options = { 'ChallengeResponseAuthentication' => 'no', 'X11Forwarding' => 'yes', 'PrintMotd' => 'no', 'Subsystem' => "sftp ${sftp_server_path}", 'HostKey' => [ "${sshd_dir}/ssh_host_rsa_key", "${sshd_dir}/ssh_host_dsa_key", ], } $ssh_default_options = { } } default: { $sshd_default_options = { 'ChallengeResponseAuthentication' => 'no', 'X11Forwarding' => 'yes', 'PrintMotd' => 'no', 'AcceptEnv' => 'LANG LC_*', 'Subsystem' => "sftp ${sftp_server_path}", 'UsePAM' => 'yes', } $ssh_default_options = { 'Host *' => { 'SendEnv' => 'LANG LC_*', 'HashKnownHosts' => 'yes', }, } } } $validate_sshd_file = false $user_ssh_directory_default_mode = '0700' $user_ssh_config_default_mode = '0600' $collect_enabled = true # Collect sshkey resources $issue_net = '/etc/issue.net' } diff --git a/manifests/server.pp b/manifests/server.pp index ed229a5..ba9f502 100644 --- a/manifests/server.pp +++ b/manifests/server.pp @@ -1,54 +1,88 @@ +# @summary +# This class managed ssh server +# +# @example Puppet usage +# class { 'ssh::server': +# ensure => present, +# storeconfigs_enabled => true, +# use_issue_net => false, +# } +# +# @param ensure +# Ensurable param to ssh server +# +# @param storeconfigs_enabled +# Host keys will be collected and distributed unless storeconfigs_enabled is false. +# +# @param options +# Dynamic hash for openssh server option +# +# @param validate_sshd_file +# Add sshd file validate cmd +# +# @param use_augeas +# Use augeas for configuration (default concat) +# +# @param options_absent +# Remove options (with augeas style) +# +# @param match_block +# Add sshd match_block (with concat) +# +# @use_issue_net +# Add issue_net banner +# class ssh::server( String $ensure = present, Boolean $storeconfigs_enabled = true, Hash $options = {}, Boolean $validate_sshd_file = false, Boolean $use_augeas = false, Array $options_absent = [], Hash $match_block = {}, Boolean $use_issue_net = false ) inherits ssh::params { # Merge hashes from multiple layer of hierarchy in hiera $hiera_options = lookup("${module_name}::server::options", Optional[Hash], 'deep', {}) $hiera_match_block = lookup("${module_name}::server::match_block", Optional[Hash], 'deep', {}) $fin_options = deep_merge($hiera_options, $options) $fin_match_block = deep_merge($hiera_match_block, $match_block) if $use_augeas { $merged_options = sshserver_options_to_augeas_sshd_config($fin_options, $options_absent, { 'target' => $::ssh::params::sshd_config }) } else { $merged_options = deep_merge($ssh::params::sshd_default_options, $fin_options) } include ::ssh::server::install include ::ssh::server::config include ::ssh::server::service anchor { 'ssh::server::start': } anchor { 'ssh::server::end': } # Provide option to *not* use storeconfigs/puppetdb, which means not managing # hostkeys and knownhosts if ($storeconfigs_enabled) { include ::ssh::hostkeys include ::ssh::knownhosts Anchor['ssh::server::start'] -> Class['ssh::server::install'] -> Class['ssh::server::config'] ~> Class['ssh::server::service'] -> Class['ssh::hostkeys'] -> Class['ssh::knownhosts'] -> Anchor['ssh::server::end'] } else { Anchor['ssh::server::start'] -> Class['ssh::server::install'] -> Class['ssh::server::config'] ~> Class['ssh::server::service'] -> Anchor['ssh::server::end'] } create_resources('::ssh::server::match_block', $fin_match_block) } diff --git a/manifests/server/config.pp b/manifests/server/config.pp index 1236c2e..97dc5ce 100644 --- a/manifests/server/config.pp +++ b/manifests/server/config.pp @@ -1,48 +1,53 @@ +# @summary +# Managed ssh server configuration +# +# @api private +# class ssh::server::config { $options = $::ssh::server::merged_options case $ssh::server::validate_sshd_file { true: { $sshd_validate_cmd = '/usr/sbin/sshd -tf %' } default: { $sshd_validate_cmd = undef } } if $::ssh::server::use_augeas { create_resources('sshd_config', $options) } else { concat { $ssh::params::sshd_config: ensure => present, owner => 0, group => 0, mode => '0600', validate_cmd => $sshd_validate_cmd, notify => Service[$ssh::params::service_name], } concat::fragment { 'global config': target => $ssh::params::sshd_config, content => template("${module_name}/sshd_config.erb"), order => '00', } } if $::ssh::server::use_issue_net { file { $ssh::params::issue_net: ensure => present, owner => 0, group => 0, mode => '0644', content => template("${module_name}/issue.net.erb"), notify => Service[$ssh::params::service_name], } concat::fragment { 'banner file': target => $ssh::params::sshd_config, content => "Banner ${ssh::params::issue_net}\n", order => '01', } } } diff --git a/manifests/server/config/setting.pp b/manifests/server/config/setting.pp index 9232bb6..a8f0327 100644 --- a/manifests/server/config/setting.pp +++ b/manifests/server/config/setting.pp @@ -1,28 +1,33 @@ +# @summary +# Internal define to managed ssh server param +# +# @api private +# define ssh::server::config::setting ( $key, $value, $order = '10' ) { include ::ssh::params if is_bool($value) { $real_value = $value ? { true => 'yes', false => 'no', default => undef } } elsif is_array($value) { $real_value = join($value, ' ') } elsif is_hash($value) { fail('Hash values are not supported') } else { $real_value = $value } concat::fragment { "ssh_setting_${name}_${key}": target => $ssh::params::sshd_config, content => "\n# added by Ssh::Server::Config::Setting[${name}]\n${key} ${real_value}\n", order => $order, } } diff --git a/manifests/server/install.pp b/manifests/server/install.pp index 973825d..5ab1e83 100644 --- a/manifests/server/install.pp +++ b/manifests/server/install.pp @@ -1,6 +1,14 @@ +# @summary +# Install ssh server package +# +# @api private +# class ssh::server::install { include ::ssh::params if $ssh::params::server_package_name { - ensure_packages([$ssh::params::server_package_name], {'ensure' => $ssh::server::ensure}) + ensure_packages( + [$ssh::params::server_package_name], + { 'ensure' => $ssh::server::ensure } + ) } } diff --git a/manifests/server/match_block.pp b/manifests/server/match_block.pp index c65d2f9..ba484b7 100644 --- a/manifests/server/match_block.pp +++ b/manifests/server/match_block.pp @@ -1,11 +1,20 @@ -define ssh::server::match_block ($options, $type = 'user', $order = 50,) { +# @summary +# Add match_block to ssh server config (concat needed) +# +# @api private +# +define ssh::server::match_block ( + Hash $options = {}, + String $type = 'user', + Integer $order = 50, +) { if $::ssh::server::use_augeas { fail('ssh::server::match_block() define not supported with use_augeas = true') } else { concat::fragment { "match_block ${name}": target => $ssh::params::sshd_config, content => template("${module_name}/sshd_match_block.erb"), order => 200+$order, } } } diff --git a/manifests/server/options.pp b/manifests/server/options.pp index 6246bd9..de451eb 100644 --- a/manifests/server/options.pp +++ b/manifests/server/options.pp @@ -1,7 +1,15 @@ -define ssh::server::options ($options, $order = 50) { +# @summary +# Managed ssh server options +# +# @api private +# +define ssh::server::options ( + Hash $options = {}, + Integer $order = 50 +) { concat::fragment { "options ${name}": target => $ssh::params::sshd_config, content => template("${module_name}/options.erb"), order => 100+$order, } } diff --git a/manifests/server/service.pp b/manifests/server/service.pp index bc278e0..fd104e6 100644 --- a/manifests/server/service.pp +++ b/manifests/server/service.pp @@ -1,15 +1,24 @@ +# @summary +# This class managed ssh server service +# +# @param ensure +# Ensurable service param +# +# @param enable +# Define if service is enable +# class ssh::server::service ( String $ensure = 'running', Boolean $enable = true ){ include ::ssh::params include ::ssh::server service { $ssh::params::service_name: ensure => $ssh::server::service::ensure, hasstatus => true, hasrestart => true, enable => $ssh::server::service::enable, require => Class['ssh::server::config'], } }