diff --git a/Puppetfile b/Puppetfile index fd7e0b61f..858b77c4a 100644 --- a/Puppetfile +++ b/Puppetfile @@ -163,7 +163,7 @@ mod 'sahara', :git => 'https://github.com/frozencemetery/puppet-sahara.git' mod 'ssh', - :commit => 'd6571f8c43ac55d20a6afd8a8ce3f86ac4b0d7a4', + :commit => 'e5cfeae06a16497382072d80c65c901aa0e696ea', :git => 'https://github.com/saz/puppet-ssh.git' mod 'staging', diff --git a/ssh/.fixtures.yml b/ssh/.fixtures.yml new file mode 100644 index 000000000..185f53b7a --- /dev/null +++ b/ssh/.fixtures.yml @@ -0,0 +1,6 @@ +fixtures: + repositories: + stdlib: "https://github.com/puppetlabs/puppetlabs-stdlib" + concat: "https://github.com/puppetlabs/puppetlabs-concat" + symlinks: + ssh: "#{source_dir}" diff --git a/ssh/.gemfile b/ssh/.gemfile new file mode 100644 index 000000000..306014445 --- /dev/null +++ b/ssh/.gemfile @@ -0,0 +1,9 @@ +source 'https://rubygems.org' + +puppetversion = ENV.key?('PUPPET_VERSION') ? "= #{ENV['PUPPET_VERSION']}" : ['>= 3.3'] +gem 'puppet', puppetversion +gem 'puppetlabs_spec_helper', '>= 0.1.0', :require => false +gem 'puppet-lint', '>= 0.3.2' +gem 'facter', '>= 1.7.0', "< 1.8.0" + +# vim:ft=ruby diff --git a/ssh/.gitignore b/ssh/.gitignore index 00769de02..f83f4b238 100644 --- a/ssh/.gitignore +++ b/ssh/.gitignore @@ -1,3 +1,3 @@ pkg/ *.swp -metadata.json +.DS_Store diff --git a/ssh/.travis.yml b/ssh/.travis.yml new file mode 100644 index 000000000..873111c16 --- /dev/null +++ b/ssh/.travis.yml @@ -0,0 +1,38 @@ +--- +branches: + only: + - master +language: ruby +bundler_args: --without development +script: 'bundle exec rake validate && bundle exec rake lint && SPEC_OPTS="--format documentation" bundle exec rake spec' +after_success: + - git clone -q git://github.com/puppetlabs/ghpublisher.git .forge-releng + - .forge-releng/publish +rvm: + - 1.8.7 + - 1.9.3 +env: + matrix: + - PUPPET_GEM_VERSION="~> 2.7.0" + - PUPPET_GEM_VERSION="~> 3.0.0" + - PUPPET_GEM_VERSION="~> 3.1.0" + - PUPPET_GEM_VERSION="~> 3.2.0" + - PUPPET_GEM_VERSION="~> 3.3.0" + - PUPPET_GEM_VERSION="~> 3.4.0" + global: + - PUBLISHER_LOGIN=saz + - secure: |- + bMAcMOMNUgKl7mVDNc47HwT7A8s3SvVRgy4Gu49XbyQ4C/pQ/TCSVlhyvNS7AHAA5BoZcypC + 23f69ykM4qVFGKDEi+oy6rfWXq8WVgyqA9r30Gcg95Plna5fRt/8lmbfBpa+DLRuUYhbzOXg + RuXT20V+nQOHDfp7fuC0EBQxIfM= +matrix: + include: + - rvm: 2.0.0 + env: PUPPET_GEM_VERSION="~> 3.2.0" + - rvm: 2.0.0 + env: PUPPET_GEM_VERSION="~> 3.3.0" + - rvm: 1.8.7 + env: PUPPET_GEM_VERSION="~> 2.6.0" +notifications: + email: false +gemfile: .gemfile diff --git a/ssh/Modulefile b/ssh/Modulefile index 950c7f721..c74c4147a 100644 --- a/ssh/Modulefile +++ b/ssh/Modulefile @@ -1,8 +1,11 @@ name 'saz-ssh' -version '1.0.3' +version '2.4.0' source 'git://github.com/saz/puppet-ssh.git' author 'saz' license 'Apache License, Version 2.0' summary 'UNKNOWN' description 'Manage SSH client and server via puppet' project_page 'https://github.com/saz/puppet-ssh' + +## Add dependencies, if any: +dependency 'puppetlabs/stdlib', '>= 2.2.1' diff --git a/ssh/README.markdown b/ssh/README.markdown index 73ee86acd..c4dfc97e8 100644 --- a/ssh/README.markdown +++ b/ssh/README.markdown @@ -1,28 +1,223 @@ -# SSH Client and Server Puppet Module +# puppet-ssh [![Build Status](https://secure.travis-ci.org/saz/puppet-ssh.png)](http://travis-ci.org/saz/puppet-ssh) Manage SSH client and server via Puppet -## Client only -Collected host keys from servers will be written to known_hosts +### Gittip +[![Support via Gittip](https://rawgithub.com/twolfson/gittip-badge/0.2.0/dist/gittip.png)](https://www.gittip.com/saz/) + +## Requirements +* Exported resources for host keys management +* puppetlabs/stdlib + +## Usage + +Since version 2.0.0 only non-default values are written to both, +client and server, configuration files. + +Multiple occurrences of one config key (e.g. sshd should be listening on +port 22 and 2222) should be passed as an array. + +``` + options => { + 'Port' => [22, 2222], + } +``` + +This is working for both, client and server. + +### Both client and server +Host keys will be collected and distributed unless + `storeconfigs_enabled` is `false`. + +``` + include ssh +``` + +or + +``` + 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', + }, + }, + } +``` + +### Hiera example +``` +ssh::storeconfigs_enabled: true, + +ssh::server_options: + Protocol: '2' + ListenAddress: + - '127.0.0.0' + - '%{::hostname}' + PasswordAuthentication: 'yes' + SyslogFacility: 'AUTHPRIV' + UsePAM: 'yes' + X11Forwarding: 'yes' + +ssh::client_options: + 'Host *': + SendEnv: 'LANG LC_*' + ForwardX11Trusted: 'yes' + ServerAliveInterval: '10' +``` + +### Client only +Collected host keys from servers will be written to `known_hosts` unless + `storeconfigs_enabled` is `false` ``` include ssh::client ``` -## Server only -Host keys will be collected for client distribution +or + +``` + class { 'ssh::client': + storeconfigs_enabled => false, + options => { + 'Host short' => { + 'User' => 'my-user', + 'HostName' => 'extreme.long.and.complicated.hostname.domain.tld', + }, + 'Host *' => { + 'User' => 'andromeda', + 'UserKnownHostsFile' => '/dev/null', + }, + }, + } +``` + +### Server only +Host keys will be collected for client distribution unless + `storeconfigs_enabled` is `false` ``` include ssh::server ``` -## Both client and server -Host keys will be collected and distributed +or ``` - include ssh + class { 'ssh::server': + storeconfigs_enabled => false, + options => { + 'Match User www-data' => { + 'ChrootDirectory' => '%h', + 'ForceCommand' => 'internal-sftp', + 'PasswordAuthentication' => 'yes', + 'AllowTcpForwarding' => 'no', + 'X11Forwarding' => 'no', + }, + 'PasswordAuthentication' => 'no', + 'PermitRootLogin' => 'no', + 'Port' => [22, 2222], + }, + } ``` + +## Default options -# Requirements -Requires Exported resources and augeas in order to work +### Client +``` + 'Host *' => { + 'SendEnv' => 'LANG LC_*', + 'HashKnownHosts' => 'yes', + 'GSSAPIAuthentication' => 'yes', + } +``` + +### Server + +``` + 'ChallengeResponseAuthentication' => 'no', + 'X11Forwarding' => 'yes', + 'PrintMotd' => 'no', + 'AcceptEnv' => 'LANG LC_*', + 'Subsystem' => 'sftp /usr/lib/openssh/sftp-server', + 'UsePAM' => 'yes', +``` + +## Overwriting default options +Default options will be merged with options passed in. +If an option is set both as default and via options parameter, the latter will +will win. + +The following example will disable X11Forwarding, which is enabled by default: + +``` + class { 'ssh::server': + options => { + 'X11Forwarding' => 'no', + }, + } +``` + +Which will lead to the following `sshd_config` file: + + ``` +# File is managed by Puppet + +ChallengeResponseAuthentication no +X11Forwarding no +PrintMotd no +AcceptEnv LANG LC_* +Subsystem sftp /usr/lib/openssh/sftp-server +UsePAM yes +PasswordAuthentication no +``` + +## Defining host keys for server +You can define host keys your server will use + +``` +ssh::server::host_key {'ssh_host_rsa_key': + private_key_content => '', + public_key_content => '', +} +``` + +Alternately, you could create the host key providing the files, instead +of the content: + +``` +ssh::server::host_key {'ssh_host_rsa_key': + private_key_source => 'puppet:///mymodule/ssh_host_rsa_key', + public_key_source => 'puppet:///mymodule/ssh_host_rsa_key.pub', +} +``` + +Both of these definitions will create ```/etc/ssh/ssh_host_rsa_key``` and +```/etc/ssh/ssh_host_rsa_key.pub``` and restart sshd daemon. + + +## Adding cutom match blocks + +``` + ssh::server::match_block { 'sftp_only': + type => 'User', + options => { + 'ChrootDirectory' => "/sftp/%u", + 'ForceCommand' => 'internal-sftp', + 'PasswordAuthentication' => 'no', + 'AllowTcpForwarding' => 'no', + 'X11Forwarding' => 'no', + } + } +``` diff --git a/ssh/Rakefile b/ssh/Rakefile new file mode 100644 index 000000000..0a28d845e --- /dev/null +++ b/ssh/Rakefile @@ -0,0 +1,18 @@ +require 'rubygems' +require 'puppetlabs_spec_helper/rake_tasks' +require 'puppet-lint/tasks/puppet-lint' +PuppetLint.configuration.send('disable_80chars') +PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp"] + +desc "Run puppet in noop mode and check for syntax errors." +task :validate do + Dir['manifests/**/*.pp'].each do |manifest| + sh "puppet parser validate --noop #{manifest}" + end + Dir['spec/**/*.rb','lib/**/*.rb'].each do |ruby_file| + sh "ruby -c #{ruby_file}" unless ruby_file =~ /spec\/fixtures/ + end + Dir['templates/**/*.erb'].each do |template| + sh "erb -P -x -T '-' #{template} | ruby -c" + end +end diff --git a/ssh/files/ssh_config b/ssh/files/ssh_config deleted file mode 100644 index 43b298e11..000000000 --- a/ssh/files/ssh_config +++ /dev/null @@ -1,54 +0,0 @@ - -# This is the ssh client system-wide configuration file. See -# ssh_config(5) for more information. This file provides defaults for -# users, and the values can be changed in per-user configuration files -# or on the command line. - -# Configuration data is parsed as follows: -# 1. command line options -# 2. user-specific file -# 3. system-wide file -# Any configuration value is only changed the first time it is set. -# Thus, host-specific definitions should be at the beginning of the -# configuration file, and defaults at the end. - -# Site-wide defaults for some commonly used options. For a comprehensive -# list of available options, their meanings and defaults, please see the -# ssh_config(5) man page. - -Host * -# ForwardAgent no -# ForwardX11 no -# ForwardX11Trusted yes -# RhostsRSAAuthentication no -# RSAAuthentication yes -# PasswordAuthentication yes -# HostbasedAuthentication no -# GSSAPIAuthentication no -# GSSAPIDelegateCredentials no -# GSSAPIKeyExchange no -# GSSAPITrustDNS no -# BatchMode no -# CheckHostIP yes -# AddressFamily any -# ConnectTimeout 0 -# StrictHostKeyChecking ask -# IdentityFile ~/.ssh/identity -# IdentityFile ~/.ssh/id_rsa -# IdentityFile ~/.ssh/id_dsa -# Port 22 -# Protocol 2,1 -# Cipher 3des -# Ciphers aes128-ctr,aes192-ctr,aes256-ctr,arcfour256,arcfour128,aes128-cbc,3des-cbc -# MACs hmac-md5,hmac-sha1,umac-64@openssh.com,hmac-ripemd160 -# EscapeChar ~ -# Tunnel no -# TunnelDevice any:any -# PermitLocalCommand no -# VisualHostKey no -# ProxyCommand ssh -q -W %h:%p gateway.example.com - SendEnv LANG LC_* - HashKnownHosts yes - GSSAPIAuthentication yes - GSSAPIDelegateCredentials no - UserKnownHostsFile /dev/null diff --git a/ssh/lib/puppet/parser/functions/ipaddresses.rb b/ssh/lib/puppet/parser/functions/ipaddresses.rb index 98a29a3c6..8190d4a5b 100644 --- a/ssh/lib/puppet/parser/functions/ipaddresses.rb +++ b/ssh/lib/puppet/parser/functions/ipaddresses.rb @@ -5,7 +5,10 @@ module Puppet::Parser::Functions ) do |args| interfaces = lookupvar('interfaces') - return false if (interfaces == :undefined) + # In Puppet v2.7, lookupvar returns :undefined if the variable does + # not exist. In Puppet 3.x, it returns nil. + # See http://docs.puppetlabs.com/guides/custom_functions.html + return false if (interfaces.nil? || interfaces == :undefined) result = [] if interfaces.count(',') > 0 @@ -14,10 +17,10 @@ module Puppet::Parser::Functions if ! iface.include?('lo') ipaddr = lookupvar("ipaddress_#{iface}") ipaddr6 = lookupvar("ipaddress6_#{iface}") - if ipaddr + if ipaddr and (ipaddr!= :undefined) result << ipaddr end - if ipaddr6 + if ipaddr6 and (ipaddr6!= :undefined) result << ipaddr6 end end @@ -26,10 +29,10 @@ module Puppet::Parser::Functions if ! interfaces.include?('lo') ipaddr = lookupvar("ipaddress_#{interfaces}") ipaddr6 = lookupvar("ipaddress6_#{interfaces}") - if ipaddr + if ipaddr and (ipaddr!= :undefined) result << ipaddr end - if ipaddr6 + if ipaddr6 and (ipaddr6!= :undefined) result << ipaddr6 end end diff --git a/ssh/manifests/client.pp b/ssh/manifests/client.pp index 8cd21e268..7acb81793 100644 --- a/ssh/manifests/client.pp +++ b/ssh/manifests/client.pp @@ -1,6 +1,30 @@ -class ssh::client { - include ssh::params +class ssh::client( + $ensure = present, + $storeconfigs_enabled = true, + $options = {} +) inherits ssh::params { + $merged_options = merge($ssh::params::ssh_default_options, $options) + include ssh::client::install include ssh::client::config - include ssh::knownhosts + + 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/ssh/manifests/client/config.pp b/ssh/manifests/client/config.pp index b48553df7..1c9d45333 100644 --- a/ssh/manifests/client/config.pp +++ b/ssh/manifests/client/config.pp @@ -1,14 +1,15 @@ class ssh::client::config { file { $ssh::params::ssh_config: ensure => present, - owner => 'root', - group => 'root', - source => "puppet:///modules/${module_name}/ssh_config", + owner => 0, + group => 0, + content => template("${module_name}/ssh_config.erb"), require => Class['ssh::client::install'], } # Workaround for http://projects.reductivelabs.com/issues/2014 file { $ssh::params::ssh_known_hosts: - mode => '0644', + ensure => present, + mode => '0644', } } diff --git a/ssh/manifests/client/install.pp b/ssh/manifests/client/install.pp index d167ee827..86771d770 100644 --- a/ssh/manifests/client/install.pp +++ b/ssh/manifests/client/install.pp @@ -1,5 +1,9 @@ class ssh::client::install { - package { $ssh::params::client_package_name: - ensure => latest, + if $ssh::params::client_package_name { + if !defined(Package[$ssh::params::client_package_name]) { + package { $ssh::params::client_package_name: + ensure => $ssh::client::ensure, + } + } } } diff --git a/ssh/manifests/hostkeys.pp b/ssh/manifests/hostkeys.pp index 68e039438..f7bbbb88d 100644 --- a/ssh/manifests/hostkeys.pp +++ b/ssh/manifests/hostkeys.pp @@ -1,14 +1,26 @@ class ssh::hostkeys { - $host_aliases = [ $::fqdn, $::hostname, $::ipaddress ] + $ipaddresses = ipaddresses() + $host_aliases = flatten([ $::fqdn, $::hostname, $ipaddresses ]) - @@sshkey { "${::fqdn}_dsa": - host_aliases => $host_aliases, - type => dsa, - key => $::sshdsakey, + if $::sshdsakey { + @@sshkey { "${::fqdn}_dsa": + host_aliases => $host_aliases, + type => dsa, + key => $::sshdsakey, + } } - @@sshkey { "${::fqdn}_rsa": - host_aliases => $host_aliases, - type => rsa, - key => $::sshrsakey, + if $::sshrsakey { + @@sshkey { "${::fqdn}_rsa": + host_aliases => $host_aliases, + type => rsa, + key => $::sshrsakey, + } + } + if $::sshecdsakey { + @@sshkey { "${::fqdn}_ecdsa": + host_aliases => $host_aliases, + type => 'ecdsa-sha2-nistp256', + key => $::sshecdsakey, + } } } diff --git a/ssh/manifests/init.pp b/ssh/manifests/init.pp index 5c9b1d653..4f913556c 100644 --- a/ssh/manifests/init.pp +++ b/ssh/manifests/init.pp @@ -1,4 +1,15 @@ -class ssh { - include ssh::server - include ssh::client +class ssh ( + $server_options = {}, + $client_options = {}, + $storeconfigs_enabled = true +) inherits ssh::params { + class { 'ssh::server': + storeconfigs_enabled => $storeconfigs_enabled, + options => $server_options, + } + + class { 'ssh::client': + storeconfigs_enabled => $storeconfigs_enabled, + options => $client_options, + } } diff --git a/ssh/manifests/params.pp b/ssh/manifests/params.pp index 52181c231..332d940a4 100644 --- a/ssh/manifests/params.pp +++ b/ssh/manifests/params.pp @@ -3,25 +3,96 @@ 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' } 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' + } + freebsd: { + $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/lib/openssh/sftp-server' + } + 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' + } + 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' + case $::operatingsystem { + Sles: { + $service_name = 'sshd' + $sftp_server_path = '/usr/lib64/ssh/sftp-server' + } + Suse: { + $service_name = 'sshd.service' + $sftp_server_path = '/usr/lib/ssh/sftp-server' + } + default: { + fail("Unsupported platform: ${::osfamily}/${::operatingsystem}") + } + } } 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' + } default: { fail("Unsupported platform: ${::osfamily}/${::operatingsystem}") } } } } + + $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', + }, + } } diff --git a/ssh/manifests/server.pp b/ssh/manifests/server.pp index f09a839f3..67ce915ae 100644 --- a/ssh/manifests/server.pp +++ b/ssh/manifests/server.pp @@ -1,8 +1,37 @@ -class ssh::server { - include ssh::params +class ssh::server( + $ensure = present, + $storeconfigs_enabled = true, + $options = {} +) inherits ssh::params { + $merged_options = merge($ssh::params::sshd_default_options, $options) + include ssh::server::install include ssh::server::config include ssh::server::service - include ssh::hostkeys - include ssh::knownhosts + + File[$ssh::params::sshd_config] ~> Service[$ssh::params::service_name] + + 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'] + } } diff --git a/ssh/manifests/server/config.pp b/ssh/manifests/server/config.pp index 48be6f317..c56944d09 100644 --- a/ssh/manifests/server/config.pp +++ b/ssh/manifests/server/config.pp @@ -1,12 +1,16 @@ class ssh::server::config { - file { $ssh::params::sshd_config: - ensure => present, - owner => 'root', - group => 'root', - mode => '0600', - replace => false, - source => "puppet:///modules/${module_name}/sshd_config", - require => Class['ssh::server::install'], - notify => Class['ssh::server::service'], + File[$ssh::params::sshd_config] ~> Service[$ssh::params::service_name] + + concat { $ssh::params::sshd_config: + ensure => present, + owner => 0, + group => 0, + mode => '0600', + } + + concat::fragment { 'global config': + target => $ssh::params::sshd_config, + content => template("${module_name}/sshd_config.erb"), + order => '00' } } diff --git a/ssh/manifests/server/configline.pp b/ssh/manifests/server/configline.pp deleted file mode 100644 index 8f8e225d6..000000000 --- a/ssh/manifests/server/configline.pp +++ /dev/null @@ -1,39 +0,0 @@ -define ssh::server::configline ( - $ensure = present, - $value = false -) { - include ssh::server - - Augeas { - context => "/files${ssh::params::sshd_config}", - notify => Class['ssh::server::service'], - require => Class['ssh::server::config'], - } - - case $ensure { - present: { - augeas { "sshd_config_${name}": - changes => "set ${name} ${value}", - onlyif => "get ${name} != ${value}", - } - } - add: { - augeas { "sshd_config_${name}": - onlyif => "get ${name}[. = '${value}'] != ${value}", - changes => [ - "ins ${name} after ${name}[last()]", - "set ${name}[last()] ${value}" - ], - } - } - absent: { - augeas { "sshd_config_${name}": - changes => "rm ${name}", - onlyif => "get ${name}", - } - } - default: { - fail("ensure value must be present, add or absent, not ${ensure}") - } - } -} diff --git a/ssh/manifests/server/host_key.pp b/ssh/manifests/server/host_key.pp new file mode 100644 index 000000000..a89606842 --- /dev/null +++ b/ssh/manifests/server/host_key.pp @@ -0,0 +1,84 @@ +# == Define: ssh::server::host_key +# +# This module install a ssh host key in the server (basically, it is +# a file resource but it also notifies to the ssh service) +# +# Important! This define does not modify any option in sshd_config, so +# you have to manually define the HostKey option in the server options +# if you haven't done yet. +# +# == Parameters +# +# [*ensure*] +# Set to 'absent' to remove host_key files +# +# [*public_key_source*] +# Sets the content of the source parameter for the public key file +# Note public_key_source and public_key_content are mutually exclusive. +# +# [*public_key_content*] +# Sets the content for the public key file. +# Note public_key_source and public_key_content are mutually exclusive. +# +# [*private_key_source*] +# Sets the content of the source parameter for the private key file +# Note private_key_source and private_key_content are mutually exclusive. +# +# [*private_key_content*] +# Sets the content for the private key file. +# Note private_key_source and private_key_content are mutually exclusive. +# +define ssh::server::host_key ( + $ensure = 'present', + $public_key_source = '', + $public_key_content = '', + $private_key_source = '', + $private_key_content = '', +) { + if $public_key_source == '' and $public_key_content == '' { + fail('You must provide either public_key_source or public_key_content parameter') + } + if $private_key_source == '' and $private_key_content == '' { + fail('You must provide either private_key_source or private_key_content parameter') + } + + $manage_pub_key_content = $public_key_source ? { + '' => $public_key_content, + default => undef, + } + $manage_pub_key_source = $public_key_source ? { + '' => undef, + default => $public_key_source, + } + + $manage_priv_key_content = $private_key_source ? { + '' => $private_key_content, + default => undef, + } + $manage_priv_key_source = $private_key_source ? { + '' => undef, + default => $private_key_source, + } + + file {"${name}_pub": + ensure => $ensure, + owner => 'root', + group => 'root', + mode => '0644', + path => "${::ssh::params::sshd_dir}/${name}.pub", + source => $manage_pub_key_source, + content => $manage_pub_key_content, + notify => Class['ssh::server::service'], + } + + file {"${name}_priv": + ensure => $ensure, + owner => 'root', + group => 'root', + mode => '0600', + path => "${::ssh::params::sshd_dir}/${name}", + source => $manage_priv_key_source, + content => $manage_priv_key_content, + notify => Class['ssh::server::service'], + } +} diff --git a/ssh/manifests/server/install.pp b/ssh/manifests/server/install.pp index bd0400edd..58b5ca1d3 100644 --- a/ssh/manifests/server/install.pp +++ b/ssh/manifests/server/install.pp @@ -1,6 +1,10 @@ class ssh::server::install { include ssh::params - package { $ssh::params::server_package_name: - ensure => present, + if $ssh::params::server_package_name { + if !defined(Package[$ssh::params::server_package_name]) { + package { $ssh::params::server_package_name: + ensure => $ssh::server::ensure, + } + } } } diff --git a/ssh/manifests/server/match_block.pp b/ssh/manifests/server/match_block.pp new file mode 100644 index 000000000..ddb06ff30 --- /dev/null +++ b/ssh/manifests/server/match_block.pp @@ -0,0 +1,7 @@ +define ssh::server::match_block ($type = 'user', $order = 50, $options,) { + concat::fragment { "match_block ${name}": + target => $ssh::params::sshd_config, + content => template("${module_name}/sshd_match_block.erb"), + order => $order, + } +} diff --git a/ssh/manifests/site.pp b/ssh/manifests/site.pp new file mode 100644 index 000000000..e69de29bb diff --git a/ssh/metadata.json b/ssh/metadata.json new file mode 100644 index 000000000..08d41906b --- /dev/null +++ b/ssh/metadata.json @@ -0,0 +1,59 @@ +{ + "operatingsystem_support": [ + { + "operatingsystem": "RedHat" + }, + { + "operatingsystem": "CentOS" + }, + { + "operatingsystem": "OracleLinux" + }, + { + "operatingsystem": "Scientific" + }, + { + "operatingsystem": "Debian" + }, + { + "operatingsystem": "Ubuntu" + }, + { + "operatingsystem": "FreeBSD" + }, + { + "operatingsystem": "Gentoo" + }, + { + "operatingsystem": "ArchLinux" + } + ], + "requirements": [ + { + "name": "pe", + "version_requirement": "3.2.x" + }, + { + "name": "puppet", + "version_requirement": "3.x" + } + ], + "name": "saz-ssh", + "version": "2.4.0", + "source": "git://github.com/saz/puppet-ssh.git", + "author": "saz", + "license": "Apache License, Version 2.0", + "summary": "UNKNOWN", + "description": "Manage SSH client and server via puppet", + "project_page": "https://github.com/saz/puppet-ssh", + "dependencies": [ + { + "name": "puppetlabs/stdlib", + "version_requirement": ">= 2.2.1" + }, + { + "name": "puppetlabs/concat", + "version_requirement": ">= 1.0.0" + } + ] +} diff --git a/ssh/spec/classes/client_spec.rb b/ssh/spec/classes/client_spec.rb new file mode 100644 index 000000000..c47ca22d3 --- /dev/null +++ b/ssh/spec/classes/client_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe 'ssh::client', :type => 'class' do + context "On Debian with no other parameters" do + let :facts do + { + :osfamily => 'Debian', + :interfaces => 'eth0', + :ipaddress_eth0 => '192.168.1.1', + :concat_basedir => '/tmp' + } + end + it { + should contain_package('openssh-client').with(:ensure => 'present') + } + end + context "On Debian with custom ensure" do + let :facts do + { + :osfamily => 'Debian', + :interfaces => 'eth0', + :ipaddress_eth0 => '192.168.1.1', + :concat_basedir => '/tmp' + } + end + let :params do + { + :ensure => 'latest' + } + end + it { + should contain_package('openssh-client').with(:ensure => 'latest') + } + end +end diff --git a/ssh/spec/classes/server_spec.rb b/ssh/spec/classes/server_spec.rb new file mode 100644 index 000000000..bdfd04dc9 --- /dev/null +++ b/ssh/spec/classes/server_spec.rb @@ -0,0 +1,124 @@ +require 'spec_helper' +describe 'ssh::server' do + let :default_params do + { + :ensure => 'present', + :storeconfigs_enabled => true, + :options => {} + } + end + + [ {}, + { + :ensure => 'latest', + :storeconfigs_enabled => true, + :options => {} + }, + { + :ensure => 'present', + :storeconfigs_enabled => false, + :options => {} + } + ].each do |param_set| + describe "when #{param_set == {} ? "using default" : "specifying"} class parameters" do + let :param_hash do + default_params.merge(param_set) + end + + let :params do + param_set + end + + ['Debian'].each do |osfamily| + let :facts do + { + :osfamily => osfamily, + :interfaces => 'eth0', + :ipaddress_eth0 => '192.168.1.1', + :concat_basedir => '/tmp' + } + end + + describe "on supported osfamily: #{osfamily}" do + it { should contain_class('ssh::params') } + it { should contain_package('openssh-server').with_ensure(param_hash[:ensure]) } + + it { should contain_file('/etc/ssh/sshd_config').with( + 'owner' => 0, + 'group' => 0 + )} + + it { should contain_service('ssh').with( + 'ensure' => 'running', + 'enable' => true, + 'hasrestart' => true, + 'hasstatus' => true + )} + + it { should contain_class('concat::setup') } + it { should contain_concat('/etc/ssh/sshd_config') } + it { should contain_concat__fragment('global config').with( + :target => '/etc/ssh/sshd_config', + :content => '# File is managed by Puppet + +AcceptEnv LANG LC_* +ChallengeResponseAuthentication no +PrintMotd no +Subsystem sftp /usr/lib/openssh/sftp-server +UsePAM yes +X11Forwarding yes +' + )} + + end + describe "on Arch" do + let :facts do + { + :osfamily => 'Archlinux', + :lsbdistdescription => 'Arch Linux', + :lsbdistid => 'Arch', + :operatingsystem => 'Archlinux', + :interfaces => 'enp4s0', + :ipaddress_eth0 => '192.168.1.1', + :concat_basedir => '/tmp' + } + end + + it { should contain_class('ssh::params') } + it { should contain_package('openssh').with( + :ensure => param_hash[:ensure], + :name => 'openssh' + )} + + it { should contain_file('/etc/ssh/sshd_config').with( + 'owner' => 0, + 'group' => 0 + )} + + it { should contain_service('sshd.service').with( + 'ensure' => 'running', + 'enable' => true, + 'hasrestart' => true, + 'hasstatus' => true + )} + + it { should contain_class('concat::setup') } + it { should contain_concat('/etc/ssh/sshd_config') } + it { should contain_concat__fragment('global config').with( + :target => '/etc/ssh/sshd_config', + :content => '# File is managed by Puppet + +AcceptEnv LANG LC_* +ChallengeResponseAuthentication no +PrintMotd no +Subsystem sftp /usr/lib/ssh/sftp-server +UsePAM yes +X11Forwarding yes +' + )} + + end + end + end + end +end diff --git a/ssh/spec/fixtures/.gitignore b/ssh/spec/fixtures/.gitignore new file mode 100644 index 000000000..0616a13e5 --- /dev/null +++ b/ssh/spec/fixtures/.gitignore @@ -0,0 +1,5 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore +!site.pp diff --git a/ssh/spec/spec_helper.rb b/ssh/spec/spec_helper.rb index a4aeeae23..6e1d96819 100644 --- a/ssh/spec/spec_helper.rb +++ b/ssh/spec/spec_helper.rb @@ -1,18 +1,2 @@ -require 'pathname' -dir = Pathname.new(__FILE__).parent -$LOAD_PATH.unshift(dir, dir + 'lib', dir + '../lib') - -require 'mocha' -require 'puppet' -gem 'rspec', '=1.2.9' -require 'spec/autorun' - -Spec::Runner.configure do |config| - config.mock_with :mocha -end - -# We need this because the RAL uses 'should' as a method. This -# allows us the same behaviour but with a different method name. -class Object - alias :must :should -end +require 'rspec-puppet' +require 'puppetlabs_spec_helper/module_spec_helper' diff --git a/ssh/templates/ssh_config.erb b/ssh/templates/ssh_config.erb new file mode 100644 index 000000000..642e89a29 --- /dev/null +++ b/ssh/templates/ssh_config.erb @@ -0,0 +1,28 @@ +# File managed by Puppet + +<%- scope.lookupvar('ssh::client::merged_options').sort.each do |k, v| -%> +<%- if v.is_a?(Hash) -%> +<%= k %> +<%- v.sort.each do |key, value| -%> + <%- if value.is_a?(Array) -%> + <%- value.each do |a| -%> + <%- if a != '' -%> + <%= key %> <%= a %> + <%- end -%> + <%- end -%> + <%- elsif value != '' -%> + <%= key %> <%= value %> + <%- end -%> +<%- end -%> +<%- else -%> +<%- if v.is_a?(Array) -%> +<%- v.each do |a| -%> +<%- if a != '' -%> +<%= k %> <%= a %> +<%- end -%> +<%- end -%> +<%- elsif v != :undef and v != '' -%> +<%= k %> <%= v %> +<%- end -%> +<%- end -%> +<%- end -%> diff --git a/ssh/templates/sshd_config.erb b/ssh/templates/sshd_config.erb new file mode 100644 index 000000000..5f755a4ea --- /dev/null +++ b/ssh/templates/sshd_config.erb @@ -0,0 +1,52 @@ +# File is managed by Puppet +<%- options = scope.lookupvar('ssh::server::merged_options') -%> +<%- if addressfamily = options.delete('AddressFamily') -%> +AddressFamily <%= addressfamily %> +<%- end -%> +<%- if port = options.delete('Port') -%> +<%- if port.is_a?(Array) -%> +<%- port.each do |p| -%> +Port <%= p %> +<%- end -%> +<%- else -%> +Port <%= port %> +<%- end -%> +<%- end -%> +<%- if listen = options.delete('ListenAddress') -%> +<%- if listen.is_a?(Array) -%> +<%- listen.each do |l| -%> +ListenAddress <%= l %> +<%- end -%> +<%- else -%> +ListenAddress <%= listen %> +<%- end -%> +<%- end -%> + +<%- options.keys.sort_by{ |sk| (sk.to_s.downcase.include? "match") ? 'zzz' + sk.to_s : sk.to_s }.each do |k| -%> +<%- v = options[k] -%> +<%- if v.is_a?(Hash) -%> +<%= k %> +<%- v.keys.sort.each do |key| -%> + <%- value = v[key] -%> + <%- if value.is_a?(Array) -%> + <%- value.each do |a| -%> + <%- if a != '' -%> + <%= key %> <%= a %> + <%- end -%> + <%- end -%> + <%- elsif value != '' -%> + <%= key %> <%= value %> + <%- end -%> +<%- end -%> +<%- else -%> +<%- if v.is_a?(Array) -%> +<%- v.each do |a| -%> +<%- if a != '' -%> +<%= k %> <%= a %> +<%- end -%> +<%- end -%> +<%- elsif v != :undef and v != '' -%> +<%= k %> <%= v %> +<%- end -%> +<%- end -%> +<%- end -%> diff --git a/ssh/templates/sshd_match_block.erb b/ssh/templates/sshd_match_block.erb new file mode 100644 index 000000000..149af6254 --- /dev/null +++ b/ssh/templates/sshd_match_block.erb @@ -0,0 +1,8 @@ + +Match <%= @type %> <%= @name %> +<%- @options.keys.each do |k| -%> +<%- v = @options[k] -%> +<%- if v != :undef -%> + <%= k %> <%= v %> +<%- end -%> +<%- end -%> diff --git a/ssh/tests/init.pp b/ssh/tests/init.pp index 13a463808..6687c2c70 100644 --- a/ssh/tests/init.pp +++ b/ssh/tests/init.pp @@ -1 +1 @@ -include ssh +class { '::ssh::server': } diff --git a/ssh/tests/server.pp b/ssh/tests/server.pp new file mode 100644 index 000000000..112640ec0 --- /dev/null +++ b/ssh/tests/server.pp @@ -0,0 +1 @@ +include ssh::server