├── .editorconfig ├── .fixtures.yml ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── labeler.yml ├── release.yml └── workflows │ ├── ci.yml │ ├── labeler.yml │ ├── prepare_release.yml │ └── release.yml ├── .gitignore ├── .msync.yml ├── .overcommit.yml ├── .pmtignore ├── .puppet-lint.rc ├── .rubocop.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── REFERENCE.md ├── Rakefile ├── data ├── AIX.yaml ├── Amazon.yaml ├── Archlinux.yaml ├── Darwin.yaml ├── Debian.yaml ├── DragonFly.yaml ├── FreeBSD.yaml ├── Gentoo.yaml ├── OpenBSD.yaml ├── OpenSuSE.yaml ├── RedHat-9.yaml ├── RedHat.yaml ├── SLES-10-x86_64.yaml ├── SLES-11-x86_64.yaml ├── SLES.yaml ├── SmartOS.yaml ├── Solaris-10.yaml ├── Solaris.yaml ├── Suse.yaml └── common.yaml ├── hiera.yaml ├── lib ├── facter │ ├── ssh_client_version.rb │ └── ssh_server_version.rb └── puppet │ ├── functions │ └── ssh │ │ └── ipaddresses.rb │ └── parser │ └── functions │ ├── sshclient_options_to_augeas_ssh_config.rb │ └── sshserver_options_to_augeas_sshd_config.rb ├── manifests ├── client.pp ├── client │ ├── config.pp │ ├── config │ │ └── user.pp │ ├── install.pp │ └── match_block.pp ├── hostkeys.pp ├── init.pp ├── knownhosts.pp ├── server.pp └── server │ ├── config.pp │ ├── config │ └── setting.pp │ ├── config_file.pp │ ├── host_key.pp │ ├── install.pp │ ├── instances.pp │ ├── match_block.pp │ ├── options.pp │ └── service.pp ├── metadata.json ├── spec ├── acceptance │ ├── client_spec.rb │ └── init_spec.rb ├── classes │ ├── client_spec.rb │ ├── hostkeys_spec.rb │ ├── init_spec.rb │ └── server_spec.rb ├── defines │ ├── client │ │ ├── config │ │ │ └── user_spec.rb │ │ └── match_block_spec.rb │ └── server │ │ ├── config │ │ └── setting_spec.rb │ │ ├── host_key_spec.rb │ │ ├── instances_spec.rb │ │ └── match_block_spec.rb ├── fixtures │ ├── .gitignore │ └── mock-interface-fact.json ├── functions │ └── ssh │ │ └── ipaddresses_spec.rb ├── setup_acceptance_node.pp ├── spec_helper.rb ├── spec_helper_acceptance.rb ├── type_aliases │ └── sshclientmatch_spec.rb └── unit │ └── facter │ └── util │ ├── fact_ssh_client_version_spec.rb │ ├── fact_ssh_server_version_major_spec.rb │ └── fact_ssh_server_version_spec.rb ├── templates ├── issue.net.erb ├── options.erb ├── ssh_config.erb ├── ssh_instance.erb ├── ssh_instance_service.erb ├── sshd_config.erb └── sshd_match_block.erb └── types └── clientmatch.pp /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | # Managed by modulesync - DO NOT EDIT 4 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 5 | 6 | root = true 7 | 8 | [*] 9 | charset = utf-8 10 | end_of_line = lf 11 | indent_size = 2 12 | tab_width = 2 13 | indent_style = space 14 | insert_final_newline = true 15 | trim_trailing_whitespace = true 16 | -------------------------------------------------------------------------------- /.fixtures.yml: -------------------------------------------------------------------------------- 1 | fixtures: 2 | repositories: 3 | stdlib: "https://github.com/puppetlabs/puppetlabs-stdlib" 4 | concat: "https://github.com/puppetlabs/puppetlabs-concat" 5 | systemd: "https://github.com/voxpupuli/puppet-systemd" 6 | sshkeys_core: "https://github.com/puppetlabs/puppetlabs-sshkeys_core" 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | ## Affected Puppet, Ruby, OS and module versions/distributions 12 | 13 | - Puppet: 14 | - Ruby: 15 | - Distribution: 16 | - Module version: 17 | 18 | ## How to reproduce (e.g Puppet code you use) 19 | 20 | ## What are you seeing 21 | 22 | ## What behaviour did you expect instead 23 | 24 | ## Output log 25 | 26 | ## Any additional information you'd like to impart 27 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | #### Pull Request (PR) description 10 | 13 | 14 | #### This Pull Request (PR) fixes the following issues 15 | 21 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | skip-changelog: 6 | - head-branch: ['^release-*', 'release'] 7 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | # https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes 6 | 7 | changelog: 8 | exclude: 9 | labels: 10 | - duplicate 11 | - invalid 12 | - modulesync 13 | - question 14 | - skip-changelog 15 | - wont-fix 16 | - wontfix 17 | 18 | categories: 19 | - title: Breaking Changes 🛠 20 | labels: 21 | - backwards-incompatible 22 | 23 | - title: New Features 🎉 24 | labels: 25 | - enhancement 26 | 27 | - title: Bug Fixes 🐛 28 | labels: 29 | - bug 30 | 31 | - title: Documentation Updates 📚 32 | labels: 33 | - documentation 34 | - docs 35 | 36 | - title: Dependency Updates ⬆️ 37 | labels: 38 | - dependencies 39 | 40 | - title: Other Changes 41 | labels: 42 | - "*" 43 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | name: CI 6 | 7 | # yamllint disable-line rule:truthy 8 | on: 9 | pull_request: {} 10 | push: 11 | branches: 12 | - main 13 | - master 14 | 15 | concurrency: 16 | group: ${{ github.ref_name }} 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | puppet: 21 | name: Puppet 22 | uses: voxpupuli/gha-puppet/.github/workflows/beaker.yml@v3 23 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | name: "Pull Request Labeler" 6 | 7 | # yamllint disable-line rule:truthy 8 | on: 9 | pull_request_target: {} 10 | 11 | jobs: 12 | labeler: 13 | permissions: 14 | contents: read 15 | pull-requests: write 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/labeler@v5 19 | -------------------------------------------------------------------------------- /.github/workflows/prepare_release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | name: 'Prepare Release' 6 | 7 | on: 8 | workflow_dispatch: 9 | inputs: 10 | version: 11 | description: 'Module version to be released. Must be a valid semver string without leading v. (1.2.3)' 12 | required: false 13 | 14 | jobs: 15 | release_prep: 16 | uses: 'voxpupuli/gha-puppet/.github/workflows/prepare_release.yml@v3' 17 | with: 18 | version: ${{ github.event.inputs.version }} 19 | allowed_owner: 'saz' 20 | secrets: 21 | # Configure secrets here: 22 | # https://docs.github.com/en/actions/security-guides/encrypted-secrets 23 | github_pat: '${{ secrets.PCCI_PAT_RELEASE_PREP }}' 24 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | name: Release 6 | 7 | # yamllint disable-line rule:truthy 8 | on: 9 | push: 10 | tags: 11 | - '*' 12 | 13 | jobs: 14 | release: 15 | name: Release 16 | uses: voxpupuli/gha-puppet/.github/workflows/release.yml@v3 17 | with: 18 | allowed_owner: 'saz' 19 | secrets: 20 | # Configure secrets here: 21 | # https://docs.github.com/en/actions/security-guides/encrypted-secrets 22 | username: ${{ secrets.PUPPET_FORGE_USERNAME }} 23 | api_key: ${{ secrets.PUPPET_FORGE_API_KEY }} 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Managed by modulesync - DO NOT EDIT 2 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 3 | 4 | /pkg/ 5 | /Gemfile.lock 6 | /Gemfile.local 7 | /vendor/ 8 | /.vendor/ 9 | /spec/fixtures/manifests/ 10 | /spec/fixtures/modules/ 11 | /.vagrant/ 12 | /.bundle/ 13 | /.ruby-version 14 | /coverage/ 15 | /log/ 16 | /.idea/ 17 | /.dependencies/ 18 | /.librarian/ 19 | /Puppetfile.lock 20 | *.iml 21 | .*.sw? 22 | /.yardoc/ 23 | /Guardfile 24 | bolt-debug.log 25 | .rerun.json 26 | -------------------------------------------------------------------------------- /.msync.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | modulesync_config_version: '9.4.0' 6 | -------------------------------------------------------------------------------- /.overcommit.yml: -------------------------------------------------------------------------------- 1 | # Managed by modulesync - DO NOT EDIT 2 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 3 | # 4 | # Hooks are only enabled if you take action. 5 | # 6 | # To enable the hooks run: 7 | # 8 | # ``` 9 | # bundle exec overcommit --install 10 | # # ensure .overcommit.yml does not harm to you and then 11 | # bundle exec overcommit --sign 12 | # ``` 13 | # 14 | # (it will manage the .git/hooks directory): 15 | # 16 | # Examples howto skip a test for a commit or push: 17 | # 18 | # ``` 19 | # SKIP=RuboCop git commit 20 | # SKIP=PuppetLint git commit 21 | # SKIP=RakeTask git push 22 | # ``` 23 | # 24 | # Don't invoke overcommit at all: 25 | # 26 | # ``` 27 | # OVERCOMMIT_DISABLE=1 git commit 28 | # ``` 29 | # 30 | # Read more about overcommit: https://github.com/brigade/overcommit 31 | # 32 | # To manage this config yourself in your module add 33 | # 34 | # ``` 35 | # .overcommit.yml: 36 | # unmanaged: true 37 | # ``` 38 | # 39 | # to your modules .sync.yml config 40 | --- 41 | PreCommit: 42 | RuboCop: 43 | enabled: true 44 | description: 'Runs rubocop on modified files only' 45 | command: ['bundle', 'exec', 'rubocop'] 46 | RakeTarget: 47 | enabled: true 48 | description: 'Runs lint on modified files only' 49 | targets: 50 | - 'lint' 51 | command: ['bundle', 'exec', 'rake'] 52 | YamlSyntax: 53 | enabled: true 54 | JsonSyntax: 55 | enabled: true 56 | TrailingWhitespace: 57 | enabled: true 58 | 59 | PrePush: 60 | RakeTarget: 61 | enabled: true 62 | description: 'Run rake targets' 63 | targets: 64 | - 'validate' 65 | - 'test' 66 | - 'rubocop' 67 | command: ['bundle', 'exec', 'rake'] 68 | -------------------------------------------------------------------------------- /.pmtignore: -------------------------------------------------------------------------------- 1 | # Managed by modulesync - DO NOT EDIT 2 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 3 | 4 | /docs/ 5 | /pkg/ 6 | /Gemfile 7 | /Gemfile.lock 8 | /Gemfile.local 9 | /vendor/ 10 | /.vendor/ 11 | /spec/ 12 | /Rakefile 13 | /.vagrant/ 14 | /.bundle/ 15 | /.ruby-version 16 | /coverage/ 17 | /log/ 18 | /.idea/ 19 | /.dependencies/ 20 | /.github/ 21 | /.librarian/ 22 | /Puppetfile.lock 23 | /Puppetfile 24 | *.iml 25 | /.editorconfig 26 | /.fixtures.yml 27 | /.gitignore 28 | /.msync.yml 29 | /.overcommit.yml 30 | /.pmtignore 31 | /.rspec 32 | /.rspec_parallel 33 | /.rubocop.yml 34 | /.sync.yml 35 | .*.sw? 36 | /.yardoc/ 37 | /.yardopts 38 | /Dockerfile 39 | /HISTORY.md 40 | -------------------------------------------------------------------------------- /.puppet-lint.rc: -------------------------------------------------------------------------------- 1 | # Managed by modulesync - DO NOT EDIT 2 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 3 | 4 | --fail-on-warnings 5 | --no-parameter_documentation-check 6 | --no-parameter_types-check 7 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | inherit_gem: 6 | voxpupuli-test: rubocop.yml 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [13.1.0] 8 | ### Added 9 | - puppet/systemd: allow 8.x (#404) 10 | 11 | ## [13.0.0] 12 | ### Removed 13 | - BREAKING CHANGE: remove Ubuntu 18.04 as supported OS (#402) 14 | ### Fixed 15 | - ssh_instance: write ciphers,macs and kex as comma-separated string (#401) 16 | - Purge and Recurse should be set together (#399) 17 | ### Added 18 | - Add support for sshd_config include files (#390) 19 | ### Changed 20 | - Set merge behavior of ssh::server_instances to deep (#395) 21 | 22 | ## [12.1.0] 23 | ### Added 24 | - allow puppet/systemd < 8, fixes #382 25 | ### Changed 26 | - set sshd config mode to 0644 on AIX, fixes #371 (#383) 27 | - use `contain` instead of `include`, fixes #367 (#387) 28 | ### Fixed 29 | - fix tests on OpenBSD (#384) 30 | - drop tag from concat_{file,fragment}, fixes #304 (#385) 31 | - fix subsystem option if use_augeas = true, fixes #376 (#386) 32 | 33 | ## [12.0.1] 34 | ### Fixed 35 | - make ssh::hostkeys::exclude_interfaces_re parameter work properly (#380) 36 | 37 | ## [12.0.0] 38 | ### Added 39 | - add parameter to exclude interfaces with a regex (#378) 40 | - Allow User to add additonal systemd options to instances (#374) 41 | ### Changed 42 | - puppet/systemd: Allow 6.x (#364) 43 | ### Fixed 44 | - allow ssh::server::ensure = latest, fixes #370 (#377) 45 | 46 | ## [11.1.0] 47 | ### Fixed 48 | - write ciphers,macs and kex as comma-separated string (#362) 49 | - Fix "No ssh_server_version_major created with OpenSSH 9.2" (#359) 50 | 51 | ## [11.0.0] 52 | ### Removed 53 | - BREAKING CHANGE: drop support for puppet 6 54 | ### Changed 55 | - puppetlabs/concat: Allow 9.x (#354) 56 | - puppet/systemd: Allow 5.x (#354) 57 | - puppetlabs/stdlib: Require 9.x (#354) 58 | ### Added 59 | - add Debian 12 as supported OS 60 | 61 | ## [10.2.0] 62 | ### Changed 63 | - bump puppetlabs/concat to < 9.0.0 (#352) 64 | - Replace deprecated functions (#350) 65 | 66 | ## [10.1.0] 67 | ### Added 68 | - Support assigning multiple tags to a hostkey (#345) 69 | - Add AIX support (#341) 70 | ### Changed 71 | - bump puppet/systemd to < 5.0.0 (#344) 72 | ### Fixed 73 | - Fix for service name on latest versions of opensuse. (#343) 74 | 75 | ## [10.0.0] 76 | ### Added 77 | - Add support for client "match blocks" (#332, #333) 78 | - Add data file for OpenBSD (#339) 79 | - Add support for service_ensure/service_enable in `ssh::server::instances` (#338) 80 | ### Changed 81 | - Use hiera instead of params.pp (#325, #328) 82 | ### Fixed 83 | - Fix parameter lookup for `ssh::server` and `ssh::client` (#331) 84 | 85 | ## [9.0.0] 86 | ### Added 87 | - Support for multiple instances (#318, #319, #321) - Thanks! 88 | ### Changed 89 | - "hostkeys.pp" isn't marked private anymore (#317) 90 | 91 | ## [8.0.0] 92 | ### Changed 93 | - update path to sftp server on Gentoo (#315, breaking change) 94 | 95 | ## [7.0.2] 96 | ### Added 97 | - allow stdlib < 9.0.0 (#314) 98 | 99 | ## [7.0.1] 100 | ### Fixed 101 | - ssh_config: Don't populate options that are set to undef (#312) 102 | 103 | ## [7.0.0] 104 | ### Fixed 105 | - Fix grammar and spelling in various places 106 | ### Changed 107 | - Use GitHub Actions instead of TravisCI 108 | - Update module dependencies 109 | ### Removed 110 | - Dropped support for puppet 4 and 5 (Breaking Change) 111 | 112 | ## [6.2.0] 113 | ### Changed 114 | - support older facter versions (#293) 115 | 116 | ## [6.1.0] 117 | ### Fixed 118 | - Fix absolute class name includes 119 | - Use gid 0 instead of group name for $host_priv_key_group (#289) 120 | - Sort hostkeys (#288) 121 | - Do not show diff when installing a ssh private host key (#283) 122 | - Don't populate options which have a value of `undef` (#281) 123 | ### Added 124 | - document exclusion of interfaces and ipaddresses within hostkeys.pp (#267) 125 | - add parameter to use trusted facts to hostkeys.pp (#226) 126 | 127 | ## [6.0.0] 128 | ### Fixed 129 | - don't fail at deep_merge if hiera data not available, see #272 130 | - Fix typo in match_block example in README, see #271, #273 131 | ### Added 132 | - Add CHANGELOG (starting with this release), see #222 133 | - Test module with Puppet 6.1, see #269 134 | ### Changed 135 | - Convert `ipaddresses` to 4x API namespaced function, see #270 136 | - Allow `puppetlabs` `stdlib` and `concat` 6.x, see #280 137 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # Managed by modulesync - DO NOT EDIT 2 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 3 | 4 | source ENV['GEM_SOURCE'] || 'https://rubygems.org' 5 | 6 | group :test do 7 | gem 'voxpupuli-test', '~> 9.0', :require => false 8 | gem 'coveralls', :require => false 9 | gem 'simplecov-console', :require => false 10 | gem 'puppet_metadata', '~> 4.0', :require => false 11 | end 12 | 13 | group :development do 14 | gem 'guard-rake', :require => false 15 | gem 'overcommit', '>= 0.39.1', :require => false 16 | end 17 | 18 | group :system_tests do 19 | gem 'voxpupuli-acceptance', '~> 3.0', :require => false 20 | end 21 | 22 | group :release do 23 | gem 'voxpupuli-release', '~> 3.0', :require => false 24 | end 25 | 26 | gem 'rake', :require => false 27 | gem 'facter', ENV['FACTER_GEM_VERSION'], :require => false, :groups => [:test] 28 | 29 | puppetversion = ENV['PUPPET_GEM_VERSION'] || [">= 7.24", "< 9"] 30 | gem 'puppet', puppetversion, :require => false, :groups => [:test] 31 | 32 | # vim: syntax=ruby 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2011 Steffen Zieger 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Puppet SSH 2 | 3 | [![Puppet Forge modules by saz](https://img.shields.io/puppetforge/mc/saz.svg)](https://forge.puppetlabs.com/saz) 4 | [![Puppet Forge](http://img.shields.io/puppetforge/v/saz/ssh.svg)](https://forge.puppetlabs.com/saz/ssh) 5 | [![Puppet Forge downloads](https://img.shields.io/puppetforge/dt/saz/ssh.svg)](https://forge.puppetlabs.com/saz/ssh) 6 | [![Puppet Forge score](https://img.shields.io/puppetforge/f/saz/ssh.svg)](https://forge.puppetlabs.com/saz/ssh) 7 | [![Build Status](https://github.com/saz/puppet-ssh/workflows/CI/badge.svg)](https://github.com/saz/puppet-ssh/actions?query=workflow%3ACI) 8 | 9 | Manage SSH client and server via Puppet. 10 | Source: https://github.com/saz/puppet-ssh 11 | 12 | ## Requirements 13 | * Exported resources for host keys management 14 | * puppetlabs/stdlib 15 | * puppetlabs/concat 16 | 17 | ## Usage 18 | 19 | Since version 2.0.0 only non-default values are written to both, 20 | client and server, configuration files. 21 | 22 | Multiple occurrences of one config key (e.g. sshd should be listening on 23 | port 22 and 2222) should be passed as an array. 24 | 25 | ```puppet 26 | options => { 27 | 'Port' => [22, 2222], 28 | } 29 | ``` 30 | 31 | This is working for both, client and server. 32 | 33 | ### Both client, server and per user client configuration 34 | Host keys will be collected and distributed unless 35 | `storeconfigs_enabled` is `false`. 36 | 37 | ```puppet 38 | include ssh 39 | ``` 40 | 41 | or 42 | 43 | ```puppet 44 | class { 'ssh': 45 | storeconfigs_enabled => false, 46 | server_options => { 47 | 'Match User www-data' => { 48 | 'ChrootDirectory' => '%h', 49 | 'ForceCommand' => 'internal-sftp', 50 | 'PasswordAuthentication' => 'yes', 51 | 'AllowTcpForwarding' => 'no', 52 | 'X11Forwarding' => 'no', 53 | }, 54 | 'Port' => [22, 2222, 2288], 55 | }, 56 | client_options => { 57 | 'Host *.amazonaws.com' => { 58 | 'User' => 'ec2-user', 59 | }, 60 | }, 61 | users_client_options => { 62 | 'bob' => { 63 | options => { 64 | 'Host *.alice.fr' => { 65 | 'User' => 'alice', 66 | }, 67 | }, 68 | }, 69 | }, 70 | } 71 | ``` 72 | 73 | ### Hiera example 74 | ```yaml 75 | ssh::storeconfigs_enabled: true 76 | 77 | ssh::server_options: 78 | Protocol: '2' 79 | ListenAddress: 80 | - '127.0.0.0' 81 | - '%{::hostname}' 82 | PasswordAuthentication: 'yes' 83 | SyslogFacility: 'AUTHPRIV' 84 | UsePAM: 'yes' 85 | X11Forwarding: 'yes' 86 | 87 | ssh::server::match_block: 88 | filetransfer: 89 | type: group 90 | options: 91 | ChrootDirectory: /home/sftp 92 | ForceCommand: internal-sftp 93 | 94 | ssh::client_options: 95 | 'Host *': 96 | SendEnv: 'LANG LC_*' 97 | ForwardX11Trusted: 'yes' 98 | ServerAliveInterval: '10' 99 | 100 | ssh::users_client_options: 101 | 'bob': 102 | 'options': 103 | 'Host *.alice.fr': 104 | 'User': 'alice' 105 | 'PasswordAuthentication': 'no' 106 | ``` 107 | 108 | ### Client only 109 | Collected host keys from servers will be written to `known_hosts` unless 110 | `storeconfigs_enabled` is `false` 111 | 112 | ```puppet 113 | include ssh::client 114 | ``` 115 | 116 | or 117 | 118 | ```puppet 119 | class { 'ssh::client': 120 | storeconfigs_enabled => false, 121 | options => { 122 | 'Host short' => { 123 | 'User' => 'my-user', 124 | 'HostName' => 'extreme.long.and.complicated.hostname.domain.tld', 125 | }, 126 | 'Host *' => { 127 | 'User' => 'andromeda', 128 | 'UserKnownHostsFile' => '/dev/null', 129 | }, 130 | }, 131 | } 132 | ``` 133 | 134 | ### Per user client configuration 135 | 136 | **User's home is expected to be /home/bob** 137 | 138 | SSH configuration file will be `/home/bob/.ssh/config`. 139 | 140 | ```puppet 141 | ::ssh::client::config::user { 'bob': 142 | ensure => present, 143 | options => { 144 | 'HashKnownHosts' => 'yes' 145 | } 146 | } 147 | ``` 148 | 149 | **User's home is passed to define type** 150 | 151 | SSH configuration file will be `/var/lib/bob/.ssh/config` and puppet will 152 | manage directory `/var/lib/bob/.ssh`. 153 | 154 | ```puppet 155 | ::ssh::client::config::user { 'bob': 156 | ensure => present, 157 | user_home_dir => '/var/lib/bob', 158 | options => { 159 | 'HashKnownHosts' => 'yes' 160 | } 161 | } 162 | ``` 163 | 164 | **User's ssh directory should not be managed by the define type** 165 | 166 | SSH configuration file will be `/var/lib/bob/.ssh/config`. 167 | 168 | ```puppet 169 | ::ssh::client::config::user { 'bob': 170 | ensure => present, 171 | user_home_dir => '/var/lib/bob', 172 | manage_user_ssh_dir => false, 173 | options => { 174 | 'HashKnownHosts' => 'yes' 175 | } 176 | } 177 | ``` 178 | 179 | **User's ssh config is specified with an absolute path** 180 | 181 | ```puppet 182 | ::ssh::client::config::user { 'bob': 183 | ensure => present, 184 | target => '/var/lib/bob/.ssh/ssh_config', 185 | options => { 186 | 'HashKnownHosts' => 'yes' 187 | } 188 | } 189 | ``` 190 | 191 | ### Server only 192 | Host keys will be collected for client distribution unless 193 | `storeconfigs_enabled` is `false` 194 | 195 | ```puppet 196 | include ssh::server 197 | ``` 198 | 199 | or 200 | 201 | ```puppet 202 | class { 'ssh::server': 203 | storeconfigs_enabled => false, 204 | options => { 205 | 'Match User www-data' => { 206 | 'ChrootDirectory' => '%h', 207 | 'ForceCommand' => 'internal-sftp', 208 | 'PasswordAuthentication' => 'yes', 209 | 'AllowTcpForwarding' => 'no', 210 | 'X11Forwarding' => 'no', 211 | }, 212 | 'PasswordAuthentication' => 'no', 213 | 'PermitRootLogin' => 'no', 214 | 'Port' => [22, 2222], 215 | }, 216 | } 217 | ``` 218 | 219 | ### Validate config before replacing it 220 | 221 | `validate_sshd_file` allows you to run `/usr/sbin/sshd -tf` against the sshd config file before it gets replaced, and will raise an error if the config is incorrect. 222 | 223 | ```puppet 224 | class { 'ssh::server': 225 | validate_sshd_file => true, 226 | } 227 | ``` 228 | 229 | 230 | ## Default options 231 | 232 | ### Client 233 | 234 | ```puppet 235 | 'Host *' => { 236 | 'SendEnv' => 'LANG LC_*', 237 | 'HashKnownHosts' => 'yes', 238 | 'GSSAPIAuthentication' => 'yes', 239 | } 240 | ``` 241 | 242 | ### Server 243 | 244 | ```puppet 245 | 'ChallengeResponseAuthentication' => 'no', 246 | 'X11Forwarding' => 'yes', 247 | 'PrintMotd' => 'no', 248 | 'AcceptEnv' => 'LANG LC_*', 249 | 'Subsystem' => 'sftp /usr/lib/openssh/sftp-server', 250 | 'UsePAM' => 'yes', 251 | ``` 252 | 253 | ## Overwriting default options 254 | Default options will be merged with options passed in. 255 | If an option is set both as default and via options parameter, the latter 256 | will win. 257 | 258 | The following example will disable X11Forwarding, which is enabled by default: 259 | 260 | ```puppet 261 | class { 'ssh::server': 262 | options => { 263 | 'X11Forwarding' => 'no', 264 | }, 265 | } 266 | ``` 267 | 268 | Which will lead to the following `sshd_config` file: 269 | 270 | ``` 271 | # File is managed by Puppet 272 | 273 | ChallengeResponseAuthentication no 274 | X11Forwarding no 275 | PrintMotd no 276 | AcceptEnv LANG LC\_\* 277 | Subsystem sftp /usr/lib/openssh/sftp-server 278 | UsePAM yes 279 | PasswordAuthentication no 280 | ``` 281 | 282 | Values can also be arrays, which will result in the option being specified multiple times 283 | 284 | ```puppet 285 | class { 'ssh::server': 286 | options => { 287 | 'HostKey' => ['/etc/ssh/ssh_host_ed25519_key', '/etc/ssh/ssh_host_rsa_key'], 288 | }, 289 | } 290 | ``` 291 | 292 | Which will lead to the following `sshd_config` file: 293 | 294 | ``` 295 | # File is managed by Puppet 296 | 297 | ChallengeResponseAuthentication no 298 | HostKey /etc/ssh/ssh_host_ed25519_key 299 | HostKey /etc/ssh/ssh_host_rsa_key 300 | PrintMotd no 301 | AcceptEnv LANG LC_\* 302 | Subsystem sftp /usr/lib/openssh/sftp-server 303 | UsePAM yes 304 | PasswordAuthentication no 305 | ``` 306 | 307 | ## Defining host keys for server 308 | You can define host keys your server will use 309 | 310 | ```puppet 311 | ssh::server::host_key {'ssh_host_rsa_key': 312 | private_key_content => '', 313 | public_key_content => '', 314 | } 315 | ``` 316 | 317 | Alternately, you could create the host key providing the files, instead 318 | of the content: 319 | 320 | ```puppet 321 | ssh::server::host_key {'ssh_host_rsa_key': 322 | private_key_source => 'puppet:///mymodule/ssh_host_rsa_key', 323 | public_key_source => 'puppet:///mymodule/ssh_host_rsa_key.pub', 324 | } 325 | ``` 326 | 327 | Both of these definitions will create ```/etc/ssh/ssh_host_rsa_key``` and 328 | ```/etc/ssh/ssh_host_rsa_key.pub``` and restart sshd daemon. 329 | 330 | 331 | ## Adding custom match blocks 332 | 333 | ```puppet 334 | class YOURCUSTOMCLASS { 335 | 336 | include ssh 337 | 338 | ssh::server::match_block { 'sftp_only': 339 | type => 'User', 340 | options => { 341 | 'ChrootDirectory' => "/sftp/%u", 342 | 'ForceCommand' => 'internal-sftp', 343 | 'PasswordAuthentication' => 'no', 344 | 'AllowTcpForwarding' => 'no', 345 | 'X11Forwarding' => 'no', 346 | } 347 | } 348 | } 349 | ``` 350 | 351 | ## Tag hostkey 352 | 353 | Assign tags to exported `sshkey` resources (when `ssh::storeconfigs_enabled` is set to `true`). 354 | 355 | ```yaml 356 | ssh::hostkeys::tags: 357 | - hostkey_group1 358 | - hostkey_group2 359 | ``` 360 | 361 | Host keys then can be imported using: 362 | 363 | ```puppet 364 | Sshkey <<| tag == "hostkey_group1" |>> 365 | ``` 366 | 367 | ## Excluding network interfaces or ipaddresses 368 | 369 | Use hiera to exclude interfaces or ipaddresses from hostkey inclusion 370 | 371 | ```yaml 372 | ssh::hostkeys::exclude_interfaces: 373 | - eth0 374 | - eth3 375 | ssh::hostkeys::exclude_ipaddresses: 376 | - 192.168.0.1 377 | - 10.42.24.242 378 | ``` 379 | 380 | ## Facts 381 | 382 | This module provides facts detailing the available SSH client and server 383 | versions. 384 | 385 | * `ssh_*_version_full` Provides the full version number including the portable 386 | version number. 387 | * `ssh_*_version_major` Provides the first two numbers in the version number. 388 | * `ssh_*_version_release` Provides the first three number components of the 389 | version, no portable version is present. 390 | 391 | Example facter output for OpenSSH `6.6.1p1`: 392 | 393 | ``` 394 | ssh_client_version_full => 6.6.1p1 395 | ssh_client_version_major => 6.6 396 | ssh_client_version_release => 6.6.1 397 | ssh_server_version_full => 6.6.1p1 398 | ssh_server_version_major => 6.6 399 | ssh_server_version_release => 6.6.1 400 | ``` 401 | -------------------------------------------------------------------------------- /REFERENCE.md: -------------------------------------------------------------------------------- 1 | # Reference 2 | 3 | 4 | 5 | ## Table of Contents 6 | 7 | ### Classes 8 | 9 | #### Public Classes 10 | 11 | * [`ssh`](#ssh): This class manages ssh client and server 12 | * [`ssh::client`](#ssh--client): This class add ssh client management 13 | * [`ssh::hostkeys`](#ssh--hostkeys): This class manages hostkeys 14 | * [`ssh::knownhosts`](#ssh--knownhosts): This class manages knownhosts if collection is enabled. 15 | * [`ssh::server`](#ssh--server): This class managed ssh server 16 | 17 | #### Private Classes 18 | 19 | * `ssh::client::config`: Manages ssh configuration 20 | * `ssh::client::install`: Install ssh client package 21 | * `ssh::server::config`: Managed ssh server configuration 22 | * `ssh::server::install`: Install ssh server package 23 | * `ssh::server::service`: This class managed ssh server service 24 | 25 | ### Defined types 26 | 27 | * [`ssh::client::config::user`](#ssh--client--config--user): This defined type manages a users ssh config 28 | * [`ssh::client::match_block`](#ssh--client--match_block): Add match_block to ssh client config (concat needed) 29 | * [`ssh::server::config::setting`](#ssh--server--config--setting): Internal define to managed ssh server param 30 | * [`ssh::server::config_file`](#ssh--server--config_file): Resource type for managing a config file in the include dir. 31 | * [`ssh::server::host_key`](#ssh--server--host_key): Manage a ssh host key 32 | 33 | This module install a ssh host key in the server (basically, it is 34 | a file resource but it also notifies to the ssh service) 35 | 36 | Important! This define does not modify any option in sshd_config, so 37 | you have to manually define the HostKey option in the server options 38 | if you haven't done yet. 39 | * [`ssh::server::instances`](#ssh--server--instances): Configure separate ssh server instances 40 | * [`ssh::server::match_block`](#ssh--server--match_block): Add match_block to ssh server config 41 | * [`ssh::server::options`](#ssh--server--options): This defined type manages ssh server options 42 | 43 | ### Functions 44 | 45 | #### Public Functions 46 | 47 | * [`sshclient_options_to_augeas_ssh_config`](#sshclient_options_to_augeas_ssh_config): This function will convert a key-value hash to a format understandable by the augeas sshd_config provider It will also optionally deal with k 48 | * [`sshserver_options_to_augeas_sshd_config`](#sshserver_options_to_augeas_sshd_config): This function will convert a key-value hash to a format understandable by the augeas sshd_config provider It will also optionally deal with k 49 | 50 | #### Private Functions 51 | 52 | * `ssh::ipaddresses`: Returns ip addresses of network interfaces (except lo) found by facter. 53 | 54 | ### Data types 55 | 56 | * [`Ssh::ClientMatch`](#Ssh--ClientMatch): OpenSSH client `Match` criteria. See `ssh_config(5)` 57 | 58 | ## Classes 59 | 60 | ### `ssh` 61 | 62 | } 63 | 64 | #### Examples 65 | 66 | ##### Puppet usage 67 | 68 | ```puppet 69 | class { 'ssh': 70 | storeconfigs_enabled => false, 71 | server_options => { 72 | 'Match User www-data' => { 73 | 'ChrootDirectory' => '%h', 74 | 'ForceCommand' => 'internal-sftp', 75 | 'PasswordAuthentication' => 'yes', 76 | 'AllowTcpForwarding' => 'no', 77 | 'X11Forwarding' => 'no', 78 | }, 79 | 'Port' => [22, 2222, 2288], 80 | }, 81 | client_options => { 82 | 'Host *.amazonaws.com' => { 83 | 'User' => 'ec2-user', 84 | }, 85 | }, 86 | users_client_options => { 87 | 'bob' => { 88 | options => { 89 | 'Host *.alice.fr' => { 90 | 'User' => 'alice', 91 | }, 92 | }, 93 | }, 94 | }, 95 | 'server_instances' => { 96 | 'sftp_server_init' => { 97 | 'ensure' => 'present', 98 | 'options' => { 99 | 'sshd_config' => { 100 | 'Port' => 8022, 101 | 'Protocol' => 2, 102 | 'AddressFamily' => 'any', 103 | 'HostKey' => '/etc/ssh/ssh_host_rsa_key', 104 | 'SyslogFacility' => 'AUTH', 105 | 'LogLevel' => 'INFO', 106 | 'PermitRootLogin' => 'no', 107 | }, 108 | 'sshd_service_options' => '', 109 | 'match_blocks' => { 110 | '*,!ssh_exempt_ldap_authkey,!sshlokey' => { 111 | 'type' => 'group', 112 | 'options' => { 113 | 'AuthorizedKeysCommand' => '/usr/local/bin/getauthkey', 114 | 'AuthorizedKeysCommandUser' => 'nobody', 115 | 'AuthorizedKeysFile' => '/dev/null', 116 | }, 117 | }, 118 | }, 119 | }, 120 | }, 121 | }, 122 | ``` 123 | 124 | ##### hiera usage 125 | 126 | ```puppet 127 | ssh::storeconfigs_enabled: true 128 | 129 | ssh::server_options: 130 | Protocol: '2' 131 | ListenAddress: 132 | - '127.0.0.0' 133 | - '%{::hostname}' 134 | PasswordAuthentication: 'yes' 135 | SyslogFacility: 'AUTHPRIV' 136 | UsePAM: 'yes' 137 | X11Forwarding: 'yes' 138 | 139 | ssh::server::match_block: 140 | filetransfer: 141 | type: group 142 | options: 143 | ChrootDirectory: /home/sftp 144 | ForceCommand: internal-sftp 145 | 146 | ssh::client_options: 147 | 'Host *': 148 | SendEnv: 'LANG LC_*' 149 | ForwardX11Trusted: 'yes' 150 | ServerAliveInterval: '10' 151 | 152 | ssh::users_client_options: 153 | 'bob': 154 | 'options': 155 | 'Host *.alice.fr': 156 | 'User': 'alice' 157 | 'PasswordAuthentication': 'no' 158 | ssh::server::server_instances: 159 | sftp_server_init: 160 | ensure: present 161 | options: 162 | sshd_config: 163 | Port: 8022 164 | Protocol: 2 165 | AddressFamily: 'any' 166 | HostKey: '/etc/ssh/ssh_host_rsa_key' 167 | SyslogFacility: 'AUTH' 168 | LogLevel: INFO 169 | PermitRootLogin: 'no' 170 | sshd_service_options: '' 171 | match_blocks: 172 | '*,!ssh_exempt_ldap_authkey,!sshlokey': 173 | type: group 174 | options: 175 | AuthorizedKeysCommand: '/usr/local/bin/getauthkey' 176 | AuthorizedKeysCommandUser: 'nobody' 177 | AuthorizedKeysFile: '/dev/null' 178 | ``` 179 | 180 | #### Parameters 181 | 182 | The following parameters are available in the `ssh` class: 183 | 184 | * [`server_options`](#-ssh--server_options) 185 | * [`server_match_block`](#-ssh--server_match_block) 186 | * [`client_options`](#-ssh--client_options) 187 | * [`client_match_block`](#-ssh--client_match_block) 188 | * [`users_client_options`](#-ssh--users_client_options) 189 | * [`version`](#-ssh--version) 190 | * [`storeconfigs_enabled`](#-ssh--storeconfigs_enabled) 191 | * [`validate_sshd_file`](#-ssh--validate_sshd_file) 192 | * [`use_augeas`](#-ssh--use_augeas) 193 | * [`server_options_absent`](#-ssh--server_options_absent) 194 | * [`client_options_absent`](#-ssh--client_options_absent) 195 | * [`use_issue_net`](#-ssh--use_issue_net) 196 | * [`purge_unmanaged_sshkeys`](#-ssh--purge_unmanaged_sshkeys) 197 | * [`server_instances`](#-ssh--server_instances) 198 | 199 | ##### `server_options` 200 | 201 | Data type: `Optional[Hash]` 202 | 203 | Add dynamic options for ssh server config 204 | 205 | Default value: `undef` 206 | 207 | ##### `server_match_block` 208 | 209 | Data type: `Hash` 210 | 211 | Add match block for ssh server config 212 | 213 | Default value: `{}` 214 | 215 | ##### `client_options` 216 | 217 | Data type: `Optional[Hash]` 218 | 219 | Add dynamic options for ssh client config 220 | 221 | Default value: `undef` 222 | 223 | ##### `client_match_block` 224 | 225 | Data type: `Hash` 226 | 227 | Add match block for ssh client config 228 | 229 | Default value: `{}` 230 | 231 | ##### `users_client_options` 232 | 233 | Data type: `Hash` 234 | 235 | Add users options for ssh client config 236 | 237 | Default value: `{}` 238 | 239 | ##### `version` 240 | 241 | Data type: `String` 242 | 243 | Define package version (package ressource) 244 | 245 | Default value: `'present'` 246 | 247 | ##### `storeconfigs_enabled` 248 | 249 | Data type: `Boolean` 250 | 251 | Default value for storeconfigs_enabled (client and server) 252 | 253 | Default value: `true` 254 | 255 | ##### `validate_sshd_file` 256 | 257 | Data type: `Boolean` 258 | 259 | Default value for validate_sshd_file (server) 260 | 261 | Default value: `false` 262 | 263 | ##### `use_augeas` 264 | 265 | Data type: `Boolean` 266 | 267 | Default value to use augeas (client and server) 268 | 269 | Default value: `false` 270 | 271 | ##### `server_options_absent` 272 | 273 | Data type: `Array` 274 | 275 | List of options to remove for server config (augeas only) 276 | 277 | Default value: `[]` 278 | 279 | ##### `client_options_absent` 280 | 281 | Data type: `Array` 282 | 283 | List of options to remove for client config (augeas only) 284 | 285 | Default value: `[]` 286 | 287 | ##### `use_issue_net` 288 | 289 | Data type: `Boolean` 290 | 291 | Use issue_net header 292 | 293 | Default value: `false` 294 | 295 | ##### `purge_unmanaged_sshkeys` 296 | 297 | Data type: `Boolean` 298 | 299 | Purge unmanaged sshkeys 300 | 301 | Default value: `true` 302 | 303 | ##### `server_instances` 304 | 305 | Data type: `Hash[String[1],Hash[String[1],NotUndef]]` 306 | 307 | Configure SSH instances 308 | 309 | Default value: `{}` 310 | 311 | ### `ssh::client` 312 | 313 | This class add ssh client management 314 | 315 | #### Examples 316 | 317 | ##### Puppet usage 318 | 319 | ```puppet 320 | class { 'ssh::client': 321 | ensure => present, 322 | storeconfigs_enabled => true, 323 | use_augeas => false, 324 | } 325 | ``` 326 | 327 | #### Parameters 328 | 329 | The following parameters are available in the `ssh::client` class: 330 | 331 | * [`ssh_config`](#-ssh--client--ssh_config) 332 | * [`client_package_name`](#-ssh--client--client_package_name) 333 | * [`ensure`](#-ssh--client--ensure) 334 | * [`storeconfigs_enabled`](#-ssh--client--storeconfigs_enabled) 335 | * [`options`](#-ssh--client--options) 336 | * [`use_augeas`](#-ssh--client--use_augeas) 337 | * [`options_absent`](#-ssh--client--options_absent) 338 | * [`default_options`](#-ssh--client--default_options) 339 | * [`match_block`](#-ssh--client--match_block) 340 | 341 | ##### `ssh_config` 342 | 343 | Data type: `Stdlib::Absolutepath` 344 | 345 | Path to ssh client config file 346 | 347 | ##### `client_package_name` 348 | 349 | Data type: `Optional[String[1]]` 350 | 351 | Name of the client package 352 | 353 | Default value: `undef` 354 | 355 | ##### `ensure` 356 | 357 | Data type: `String` 358 | 359 | Ensurable param to ssh client 360 | 361 | Default value: `present` 362 | 363 | ##### `storeconfigs_enabled` 364 | 365 | Data type: `Boolean` 366 | 367 | Collected host keys from servers will be written to known_hosts unless storeconfigs_enabled is false 368 | 369 | Default value: `true` 370 | 371 | ##### `options` 372 | 373 | Data type: `Hash` 374 | 375 | SSH client options, will be deep_merged with default_options. This parameter takes precedence over default_options 376 | 377 | Default value: `{}` 378 | 379 | ##### `use_augeas` 380 | 381 | Data type: `Boolean` 382 | 383 | Use augeas to configure ssh client 384 | 385 | Default value: `false` 386 | 387 | ##### `options_absent` 388 | 389 | Data type: `Array` 390 | 391 | Remove options (with augeas style) 392 | 393 | Default value: `[]` 394 | 395 | ##### `default_options` 396 | 397 | Data type: `Hash` 398 | 399 | Default options to set, will be merged with options parameter 400 | 401 | ##### `match_block` 402 | 403 | Data type: `Hash` 404 | 405 | Add ssh match_block (with concat) 406 | 407 | Default value: `{}` 408 | 409 | ### `ssh::hostkeys` 410 | 411 | This class manages hostkeys 412 | 413 | #### Parameters 414 | 415 | The following parameters are available in the `ssh::hostkeys` class: 416 | 417 | * [`export_ipaddresses`](#-ssh--hostkeys--export_ipaddresses) 418 | * [`storeconfigs_group`](#-ssh--hostkeys--storeconfigs_group) 419 | * [`extra_aliases`](#-ssh--hostkeys--extra_aliases) 420 | * [`exclude_interfaces`](#-ssh--hostkeys--exclude_interfaces) 421 | * [`exclude_interfaces_re`](#-ssh--hostkeys--exclude_interfaces_re) 422 | * [`exclude_ipaddresses`](#-ssh--hostkeys--exclude_ipaddresses) 423 | * [`use_trusted_facts`](#-ssh--hostkeys--use_trusted_facts) 424 | * [`tags`](#-ssh--hostkeys--tags) 425 | 426 | ##### `export_ipaddresses` 427 | 428 | Data type: `Boolean` 429 | 430 | Whether ip addresses should be added as aliases 431 | 432 | Default value: `true` 433 | 434 | ##### `storeconfigs_group` 435 | 436 | Data type: `Optional[String[1]]` 437 | 438 | Tag hostkeys with this group to allow segregation 439 | 440 | Default value: `undef` 441 | 442 | ##### `extra_aliases` 443 | 444 | Data type: `Array` 445 | 446 | Additional aliases to set for host keys 447 | 448 | Default value: `[]` 449 | 450 | ##### `exclude_interfaces` 451 | 452 | Data type: `Array` 453 | 454 | List of interfaces to exclude 455 | 456 | Default value: `[]` 457 | 458 | ##### `exclude_interfaces_re` 459 | 460 | Data type: `Array` 461 | 462 | List of regular expressions to exclude interfaces 463 | 464 | Default value: `[]` 465 | 466 | ##### `exclude_ipaddresses` 467 | 468 | Data type: `Array` 469 | 470 | List of ip addresses to exclude 471 | 472 | Default value: `[]` 473 | 474 | ##### `use_trusted_facts` 475 | 476 | Data type: `Boolean` 477 | 478 | Whether to use trusted or normal facts 479 | 480 | Default value: `false` 481 | 482 | ##### `tags` 483 | 484 | Data type: `Optional[Array[String[1]]]` 485 | 486 | Array of custom tags 487 | 488 | Default value: `undef` 489 | 490 | ### `ssh::knownhosts` 491 | 492 | This class manages knownhosts if collection is enabled. 493 | 494 | #### Parameters 495 | 496 | The following parameters are available in the `ssh::knownhosts` class: 497 | 498 | * [`collect_enabled`](#-ssh--knownhosts--collect_enabled) 499 | * [`storeconfigs_group`](#-ssh--knownhosts--storeconfigs_group) 500 | 501 | ##### `collect_enabled` 502 | 503 | Data type: `Boolean` 504 | 505 | Enable collection 506 | 507 | Default value: `$ssh::knownhosts::collect_enabled` 508 | 509 | ##### `storeconfigs_group` 510 | 511 | Data type: `Optional[String[1]]` 512 | 513 | Define the hostkeys group storage 514 | 515 | Default value: `undef` 516 | 517 | ### `ssh::server` 518 | 519 | This class managed ssh server 520 | 521 | #### Examples 522 | 523 | ##### Puppet usage 524 | 525 | ```puppet 526 | class { 'ssh::server': 527 | ensure => present, 528 | storeconfigs_enabled => true, 529 | use_issue_net => false, 530 | } 531 | ``` 532 | 533 | #### Parameters 534 | 535 | The following parameters are available in the `ssh::server` class: 536 | 537 | * [`service_name`](#-ssh--server--service_name) 538 | * [`sshd_config`](#-ssh--server--sshd_config) 539 | * [`sshd_dir`](#-ssh--server--sshd_dir) 540 | * [`sshd_binary`](#-ssh--server--sshd_binary) 541 | * [`sshd_config_mode`](#-ssh--server--sshd_config_mode) 542 | * [`host_priv_key_group`](#-ssh--server--host_priv_key_group) 543 | * [`default_options`](#-ssh--server--default_options) 544 | * [`ensure`](#-ssh--server--ensure) 545 | * [`include_dir`](#-ssh--server--include_dir) 546 | * [`include_dir_mode`](#-ssh--server--include_dir_mode) 547 | * [`include_dir_purge`](#-ssh--server--include_dir_purge) 548 | * [`config_files`](#-ssh--server--config_files) 549 | * [`storeconfigs_enabled`](#-ssh--server--storeconfigs_enabled) 550 | * [`options`](#-ssh--server--options) 551 | * [`validate_sshd_file`](#-ssh--server--validate_sshd_file) 552 | * [`use_augeas`](#-ssh--server--use_augeas) 553 | * [`options_absent`](#-ssh--server--options_absent) 554 | * [`match_block`](#-ssh--server--match_block) 555 | * [`use_issue_net`](#-ssh--server--use_issue_net) 556 | * [`sshd_environments_file`](#-ssh--server--sshd_environments_file) 557 | * [`server_package_name`](#-ssh--server--server_package_name) 558 | 559 | ##### `service_name` 560 | 561 | Data type: `String[1]` 562 | 563 | Name of the sshd service 564 | 565 | ##### `sshd_config` 566 | 567 | Data type: `Stdlib::Absolutepath` 568 | 569 | Path to the sshd_config file 570 | 571 | ##### `sshd_dir` 572 | 573 | Data type: `Stdlib::Absolutepath` 574 | 575 | Path to the sshd dir (e.g. /etc/ssh) 576 | 577 | ##### `sshd_binary` 578 | 579 | Data type: `Stdlib::Absolutepath` 580 | 581 | Path to the sshd binary 582 | 583 | ##### `sshd_config_mode` 584 | 585 | Data type: `Stdlib::Filemode` 586 | 587 | Mode to set on the sshd config file 588 | 589 | ##### `host_priv_key_group` 590 | 591 | Data type: `Integer` 592 | 593 | Name of the group for the private host key 594 | 595 | ##### `default_options` 596 | 597 | Data type: `Hash` 598 | 599 | Default options to set, will be merged with options parameter 600 | 601 | ##### `ensure` 602 | 603 | Data type: `Enum[present,absent,latest]` 604 | 605 | Ensurable param to ssh server 606 | 607 | Default value: `present` 608 | 609 | ##### `include_dir` 610 | 611 | Data type: `Optional[Stdlib::Absolutepath]` 612 | 613 | Path to sshd include directory. 614 | 615 | Default value: `undef` 616 | 617 | ##### `include_dir_mode` 618 | 619 | Data type: `Stdlib::Filemode` 620 | 621 | Mode to set on the sshd include directory. 622 | 623 | Default value: `'0700'` 624 | 625 | ##### `include_dir_purge` 626 | 627 | Data type: `Boolean` 628 | 629 | Purge the include directory if true. 630 | 631 | Default value: `true` 632 | 633 | ##### `config_files` 634 | 635 | Data type: `Hash[String, Hash]` 636 | 637 | Hash of config files to add to the ssh include directory. 638 | 639 | Default value: `{}` 640 | 641 | ##### `storeconfigs_enabled` 642 | 643 | Data type: `Boolean` 644 | 645 | Host keys will be collected and distributed unless storeconfigs_enabled is false. 646 | 647 | Default value: `true` 648 | 649 | ##### `options` 650 | 651 | Data type: `Hash` 652 | 653 | Dynamic hash for openssh server option 654 | 655 | Default value: `{}` 656 | 657 | ##### `validate_sshd_file` 658 | 659 | Data type: `Boolean` 660 | 661 | Add sshd file validate cmd 662 | 663 | Default value: `false` 664 | 665 | ##### `use_augeas` 666 | 667 | Data type: `Boolean` 668 | 669 | Use augeas for configuration (default concat) 670 | 671 | Default value: `false` 672 | 673 | ##### `options_absent` 674 | 675 | Data type: `Array` 676 | 677 | Remove options (with augeas style) 678 | 679 | Default value: `[]` 680 | 681 | ##### `match_block` 682 | 683 | Data type: `Hash` 684 | 685 | Add sshd match_block (with concat) 686 | 687 | Default value: `{}` 688 | 689 | ##### `use_issue_net` 690 | 691 | Data type: `Boolean` 692 | 693 | Add issue_net banner 694 | 695 | Default value: `false` 696 | 697 | ##### `sshd_environments_file` 698 | 699 | Data type: `Optional[Stdlib::Absolutepath]` 700 | 701 | Path to a sshd environments file (e.g. /etc/defaults/ssh on Debian) 702 | 703 | Default value: `undef` 704 | 705 | ##### `server_package_name` 706 | 707 | Data type: `Optional[String[1]]` 708 | 709 | Name of the server package to install 710 | 711 | Default value: `undef` 712 | 713 | ## Defined types 714 | 715 | ### `ssh::client::config::user` 716 | 717 | Copyright (c) IN2P3 Computing Centre, IN2P3, CNRS 718 | Contributor: Remi Ferrand (2015) 719 | Contributor: Tim Meusel (2017) 720 | 721 | #### Parameters 722 | 723 | The following parameters are available in the `ssh::client::config::user` defined type: 724 | 725 | * [`ensure`](#-ssh--client--config--user--ensure) 726 | * [`target`](#-ssh--client--config--user--target) 727 | * [`user_home_dir`](#-ssh--client--config--user--user_home_dir) 728 | * [`manage_user_ssh_dir`](#-ssh--client--config--user--manage_user_ssh_dir) 729 | * [`options`](#-ssh--client--config--user--options) 730 | * [`user`](#-ssh--client--config--user--user) 731 | * [`ssh_directory_default_mode`](#-ssh--client--config--user--ssh_directory_default_mode) 732 | * [`ssh_config_default_mode`](#-ssh--client--config--user--ssh_config_default_mode) 733 | 734 | ##### `ensure` 735 | 736 | Data type: `Enum['present', 'absent']` 737 | 738 | Specifies whether the config file should be present or absent 739 | 740 | Default value: `present` 741 | 742 | ##### `target` 743 | 744 | Data type: `Optional[Stdlib::Absolutepath]` 745 | 746 | Sets the config file location, defaults to `~/.ssh/config` if $target and $user_home_dir are not set 747 | 748 | Default value: `undef` 749 | 750 | ##### `user_home_dir` 751 | 752 | Data type: `Optional[Stdlib::Absolutepath]` 753 | 754 | Sets the location of users home dir, defaults to `/home/$user` 755 | 756 | Default value: `undef` 757 | 758 | ##### `manage_user_ssh_dir` 759 | 760 | Data type: `Boolean` 761 | 762 | Whether the users ssh dir should be managed or not 763 | 764 | Default value: `true` 765 | 766 | ##### `options` 767 | 768 | Data type: `Hash` 769 | 770 | Options which should be set 771 | 772 | Default value: `{}` 773 | 774 | ##### `user` 775 | 776 | Data type: `String[1]` 777 | 778 | The name of the user the config should be managed for 779 | 780 | Default value: `$name` 781 | 782 | ##### `ssh_directory_default_mode` 783 | 784 | Data type: `String[1]` 785 | 786 | Default mode for the users ssh dir 787 | 788 | Default value: `'0700'` 789 | 790 | ##### `ssh_config_default_mode` 791 | 792 | Data type: `String[1]` 793 | 794 | Default mode for the ssh config file 795 | 796 | Default value: `'0600'` 797 | 798 | ### `ssh::client::match_block` 799 | 800 | Add match_block to ssh client config (concat needed) 801 | 802 | #### Parameters 803 | 804 | The following parameters are available in the `ssh::client::match_block` defined type: 805 | 806 | * [`options`](#-ssh--client--match_block--options) 807 | * [`type`](#-ssh--client--match_block--type) 808 | * [`order`](#-ssh--client--match_block--order) 809 | * [`target`](#-ssh--client--match_block--target) 810 | 811 | ##### `options` 812 | 813 | Data type: `Hash` 814 | 815 | Options which should be set 816 | 817 | Default value: `{}` 818 | 819 | ##### `type` 820 | 821 | Data type: `Ssh::ClientMatch` 822 | 823 | Type of match_block, e.g. user, group, host, ... 824 | 825 | Default value: `'user'` 826 | 827 | ##### `order` 828 | 829 | Data type: `Integer` 830 | 831 | Orders your settings within the config file 832 | 833 | Default value: `50` 834 | 835 | ##### `target` 836 | 837 | Data type: `Stdlib::Absolutepath` 838 | 839 | Sets the target file of the concat fragment 840 | 841 | Default value: `$ssh::client::ssh_config` 842 | 843 | ### `ssh::server::config::setting` 844 | 845 | Internal define to managed ssh server param 846 | 847 | #### Parameters 848 | 849 | The following parameters are available in the `ssh::server::config::setting` defined type: 850 | 851 | * [`key`](#-ssh--server--config--setting--key) 852 | * [`value`](#-ssh--server--config--setting--value) 853 | * [`order`](#-ssh--server--config--setting--order) 854 | 855 | ##### `key` 856 | 857 | Data type: `String[1]` 858 | 859 | Key of the value which should be set 860 | 861 | ##### `value` 862 | 863 | Data type: `Variant[Boolean, Array, Hash, String]` 864 | 865 | Value which should be set 866 | 867 | ##### `order` 868 | 869 | Data type: `Variant[String[1], Integer]` 870 | 871 | Orders your setting within the config file 872 | 873 | Default value: `'10'` 874 | 875 | ### `ssh::server::config_file` 876 | 877 | Resource type for managing a config file in the include dir. 878 | 879 | #### Parameters 880 | 881 | The following parameters are available in the `ssh::server::config_file` defined type: 882 | 883 | * [`mode`](#-ssh--server--config_file--mode) 884 | * [`include`](#-ssh--server--config_file--include) 885 | * [`options`](#-ssh--server--config_file--options) 886 | * [`path`](#-ssh--server--config_file--path) 887 | 888 | ##### `mode` 889 | 890 | Data type: `Stdlib::Filemode` 891 | 892 | File mode for the config file. 893 | 894 | Default value: `$ssh::server::sshd_config_mode` 895 | 896 | ##### `include` 897 | 898 | Data type: `Optional[Stdlib::Absolutepath]` 899 | 900 | Absolute path to config file to include at the top of the config file. This 901 | is intended for including files not managed by this module (crypto policies). 902 | 903 | Default value: `undef` 904 | 905 | ##### `options` 906 | 907 | Data type: `Hash` 908 | 909 | Dynamic hash for openssh server option 910 | 911 | Default value: `{}` 912 | 913 | ##### `path` 914 | 915 | Data type: `Stdlib::Absolutepath` 916 | 917 | 918 | 919 | Default value: `"${ssh::server::include_dir}/${name}.conf"` 920 | 921 | ### `ssh::server::host_key` 922 | 923 | Manage a ssh host key 924 | 925 | This module install a ssh host key in the server (basically, it is 926 | a file resource but it also notifies to the ssh service) 927 | 928 | Important! This define does not modify any option in sshd_config, so 929 | you have to manually define the HostKey option in the server options 930 | if you haven't done yet. 931 | 932 | #### Parameters 933 | 934 | The following parameters are available in the `ssh::server::host_key` defined type: 935 | 936 | * [`ensure`](#-ssh--server--host_key--ensure) 937 | * [`public_key_source`](#-ssh--server--host_key--public_key_source) 938 | * [`public_key_content`](#-ssh--server--host_key--public_key_content) 939 | * [`private_key_source`](#-ssh--server--host_key--private_key_source) 940 | * [`private_key_content`](#-ssh--server--host_key--private_key_content) 941 | * [`certificate_source`](#-ssh--server--host_key--certificate_source) 942 | * [`certificate_content`](#-ssh--server--host_key--certificate_content) 943 | 944 | ##### `ensure` 945 | 946 | Data type: `Enum[present, absent]` 947 | 948 | Set to 'absent' to remove host_key files 949 | 950 | Default value: `'present'` 951 | 952 | ##### `public_key_source` 953 | 954 | Data type: `Optional[String[1]]` 955 | 956 | Sets the content of the source parameter for the public key file 957 | Note public_key_source and public_key_content are mutually exclusive. 958 | 959 | Default value: `undef` 960 | 961 | ##### `public_key_content` 962 | 963 | Data type: `Optional[String[1]]` 964 | 965 | Sets the content for the public key file. 966 | Note public_key_source and public_key_content are mutually exclusive. 967 | 968 | Default value: `undef` 969 | 970 | ##### `private_key_source` 971 | 972 | Data type: `Optional[String[1]]` 973 | 974 | Sets the content of the source parameter for the private key file 975 | Note private_key_source and private_key_content are mutually exclusive. 976 | 977 | Default value: `undef` 978 | 979 | ##### `private_key_content` 980 | 981 | Data type: `Optional[String[1]]` 982 | 983 | Sets the content for the private key file. 984 | Note private_key_source and private_key_content are mutually exclusive. 985 | 986 | Default value: `undef` 987 | 988 | ##### `certificate_source` 989 | 990 | Data type: `Optional[String[1]]` 991 | 992 | Sets the content of the source parameter for the host key certificate. 993 | Note certificate_source and certificate_content are mutually exclusive. 994 | 995 | Default value: `undef` 996 | 997 | ##### `certificate_content` 998 | 999 | Data type: `Optional[String[1]]` 1000 | 1001 | Sets the content for the host key certificate. 1002 | Note certificate_source and certificate_content are mutually exclusive. 1003 | 1004 | Default value: `undef` 1005 | 1006 | ### `ssh::server::instances` 1007 | 1008 | Configure separate ssh server instances 1009 | 1010 | #### Parameters 1011 | 1012 | The following parameters are available in the `ssh::server::instances` defined type: 1013 | 1014 | * [`ensure`](#-ssh--server--instances--ensure) 1015 | * [`options`](#-ssh--server--instances--options) 1016 | * [`service_ensure`](#-ssh--server--instances--service_ensure) 1017 | * [`service_enable`](#-ssh--server--instances--service_enable) 1018 | * [`validate_config_file`](#-ssh--server--instances--validate_config_file) 1019 | * [`sshd_instance_config_file`](#-ssh--server--instances--sshd_instance_config_file) 1020 | * [`sshd_binary`](#-ssh--server--instances--sshd_binary) 1021 | * [`sshd_environments_file`](#-ssh--server--instances--sshd_environments_file) 1022 | 1023 | ##### `ensure` 1024 | 1025 | Data type: `Enum[present, absent]` 1026 | 1027 | Specifies whether the instance should be added or removed 1028 | 1029 | Default value: `present` 1030 | 1031 | ##### `options` 1032 | 1033 | Data type: `Hash` 1034 | 1035 | Set options for the instance 1036 | 1037 | Default value: `{}` 1038 | 1039 | ##### `service_ensure` 1040 | 1041 | Data type: `Stdlib::Ensure::Service` 1042 | 1043 | Whether this instance service should be running or stopped, defaults to true when ensure is set to present, otherwise false 1044 | 1045 | Default value: `$ensure ? { 'present' => 'running', 'absent' => 'stopped'` 1046 | 1047 | ##### `service_enable` 1048 | 1049 | Data type: `Boolean` 1050 | 1051 | Whether this instance service should be started at boot. Will be added automatically if ensure is running/removed if ensure is stopped 1052 | 1053 | Default value: `($service_ensure == 'running'` 1054 | 1055 | ##### `validate_config_file` 1056 | 1057 | Data type: `Boolean` 1058 | 1059 | Validate config file before applying 1060 | 1061 | Default value: `false` 1062 | 1063 | ##### `sshd_instance_config_file` 1064 | 1065 | Data type: `Stdlib::Absolutepath` 1066 | 1067 | Path of the instance sshd config 1068 | 1069 | Default value: `"${ssh::server::sshd_dir}/sshd_config.${title}"` 1070 | 1071 | ##### `sshd_binary` 1072 | 1073 | Data type: `Stdlib::Absolutepath` 1074 | 1075 | Path to sshd binary 1076 | 1077 | Default value: `$ssh::server::sshd_binary` 1078 | 1079 | ##### `sshd_environments_file` 1080 | 1081 | Data type: `Optional[Stdlib::Absolutepath]` 1082 | 1083 | Path to environments file, if any 1084 | 1085 | Default value: `$ssh::server::sshd_environments_file` 1086 | 1087 | ### `ssh::server::match_block` 1088 | 1089 | Add match_block to ssh server config 1090 | 1091 | #### Parameters 1092 | 1093 | The following parameters are available in the `ssh::server::match_block` defined type: 1094 | 1095 | * [`options`](#-ssh--server--match_block--options) 1096 | * [`type`](#-ssh--server--match_block--type) 1097 | * [`order`](#-ssh--server--match_block--order) 1098 | * [`target`](#-ssh--server--match_block--target) 1099 | 1100 | ##### `options` 1101 | 1102 | Data type: `Hash` 1103 | 1104 | Options which should be set 1105 | 1106 | Default value: `{}` 1107 | 1108 | ##### `type` 1109 | 1110 | Data type: `String[1]` 1111 | 1112 | Type of match_block, e.g. user, group, host, ... 1113 | 1114 | Default value: `'user'` 1115 | 1116 | ##### `order` 1117 | 1118 | Data type: `Integer` 1119 | 1120 | Orders your settings within the config file 1121 | 1122 | Default value: `50` 1123 | 1124 | ##### `target` 1125 | 1126 | Data type: `Stdlib::Absolutepath` 1127 | 1128 | Sets the target file of the concat fragment 1129 | 1130 | Default value: `$ssh::server::sshd_config` 1131 | 1132 | ### `ssh::server::options` 1133 | 1134 | This defined type manages ssh server options 1135 | 1136 | #### Parameters 1137 | 1138 | The following parameters are available in the `ssh::server::options` defined type: 1139 | 1140 | * [`options`](#-ssh--server--options--options) 1141 | * [`order`](#-ssh--server--options--order) 1142 | 1143 | ##### `options` 1144 | 1145 | Data type: `Hash` 1146 | 1147 | Options which should be set 1148 | 1149 | Default value: `{}` 1150 | 1151 | ##### `order` 1152 | 1153 | Data type: `Integer` 1154 | 1155 | Orders your settings within the config file 1156 | 1157 | Default value: `50` 1158 | 1159 | ## Functions 1160 | 1161 | ### `sshclient_options_to_augeas_ssh_config` 1162 | 1163 | Type: Ruby 3.x API 1164 | 1165 | This function will convert a key-value hash to a format understandable by the augeas sshd_config provider 1166 | It will also optionally deal with keys that should be absent, and inject static parameters if supplied. 1167 | 1168 | Usage: sshclient_options_to_augeas_ssh_config($options_present, $options_absent, $other_parameters) 1169 | - $options_hash is mandatory and must be a hash. 1170 | - $options_absent is optional and can be either a single value or an array. 1171 | - $other_parameters is optional and must be a hash. 1172 | 1173 | Example: 1174 | $options = { 1175 | 'Host *.example.com' => { 1176 | 'ForwardAgent' => 'yes', 1177 | 'BatchMode' => 'yes', 1178 | }, 1179 | 'ForwardAgent' => 'no', 1180 | 'BatchMode' => 'no', 1181 | 'StrictHostKeyChecking' => 'no', 1182 | } 1183 | $options_absent = ['StrictHostKeyChecking','NoneField'] 1184 | $other_parameters = { 'target' => '/etc/ssh/ssh_config' } 1185 | 1186 | $options_final_augeas = sshclient_options_to_augeas_ssh_config($options, $options_absent, $other_parameters) 1187 | 1188 | In this case, the value of $options_final_augeas would be: 1189 | 1190 | 'ForwardAgent *.example.com' => { 1191 | 'ensure' => 'present', 1192 | 'host' => '*.example.com', 1193 | 'key' => 'ForwardAgent', 1194 | 'value' => 'yes', 1195 | 'target' => '/etc/ssh/ssh_config', 1196 | } 1197 | 'BatchMode *.example.com' => { 1198 | 'ensure' => 'present', 1199 | 'host' => '*.example.com', 1200 | 'key' => 'BatchMode', 1201 | 'value' => 'yes', 1202 | 'target' => '/etc/ssh/ssh_config', 1203 | } 1204 | 'ForwardAgent' => { 1205 | 'ensure' => 'present', 1206 | 'key' => 'ForwardAgent', 1207 | 'value' => 'yes', 1208 | 'target' => '/etc/ssh/ssh_config', 1209 | } 1210 | 'BatchMode' => { 1211 | 'ensure' => 'present', 1212 | 'key' => 'BatchMode', 1213 | 'value' => 'yes', 1214 | 'target' => '/etc/ssh/ssh_config', 1215 | } 1216 | 'StrictHostKeyChecking' => { 1217 | 'ensure' => 'absent', 1218 | 'key' => 'StrictHostKeyChecking', 1219 | 'target' => '/etc/ssh/ssh_config', 1220 | } 1221 | 'NoneField' => { 1222 | 'ensure' => 'absent', 1223 | 'key' => 'NoneField', 1224 | 'target' => '/etc/ssh/ssh_config', 1225 | } 1226 | 1227 | Note how the word "Host" is stripped a 1228 | 1229 | #### `sshclient_options_to_augeas_ssh_config()` 1230 | 1231 | This function will convert a key-value hash to a format understandable by the augeas sshd_config provider 1232 | It will also optionally deal with keys that should be absent, and inject static parameters if supplied. 1233 | 1234 | Usage: sshclient_options_to_augeas_ssh_config($options_present, $options_absent, $other_parameters) 1235 | - $options_hash is mandatory and must be a hash. 1236 | - $options_absent is optional and can be either a single value or an array. 1237 | - $other_parameters is optional and must be a hash. 1238 | 1239 | Example: 1240 | $options = { 1241 | 'Host *.example.com' => { 1242 | 'ForwardAgent' => 'yes', 1243 | 'BatchMode' => 'yes', 1244 | }, 1245 | 'ForwardAgent' => 'no', 1246 | 'BatchMode' => 'no', 1247 | 'StrictHostKeyChecking' => 'no', 1248 | } 1249 | $options_absent = ['StrictHostKeyChecking','NoneField'] 1250 | $other_parameters = { 'target' => '/etc/ssh/ssh_config' } 1251 | 1252 | $options_final_augeas = sshclient_options_to_augeas_ssh_config($options, $options_absent, $other_parameters) 1253 | 1254 | In this case, the value of $options_final_augeas would be: 1255 | 1256 | 'ForwardAgent *.example.com' => { 1257 | 'ensure' => 'present', 1258 | 'host' => '*.example.com', 1259 | 'key' => 'ForwardAgent', 1260 | 'value' => 'yes', 1261 | 'target' => '/etc/ssh/ssh_config', 1262 | } 1263 | 'BatchMode *.example.com' => { 1264 | 'ensure' => 'present', 1265 | 'host' => '*.example.com', 1266 | 'key' => 'BatchMode', 1267 | 'value' => 'yes', 1268 | 'target' => '/etc/ssh/ssh_config', 1269 | } 1270 | 'ForwardAgent' => { 1271 | 'ensure' => 'present', 1272 | 'key' => 'ForwardAgent', 1273 | 'value' => 'yes', 1274 | 'target' => '/etc/ssh/ssh_config', 1275 | } 1276 | 'BatchMode' => { 1277 | 'ensure' => 'present', 1278 | 'key' => 'BatchMode', 1279 | 'value' => 'yes', 1280 | 'target' => '/etc/ssh/ssh_config', 1281 | } 1282 | 'StrictHostKeyChecking' => { 1283 | 'ensure' => 'absent', 1284 | 'key' => 'StrictHostKeyChecking', 1285 | 'target' => '/etc/ssh/ssh_config', 1286 | } 1287 | 'NoneField' => { 1288 | 'ensure' => 'absent', 1289 | 'key' => 'NoneField', 1290 | 'target' => '/etc/ssh/ssh_config', 1291 | } 1292 | 1293 | Note how the word "Host" is stripped a 1294 | 1295 | Returns: `Any` 1296 | 1297 | ### `sshserver_options_to_augeas_sshd_config` 1298 | 1299 | Type: Ruby 3.x API 1300 | 1301 | This function will convert a key-value hash to a format understandable by the augeas sshd_config provider 1302 | It will also optionally deal with keys that should be absent, and inject static parameters if supplied. 1303 | 1304 | Usage: sshserver_options_to_augeas_sshd_config($options_present, $options_absent, $other_parameters) 1305 | - $options_hash is mandatory and must be a hash. 1306 | - $options_absent is optional and can be either a single value or an array. 1307 | - $other_parameters is optional and must be a hash. 1308 | 1309 | Example: 1310 | $options = { 1311 | 'Match User www-data' => { 1312 | 'PasswordAuthentication' => 'yes', 1313 | 'X11Forwarding' => 'no', 1314 | }, 1315 | 'Match Group bamboo' => { 1316 | 'ForcedCommand' => '/bin/echo hello world', 1317 | }, 1318 | 'X11Forwarding' => 'yes', 1319 | 'DebianBanner' => '/etc/banner.net', 1320 | 'AllowGroups' => ["sshgroups", "admins"], 1321 | } 1322 | $options_absent = ['DebianBanner','NoneField'] 1323 | $other_parameters = { 'target' => '/etc/ssh/sshd_config' } 1324 | 1325 | $options_final_augeas = sshserver_options_to_augeas_sshd_config($options, $options_absent, $other_parameters) 1326 | 1327 | In this case, the value of $options_final_augeas would be: 1328 | 1329 | 'PasswordAuthentication User www-data' => { 1330 | 'ensure' => 'present', 1331 | 'condition' => 'User www-data', 1332 | 'key' => 'PasswordAuthentication', 1333 | 'value' => 'yes', 1334 | 'target' => '/etc/ssh/sshd_config', 1335 | } 1336 | 'X11Forwarding User www-data' => { 1337 | 'ensure' => 'present', 1338 | 'condition' => 'User www-data', 1339 | 'key' => 'X11Forwarding', 1340 | 'value' => 'no', 1341 | 'target' => '/etc/ssh/sshd_config', 1342 | } 1343 | 'ForcedCommand Group bamboo' => { 1344 | 'ensure' => 'present', 1345 | 'condition' => 'Group bamboo', 1346 | 'key' => 'ForcedCommand', 1347 | 'value' => '/bin/echo hello world', 1348 | 'target' => '/etc/ssh/sshd_config', 1349 | } 1350 | 'X11Forwarding' => { 1351 | 'ensure' => 'present', 1352 | 'key' => 'X11Forwarding', 1353 | 'value' => 'yes', 1354 | 'target' => '/etc/ssh/sshd_config', 1355 | } 1356 | 'DebianBanner' => { 1357 | 'ensure' => 'absent', 1358 | 'key' => 'DebianBanner', 1359 | 'target' => '/etc/ssh/sshd_config', 1360 | } 1361 | 'AllowGroups' => { 1362 | 'ensure' => 'present', 1363 | 'key' => 'AllowGroups', 1364 | 'value' => ['sshgroups','admins'], 1365 | 'target' => '/etc/ssh/sshd_config', 1366 | } 1367 | 'NoneField' => { 1368 | 'ensure' => 'absent', 1369 | 'key' => 'NoneField', 1370 | 'target' => '/etc/ssh/sshd_config', 1371 | } 1372 | 1373 | Note how the word "Match" is stripped a 1374 | 1375 | #### `sshserver_options_to_augeas_sshd_config()` 1376 | 1377 | This function will convert a key-value hash to a format understandable by the augeas sshd_config provider 1378 | It will also optionally deal with keys that should be absent, and inject static parameters if supplied. 1379 | 1380 | Usage: sshserver_options_to_augeas_sshd_config($options_present, $options_absent, $other_parameters) 1381 | - $options_hash is mandatory and must be a hash. 1382 | - $options_absent is optional and can be either a single value or an array. 1383 | - $other_parameters is optional and must be a hash. 1384 | 1385 | Example: 1386 | $options = { 1387 | 'Match User www-data' => { 1388 | 'PasswordAuthentication' => 'yes', 1389 | 'X11Forwarding' => 'no', 1390 | }, 1391 | 'Match Group bamboo' => { 1392 | 'ForcedCommand' => '/bin/echo hello world', 1393 | }, 1394 | 'X11Forwarding' => 'yes', 1395 | 'DebianBanner' => '/etc/banner.net', 1396 | 'AllowGroups' => ["sshgroups", "admins"], 1397 | } 1398 | $options_absent = ['DebianBanner','NoneField'] 1399 | $other_parameters = { 'target' => '/etc/ssh/sshd_config' } 1400 | 1401 | $options_final_augeas = sshserver_options_to_augeas_sshd_config($options, $options_absent, $other_parameters) 1402 | 1403 | In this case, the value of $options_final_augeas would be: 1404 | 1405 | 'PasswordAuthentication User www-data' => { 1406 | 'ensure' => 'present', 1407 | 'condition' => 'User www-data', 1408 | 'key' => 'PasswordAuthentication', 1409 | 'value' => 'yes', 1410 | 'target' => '/etc/ssh/sshd_config', 1411 | } 1412 | 'X11Forwarding User www-data' => { 1413 | 'ensure' => 'present', 1414 | 'condition' => 'User www-data', 1415 | 'key' => 'X11Forwarding', 1416 | 'value' => 'no', 1417 | 'target' => '/etc/ssh/sshd_config', 1418 | } 1419 | 'ForcedCommand Group bamboo' => { 1420 | 'ensure' => 'present', 1421 | 'condition' => 'Group bamboo', 1422 | 'key' => 'ForcedCommand', 1423 | 'value' => '/bin/echo hello world', 1424 | 'target' => '/etc/ssh/sshd_config', 1425 | } 1426 | 'X11Forwarding' => { 1427 | 'ensure' => 'present', 1428 | 'key' => 'X11Forwarding', 1429 | 'value' => 'yes', 1430 | 'target' => '/etc/ssh/sshd_config', 1431 | } 1432 | 'DebianBanner' => { 1433 | 'ensure' => 'absent', 1434 | 'key' => 'DebianBanner', 1435 | 'target' => '/etc/ssh/sshd_config', 1436 | } 1437 | 'AllowGroups' => { 1438 | 'ensure' => 'present', 1439 | 'key' => 'AllowGroups', 1440 | 'value' => ['sshgroups','admins'], 1441 | 'target' => '/etc/ssh/sshd_config', 1442 | } 1443 | 'NoneField' => { 1444 | 'ensure' => 'absent', 1445 | 'key' => 'NoneField', 1446 | 'target' => '/etc/ssh/sshd_config', 1447 | } 1448 | 1449 | Note how the word "Match" is stripped a 1450 | 1451 | Returns: `Any` 1452 | 1453 | ## Data types 1454 | 1455 | ### `Ssh::ClientMatch` 1456 | 1457 | OpenSSH client `Match` criteria. See `ssh_config(5)` 1458 | 1459 | Alias of `Enum['!all', 'all', '!canonical', 'canonical', '!exec', 'exec', '!final', 'final', '!host', 'host', '!localuser', 'localuser', '!originalhost', 'originalhost', '!user', 'user']` 1460 | 1461 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Managed by modulesync - DO NOT EDIT 2 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 3 | 4 | # Attempt to load voxpupuli-test (which pulls in puppetlabs_spec_helper), 5 | # otherwise attempt to load it directly. 6 | begin 7 | require 'voxpupuli/test/rake' 8 | rescue LoadError 9 | begin 10 | require 'puppetlabs_spec_helper/rake_tasks' 11 | rescue LoadError 12 | end 13 | end 14 | 15 | # load optional tasks for acceptance 16 | # only available if gem group releases is installed 17 | begin 18 | require 'voxpupuli/acceptance/rake' 19 | rescue LoadError 20 | end 21 | 22 | # load optional tasks for releases 23 | # only available if gem group releases is installed 24 | begin 25 | require 'voxpupuli/release/rake_tasks' 26 | rescue LoadError 27 | # voxpupuli-release not present 28 | else 29 | GCGConfig.user = 'saz' 30 | GCGConfig.project = 'puppet-ssh' 31 | end 32 | 33 | desc "Run main 'test' task and report merged results to coveralls" 34 | task test_with_coveralls: [:test] do 35 | if Dir.exist?(File.expand_path('../lib', __FILE__)) 36 | require 'coveralls/rake/task' 37 | Coveralls::RakeTask.new 38 | Rake::Task['coveralls:push'].invoke 39 | else 40 | puts 'Skipping reporting to coveralls. Module has no lib dir' 41 | end 42 | end 43 | 44 | # vim: syntax=ruby 45 | -------------------------------------------------------------------------------- /data/AIX.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh::server::sshd_dir: '/etc/ssh' 3 | ssh::server::sshd_binary: '/usr/sbin/sshd' 4 | ssh::server::sshd_config: '/etc/ssh/sshd_config' 5 | ssh::server::sshd_config_mode: '0644' 6 | ssh::server::ssh_config: '/etc/ssh/ssh_config' 7 | ssh::server::ssh_known_hosts: '/etc/ssh/ssh_known_hosts' 8 | ssh::server::service_name: 'sshd' 9 | ssh::sftp_server_path: '/usr/sbin/sftp-server' 10 | ssh::server::host_priv_key_group: 0 11 | ssh::server::default_options: 12 | AcceptEnv: 'LANG LC_*' 13 | ChallengeResponseAuthentication: 'no' 14 | PrintMotd: 'no' 15 | Subsystem: "sftp %{lookup('ssh::sftp_server_path')}" 16 | UsePAM: 'no' 17 | X11Forwarding: 'yes' 18 | -------------------------------------------------------------------------------- /data/Amazon.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh::server::server_package_name: 'openssh-server' 3 | ssh::client::client_package_name: 'openssh-clients' 4 | ssh::server::sshd_dir: '/etc/ssh' 5 | ssh::server::sshd_binary: '/usr/sbin/sshd' 6 | ssh::server::sshd_environments_file: '/etc/sysconfig/sshd' 7 | ssh::server::sshd_config: '/etc/ssh/sshd_config' 8 | ssh::client::ssh_config: '/etc/ssh/ssh_config' 9 | ssh::server::service_name: 'sshd' 10 | ssh::sftp_server_path: '/usr/libexec/openssh/sftp-server' 11 | ssh::server::host_priv_key_group: 0 12 | -------------------------------------------------------------------------------- /data/Archlinux.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh::server::server_package_name: 'openssh' 3 | ssh::client::client_package_name: 'openssh' 4 | ssh::server::sshd_dir: '/etc/ssh' 5 | ssh::server::sshd_binary: '/usr/bin/sshd' 6 | ssh::server::sshd_config: '/etc/ssh/sshd_config' 7 | ssh::client::ssh_config: '/etc/ssh/ssh_config' 8 | ssh::server::service_name: 'sshd.service' 9 | ssh::sftp_server_path: '/usr/lib/ssh/sftp-server' 10 | ssh::server::host_priv_key_group: 0 11 | -------------------------------------------------------------------------------- /data/Darwin.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh::server::sshd_dir: '/etc/ssh' 3 | ssh::server::sshd_config: '/etc/ssh/sshd_config' 4 | ssh::client::ssh_config: '/etc/ssh/ssh_config' 5 | ssh::server::service_name: 'com.openssh.sshd' 6 | ssh::sftp_server_path: '/usr/libexec/sftp-server' 7 | ssh::server::host_priv_key_group: 0 8 | -------------------------------------------------------------------------------- /data/Debian.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh::server::server_package_name: 'openssh-server' 3 | ssh::client::client_package_name: 'openssh-client' 4 | ssh::server::sshd_dir: '/etc/ssh' 5 | ssh::server::sshd_binary: '/usr/sbin/sshd' 6 | ssh::server::sshd_config: '/etc/ssh/sshd_config' 7 | ssh::server::sshd_environments_file: '/etc/default/ssh' 8 | ssh::client::ssh_config: '/etc/ssh/ssh_config' 9 | ssh::server::service_name: 'ssh' 10 | ssh::sftp_server_path: '/usr/lib/openssh/sftp-server' 11 | ssh::server::host_priv_key_group: 0 12 | -------------------------------------------------------------------------------- /data/DragonFly.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh::server::sshd_dir: '/etc/ssh' 3 | ssh::server::sshd_binary: '/usr/local/sbin/sshd' 4 | ssh::server::sshd_config: '/etc/ssh/sshd_config' 5 | ssh::client::ssh_config: '/etc/ssh/ssh_config' 6 | ssh::server::service_name: 'sshd' 7 | ssh::sftp_server_path: '/usr/libexec/sftp-server' 8 | ssh::server::host_priv_key_group: 0 9 | -------------------------------------------------------------------------------- /data/FreeBSD.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh::server::sshd_dir: '/etc/ssh' 3 | ssh::server::sshd_binary: '/usr/local/sbin/sshd' 4 | ssh::server::sshd_config: '/etc/ssh/sshd_config' 5 | ssh::client::ssh_config: '/etc/ssh/ssh_config' 6 | ssh::server::service_name: 'sshd' 7 | ssh::sftp_server_path: '/usr/libexec/sftp-server' 8 | ssh::server::host_priv_key_group: 0 9 | -------------------------------------------------------------------------------- /data/Gentoo.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh::server::server_package_name: 'openssh' 3 | ssh::client::client_package_name: 'openssh' 4 | ssh::server::sshd_dir: '/etc/ssh' 5 | ssh::server::sshd_binary: '/usr/sbin/sshd' 6 | ssh::server::sshd_config: '/etc/ssh/sshd_config' 7 | ssh::client::ssh_config: '/etc/ssh/ssh_config' 8 | ssh::server::service_name: 'sshd' 9 | ssh::sftp_server_path: '/usr/lib64/misc/sftp-server' 10 | ssh::server::host_priv_key_group: 0 11 | -------------------------------------------------------------------------------- /data/OpenBSD.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh::server::sshd_dir: '/etc/ssh' 3 | ssh::server::sshd_binary: '/usr/sbin/sshd' 4 | ssh::server::sshd_config: '/etc/ssh/sshd_config' 5 | ssh::client::ssh_config: '/etc/ssh/ssh_config' 6 | ssh::server::service_name: 'sshd' 7 | ssh::sftp_server_path: '/usr/libexec/sftp-server' 8 | ssh::server::host_priv_key_group: 0 9 | ssh::server::default_options: 10 | ChallengeResponseAuthentication: 'no' 11 | X11Forwarding: 'yes' 12 | PrintMotd: 'no' 13 | AcceptEnv: 'LANG LC_*' 14 | Subsystem: "sftp %{lookup('ssh::sftp_server_path')}" 15 | -------------------------------------------------------------------------------- /data/OpenSuSE.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh::server::service_name: 'sshd' 3 | ssh::sftp_server_path: '/usr/lib/ssh/sftp-server' 4 | -------------------------------------------------------------------------------- /data/RedHat-9.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh::server::include_dir: '/etc/ssh/sshd_config.d' 3 | ssh::server::config_files: 4 | 50-redhat: 5 | include: '/etc/crypto-policies/back-ends/opensshserver.config' 6 | -------------------------------------------------------------------------------- /data/RedHat.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh::server::server_package_name: 'openssh-server' 3 | ssh::client::client_package_name: 'openssh-clients' 4 | ssh::server::sshd_dir: '/etc/ssh' 5 | ssh::server::sshd_binary: '/usr/sbin/sshd' 6 | ssh::server::sshd_config: '/etc/ssh/sshd_config' 7 | ssh::server::sshd_environments_file: '/etc/sysconfig/sshd' 8 | ssh::client::ssh_config: '/etc/ssh/ssh_config' 9 | ssh::server::service_name: 'sshd' 10 | ssh::sftp_server_path: '/usr/libexec/openssh/sftp-server' 11 | ssh::server::host_priv_key_group: 0 12 | -------------------------------------------------------------------------------- /data/SLES-10-x86_64.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh::sftp_server_path: '/usr/lib64/ssh/sftp-server' 3 | -------------------------------------------------------------------------------- /data/SLES-11-x86_64.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh::sftp_server_path: '/usr/lib64/ssh/sftp-server' 3 | -------------------------------------------------------------------------------- /data/SLES.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh::server::service_name: 'sshd' 3 | ssh::sftp_server_path: '/usr/lib/ssh/sftp-server' 4 | -------------------------------------------------------------------------------- /data/SmartOS.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh::server::sshd_dir: '/etc/ssh' 3 | ssh::server::sshd_config: '/etc/ssh/sshd_config' 4 | ssh::client::ssh_config: '/etc/ssh/ssh_config' 5 | ssh::server::service_name: 'svc:/network/ssh:default' 6 | ssh::sftp_server_path: 'internal-sftp' 7 | ssh::server::host_priv_key_group: 0 8 | -------------------------------------------------------------------------------- /data/Solaris-10.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh::server::server_package_name: 'SUNWsshdu' 3 | ssh::client::client_package_name: 'SUNWsshu' 4 | -------------------------------------------------------------------------------- /data/Solaris.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh::server::server_package_name: '/service/network/ssh' 3 | ssh::client::client_package_name: '/network/ssh' 4 | ssh::server::sshd_binary: '/lib/svc/method/sshd' 5 | ssh::server::service_name: 'svc:/network/ssh:default' 6 | ssh::sftp_server_path: 'internal-sftp' 7 | 8 | ssh::server::default_options: 9 | ChallengeResponseAuthentication: 'no' 10 | X11Forwarding: 'yes' 11 | PrintMotd: 'no' 12 | Subsystem: "sftp %{lookup('ssh::sftp_server_path')}" 13 | HostKey: 14 | - "%{lookup('ssh::server::sshd_dir')}/ssh_host_rsa_key" 15 | - "%{lookup('ssh::server::sshd_dir')}/ssh_host_dsa_key" 16 | 17 | ssh::client::default_options: {} 18 | -------------------------------------------------------------------------------- /data/Suse.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ssh::server::server_package_name: 'openssh' 3 | ssh::client::client_package_name: 'openssh' 4 | ssh::server::sshd_dir: '/etc/ssh' 5 | ssh::server::sshd_binary: '/usr/sbin/sshd' 6 | ssh::server::sshd_config: '/etc/ssh/sshd_config' 7 | ssh::server::sshd_environments_file: '/etc/sysconfig/ssh' 8 | ssh::server::service_name: 'sshd' 9 | ssh::client::ssh_config: '/etc/ssh/ssh_config' 10 | ssh::server::host_priv_key_group: 0 11 | -------------------------------------------------------------------------------- /data/common.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | lookup_options: 3 | ssh::server_options: 4 | merge: deep 5 | ssh::server_match_block: 6 | merge: deep 7 | ssh::client_options: 8 | merge: deep 9 | ssh::users_client_options: 10 | merge: deep 11 | ssh::server::options: 12 | merge: deep 13 | ssh::client::options: 14 | merge: deep 15 | ssh::server_instances: 16 | merge: deep 17 | 18 | ssh::server::sshd_dir: '/etc/ssh' 19 | ssh::server::sshd_config: '/etc/ssh/sshd_config' 20 | ssh::server::sshd_config_mode: '0600' 21 | ssh::client::ssh_config: '/etc/ssh/ssh_config' 22 | ssh::server::service_name: 'svc:/network/ssh:default' 23 | ssh::sftp_server_path: 'internal-sftp' 24 | ssh::server::host_priv_key_group: 0 25 | ssh::validate_sshd_file : false 26 | ssh::collect_enabled : true # Collect sshkey resources 27 | ssh::server::issue_net : '/etc/issue.net' 28 | ssh::knownhosts::collect_enabled : true 29 | 30 | ssh::server::default_options: 31 | ChallengeResponseAuthentication: 'no' 32 | X11Forwarding: 'yes' 33 | PrintMotd: 'no' 34 | AcceptEnv: 'LANG LC_*' 35 | Subsystem: "sftp %{lookup('ssh::sftp_server_path')}" 36 | UsePAM: 'yes' 37 | 38 | ssh::client::default_options: 39 | 'Host *': 40 | SendEnv: 'LANG LC_*' 41 | HashKnownHosts: 'yes' 42 | -------------------------------------------------------------------------------- /hiera.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 5 3 | 4 | defaults: 5 | datadir: 'data' 6 | data_hash: 'yaml_data' 7 | 8 | hierarchy: 9 | - name: 'Operating System Family' 10 | path: '%{facts.os.family}.yaml' 11 | 12 | - name: 'Full Version' 13 | path: '%{facts.os.name}-%{facts.os.release.full}.yaml' 14 | 15 | - name: 'Distribution Name' 16 | path: '%{facts.os.name}.yaml' 17 | 18 | - name: 'Major Version' 19 | paths: 20 | - '%{facts.os.name}-%{facts.os.release.major}.yaml' 21 | - '%{facts.os.family}-%{facts.os.release.major}.yaml' 22 | 23 | - name: 'Major Version with architecture' 24 | path: '%{facts.os.name}-%{facts.os.release.major}-%{facts.os.architecture}.yaml' 25 | 26 | - name: 'common' 27 | path: 'common.yaml' 28 | -------------------------------------------------------------------------------- /lib/facter/ssh_client_version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Facter.add('ssh_client_version_full') do 4 | confine kernel: %w[Linux SunOS FreeBSD DragonFly Darwin] 5 | 6 | setcode do 7 | if Facter::Util::Resolution.which('ssh') 8 | version = Facter::Util::Resolution.exec('ssh -V 2>&1'). 9 | lines. 10 | to_a. 11 | select { |line| line.match(%r{^OpenSSH_|^Sun_SSH_}) }. 12 | first. 13 | rstrip 14 | 15 | version&.gsub(%r{^(OpenSSH_|Sun_SSH_)([^ ,]+).*$}, '\2') 16 | end 17 | end 18 | end 19 | 20 | Facter.add('ssh_client_version_major') do 21 | confine kernel: %w[Linux SunOS FreeBSD DragonFly Darwin] 22 | confine ssh_client_version_full: true 23 | setcode do 24 | version = Facter.value('ssh_client_version_full') 25 | 26 | version&.gsub(%r{^([0-9]+\.[0-9]+).*$}, '\1') 27 | end 28 | end 29 | 30 | Facter.add('ssh_client_version_release') do 31 | confine kernel: %w[Linux SunOS FreeBSD DragonFly Darwin] 32 | confine ssh_client_version_full: true 33 | setcode do 34 | version = Facter.value('ssh_client_version_full') 35 | 36 | version&.gsub(%r{^([0-9]+\.[0-9]+(?:\.[0-9]+)?).*$}, '\1') 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/facter/ssh_server_version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Facter.add('ssh_server_version_full') do 4 | confine kernel: %w[Linux SunOS FreeBSD DragonFly Darwin] 5 | 6 | setcode do 7 | if Facter::Util::Resolution.which('sshd') 8 | # sshd doesn't actually have a -V option (hopefully one will be added), 9 | # by happy coincidence the usage information that is printed includes the 10 | # version number. 11 | version = Facter::Util::Resolution.exec('sshd -V 2>&1'). 12 | lines. 13 | to_a. 14 | select { |line| line.match(%r{^OpenSSH_|^Sun_SSH_}) }. 15 | first. 16 | rstrip 17 | 18 | version&.gsub(%r{^(OpenSSH_|Sun_SSH_)([^ ,]+).*$}, '\2') 19 | end 20 | end 21 | end 22 | 23 | Facter.add('ssh_server_version_major') do 24 | confine kernel: %w[Linux SunOS FreeBSD DragonFly DragonFly Darwin] 25 | confine ssh_server_version_full: %r{\d+} 26 | setcode do 27 | version = Facter.value('ssh_server_version_full') 28 | 29 | version.gsub(%r{^([0-9]+)\..*$}, '\1') 30 | end 31 | end 32 | 33 | Facter.add('ssh_server_version_release') do 34 | confine ssh_server_version_full: %r{\d+} 35 | setcode do 36 | version = Facter.value('ssh_server_version_full') 37 | 38 | version&.gsub(%r{^([0-9]+\.[0-9]+(?:\.[0-9]+)?).*$}, '\1') 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/puppet/functions/ssh/ipaddresses.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @summary Returns ip addresses of network interfaces (except lo) found by facter. 4 | # @api private 5 | # 6 | # Returns all ip addresses of network interfaces (except lo) found by facter. 7 | # Special network interfaces (e.g. docker0) can be excluded by an exclude list. 8 | Puppet::Functions.create_function(:'ssh::ipaddresses') do 9 | dispatch :ipaddresses do 10 | # @param excluded_interfaces An array of interface names to be excluded. 11 | param 'Array[String[1]]', :excluded_interfaces 12 | # @param excluded_interfaces_re An array of regexp matching interface names to be excluded. 13 | param 'Array', :excluded_interfaces_re 14 | # @return The IP addresses found. 15 | return_type 'Array[Stdlib::IP::Address]' 16 | end 17 | 18 | def ipaddresses(excluded_interfaces, excluded_interfaces_re) 19 | facts = closure_scope['facts'] 20 | 21 | # always exclude loopback interface 22 | excluded_interfaces += ['lo'] 23 | 24 | if !facts['networking'].nil? && !facts['networking'].empty? 25 | interfaces = facts['networking']['interfaces'] 26 | else 27 | interfaces = {} 28 | facts['interfaces'].split(',').each do |iface| 29 | next if facts["ipaddress_#{iface}"].nil? && facts["ipaddress6_#{iface}"].nil? 30 | 31 | interfaces[iface] = {} 32 | interfaces[iface]['bindings'] = [{ 'address' => facts["ipaddress_#{iface}"] }] if !facts["ipaddress_#{iface}"].nil? && !facts["ipaddress_#{iface}"].empty? 33 | interfaces[iface]['bindings6'] = [{ 'address' => facts["ipaddress6_#{iface}"] }] if !facts["ipaddress6_#{iface}"].nil? && !facts["ipaddress6_#{iface}"].empty? 34 | end 35 | end 36 | 37 | result = [] 38 | interfaces.each do |iface, data| 39 | # skip excluded interfaces 40 | next if excluded_interfaces.include?(iface) 41 | next if excluded_interfaces_re.any? { |pattern| Regexp.new(pattern).match?(iface) } 42 | 43 | %w[bindings bindings6].each do |binding_type| 44 | next unless data.key?(binding_type) 45 | 46 | data[binding_type].each do |binding| 47 | next unless binding.key?('address') 48 | 49 | result << binding['address'] 50 | end 51 | end 52 | end 53 | 54 | # Throw away any v6 link-local addresses 55 | fe8064 = IPAddr.new('fe80::/64') 56 | result.delete_if { |ip| fe8064.include? IPAddr.new(ip) } 57 | 58 | result.uniq 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/puppet/parser/functions/sshclient_options_to_augeas_ssh_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Puppet::Parser::Functions 4 | newfunction(:sshclient_options_to_augeas_ssh_config, type: :rvalue, doc: <<-DOC) do |args| 5 | This function will convert a key-value hash to a format understandable by the augeas sshd_config provider 6 | It will also optionally deal with keys that should be absent, and inject static parameters if supplied. 7 | 8 | Usage: sshclient_options_to_augeas_ssh_config($options_present, $options_absent, $other_parameters) 9 | - $options_hash is mandatory and must be a hash. 10 | - $options_absent is optional and can be either a single value or an array. 11 | - $other_parameters is optional and must be a hash. 12 | 13 | Example: 14 | $options = { 15 | 'Host *.example.com' => { 16 | 'ForwardAgent' => 'yes', 17 | 'BatchMode' => 'yes', 18 | }, 19 | 'ForwardAgent' => 'no', 20 | 'BatchMode' => 'no', 21 | 'StrictHostKeyChecking' => 'no', 22 | } 23 | $options_absent = ['StrictHostKeyChecking','NoneField'] 24 | $other_parameters = { 'target' => '/etc/ssh/ssh_config' } 25 | 26 | $options_final_augeas = sshclient_options_to_augeas_ssh_config($options, $options_absent, $other_parameters) 27 | 28 | In this case, the value of $options_final_augeas would be: 29 | 30 | 'ForwardAgent *.example.com' => { 31 | 'ensure' => 'present', 32 | 'host' => '*.example.com', 33 | 'key' => 'ForwardAgent', 34 | 'value' => 'yes', 35 | 'target' => '/etc/ssh/ssh_config', 36 | } 37 | 'BatchMode *.example.com' => { 38 | 'ensure' => 'present', 39 | 'host' => '*.example.com', 40 | 'key' => 'BatchMode', 41 | 'value' => 'yes', 42 | 'target' => '/etc/ssh/ssh_config', 43 | } 44 | 'ForwardAgent' => { 45 | 'ensure' => 'present', 46 | 'key' => 'ForwardAgent', 47 | 'value' => 'yes', 48 | 'target' => '/etc/ssh/ssh_config', 49 | } 50 | 'BatchMode' => { 51 | 'ensure' => 'present', 52 | 'key' => 'BatchMode', 53 | 'value' => 'yes', 54 | 'target' => '/etc/ssh/ssh_config', 55 | } 56 | 'StrictHostKeyChecking' => { 57 | 'ensure' => 'absent', 58 | 'key' => 'StrictHostKeyChecking', 59 | 'target' => '/etc/ssh/ssh_config', 60 | } 61 | 'NoneField' => { 62 | 'ensure' => 'absent', 63 | 'key' => 'NoneField', 64 | 'target' => '/etc/ssh/ssh_config', 65 | } 66 | 67 | Note how the word "Host" is stripped away. 68 | 69 | DOC 70 | 71 | raise Puppet::ParseError, 'sshclient_options_to_augeas_ssh_config: expects at least one argument' if args.empty? 72 | 73 | options = args[0] 74 | raise Puppet::ParseError, 'sshclient_options_to_augeas_ssh_config: first argument must be a hash' unless options.is_a?(Hash) 75 | 76 | options_absent = args[1] if args[1] 77 | other_parameters = args[2] if args[2] 78 | raise Puppet::ParseError, 'sshclient_options_to_augeas_ssh_config: second argument, if supplied, must be an array or a string' if options_absent && !(options_absent.is_a?(Array) || options_absent.is_a?(String)) 79 | raise Puppet::ParseError, 'sshclient_options_to_augeas_ssh_config: third argument, if supplied, must be a hash' if other_parameters && !other_parameters.is_a?(Hash) 80 | 81 | options_final_augeas = {} 82 | options.each do |key1, value1| 83 | if value1.is_a?(Hash) 84 | value1.each do |key2, value2| 85 | v = { 'ensure' => 'present' }.merge('host' => key1.gsub('Host ', '')).merge('key' => key2, 'value' => value2) 86 | options_final_augeas["#{key2} #{key1.gsub('Host ', '')}"] = v.merge(other_parameters) 87 | end 88 | else 89 | options_final_augeas[key1] = { 'ensure' => 'present' }.merge('key' => key1, 'value' => value1).merge(other_parameters) 90 | end 91 | end 92 | options_absent.each do |value| 93 | options_final_augeas[value] = { 'ensure' => 'absent' }.merge('key' => value).merge(other_parameters) 94 | end 95 | return options_final_augeas 96 | end 97 | # newfunction 98 | end 99 | # module 100 | -------------------------------------------------------------------------------- /lib/puppet/parser/functions/sshserver_options_to_augeas_sshd_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Puppet::Parser::Functions 4 | newfunction(:sshserver_options_to_augeas_sshd_config, type: :rvalue, doc: <<-DOC) do |args| 5 | This function will convert a key-value hash to a format understandable by the augeas sshd_config provider 6 | It will also optionally deal with keys that should be absent, and inject static parameters if supplied. 7 | 8 | Usage: sshserver_options_to_augeas_sshd_config($options_present, $options_absent, $other_parameters) 9 | - $options_hash is mandatory and must be a hash. 10 | - $options_absent is optional and can be either a single value or an array. 11 | - $other_parameters is optional and must be a hash. 12 | 13 | Example: 14 | $options = { 15 | 'Match User www-data' => { 16 | 'PasswordAuthentication' => 'yes', 17 | 'X11Forwarding' => 'no', 18 | }, 19 | 'Match Group bamboo' => { 20 | 'ForcedCommand' => '/bin/echo hello world', 21 | }, 22 | 'X11Forwarding' => 'yes', 23 | 'DebianBanner' => '/etc/banner.net', 24 | 'AllowGroups' => ["sshgroups", "admins"], 25 | } 26 | $options_absent = ['DebianBanner','NoneField'] 27 | $other_parameters = { 'target' => '/etc/ssh/sshd_config' } 28 | 29 | $options_final_augeas = sshserver_options_to_augeas_sshd_config($options, $options_absent, $other_parameters) 30 | 31 | In this case, the value of $options_final_augeas would be: 32 | 33 | 'PasswordAuthentication User www-data' => { 34 | 'ensure' => 'present', 35 | 'condition' => 'User www-data', 36 | 'key' => 'PasswordAuthentication', 37 | 'value' => 'yes', 38 | 'target' => '/etc/ssh/sshd_config', 39 | } 40 | 'X11Forwarding User www-data' => { 41 | 'ensure' => 'present', 42 | 'condition' => 'User www-data', 43 | 'key' => 'X11Forwarding', 44 | 'value' => 'no', 45 | 'target' => '/etc/ssh/sshd_config', 46 | } 47 | 'ForcedCommand Group bamboo' => { 48 | 'ensure' => 'present', 49 | 'condition' => 'Group bamboo', 50 | 'key' => 'ForcedCommand', 51 | 'value' => '/bin/echo hello world', 52 | 'target' => '/etc/ssh/sshd_config', 53 | } 54 | 'X11Forwarding' => { 55 | 'ensure' => 'present', 56 | 'key' => 'X11Forwarding', 57 | 'value' => 'yes', 58 | 'target' => '/etc/ssh/sshd_config', 59 | } 60 | 'DebianBanner' => { 61 | 'ensure' => 'absent', 62 | 'key' => 'DebianBanner', 63 | 'target' => '/etc/ssh/sshd_config', 64 | } 65 | 'AllowGroups' => { 66 | 'ensure' => 'present', 67 | 'key' => 'AllowGroups', 68 | 'value' => ['sshgroups','admins'], 69 | 'target' => '/etc/ssh/sshd_config', 70 | } 71 | 'NoneField' => { 72 | 'ensure' => 'absent', 73 | 'key' => 'NoneField', 74 | 'target' => '/etc/ssh/sshd_config', 75 | } 76 | 77 | Note how the word "Match" is stripped away. 78 | 79 | DOC 80 | 81 | raise Puppet::ParseError, 'sshserver_options_to_augeas_sshd_config: expects at least one argument' if args.empty? 82 | 83 | options = args[0] 84 | raise Puppet::ParseError, 'sshserver_options_to_augeas_sshd_config: first argument must be a hash' unless options.is_a?(Hash) 85 | 86 | options_absent = args[1] if args[1] 87 | other_parameters = args[2] if args[2] 88 | raise Puppet::ParseError, 'sshserver_options_to_augeas_sshd_config: second argument, if supplied, must be an array or a string' if options_absent && !(options_absent.is_a?(Array) || options_absent.is_a?(String)) 89 | raise Puppet::ParseError, 'sshserver_options_to_augeas_sshd_config: third argument, if supplied, must be a hash' if other_parameters && !other_parameters.is_a?(Hash) 90 | 91 | options_final_augeas = {} 92 | options.each do |key1, value1| 93 | if value1.is_a?(Hash) 94 | value1.each do |key2, value2| 95 | v = { 'ensure' => 'present' }.merge('condition' => key1.gsub('Match ', '')).merge('key' => key2, 'value' => value2) 96 | options_final_augeas["#{key2} #{key1.gsub('Match ', '')}"] = v.merge(other_parameters) 97 | end 98 | else 99 | options_final_augeas[key1] = { 'ensure' => 'present' }.merge('key' => key1, 'value' => value1).merge(other_parameters) 100 | end 101 | end 102 | options_absent.each do |value| 103 | options_final_augeas[value] = { 'ensure' => 'absent' }.merge('key' => value).merge(other_parameters) 104 | end 105 | return options_final_augeas 106 | end 107 | # newfunction 108 | end 109 | # module 110 | -------------------------------------------------------------------------------- /manifests/client.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # This class add ssh client management 3 | # 4 | # @example Puppet usage 5 | # class { 'ssh::client': 6 | # ensure => present, 7 | # storeconfigs_enabled => true, 8 | # use_augeas => false, 9 | # } 10 | # 11 | # @param ssh_config 12 | # Path to ssh client config file 13 | # 14 | # @param client_package_name 15 | # Name of the client package 16 | # 17 | # @param ensure 18 | # Ensurable param to ssh client 19 | # 20 | # @param storeconfigs_enabled 21 | # Collected host keys from servers will be written to known_hosts unless storeconfigs_enabled is false 22 | # 23 | # @param options 24 | # SSH client options, will be deep_merged with default_options. This parameter takes precedence over default_options 25 | # 26 | # @param use_augeas 27 | # Use augeas to configure ssh client 28 | # 29 | # @param options_absent 30 | # Remove options (with augeas style) 31 | # 32 | # @param default_options 33 | # Default options to set, will be merged with options parameter 34 | # 35 | # @param match_block 36 | # Add ssh match_block (with concat) 37 | # 38 | class ssh::client ( 39 | Stdlib::Absolutepath $ssh_config, 40 | Hash $default_options, 41 | Optional[String[1]] $client_package_name = undef, 42 | String $ensure = present, 43 | Boolean $storeconfigs_enabled = true, 44 | Hash $options = {}, 45 | Boolean $use_augeas = false, 46 | Array $options_absent = [], 47 | Hash $match_block = {}, 48 | ) { 49 | if $use_augeas { 50 | $merged_options = sshclient_options_to_augeas_ssh_config($options, $options_absent, { 'target' => $ssh_config }) 51 | } else { 52 | $merged_options = deep_merge($options, delete($default_options, keys($options))) 53 | } 54 | 55 | contain ssh::client::install 56 | contain ssh::client::config 57 | 58 | # Provide option to *not* use storeconfigs/puppetdb, which means not managing 59 | # hostkeys and knownhosts 60 | if ($storeconfigs_enabled) { 61 | contain ssh::knownhosts 62 | 63 | Class['ssh::client::install'] 64 | -> Class['ssh::client::config'] 65 | -> Class['ssh::knownhosts'] 66 | } else { 67 | Class['ssh::client::install'] 68 | -> Class['ssh::client::config'] 69 | } 70 | 71 | $match_block.each |String $k, Hash $v| { 72 | ssh::client::match_block { $k: 73 | * => $v, 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /manifests/client/config.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Manages ssh configuration 3 | # 4 | # @api private 5 | # 6 | class ssh::client::config { 7 | assert_private() 8 | 9 | $options = $ssh::client::merged_options 10 | $use_augeas = $ssh::client::use_augeas 11 | 12 | if $use_augeas { 13 | $options.each |String $k, Hash $v| { 14 | ssh_config { $k: 15 | * => $v, 16 | } 17 | } 18 | } else { 19 | concat { $ssh::client::ssh_config: 20 | ensure => present, 21 | owner => 0, 22 | group => 0, 23 | mode => '0644', 24 | } 25 | 26 | concat::fragment { 'ssh_config global config': 27 | target => $ssh::client::ssh_config, 28 | content => template("${module_name}/ssh_config.erb"), 29 | order => '00', 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /manifests/client/config/user.pp: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) IN2P3 Computing Centre, IN2P3, CNRS 3 | # Contributor: Remi Ferrand (2015) 4 | # Contributor: Tim Meusel (2017) 5 | # 6 | # @summary 7 | # This defined type manages a users ssh config 8 | # 9 | # @param ensure 10 | # Specifies whether the config file should be present or absent 11 | # 12 | # @param target 13 | # Sets the config file location, defaults to `~/.ssh/config` if $target and $user_home_dir are not set 14 | # 15 | # @param user_home_dir 16 | # Sets the location of users home dir, defaults to `/home/$user` 17 | # 18 | # @param manage_user_ssh_dir 19 | # Whether the users ssh dir should be managed or not 20 | # 21 | # @param options 22 | # Options which should be set 23 | # 24 | # @param user 25 | # The name of the user the config should be managed for 26 | # 27 | # @param ssh_directory_default_mode 28 | # Default mode for the users ssh dir 29 | # 30 | # @param ssh_config_default_mode 31 | # Default mode for the ssh config file 32 | # 33 | define ssh::client::config::user ( 34 | Enum['present', 'absent'] $ensure = present, 35 | Optional[Stdlib::Absolutepath] $target = undef, 36 | Optional[Stdlib::Absolutepath] $user_home_dir = undef, 37 | Boolean $manage_user_ssh_dir = true, 38 | Hash $options = {}, 39 | String[1] $user = $name, 40 | String[1] $ssh_directory_default_mode = '0700', 41 | String[1] $ssh_config_default_mode = '0600', 42 | ) { 43 | contain ssh::client 44 | 45 | # If a specific target file was specified, 46 | # it must have higher priority than any 47 | # other parameter. 48 | if ($target != undef) { 49 | $_target = $target 50 | } else { 51 | if ($user_home_dir == undef) { 52 | $_user_home_dir = "/home/${user}" 53 | } else { 54 | $_user_home_dir = $user_home_dir 55 | } 56 | 57 | $user_ssh_dir = "${_user_home_dir}/.ssh" 58 | $_target = "${user_ssh_dir}/config" 59 | 60 | if ($manage_user_ssh_dir == true) { 61 | unless defined(File[$user_ssh_dir]) { 62 | file { $user_ssh_dir: 63 | ensure => directory, 64 | owner => $user, 65 | mode => $ssh_directory_default_mode, 66 | before => Concat_file[$_target], 67 | } 68 | } 69 | } 70 | } 71 | 72 | unless defined(Concat_file[$_target]) { 73 | concat_file { $_target: 74 | ensure => $ensure, 75 | owner => $user, 76 | mode => $ssh_config_default_mode, 77 | } 78 | } 79 | concat_fragment { $name: 80 | content => template("${module_name}/ssh_config.erb"), 81 | target => $_target, 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /manifests/client/install.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Install ssh client package 3 | # 4 | # @api private 5 | # 6 | class ssh::client::install { 7 | assert_private() 8 | 9 | if $ssh::client::client_package_name { 10 | stdlib::ensure_packages([ 11 | $ssh::client::client_package_name, 12 | ], { 13 | 'ensure' => $ssh::client::ensure, 14 | }) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /manifests/client/match_block.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Add match_block to ssh client config (concat needed) 3 | # 4 | # @param options 5 | # Options which should be set 6 | # 7 | # @param type 8 | # Type of match_block, e.g. user, group, host, ... 9 | # 10 | # @param order 11 | # Orders your settings within the config file 12 | # 13 | # @param target 14 | # Sets the target file of the concat fragment 15 | # 16 | define ssh::client::match_block ( 17 | Hash $options = {}, 18 | Ssh::ClientMatch $type = 'user', 19 | Integer $order = 50, 20 | Stdlib::Absolutepath $target = $ssh::client::ssh_config, 21 | ) { 22 | if $ssh::client::use_augeas { 23 | fail('ssh::client::match_block() define not supported with use_augeas = true') 24 | } else { 25 | concat::fragment { "match_block ${name}": 26 | target => $target, 27 | # same template may be used for ssh_config & sshd_config 28 | content => template("${module_name}/sshd_match_block.erb"), 29 | order => 200+$order, 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /manifests/hostkeys.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # This class manages hostkeys 3 | # 4 | # @param export_ipaddresses 5 | # Whether ip addresses should be added as aliases 6 | # 7 | # @param storeconfigs_group 8 | # Tag hostkeys with this group to allow segregation 9 | # 10 | # @param extra_aliases 11 | # Additional aliases to set for host keys 12 | # 13 | # @param exclude_interfaces 14 | # List of interfaces to exclude 15 | # 16 | # @param exclude_interfaces_re 17 | # List of regular expressions to exclude interfaces 18 | # 19 | # @param exclude_ipaddresses 20 | # List of ip addresses to exclude 21 | # 22 | # @param use_trusted_facts 23 | # Whether to use trusted or normal facts 24 | # 25 | # @param tags 26 | # Array of custom tags 27 | # 28 | class ssh::hostkeys ( 29 | Boolean $export_ipaddresses = true, 30 | Optional[String[1]] $storeconfigs_group = undef, 31 | Array $extra_aliases = [], 32 | Array $exclude_interfaces = [], 33 | Array $exclude_interfaces_re = [], 34 | Array $exclude_ipaddresses = [], 35 | Boolean $use_trusted_facts = false, 36 | Optional[Array[String[1]]] $tags = undef, 37 | ) { 38 | if $use_trusted_facts { 39 | $fqdn_real = $trusted['certname'] 40 | $hostname_real = $trusted['hostname'] 41 | } else { 42 | # stick to legacy facts for older versions of facter 43 | $fqdn_real = $facts['networking']['fqdn'] 44 | $hostname_real = $facts['networking']['hostname'] 45 | } 46 | 47 | if $export_ipaddresses == true { 48 | $ipaddresses = ssh::ipaddresses($exclude_interfaces, $exclude_interfaces_re) 49 | $ipaddresses_real = $ipaddresses - $exclude_ipaddresses 50 | $host_aliases = sort(unique(flatten([$fqdn_real, $hostname_real, $extra_aliases, $ipaddresses_real]))) 51 | } else { 52 | $host_aliases = sort(unique(flatten([$fqdn_real, $hostname_real, $extra_aliases]))) 53 | } 54 | 55 | $storeconfigs_groups = $storeconfigs_group ? { 56 | undef => [], 57 | default => ['hostkey_all', "hostkey_${storeconfigs_group}"], 58 | } 59 | 60 | $_tags = $tags ? { 61 | undef => $storeconfigs_groups, 62 | default => $storeconfigs_groups + $tags, 63 | } 64 | 65 | ['dsa', 'rsa', 'ecdsa', 'ed25519'].each |String $key_type| { 66 | # can be removed as soon as we drop support for puppet 4 67 | # see https://tickets.puppetlabs.com/browse/FACT-1377?jql=project%20%3D%20FACT%20AND%20fixVersion%20%3D%20%22FACT%203.12.0%22 68 | if $key_type == 'ecdsa' { 69 | $key_type_real = 'ecdsa-sha2-nistp256' 70 | } else { 71 | $key_type_real = $key_type 72 | } 73 | 74 | if $key_type in $facts['ssh'] { 75 | @@sshkey { "${fqdn_real}_${key_type}": 76 | ensure => present, 77 | host_aliases => $host_aliases, 78 | type => $key_type_real, 79 | key => $facts['ssh'][$key_type]['key'], 80 | tag => $_tags, 81 | } 82 | } else { 83 | @@sshkey { "${fqdn_real}_${key_type}": 84 | ensure => absent, 85 | type => $key_type_real, 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /manifests/init.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # This class manages ssh client and server 3 | # 4 | # @example Puppet usage 5 | # class { 'ssh': 6 | # storeconfigs_enabled => false, 7 | # server_options => { 8 | # 'Match User www-data' => { 9 | # 'ChrootDirectory' => '%h', 10 | # 'ForceCommand' => 'internal-sftp', 11 | # 'PasswordAuthentication' => 'yes', 12 | # 'AllowTcpForwarding' => 'no', 13 | # 'X11Forwarding' => 'no', 14 | # }, 15 | # 'Port' => [22, 2222, 2288], 16 | # }, 17 | # client_options => { 18 | # 'Host *.amazonaws.com' => { 19 | # 'User' => 'ec2-user', 20 | # }, 21 | # }, 22 | # users_client_options => { 23 | # 'bob' => { 24 | # options => { 25 | # 'Host *.alice.fr' => { 26 | # 'User' => 'alice', 27 | # }, 28 | # }, 29 | # }, 30 | # }, 31 | # 'server_instances' => { 32 | # 'sftp_server_init' => { 33 | # 'ensure' => 'present', 34 | # 'options' => { 35 | # 'sshd_config' => { 36 | # 'Port' => 8022, 37 | # 'Protocol' => 2, 38 | # 'AddressFamily' => 'any', 39 | # 'HostKey' => '/etc/ssh/ssh_host_rsa_key', 40 | # 'SyslogFacility' => 'AUTH', 41 | # 'LogLevel' => 'INFO', 42 | # 'PermitRootLogin' => 'no', 43 | # }, 44 | # 'sshd_service_options' => '', 45 | # 'match_blocks' => { 46 | # '*,!ssh_exempt_ldap_authkey,!sshlokey' => { 47 | # 'type' => 'group', 48 | # 'options' => { 49 | # 'AuthorizedKeysCommand' => '/usr/local/bin/getauthkey', 50 | # 'AuthorizedKeysCommandUser' => 'nobody', 51 | # 'AuthorizedKeysFile' => '/dev/null', 52 | # }, 53 | # }, 54 | # }, 55 | # }, 56 | # }, 57 | # }, 58 | # } 59 | # 60 | # @example hiera usage 61 | # ssh::storeconfigs_enabled: true 62 | # 63 | # ssh::server_options: 64 | # Protocol: '2' 65 | # ListenAddress: 66 | # - '127.0.0.0' 67 | # - '%{::hostname}' 68 | # PasswordAuthentication: 'yes' 69 | # SyslogFacility: 'AUTHPRIV' 70 | # UsePAM: 'yes' 71 | # X11Forwarding: 'yes' 72 | # 73 | # ssh::server::match_block: 74 | # filetransfer: 75 | # type: group 76 | # options: 77 | # ChrootDirectory: /home/sftp 78 | # ForceCommand: internal-sftp 79 | # 80 | # ssh::client_options: 81 | # 'Host *': 82 | # SendEnv: 'LANG LC_*' 83 | # ForwardX11Trusted: 'yes' 84 | # ServerAliveInterval: '10' 85 | # 86 | # ssh::users_client_options: 87 | # 'bob': 88 | # 'options': 89 | # 'Host *.alice.fr': 90 | # 'User': 'alice' 91 | # 'PasswordAuthentication': 'no' 92 | # ssh::server::server_instances: 93 | # sftp_server_init: 94 | # ensure: present 95 | # options: 96 | # sshd_config: 97 | # Port: 8022 98 | # Protocol: 2 99 | # AddressFamily: 'any' 100 | # HostKey: '/etc/ssh/ssh_host_rsa_key' 101 | # SyslogFacility: 'AUTH' 102 | # LogLevel: INFO 103 | # PermitRootLogin: 'no' 104 | # sshd_service_options: '' 105 | # match_blocks: 106 | # '*,!ssh_exempt_ldap_authkey,!sshlokey': 107 | # type: group 108 | # options: 109 | # AuthorizedKeysCommand: '/usr/local/bin/getauthkey' 110 | # AuthorizedKeysCommandUser: 'nobody' 111 | # AuthorizedKeysFile: '/dev/null' 112 | # 113 | # 114 | # @param server_options 115 | # Add dynamic options for ssh server config 116 | # 117 | # @param server_match_block 118 | # Add match block for ssh server config 119 | # 120 | # @param client_options 121 | # Add dynamic options for ssh client config 122 | # 123 | # @param client_match_block 124 | # Add match block for ssh client config 125 | # 126 | # @param users_client_options 127 | # Add users options for ssh client config 128 | # 129 | # @param version 130 | # Define package version (package ressource) 131 | # 132 | # @param storeconfigs_enabled 133 | # Default value for storeconfigs_enabled (client and server) 134 | # 135 | # @param validate_sshd_file 136 | # Default value for validate_sshd_file (server) 137 | # 138 | # @param use_augeas 139 | # Default value to use augeas (client and server) 140 | # 141 | # @param server_options_absent 142 | # List of options to remove for server config (augeas only) 143 | # 144 | # @param client_options_absent 145 | # List of options to remove for client config (augeas only) 146 | # 147 | # @param use_issue_net 148 | # Use issue_net header 149 | # 150 | # @param purge_unmanaged_sshkeys 151 | # Purge unmanaged sshkeys 152 | # 153 | # @param server_instances 154 | # Configure SSH instances 155 | # 156 | class ssh ( 157 | Optional[Hash] $server_options = undef, 158 | Hash $server_match_block = {}, 159 | Optional[Hash] $client_options = undef, 160 | Hash $client_match_block = {}, 161 | Hash $users_client_options = {}, 162 | String $version = 'present', 163 | Boolean $storeconfigs_enabled = true, 164 | Boolean $validate_sshd_file = false, 165 | Boolean $use_augeas = false, 166 | Array $server_options_absent = [], 167 | Array $client_options_absent = [], 168 | Boolean $use_issue_net = false, 169 | Boolean $purge_unmanaged_sshkeys = true, 170 | Hash[String[1],Hash[String[1],NotUndef]] $server_instances = {}, 171 | ) { 172 | class { 'ssh::server': 173 | ensure => $version, 174 | storeconfigs_enabled => $storeconfigs_enabled, 175 | options => $server_options, 176 | validate_sshd_file => $validate_sshd_file, 177 | use_augeas => $use_augeas, 178 | options_absent => $server_options_absent, 179 | use_issue_net => $use_issue_net, 180 | } 181 | 182 | class { 'ssh::client': 183 | ensure => $version, 184 | storeconfigs_enabled => $storeconfigs_enabled, 185 | options => $client_options, 186 | use_augeas => $use_augeas, 187 | options_absent => $client_options_absent, 188 | } 189 | 190 | $server_instances.each | String $instance_name, Hash $instance_settings | { 191 | ssh::server::instances { $instance_name: 192 | * => $instance_settings, 193 | } 194 | } 195 | 196 | # If host keys are being managed, optionally purge unmanaged ones as well. 197 | if ($storeconfigs_enabled and $purge_unmanaged_sshkeys) { 198 | resources { 'sshkey': 199 | purge => true, 200 | } 201 | } 202 | 203 | $users_client_options.each |String $k, Hash $v| { 204 | ssh::client::config::user { $k: 205 | * => $v, 206 | } 207 | } 208 | 209 | $server_match_block.each |String $k, Hash $v| { 210 | ssh::server::match_block { $k: 211 | * => $v, 212 | } 213 | } 214 | 215 | $client_match_block.each |String $k, Hash $v| { 216 | ssh::client::match_block { $k: 217 | * => $v, 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /manifests/knownhosts.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # This class manages knownhosts if collection is enabled. 3 | # 4 | # @param collect_enabled 5 | # Enable collection 6 | # 7 | # @param storeconfigs_group 8 | # Define the hostkeys group storage 9 | # 10 | class ssh::knownhosts ( 11 | Boolean $collect_enabled = $ssh::knownhosts::collect_enabled, 12 | Optional[String[1]] $storeconfigs_group = undef, 13 | ) { 14 | if ($collect_enabled) { 15 | if $storeconfigs_group { 16 | Sshkey <<| tag == "hostkey_${storeconfigs_group}" |>> 17 | } else { 18 | Sshkey <<| |>> 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /manifests/server.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # This class managed ssh server 3 | # 4 | # @example Puppet usage 5 | # class { 'ssh::server': 6 | # ensure => present, 7 | # storeconfigs_enabled => true, 8 | # use_issue_net => false, 9 | # } 10 | # 11 | # @param service_name 12 | # Name of the sshd service 13 | # 14 | # @param sshd_config 15 | # Path to the sshd_config file 16 | # 17 | # @param sshd_dir 18 | # Path to the sshd dir (e.g. /etc/ssh) 19 | # 20 | # @param sshd_binary 21 | # Path to the sshd binary 22 | # 23 | # @param sshd_config_mode 24 | # Mode to set on the sshd config file 25 | # 26 | # @param host_priv_key_group 27 | # Name of the group for the private host key 28 | # 29 | # @param default_options 30 | # Default options to set, will be merged with options parameter 31 | # 32 | # @param ensure 33 | # Ensurable param to ssh server 34 | # 35 | # @param include_dir 36 | # Path to sshd include directory. 37 | # 38 | # @param include_dir_mode 39 | # Mode to set on the sshd include directory. 40 | # 41 | # @param include_dir_purge 42 | # Purge the include directory if true. 43 | # 44 | # @param config_files 45 | # Hash of config files to add to the ssh include directory. 46 | # 47 | # @param storeconfigs_enabled 48 | # Host keys will be collected and distributed unless storeconfigs_enabled is false. 49 | # 50 | # @param options 51 | # Dynamic hash for openssh server option 52 | # 53 | # @param validate_sshd_file 54 | # Add sshd file validate cmd 55 | # 56 | # @param use_augeas 57 | # Use augeas for configuration (default concat) 58 | # 59 | # @param options_absent 60 | # Remove options (with augeas style) 61 | # 62 | # @param match_block 63 | # Add sshd match_block (with concat) 64 | # 65 | # @param use_issue_net 66 | # Add issue_net banner 67 | # 68 | # @param sshd_environments_file 69 | # Path to a sshd environments file (e.g. /etc/defaults/ssh on Debian) 70 | # 71 | # @param server_package_name 72 | # Name of the server package to install 73 | # 74 | class ssh::server ( 75 | String[1] $service_name, 76 | Stdlib::Absolutepath $sshd_config, 77 | Stdlib::Absolutepath $sshd_dir, 78 | Stdlib::Absolutepath $sshd_binary, 79 | Stdlib::Filemode $sshd_config_mode, 80 | Integer $host_priv_key_group, 81 | Hash $default_options, 82 | Enum[present,absent,latest] $ensure = present, 83 | Optional[Stdlib::Absolutepath] $include_dir = undef, 84 | Stdlib::Filemode $include_dir_mode = '0700', 85 | Boolean $include_dir_purge = true, 86 | Hash[String, Hash] $config_files = {}, 87 | Boolean $storeconfigs_enabled = true, 88 | Hash $options = {}, 89 | Boolean $validate_sshd_file = false, 90 | Boolean $use_augeas = false, 91 | Array $options_absent = [], 92 | Hash $match_block = {}, 93 | Boolean $use_issue_net = false, 94 | Optional[Stdlib::Absolutepath] $sshd_environments_file = undef, 95 | Optional[String[1]] $server_package_name = undef, 96 | ) { 97 | if $use_augeas { 98 | $merged_options = sshserver_options_to_augeas_sshd_config($options, $options_absent, { 'target' => $ssh::server::sshd_config }) 99 | } else { 100 | $merged_options = deep_merge($default_options, $options) 101 | } 102 | 103 | contain ssh::server::install 104 | contain ssh::server::config 105 | contain ssh::server::service 106 | 107 | # Provide option to *not* use storeconfigs/puppetdb, which means not managing 108 | # hostkeys and knownhosts 109 | if ($storeconfigs_enabled) { 110 | contain ssh::hostkeys 111 | contain ssh::knownhosts 112 | 113 | Class['ssh::server::install'] 114 | -> Class['ssh::server::config'] 115 | ~> Class['ssh::server::service'] 116 | -> Class['ssh::hostkeys'] 117 | -> Class['ssh::knownhosts'] 118 | } else { 119 | Class['ssh::server::install'] 120 | -> Class['ssh::server::config'] 121 | ~> Class['ssh::server::service'] 122 | } 123 | 124 | $match_block.each |String $k, Hash $v| { 125 | ssh::server::match_block { $k: 126 | * => $v, 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /manifests/server/config.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Managed ssh server configuration 3 | # 4 | # @api private 5 | # 6 | class ssh::server::config { 7 | assert_private() 8 | 9 | $options = $ssh::server::merged_options 10 | $include_dir = $ssh::server::include_dir 11 | 12 | case $ssh::server::validate_sshd_file { 13 | true: { 14 | $sshd_validate_cmd = '/usr/sbin/sshd -tf %' 15 | } 16 | default: { 17 | $sshd_validate_cmd = undef 18 | } 19 | } 20 | 21 | if $ssh::server::use_augeas { 22 | $options.each |String $k, Hash $v| { 23 | if $k.downcase == 'subsystem' { 24 | $_v = $v.match(/(^(\w+)\s+(.*)$)/) 25 | sshd_config_subsystem { $v[2]: 26 | command => $v[3], 27 | } 28 | } else { 29 | sshd_config { $k: 30 | * => $v, 31 | } 32 | } 33 | } 34 | } else { 35 | concat { $ssh::server::sshd_config: 36 | ensure => present, 37 | owner => 0, 38 | group => 0, 39 | mode => $ssh::server::sshd_config_mode, 40 | validate_cmd => $sshd_validate_cmd, 41 | notify => Service[$ssh::server::service_name], 42 | } 43 | 44 | concat::fragment { 'global config': 45 | target => $ssh::server::sshd_config, 46 | content => template("${module_name}/sshd_config.erb"), 47 | order => '00', 48 | } 49 | } 50 | 51 | if $ssh::server::include_dir { 52 | file { $ssh::server::include_dir: 53 | ensure => directory, 54 | owner => 0, 55 | group => 0, 56 | mode => $ssh::server::include_dir_mode, 57 | purge => $ssh::server::include_dir_purge, 58 | recurse => $ssh::server::include_dir_purge, 59 | } 60 | 61 | $ssh::server::config_files.each |$file, $params| { 62 | ssh::server::config_file { $file: 63 | * => $params, 64 | } 65 | } 66 | } 67 | 68 | if $ssh::server::use_issue_net { 69 | file { $ssh::server::issue_net: 70 | ensure => file, 71 | owner => 0, 72 | group => 0, 73 | mode => $ssh::server::sshd_config_mode, 74 | content => template("${module_name}/issue.net.erb"), 75 | notify => Service[$ssh::server::service_name], 76 | } 77 | 78 | concat::fragment { 'banner file': 79 | target => $ssh::server::sshd_config, 80 | content => "Banner ${ssh::server::issue_net}\n", 81 | order => '01', 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /manifests/server/config/setting.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Internal define to managed ssh server param 3 | # 4 | # @param key 5 | # Key of the value which should be set 6 | # 7 | # @param value 8 | # Value which should be set 9 | # 10 | # @param order 11 | # Orders your setting within the config file 12 | # 13 | define ssh::server::config::setting ( 14 | String[1] $key, 15 | Variant[Boolean, Array, Hash, String] $value, 16 | Variant[String[1], Integer] $order = '10' 17 | ) { 18 | contain ssh::server 19 | 20 | $real_value = $value ? { 21 | Boolean => $value ? { 22 | true => 'yes', 23 | false => 'no', 24 | default => undef 25 | }, 26 | Array => join($value, ' '), 27 | Hash => fail('Hash values are not supported'), 28 | default => $value, 29 | } 30 | 31 | concat::fragment { "ssh_setting_${name}_${key}": 32 | target => $ssh::server::sshd_config, 33 | content => "\n# added by Ssh::Server::Config::Setting[${name}]\n${key} ${real_value}\n", 34 | order => $order, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /manifests/server/config_file.pp: -------------------------------------------------------------------------------- 1 | # @summary Resource type for managing a config file in the include dir. 2 | # 3 | # @param mode 4 | # File mode for the config file. 5 | # 6 | # @param include 7 | # Absolute path to config file to include at the top of the config file. This 8 | # is intended for including files not managed by this module (crypto policies). 9 | # 10 | # @param options 11 | # Dynamic hash for openssh server option 12 | # 13 | define ssh::server::config_file ( 14 | Stdlib::Absolutepath $path = "${ssh::server::include_dir}/${name}.conf", 15 | Stdlib::Filemode $mode = $ssh::server::sshd_config_mode, 16 | Optional[Stdlib::Absolutepath] $include = undef, 17 | Hash $options = {}, 18 | ) { 19 | if !$ssh::server::include_dir { 20 | fail('ssh::server::config_file() define not supported if ssh::server::include_dir not set') 21 | } 22 | 23 | case $ssh::server::validate_sshd_file { 24 | true: { 25 | $sshd_validate_cmd = '/usr/sbin/sshd -tf %' 26 | } 27 | default: { 28 | $sshd_validate_cmd = undef 29 | } 30 | } 31 | 32 | concat { $path: 33 | ensure => present, 34 | owner => 0, 35 | group => 0, 36 | mode => $mode, 37 | validate_cmd => $sshd_validate_cmd, 38 | notify => Service[$ssh::server::service_name], 39 | } 40 | 41 | concat::fragment { "sshd_config_file ${title}": 42 | target => $path, 43 | content => template("${module_name}/sshd_config.erb"), 44 | order => '00', 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /manifests/server/host_key.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Manage a ssh host key 3 | # 4 | # This module install a ssh host key in the server (basically, it is 5 | # a file resource but it also notifies to the ssh service) 6 | # 7 | # Important! This define does not modify any option in sshd_config, so 8 | # you have to manually define the HostKey option in the server options 9 | # if you haven't done yet. 10 | # 11 | # @param ensure 12 | # Set to 'absent' to remove host_key files 13 | # 14 | # @param public_key_source 15 | # Sets the content of the source parameter for the public key file 16 | # Note public_key_source and public_key_content are mutually exclusive. 17 | # 18 | # @param public_key_content 19 | # Sets the content for the public key file. 20 | # Note public_key_source and public_key_content are mutually exclusive. 21 | # 22 | # @param private_key_source 23 | # Sets the content of the source parameter for the private key file 24 | # Note private_key_source and private_key_content are mutually exclusive. 25 | # 26 | # @param private_key_content 27 | # Sets the content for the private key file. 28 | # Note private_key_source and private_key_content are mutually exclusive. 29 | # 30 | # @param certificate_source 31 | # Sets the content of the source parameter for the host key certificate. 32 | # Note certificate_source and certificate_content are mutually exclusive. 33 | # 34 | # @param certificate_content 35 | # Sets the content for the host key certificate. 36 | # Note certificate_source and certificate_content are mutually exclusive. 37 | # 38 | define ssh::server::host_key ( 39 | Enum[present, absent] $ensure = 'present', 40 | Optional[String[1]] $public_key_source = undef, 41 | Optional[String[1]] $public_key_content = undef, 42 | Optional[String[1]] $private_key_source = undef, 43 | Optional[String[1]] $private_key_content = undef, 44 | Optional[String[1]] $certificate_source = undef, 45 | Optional[String[1]] $certificate_content = undef, 46 | ) { 47 | # Ensure the ssh::server class is included in the manifest 48 | contain ssh::server 49 | 50 | if $ensure == 'present' { 51 | if ! $public_key_source and ! $public_key_content { 52 | fail('You must provide either public_key_source or public_key_content parameter') 53 | } 54 | 55 | if ! $private_key_source and ! $private_key_content { 56 | fail('You must provide either private_key_source or private_key_content parameter') 57 | } 58 | } 59 | 60 | $manage_pub_key_content = $public_key_source ? { 61 | undef => $public_key_content, 62 | default => undef, 63 | } 64 | $manage_pub_key_source = $public_key_source ? { 65 | undef => undef, 66 | default => $public_key_source, 67 | } 68 | 69 | $manage_priv_key_content = $private_key_source ? { 70 | undef => $private_key_content, 71 | default => undef, 72 | } 73 | $manage_priv_key_source = $private_key_source ? { 74 | undef => undef, 75 | default => $private_key_source, 76 | } 77 | 78 | $manage_cert_content = $certificate_source ? { 79 | undef => $certificate_content, 80 | default => undef, 81 | } 82 | $manage_cert_source = $certificate_source ? { 83 | undef => undef, 84 | default => $certificate_source, 85 | } 86 | 87 | if $ensure == 'present' { 88 | file { "${name}_pub": 89 | ensure => $ensure, 90 | owner => 0, 91 | group => 0, 92 | mode => '0644', 93 | path => "${ssh::server::sshd_dir}/${name}.pub", 94 | source => $manage_pub_key_source, 95 | content => $manage_pub_key_content, 96 | notify => Class['ssh::server::service'], 97 | } 98 | 99 | file { "${name}_priv": 100 | ensure => $ensure, 101 | owner => 0, 102 | group => $ssh::server::host_priv_key_group, 103 | mode => '0600', 104 | path => "${ssh::server::sshd_dir}/${name}", 105 | source => $manage_priv_key_source, 106 | content => $manage_priv_key_content, 107 | show_diff => false, 108 | notify => Class['ssh::server::service'], 109 | } 110 | } else { 111 | file { "${name}_pub": 112 | ensure => $ensure, 113 | owner => 0, 114 | group => 0, 115 | mode => '0644', 116 | path => "${ssh::server::sshd_dir}/${name}.pub", 117 | notify => Class['ssh::server::service'], 118 | } 119 | 120 | file { "${name}_priv": 121 | ensure => $ensure, 122 | owner => 0, 123 | group => $ssh::server::host_priv_key_group, 124 | mode => '0600', 125 | path => "${ssh::server::sshd_dir}/${name}", 126 | show_diff => false, 127 | notify => Class['ssh::server::service'], 128 | } 129 | } 130 | 131 | if !empty($certificate_source) or !empty($certificate_content) { 132 | if $ensure == 'present' { 133 | file { "${name}_cert": 134 | ensure => $ensure, 135 | owner => 0, 136 | group => 0, 137 | mode => '0644', 138 | path => "${ssh::server::sshd_dir}/${name}-cert.pub", 139 | source => $manage_cert_source, 140 | content => $manage_cert_content, 141 | notify => Class['ssh::server::service'], 142 | } 143 | } else { 144 | file { "${name}_cert": 145 | ensure => $ensure, 146 | owner => 0, 147 | group => 0, 148 | mode => '0644', 149 | path => "${ssh::server::sshd_dir}/${name}-cert.pub", 150 | notify => Class['ssh::server::service'], 151 | } 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /manifests/server/install.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Install ssh server package 3 | # 4 | # @api private 5 | # 6 | class ssh::server::install { 7 | assert_private() 8 | 9 | if $ssh::server::server_package_name { 10 | stdlib::ensure_packages ([ 11 | $ssh::server::server_package_name, 12 | ], { 13 | 'ensure' => $ssh::server::ensure, 14 | }) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /manifests/server/instances.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Configure separate ssh server instances 3 | # 4 | # @param ensure 5 | # Specifies whether the instance should be added or removed 6 | # 7 | # @param options 8 | # Set options for the instance 9 | # 10 | # @param service_ensure 11 | # Whether this instance service should be running or stopped, defaults to true when ensure is set to present, otherwise false 12 | # 13 | # @param service_enable 14 | # Whether this instance service should be started at boot. Will be added automatically if ensure is running/removed if ensure is stopped 15 | # 16 | # @param validate_config_file 17 | # Validate config file before applying 18 | # 19 | # @param sshd_instance_config_file 20 | # Path of the instance sshd config 21 | # 22 | # @param sshd_binary 23 | # Path to sshd binary 24 | # 25 | # @param sshd_environments_file 26 | # Path to environments file, if any 27 | # 28 | define ssh::server::instances ( 29 | Enum[present, absent] $ensure = present, 30 | Hash $options = {}, 31 | Stdlib::Ensure::Service $service_ensure = $ensure ? { 'present' => 'running', 'absent' => 'stopped' }, 32 | Boolean $service_enable = ($service_ensure == 'running'), 33 | Boolean $validate_config_file = false, 34 | Stdlib::Absolutepath $sshd_instance_config_file = "${ssh::server::sshd_dir}/sshd_config.${title}", 35 | Stdlib::Absolutepath $sshd_binary = $ssh::server::sshd_binary, 36 | Optional[Stdlib::Absolutepath] $sshd_environments_file = $ssh::server::sshd_environments_file, 37 | ) { 38 | contain ssh::server 39 | 40 | $sshd_instance_config = assert_type(Hash, pick($options['sshd_config'], {})) 41 | $sshd_instance_matchblocks = assert_type(Hash, pick($options['match_blocks'], {})) 42 | $sshd_service_options = $options['sshd_service_options'] 43 | $sshd_additional_service_options = $options['sshd_additional_service_options'] 44 | 45 | #check if server is a linux 46 | if $facts['kernel'] == 'Linux' { 47 | case $validate_config_file { 48 | true: { 49 | $validate_cmd = '/usr/sbin/sshd -tf %' 50 | } 51 | default: { 52 | $validate_cmd = undef 53 | } 54 | } 55 | 56 | concat { $sshd_instance_config_file: 57 | ensure => $ensure, 58 | owner => 0, 59 | group => 0, 60 | mode => '0600', 61 | validate_cmd => $validate_cmd, 62 | notify => Service["${title}.service"], 63 | } 64 | 65 | concat::fragment { "sshd instance ${title} config": 66 | target => $sshd_instance_config_file, 67 | content => template("${module_name}/ssh_instance.erb"), 68 | order => '00', 69 | } 70 | 71 | $sshd_instance_matchblocks.each |String $matchblock_name, Hash $matchblock_options| { 72 | ssh::server::match_block { $matchblock_name: 73 | * => $matchblock_options, 74 | target => $sshd_instance_config_file, 75 | } 76 | } 77 | 78 | systemd::unit_file { "${title}.service": 79 | content => template("${module_name}/ssh_instance_service.erb"), 80 | active => ($service_ensure == 'running'), 81 | enable => $service_enable, 82 | } 83 | } else { 84 | fail ("Operating System ${facts['os']['name']} not supported, because Systemd is not available") 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /manifests/server/match_block.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Add match_block to ssh server config 3 | # 4 | # @param options 5 | # Options which should be set 6 | # 7 | # @param type 8 | # Type of match_block, e.g. user, group, host, ... 9 | # 10 | # @param order 11 | # Orders your settings within the config file 12 | # 13 | # @param target 14 | # Sets the target file of the concat fragment 15 | # 16 | define ssh::server::match_block ( 17 | Hash $options = {}, 18 | String[1] $type = 'user', 19 | Integer $order = 50, 20 | Stdlib::Absolutepath $target = $ssh::server::sshd_config, 21 | ) { 22 | if $ssh::server::use_augeas { 23 | fail('ssh::server::match_block() define not supported with use_augeas = true') 24 | } else { 25 | concat::fragment { "match_block ${name}": 26 | target => $target, 27 | content => template("${module_name}/sshd_match_block.erb"), 28 | order => 200+$order, 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /manifests/server/options.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # This defined type manages ssh server options 3 | # 4 | # @param options 5 | # Options which should be set 6 | # 7 | # @param order 8 | # Orders your settings within the config file 9 | # 10 | define ssh::server::options ( 11 | Hash $options = {}, 12 | Integer $order = 50 13 | ) { 14 | concat::fragment { "options ${name}": 15 | target => $ssh::server::sshd_config, 16 | content => template("${module_name}/options.erb"), 17 | order => 100+$order, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /manifests/server/service.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # This class managed ssh server service 3 | # 4 | # @api private 5 | # 6 | # @param ensure 7 | # Ensurable service param 8 | # 9 | # @param enable 10 | # Define if service is enable 11 | # 12 | class ssh::server::service ( 13 | Stdlib::Ensure::Service $ensure = 'running', 14 | Boolean $enable = true, 15 | ) { 16 | assert_private() 17 | 18 | service { $ssh::server::service_name: 19 | ensure => $ssh::server::service::ensure, 20 | hasstatus => true, 21 | hasrestart => true, 22 | enable => $ssh::server::service::enable, 23 | require => Class['ssh::server::config'], 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "saz-ssh", 3 | "version": "13.1.0", 4 | "author": "saz", 5 | "summary": "Manage SSH client and server via Puppet.", 6 | "license": "Apache-2.0", 7 | "source": "https://github.com/saz/puppet-ssh.git", 8 | "project_page": "https://github.com/saz/puppet-ssh", 9 | "dependencies": [ 10 | { 11 | "name": "puppetlabs/stdlib", 12 | "version_requirement": ">= 9.0.0 < 10.0.0" 13 | }, 14 | { 15 | "name": "puppetlabs/concat", 16 | "version_requirement": ">= 2.2.0 < 10.0.0" 17 | }, 18 | { 19 | "name": "puppet/systemd", 20 | "version_requirement": ">= 3.7.0 < 9.0.0" 21 | } 22 | ], 23 | "operatingsystem_support": [ 24 | { 25 | "operatingsystem": "RedHat", 26 | "operatingsystemrelease": [ 27 | "8", 28 | "9" 29 | ] 30 | }, 31 | { 32 | "operatingsystem": "CentOS", 33 | "operatingsystemrelease": [ 34 | "9" 35 | ] 36 | }, 37 | { 38 | "operatingsystem": "OracleLinux", 39 | "operatingsystemrelease": [ 40 | "8", 41 | "9" 42 | ] 43 | }, 44 | { 45 | "operatingsystem": "Debian", 46 | "operatingsystemrelease": [ 47 | "11", 48 | "12" 49 | ] 50 | }, 51 | { 52 | "operatingsystem": "Ubuntu", 53 | "operatingsystemrelease": [ 54 | "20.04", 55 | "22.04" 56 | ] 57 | }, 58 | { 59 | "operatingsystem": "SLES", 60 | "operatingsystemrelease": [ 61 | "12", 62 | "15" 63 | ] 64 | }, 65 | { 66 | "operatingsystem": "OpenSuSE", 67 | "operatingsystemrelease": [ 68 | "42" 69 | ] 70 | }, 71 | { 72 | "operatingsystem": "FreeBSD", 73 | "operatingsystemrelease": [ 74 | "13" 75 | ] 76 | }, 77 | { 78 | "operatingsystem": "DragonFly", 79 | "operatingsystemrelease": [ 80 | "6" 81 | ] 82 | }, 83 | { 84 | "operatingsystem": "OpenBSD", 85 | "operatingsystemrelease": [ 86 | "7" 87 | ] 88 | }, 89 | { 90 | "operatingsystem": "Gentoo" 91 | }, 92 | { 93 | "operatingsystem": "Solaris", 94 | "operatingsystemrelease": [ 95 | "11" 96 | ] 97 | }, 98 | { 99 | "operatingsystem": "Archlinux" 100 | }, 101 | { 102 | "operatingsystem": "AIX" 103 | } 104 | ], 105 | "requirements": [ 106 | { 107 | "name": "puppet", 108 | "version_requirement": ">= 7.0.0 < 9.0.0" 109 | } 110 | ] 111 | } 112 | -------------------------------------------------------------------------------- /spec/acceptance/client_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper_acceptance' 4 | 5 | describe 'ssh' do 6 | context 'with client_match_block' do 7 | it_behaves_like 'an idempotent resource' do 8 | let(:manifest) do 9 | <<~PP 10 | class { 'ssh': 11 | client_options => { 12 | 'GlobalKnownHostsFile' => "/var/lib/sss/pubconf/known_hosts", 13 | 'PubkeyAuthentication' => "yes", 14 | 'GSSAPIAuthentication' => "yes", 15 | 'GSSAPIDelegateCredentials' => "yes", 16 | }, 17 | client_match_block => { 18 | 'foo' => { 19 | 'type' => '!localuser', 20 | 'options' => { 21 | 'ProxyCommand' => '/usr/bin/sss_ssh_knownhostsproxy -p %p %h', 22 | }, 23 | }, 24 | 'bar' => { 25 | 'type' => 'host', 26 | 'options' => { 27 | 'ForwardX11' => 'no', 28 | 'PasswordAuthentication' => 'yes', 29 | }, 30 | }, 31 | }, 32 | } 33 | PP 34 | end 35 | 36 | describe file('/etc/ssh/ssh_config') do 37 | it { is_expected.to be_file } 38 | it { is_expected.to be_owned_by 'root' } 39 | it { is_expected.to be_grouped_into 'root' } 40 | it { is_expected.to be_mode '644' } # serverspec does not like a leading 0 41 | 42 | its(:content) do 43 | is_expected.to match <<~SSH 44 | # File managed by Puppet 45 | 46 | GlobalKnownHostsFile /var/lib/sss/pubconf/known_hosts 47 | PubkeyAuthentication yes 48 | GSSAPIAuthentication yes 49 | GSSAPIDelegateCredentials yes 50 | Host * 51 | HashKnownHosts yes 52 | SendEnv LANG LC_* 53 | Match host bar 54 | ForwardX11 no 55 | PasswordAuthentication yes 56 | Match !localuser foo 57 | ProxyCommand /usr/bin/sss_ssh_knownhostsproxy -p %p %h 58 | SSH 59 | end 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /spec/acceptance/init_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper_acceptance' 4 | 5 | describe 'ssh' do 6 | package_name = case fact('os.family') 7 | when 'Archlinux' 8 | 'openssh' 9 | else 10 | 'openssh-server' 11 | end 12 | context 'with defaults' do 13 | it_behaves_like 'an idempotent resource' do 14 | let(:manifest) do 15 | 'include ssh' 16 | end 17 | 18 | describe package(package_name) do 19 | it { is_expected.to be_installed } 20 | end 21 | 22 | describe port(22) do 23 | it { is_expected.to be_listening } 24 | end 25 | 26 | describe service('sshd') do 27 | it { is_expected.to be_enabled } 28 | it { is_expected.to be_running } 29 | end 30 | end 31 | end 32 | 33 | context 'Server with a seperate sftp_server_init instance on Port 8022' do 34 | it_behaves_like 'an idempotent resource' do 35 | let(:manifest) do 36 | <<-PUPPET 37 | class { 'ssh': 38 | server_instances => { 39 | 'sftp_server_init' => { 40 | 'ensure' => 'present', 41 | 'options' => { 42 | 'sshd_config' => { 43 | 'Port' => 8022, 44 | 'Protocol' => 2, 45 | 'AddressFamily' => 'any', 46 | 'HostKey' => '/etc/ssh/ssh_host_rsa_key', 47 | 'SyslogFacility' => 'AUTH', 48 | 'LogLevel' => 'INFO', 49 | 'PermitRootLogin' => 'no', 50 | }, 51 | 'sshd_service_options' => '', 52 | 'match_blocks' => {}, 53 | }, 54 | }, 55 | }, 56 | } 57 | PUPPET 58 | end 59 | 60 | describe package(package_name) do 61 | it { is_expected.to be_installed } 62 | end 63 | 64 | describe port(8022) do 65 | it { is_expected.to be_listening } 66 | end 67 | 68 | describe service('sftp_server_init') do 69 | it { is_expected.to be_enabled } 70 | it { is_expected.to be_running } 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/classes/client_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'ssh::client', type: 'class' do 6 | on_supported_os.each do |os, os_facts| 7 | context "on #{os}" do 8 | let(:facts) { os_facts } 9 | 10 | context 'with no other parameters' do 11 | it { is_expected.to compile.with_all_deps } 12 | it { is_expected.to contain_class('ssh::knownhosts') } 13 | it { is_expected.to contain_class('ssh::client::config') } 14 | it { is_expected.to contain_class('ssh::client::install') } 15 | it { is_expected.to contain_concat('/etc/ssh/ssh_config') } 16 | end 17 | 18 | context 'with a different ssh_config location' do 19 | let :params do 20 | { 21 | ssh_config: '/etc/ssh/another_ssh_config' 22 | } 23 | end 24 | 25 | it { is_expected.to contain_concat('/etc/ssh/another_ssh_config') } 26 | end 27 | 28 | context 'with storeconfigs_enabled set to false' do 29 | let :params do 30 | { 31 | storeconfigs_enabled: false 32 | } 33 | end 34 | 35 | it { is_expected.not_to contain_class('ssh::knownhosts') } 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/classes/hostkeys_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'ssh::hostkeys', type: 'class' do 6 | _, os_facts = on_supported_os.first 7 | 8 | let(:facts) { os_facts } 9 | 10 | context 'with tags' do 11 | let(:params) do 12 | { 13 | tags: %w[group1 group2] 14 | } 15 | end 16 | 17 | %w[rsa].each do |key_type| 18 | it { 19 | expect(exported_resources).to contain_sshkey("foo.example.com_#{key_type}"). 20 | with( 21 | ensure: 'present', 22 | type: %r{^#{key_type}}, 23 | tag: %w[group1 group2] 24 | ) 25 | } 26 | end 27 | end 28 | 29 | context 'with storeconfigs_group' do 30 | let(:params) do 31 | { 32 | storeconfigs_group: 'server_group', 33 | } 34 | end 35 | 36 | %w[rsa].each do |key_type| 37 | it { 38 | expect(exported_resources).to contain_sshkey("foo.example.com_#{key_type}"). 39 | with( 40 | ensure: 'present', 41 | type: %r{^#{key_type}}, 42 | tag: %w[hostkey_all hostkey_server_group] 43 | ) 44 | } 45 | end 46 | end 47 | 48 | context 'with storeconfigs_group and tags' do 49 | let(:params) do 50 | { 51 | storeconfigs_group: 'server_group', 52 | tags: %w[group1 group2], 53 | } 54 | end 55 | 56 | %w[rsa].each do |key_type| 57 | it { 58 | expect(exported_resources).to contain_sshkey("foo.example.com_#{key_type}"). 59 | with( 60 | ensure: 'present', 61 | type: %r{^#{key_type}}, 62 | tag: %w[hostkey_all hostkey_server_group group1 group2] 63 | ) 64 | } 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/classes/init_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'ssh', type: 'class' do 6 | on_supported_os.each do |os, os_facts| 7 | context "on #{os}" do 8 | let(:facts) { os_facts } 9 | 10 | case os_facts[:os]['family'] 11 | when 'Debian' 12 | client_package = 'openssh-client' 13 | server_package = 'openssh-server' 14 | sftp_server_path = '/usr/lib/openssh/sftp-server' 15 | when 'Archlinux' 16 | client_package = 'openssh' 17 | server_package = 'openssh' 18 | sftp_server_path = '/usr/lib/ssh/sftp-server' 19 | when 'Amazon', 'RedHat' 20 | client_package = 'openssh-clients' 21 | server_package = 'openssh-server' 22 | sftp_server_path = '/usr/libexec/openssh/sftp-server' 23 | when 'Gentoo' 24 | client_package = 'openssh' 25 | server_package = 'openssh' 26 | sftp_server_path = '/usr/lib64/misc/sftp-server' 27 | when 'Solaris' 28 | case os_facts[:os]['release']['major'] 29 | when 10 30 | client_package = 'SUNWsshu' 31 | server_package = 'SUNWsshdu' 32 | else 33 | client_package = '/network/ssh' 34 | server_package = '/service/network/ssh' 35 | end 36 | sftp_server_path = 'internal-sftp' 37 | when 'SmartOS' 38 | sftp_server_path = 'internal-sftp' 39 | when 'Suse' 40 | client_package = 'openssh' 41 | server_package = 'openssh' 42 | case os_facts[:os]['name'] 43 | when 'OpenSuSE' 44 | sftp_server_path = '/usr/lib/ssh/sftp-server' 45 | when 'SLES' 46 | sftp_server_path = case os_facts[:os]['release']['major'] 47 | when 10, 11 48 | '/usr/lib64/ssh/sftp-server' 49 | else 50 | '/usr/lib/ssh/sftp-server' 51 | end 52 | end 53 | else 54 | client_package = nil 55 | server_package = nil 56 | sftp_server_path = '/usr/libexec/sftp-server' 57 | end 58 | 59 | case os_facts[:os]['family'] 60 | when 'Solaris' 61 | ssh_config_expected_default = "# File managed by Puppet\n\n" 62 | ssh_config_expected_custom = "# File managed by Puppet\n\nHostFoo\n HostName bar\nSomeOtherKey someValue\n" 63 | sshd_config_default = "# File is managed by Puppet\n\nChallengeResponseAuthentication no\nHostKey /etc/ssh/ssh_host_rsa_key\nHostKey /etc/ssh/ssh_host_dsa_key\nPrintMotd no\nSubsystem sftp #{sftp_server_path}\nX11Forwarding yes\n" 64 | sshd_config_custom = "# File is managed by Puppet\n\nChallengeResponseAuthentication no\nHostKey /etc/ssh/ssh_host_rsa_key\nHostKey /etc/ssh/ssh_host_dsa_key\nPrintMotd no\nSomeOtherKey someValue\nSubsystem sftp #{sftp_server_path}\nUsePAM no\nX11Forwarding no\n" 65 | when 'OpenBSD' 66 | ssh_config_expected_default = "# File managed by Puppet\n\nHost *\n HashKnownHosts yes\n SendEnv LANG LC_*\n" 67 | ssh_config_expected_custom = "# File managed by Puppet\n\nHostFoo\n HostName bar\nSomeOtherKey someValue\nHost *\n HashKnownHosts yes\n SendEnv LANG LC_*\n" 68 | sshd_config_default = "# File is managed by Puppet\n\nAcceptEnv LANG LC_*\nChallengeResponseAuthentication no\nPrintMotd no\nSubsystem sftp #{sftp_server_path}\nX11Forwarding yes\n" 69 | sshd_config_custom = "# File is managed by Puppet\n\nAcceptEnv LANG LC_*\nChallengeResponseAuthentication no\nPrintMotd no\nSomeOtherKey someValue\nSubsystem sftp #{sftp_server_path}\nUsePAM no\nX11Forwarding no\n" 70 | when 'RedHat' 71 | ssh_config_expected_default = "# File managed by Puppet\n\nHost *\n HashKnownHosts yes\n SendEnv LANG LC_*\n" 72 | ssh_config_expected_custom = "# File managed by Puppet\n\nHostFoo\n HostName bar\nSomeOtherKey someValue\nHost *\n HashKnownHosts yes\n SendEnv LANG LC_*\n" 73 | 74 | if os_facts[:os]['release']['major'] == '8' 75 | sshd_config_default = "# File is managed by Puppet\n\nAcceptEnv LANG LC_*\nChallengeResponseAuthentication no\nPrintMotd no\nSubsystem sftp #{sftp_server_path}\nUsePAM yes\nX11Forwarding yes\n" 76 | sshd_config_custom = "# File is managed by Puppet\n\nAcceptEnv LANG LC_*\nChallengeResponseAuthentication no\nPrintMotd no\nSomeOtherKey someValue\nSubsystem sftp #{sftp_server_path}\nUsePAM no\nX11Forwarding no\n" 77 | else 78 | sshd_config_default = "# File is managed by Puppet\nInclude /etc/ssh/sshd_config.d/*.conf\n\nAcceptEnv LANG LC_*\nChallengeResponseAuthentication no\nPrintMotd no\nSubsystem sftp #{sftp_server_path}\nUsePAM yes\nX11Forwarding yes\n" 79 | sshd_config_custom = "# File is managed by Puppet\nInclude /etc/ssh/sshd_config.d/*.conf\n\nAcceptEnv LANG LC_*\nChallengeResponseAuthentication no\nPrintMotd no\nSomeOtherKey someValue\nSubsystem sftp #{sftp_server_path}\nUsePAM no\nX11Forwarding no\n" 80 | end 81 | else 82 | ssh_config_expected_default = "# File managed by Puppet\n\nHost *\n HashKnownHosts yes\n SendEnv LANG LC_*\n" 83 | ssh_config_expected_custom = "# File managed by Puppet\n\nHostFoo\n HostName bar\nSomeOtherKey someValue\nHost *\n HashKnownHosts yes\n SendEnv LANG LC_*\n" 84 | sshd_config_default = "# File is managed by Puppet\n\nAcceptEnv LANG LC_*\nChallengeResponseAuthentication no\nPrintMotd no\nSubsystem sftp #{sftp_server_path}\nUsePAM yes\nX11Forwarding yes\n" 85 | sshd_config_custom = "# File is managed by Puppet\n\nAcceptEnv LANG LC_*\nChallengeResponseAuthentication no\nPrintMotd no\nSomeOtherKey someValue\nSubsystem sftp #{sftp_server_path}\nUsePAM no\nX11Forwarding no\n" 86 | end 87 | 88 | if os_facts[:kernel] == 'Linux' 89 | context 'Server with a separate sftp_server_init instance on Port 8022' do 90 | let :params do 91 | { 92 | 'server_instances' => { 93 | 'sftp_server_init' => { 94 | 'ensure' => 'present', 95 | 'options' => { 96 | 'sshd_config' => { 97 | 'Port' => 8022, 98 | 'Protocol' => 2, 99 | 'AddressFamily' => 'any', 100 | 'HostKey' => '/etc/ssh/ssh_host_rsa_key', 101 | 'SyslogFacility' => 'AUTH', 102 | 'LogLevel' => 'INFO', 103 | 'PermitRootLogin' => 'no', 104 | }, 105 | 'sshd_service_options' => '', 106 | 'match_blocks' => {}, 107 | }, 108 | }, 109 | }, 110 | } 111 | end 112 | 113 | it { is_expected.to compile.with_all_deps } 114 | it { is_expected.to contain_concat('/etc/ssh/sshd_config.sftp_server_init') } 115 | it { is_expected.to contain_concat__fragment('sshd instance sftp_server_init config').with_content("# File is managed by Puppet\nAddressFamily any\nPort 8022\n\nHostKey /etc/ssh/ssh_host_rsa_key\nLogLevel INFO\nPermitRootLogin no\nProtocol 2\nSyslogFacility AUTH\n") } 116 | it { is_expected.to contain_systemd__unit_file('sftp_server_init.service') } 117 | it { is_expected.to contain_service('sftp_server_init.service') } 118 | it { is_expected.to contain_ssh__server__instances('sftp_server_init') } 119 | it { is_expected.to contain_class('ssh::client') } 120 | it { is_expected.to contain_class('ssh::server') } 121 | it { is_expected.to contain_concat('/etc/ssh/sshd_config').with_validate_cmd(nil) } 122 | it { is_expected.to contain_resources('sshkey').with_purge(true) } 123 | end 124 | end 125 | 126 | context 'with all defaults' do 127 | it { is_expected.to compile.with_all_deps } 128 | end 129 | 130 | context 'with the validate_sshd_file setting' do 131 | let :params do 132 | { 133 | validate_sshd_file: true 134 | } 135 | end 136 | 137 | it { is_expected.to contain_concat('/etc/ssh/sshd_config').with_validate_cmd('/usr/sbin/sshd -tf %') } 138 | end 139 | 140 | context 'without resource purging' do 141 | let :params do 142 | { 143 | purge_unmanaged_sshkeys: false 144 | } 145 | end 146 | 147 | it { is_expected.not_to contain_resources('sshkey') } 148 | end 149 | 150 | context 'with no other parameters' do 151 | it { is_expected.to contain_class('ssh::client') } 152 | it { is_expected.to contain_class('ssh::server') } 153 | it { is_expected.to contain_concat('/etc/ssh/ssh_config') } 154 | it { is_expected.to contain_concat('/etc/ssh/sshd_config').with_validate_cmd(nil) } 155 | it { is_expected.to contain_resources('sshkey').with_purge(true) } 156 | it { is_expected.to contain_concat__fragment('global config').with_content(sshd_config_default) } 157 | it { is_expected.to contain_concat__fragment('ssh_config global config').with_content(ssh_config_expected_default) } 158 | 159 | it { is_expected.to contain_package(client_package).with_ensure('installed') } if client_package 160 | it { is_expected.to contain_package(server_package).with_ensure('installed') } if server_package 161 | end 162 | 163 | context 'with custom server options' do 164 | let :params do 165 | { 166 | server_options: { 167 | X11Forwarding: 'no', 168 | UsePAM: 'no', 169 | SomeOtherKey: 'someValue' 170 | } 171 | } 172 | end 173 | 174 | it { is_expected.to contain_concat__fragment('global config').with_content(sshd_config_custom) } 175 | end 176 | 177 | context 'with custom client options' do 178 | let :params do 179 | { 180 | client_options: { 181 | HostFoo: { 182 | HostName: 'bar' 183 | }, 184 | SomeOtherKey: 'someValue' 185 | } 186 | } 187 | end 188 | 189 | it { is_expected.to contain_concat__fragment('ssh_config global config').with_content(ssh_config_expected_custom) } 190 | end 191 | 192 | context 'with storeconfigs_enabled set to false' do 193 | let :params do 194 | { 195 | storeconfigs_enabled: false 196 | } 197 | end 198 | 199 | it { is_expected.not_to contain_class('ssh::knownhosts') } 200 | end 201 | 202 | context 'with client_match_block' do 203 | let :params do 204 | { 205 | client_match_block: { 206 | 'foo' => { 207 | 'type' => '!localuser', 208 | 'options' => { 209 | 'ProxyCommand' => '/usr/bin/sss_ssh_knownhostsproxy -p %p %h', 210 | }, 211 | }, 212 | 'bar' => { 213 | 'type' => 'host', 214 | 'options' => { 215 | 'ForwardX11' => 'no', 216 | 'PasswordAuthentication' => 'yes', 217 | }, 218 | }, 219 | }, 220 | } 221 | end 222 | 223 | it do 224 | is_expected.not_to contain_ssh__client__matchblock('foo').with( 225 | type: '!localuser', 226 | options: { 227 | 'ProxyCommand' => '/usr/bin/sss_ssh_knownhostsproxy -p %p %h', 228 | }, 229 | target: '/etc/ssh/ssh_config_foo' 230 | ) 231 | end 232 | 233 | it do 234 | is_expected.not_to contain_ssh__client__matchblock('bar').with( 235 | type: 'host', 236 | options: { 237 | 'FowardX11' => 'no', 238 | 'PasswordAuthentication' => 'yes', 239 | }, 240 | target: '/etc/ssh/ssh_config_foo' 241 | ) 242 | end 243 | 244 | it { is_expected.not_to have_ssh__client__matchblock_resource_count(2) } 245 | end 246 | end 247 | end 248 | end 249 | -------------------------------------------------------------------------------- /spec/classes/server_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'ssh::server', type: 'class' do 6 | on_supported_os.each do |os, os_facts| 7 | context "on #{os}" do 8 | let(:facts) { os_facts } 9 | 10 | svc_name = case os_facts[:os]['family'] 11 | when 'Debian' 12 | 'ssh' 13 | when 'Archlinux' 14 | 'sshd.service' 15 | when 'Darwin' 16 | 'com.openssh.sshd' 17 | when 'Solaris', 'SmartOS' 18 | 'svc:/network/ssh:default' 19 | else 20 | 'sshd' 21 | end 22 | 23 | sshd_config_custom = case os_facts[:os]['family'] 24 | when 'Solaris' 25 | "# File is managed by Puppet\n\nChallengeResponseAuthentication no\nHostKey /etc/ssh/ssh_host_rsa_key\nHostKey /etc/ssh/ssh_host_dsa_key\nPrintMotd no\nSomeOtherKey someValue\nSubsystem sftp /some/path\nUsePAM no\nX11Forwarding no\n" 26 | when 'RedHat' 27 | if os_facts[:os]['release']['major'] == '8' 28 | "# File is managed by Puppet\n\nAcceptEnv LANG LC_*\nChallengeResponseAuthentication no\nPrintMotd no\nSomeOtherKey someValue\nSubsystem sftp /some/path\nUsePAM no\nX11Forwarding no\n" 29 | else 30 | "# File is managed by Puppet\nInclude /etc/ssh/sshd_config.d/*.conf\n\nAcceptEnv LANG LC_*\nChallengeResponseAuthentication no\nPrintMotd no\nSomeOtherKey someValue\nSubsystem sftp /some/path\nUsePAM no\nX11Forwarding no\n" 31 | end 32 | else 33 | "# File is managed by Puppet\n\nAcceptEnv LANG LC_*\nChallengeResponseAuthentication no\nPrintMotd no\nSomeOtherKey someValue\nSubsystem sftp /some/path\nUsePAM no\nX11Forwarding no\n" 34 | end 35 | 36 | context 'with no other parameters' do 37 | it { is_expected.to compile.with_all_deps } 38 | it { is_expected.to contain_class('ssh::knownhosts') } 39 | it { is_expected.to contain_class('ssh::server::config') } 40 | it { is_expected.to contain_class('ssh::server::install') } 41 | it { is_expected.to contain_class('ssh::server::service') } 42 | it { is_expected.to contain_service(svc_name) } 43 | it { is_expected.to contain_concat('/etc/ssh/sshd_config').with_validate_cmd(nil) } 44 | it { is_expected.to contain_concat__fragment('global config') } 45 | end 46 | 47 | context 'with custom options' do 48 | let :params do 49 | { 50 | options: { 51 | Subsystem: 'sftp /some/path', 52 | X11Forwarding: 'no', 53 | UsePAM: 'no', 54 | SomeOtherKey: 'someValue' 55 | } 56 | } 57 | end 58 | 59 | it { is_expected.to contain_concat__fragment('global config').with_content(sshd_config_custom) } 60 | end 61 | 62 | context 'with a custom service_name' do 63 | let :params do 64 | { 65 | service_name: 'custom_sshd_name' 66 | } 67 | end 68 | 69 | it { is_expected.to contain_service('custom_sshd_name') } 70 | end 71 | 72 | context 'with the validate_sshd_file setting' do 73 | let :params do 74 | { 75 | validate_sshd_file: true 76 | } 77 | end 78 | 79 | it { is_expected.to contain_concat('/etc/ssh/sshd_config').with_validate_cmd('/usr/sbin/sshd -tf %') } 80 | end 81 | 82 | context 'with a different sshd_config location' do 83 | let :params do 84 | { 85 | sshd_config: '/etc/ssh/another_sshd_config' 86 | } 87 | end 88 | 89 | it { is_expected.to contain_concat('/etc/ssh/another_sshd_config') } 90 | end 91 | 92 | context 'with storeconfigs_enabled set to false' do 93 | let :params do 94 | { 95 | storeconfigs_enabled: false 96 | } 97 | end 98 | 99 | it { is_expected.not_to contain_class('ssh::knownhosts') } 100 | end 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /spec/defines/client/config/user_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'ssh::client::config::user' do 6 | on_supported_os.each do |os, os_facts| 7 | let(:facts) { os_facts } 8 | 9 | context "on #{os}" do 10 | let(:title) { 'riton' } 11 | 12 | let :ssh_options do 13 | { 14 | 'HashKnownHosts' => 'yes', 15 | 'Host *.in2p3.fr' => { 16 | 'User' => 'riton', 17 | 'GSSAPIAuthentication' => 'no' 18 | } 19 | } 20 | end 21 | 22 | context 'with all defaults' do 23 | it { is_expected.to compile.with_all_deps } 24 | end 25 | 26 | describe 'with invalid parameters' do 27 | params = { 28 | ensure: ['somestate', 'expects a match for Enum'], 29 | target: ['./somedir', 'Pattern'], 30 | user_home_dir: ['./somedir', 'Pattern'], 31 | manage_user_ssh_dir: ['maybe', 'expects a Boolean'], 32 | options: ['the_options', 'Hash value'] 33 | } 34 | 35 | params.each do |param, value| 36 | context "with invalid value for #{param}" do 37 | let :params do 38 | { 39 | param => value[0] 40 | } 41 | end 42 | 43 | it { is_expected.not_to compile } 44 | end 45 | end 46 | end 47 | # describe 'with invalid parameters' 48 | 49 | describe 'with correct values' do 50 | describe 'with a user provided target' do 51 | let(:target) { '/root/.ssh/config' } 52 | 53 | let :params do 54 | { 55 | target: target 56 | } 57 | end 58 | 59 | it { 60 | is_expected.to contain_concat_file(target).with(ensure: 'present') 61 | is_expected.to contain_concat_fragment(title).with(target: target) 62 | } 63 | end 64 | # describe 'with a user provided target' 65 | 66 | describe 'user_home_dir behavior' do 67 | context 'with a user provided user_home_dir' do 68 | let(:user_home_dir) { '/path/to/home' } 69 | 70 | context 'with manage_user_ssh_dir default value' do 71 | let :params do 72 | { 73 | user_home_dir: user_home_dir 74 | } 75 | end 76 | 77 | it 'contains ssh directory and ssh config' do 78 | is_expected.to contain_file("#{user_home_dir}/.ssh").with( 79 | ensure: 'directory', 80 | owner: title, 81 | mode: '0700' 82 | ).that_comes_before("Concat_file[#{user_home_dir}/.ssh/config]") 83 | 84 | is_expected.to contain_concat_file("#{user_home_dir}/.ssh/config").with( 85 | ensure: 'present', 86 | owner: title, 87 | mode: '0600' 88 | ) 89 | end 90 | end 91 | # context 'with manage_user_ssh_dir default value' 92 | 93 | context 'with manage_user_ssh_dir set to false' do 94 | let :params do 95 | { 96 | user_home_dir: user_home_dir, 97 | manage_user_ssh_dir: false 98 | } 99 | end 100 | 101 | it do 102 | is_expected.not_to contain_file("#{user_home_dir}/.ssh") 103 | end 104 | end 105 | # context 'with manage_user_ssh_dir set to false' 106 | end 107 | # context 'with a user provided user_home_dir' 108 | 109 | context 'with no user provided user_home_dir' do 110 | it 'with manage_user_ssh_dir default value' do 111 | is_expected.to contain_file("/home/#{title}/.ssh").that_comes_before("Concat_file[/home/#{title}/.ssh/config]") 112 | is_expected.to contain_concat_file("/home/#{title}/.ssh/config") 113 | end 114 | 115 | context 'with manage_user_ssh_dir set to false' do 116 | let :params do 117 | { 118 | manage_user_ssh_dir: false 119 | } 120 | end 121 | 122 | it do 123 | is_expected.not_to contain_file("/home/#{title}/.ssh") 124 | end 125 | 126 | it do 127 | is_expected.to contain_concat_file("/home/#{title}/.ssh/config") 128 | end 129 | end 130 | # context 'with manage_user_ssh_dir set to false' 131 | end 132 | # context 'with no user provided user_home_dir' 133 | end 134 | # describe 'user_home_dir behavior' 135 | 136 | describe 'ssh configuration content' do 137 | let :params do 138 | { 139 | options: ssh_options 140 | } 141 | end 142 | 143 | it 'has single value' do 144 | is_expected.to contain_concat_fragment(title).with( 145 | content: %r{HashKnownHosts\s+yes}, 146 | target: "/home/#{title}/.ssh/config" 147 | ) 148 | end 149 | 150 | it 'has Hash value' do 151 | is_expected.to contain_concat_fragment(title).with( 152 | content: %r{Host \*\.in2p3\.fr\s*\n\s+GSSAPIAuthentication\s+no\s*\n\s+User\s+riton}, 153 | target: "/home/#{title}/.ssh/config" 154 | ) 155 | end 156 | end 157 | end 158 | end 159 | end 160 | end 161 | # vim: tabstop=2 shiftwidth=2 softtabstop=2 162 | -------------------------------------------------------------------------------- /spec/defines/client/match_block_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'ssh::client::match_block' do 6 | on_supported_os.each do |os, os_facts| 7 | context "on #{os}" do 8 | let(:facts) { os_facts } 9 | let :pre_condition do 10 | 'include ssh' 11 | end 12 | 13 | context 'with !foo' do 14 | let(:title) { '!foo' } 15 | let(:params) do 16 | { 17 | 'type' => 'user', 18 | 'options' => { 19 | 'ProxyCommand' => '/usr/bin/sss_ssh_knownhostsproxy -p %p %h', 20 | }, 21 | 'target' => '/etc/ssh/ssh_config_foo', 22 | } 23 | end 24 | 25 | it { is_expected.to compile.with_all_deps } 26 | 27 | it do 28 | is_expected.to contain_concat__fragment('match_block !foo').with( 29 | target: '/etc/ssh/ssh_config_foo', 30 | content: <<~SSH, 31 | Match user !foo 32 | ProxyCommand /usr/bin/sss_ssh_knownhostsproxy -p %p %h 33 | SSH 34 | order: 250 35 | ) 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/defines/server/config/setting_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'ssh::server::config::setting' do 6 | on_supported_os.each do |os, os_facts| 7 | let(:facts) { os_facts } 8 | 9 | context "on #{os}" do 10 | let(:title) { 'something' } 11 | 12 | context 'with all defaults' do 13 | it { is_expected.not_to compile } 14 | end 15 | 16 | describe 'with key => "AllowGroups", value => "group1 group2"' do 17 | let :params do 18 | { 19 | key: 'AllowGroups', 20 | value: 'group1 group2' 21 | } 22 | end 23 | 24 | it { is_expected.to compile.with_all_deps } 25 | it { is_expected.to contain_concat__fragment('ssh_setting_something_AllowGroups').with_content(%r{\nAllowGroups group1 group2\n}) } 26 | end 27 | 28 | describe 'with key => "Somesetting", value => true' do 29 | let :params do 30 | { 31 | key: 'Somesetting', 32 | value: true 33 | } 34 | end 35 | 36 | it { is_expected.to contain_concat__fragment('ssh_setting_something_Somesetting').with_content(%r{\nSomesetting yes\n}) } 37 | end 38 | 39 | describe 'with key => "Foo", value => [1, 2]' do 40 | let :params do 41 | { 42 | key: 'Foo', 43 | value: [1, 2] 44 | } 45 | end 46 | 47 | it { is_expected.to contain_concat__fragment('ssh_setting_something_Foo').with_content(%r{\nFoo 1 2\n}) } 48 | end 49 | 50 | describe 'with key => "Bar", value => {"a" => "b"}' do 51 | let :params do 52 | { 53 | key: 'Bar', 54 | value: { 55 | 'a' => 'b' 56 | } 57 | } 58 | end 59 | 60 | it { is_expected.to compile.and_raise_error(%r{Hash values are not supported}) } 61 | end 62 | end 63 | end 64 | end 65 | 66 | # vim: tabstop=2 shiftwidth=2 softtabstop=2 67 | -------------------------------------------------------------------------------- /spec/defines/server/host_key_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'ssh::server::host_key', type: :define do 6 | on_supported_os.each do |os, os_facts| 7 | context "on #{os}" do 8 | let(:facts) { os_facts } 9 | let(:title) { 'something' } 10 | let(:pre_condition) { 'include ssh' } 11 | 12 | context 'with all defaults' do 13 | it { is_expected.to compile.and_raise_error(%r{You must provide either public_key_source or public_key_content parameter}) } 14 | end 15 | 16 | describe 'with public_key_content, private_key_content and certificate_content' do 17 | let :params do 18 | { 19 | public_key_content: 'abc', 20 | private_key_content: 'bcd', 21 | certificate_content: 'cde' 22 | } 23 | end 24 | 25 | it { is_expected.to compile.with_all_deps } 26 | 27 | it { 28 | is_expected.to contain_file('something_pub'). 29 | with_content('abc'). 30 | with_ensure('present'). 31 | with_owner(0). 32 | with_group(0). 33 | with_mode('0644'). 34 | with_path('/etc/ssh/something.pub') 35 | is_expected.to contain_file('something_priv'). 36 | with_content('bcd'). 37 | with_ensure('present'). 38 | with_owner(0). 39 | with_group(0). 40 | with_mode('0600'). 41 | with_path('/etc/ssh/something') 42 | is_expected.to contain_file('something_cert'). 43 | with_content('cde'). 44 | with_ensure('present'). 45 | with_owner(0). 46 | with_group(0). 47 | with_mode('0644'). 48 | with_path('/etc/ssh/something-cert.pub') 49 | } 50 | end 51 | 52 | describe 'with public_key_content and private_key_content' do 53 | let :params do 54 | { 55 | public_key_content: 'abc', 56 | private_key_content: 'bcd' 57 | } 58 | end 59 | 60 | it { 61 | is_expected.to contain_file('something_pub'). 62 | with_content('abc'). 63 | with_ensure('present'). 64 | with_owner(0). 65 | with_group(0). 66 | with_mode('0644'). 67 | with_path('/etc/ssh/something.pub') 68 | is_expected.to contain_file('something_priv'). 69 | with_content('bcd'). 70 | with_ensure('present'). 71 | with_owner(0). 72 | with_group(0). 73 | with_mode('0600'). 74 | with_path('/etc/ssh/something') 75 | is_expected.not_to contain_file('something_cert') 76 | } 77 | end 78 | 79 | describe 'with *_key_content and *_key_source, *_key_source takes precedence' do 80 | let :params do 81 | { 82 | public_key_content: 'abc', 83 | public_key_source: 'a', 84 | private_key_content: 'bcd', 85 | private_key_source: 'b' 86 | } 87 | end 88 | 89 | it { 90 | is_expected.to contain_file('something_pub'). 91 | without_content. 92 | with_source('a'). 93 | with_ensure('present'). 94 | with_owner(0). 95 | with_group(0). 96 | with_mode('0644'). 97 | with_path('/etc/ssh/something.pub') 98 | is_expected.to contain_file('something_priv'). 99 | without_content. 100 | with_source('b'). 101 | with_ensure('present'). 102 | with_owner(0). 103 | with_group(0). 104 | with_mode('0600'). 105 | with_path('/etc/ssh/something') 106 | is_expected.not_to contain_file('something_cert') 107 | } 108 | end 109 | 110 | describe 'with private_key_content and no public_key_content' do 111 | let :params do 112 | { 113 | private_key_content: 'bcd' 114 | } 115 | end 116 | 117 | it { is_expected.to compile.and_raise_error(%r{You must provide either public_key_source or public_key_content parameter}) } 118 | end 119 | 120 | describe 'with public_key_content and no private_key_content' do 121 | let :params do 122 | { 123 | public_key_content: 'abc' 124 | } 125 | end 126 | 127 | it { is_expected.to compile.and_raise_error(%r{You must provide either private_key_source or private_key_content parameter}) } 128 | end 129 | 130 | describe 'with private_key_source and no public_key_source' do 131 | let :params do 132 | { 133 | private_key_source: 'bcd' 134 | } 135 | end 136 | 137 | it { is_expected.to compile.and_raise_error(%r{You must provide either public_key_source or public_key_content parameter}) } 138 | end 139 | 140 | describe 'with public_key_source and no private_key_source' do 141 | let :params do 142 | { 143 | public_key_source: 'abc' 144 | } 145 | end 146 | 147 | it { is_expected.to compile.and_raise_error(%r{You must provide either private_key_source or private_key_content parameter}) } 148 | end 149 | end 150 | end 151 | end 152 | # vim: tabstop=2 shiftwidth=2 softtabstop=2 153 | -------------------------------------------------------------------------------- /spec/defines/server/instances_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'ssh::server::instances' do 6 | on_supported_os.each do |os, os_facts| 7 | context "on #{os}", if: os_facts[:kernel] == 'Linux' do 8 | let(:facts) { os_facts } 9 | let(:title) { 'sftp_server' } 10 | let :pre_condition do 11 | 'include ssh' 12 | end 13 | 14 | context 'with sftp_server present' do 15 | let(:params) do 16 | { 17 | 'ensure' => 'present', 18 | 'options' => { 19 | 'sshd_config' => { 20 | 'Port' => 8022, 21 | 'Protocol' => 2, 22 | 'AddressFamily' => 'any', 23 | 'HostKey' => '/etc/ssh/ssh_host_rsa_key', 24 | 'SyslogFacility' => 'AUTH', 25 | 'LogLevel' => 'INFO', 26 | 'LoginGraceTime' => 120, 27 | 'PermitRootLogin' => 'no', 28 | 'StrictModes' => 'yes', 29 | 'PubkeyAuthentication' => 'yes', 30 | 'HostbasedAuthentication' => 'no', 31 | 'IgnoreUserKnownHosts' => 'no', 32 | 'IgnoreRhosts' => 'yes', 33 | 'PasswordAuthentication' => 'yes', 34 | 'ChallengeResponseAuthentication' => 'no', 35 | 'GSSAPIAuthentication' => 'no', 36 | 'GSSAPIKeyExchange' => 'no', 37 | 'GSSAPICleanupCredentials' => 'yes', 38 | 'UsePAM' => 'yes', 39 | 'AcceptEnv' => %w[LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION LC_ALL], 40 | 'AllowTcpForwarding' => 'no', 41 | 'X11Forwarding' => 'no', 42 | 'X11UseLocalhost' => 'yes', 43 | 'PrintMotd' => 'yes', 44 | 'TCPKeepAlive' => 'yes', 45 | 'ClientAliveInterval' => 0, 46 | 'ClientAliveCountMax' => 0, 47 | 'UseDNS' => 'no', 48 | 'PermitTunnel' => 'no', 49 | 'Banner' => '/etc/ssh/sshd_banner.txt', 50 | 'XAuthLocation' => '/usr/bin/xauth', 51 | 'Subsystem' => 'sftp /usr/libexec/openssh/sftp-server', 52 | 'Ciphers' => %w[aes128-ctr aes192-ctr aes256-ctr aes128-cbc 3des-cbc aes192-cbc aes256-cbc], 53 | 'AllowGroups' => 'root lclssh ssh_all_systems VmAdmins', 54 | }, 55 | 'sshd_service_options' => '', 56 | 'match_blocks' => { 57 | '*,!ssh_exempt_ldap_authkey,!sshlokey' => { 58 | 'type' => 'group', 59 | 'options' => { 60 | 'AuthorizedKeysCommand' => '/usr/local/bin/getauthkey', 61 | 'AuthorizedKeysCommandUser' => 'nobody', 62 | 'AuthorizedKeysFile' => '/dev/null', 63 | }, 64 | }, 65 | 'ssh_deny_pw_auth,sshdnypw' => { 66 | 'type' => 'group', 67 | 'options' => { 68 | 'KbdInteractiveAuthentication' => 'no', 69 | 'PasswordAuthentication' => 'no', 70 | }, 71 | }, 72 | }, 73 | }, 74 | 'service_ensure' => 'running', 75 | 'service_enable' => true, 76 | 'validate_config_file' => true, 77 | } 78 | end 79 | 80 | it { is_expected.to compile.with_all_deps } 81 | it { is_expected.to contain_concat('/etc/ssh/sshd_config.sftp_server') } 82 | it { is_expected.to contain_concat__fragment('sshd instance sftp_server config') } 83 | it { is_expected.to contain_ssh__server__match_block('ssh_deny_pw_auth,sshdnypw') } 84 | it { is_expected.to contain_ssh__server__match_block('*,!ssh_exempt_ldap_authkey,!sshlokey') } 85 | it { is_expected.to contain_systemd__unit_file('sftp_server.service') } 86 | it { is_expected.to contain_service('sftp_server.service') } 87 | end 88 | 89 | context 'with minimal params' do 90 | let(:params) do 91 | { 92 | 'ensure' => 'present', 93 | 'options' => { 94 | 'sshd_config' => { 95 | 'Port' => 8022, 96 | 'Protocol' => 2, 97 | 'AddressFamily' => 'any', 98 | 'HostKey' => '/etc/ssh/ssh_host_rsa_key', 99 | 'SyslogFacility' => 'AUTH', 100 | 'LogLevel' => 'INFO', 101 | 'PermitRootLogin' => 'no', 102 | }, 103 | 'sshd_service_options' => '', 104 | 'match_blocks' => {}, 105 | }, 106 | } 107 | end 108 | 109 | it { is_expected.to compile.with_all_deps } 110 | it { is_expected.to contain_concat__fragment('sshd instance sftp_server config') } 111 | it { is_expected.to contain_systemd__unit_file('sftp_server.service').with_enable(true).with_active(true) } 112 | it { is_expected.to contain_service('sftp_server.service').with_ensure(true).with_enable(true) } 113 | end 114 | 115 | context 'with minimal example and ensure stopped' do 116 | let(:params) do 117 | { 118 | 'ensure' => 'absent', 119 | } 120 | end 121 | 122 | it { is_expected.to compile.with_all_deps } 123 | it { is_expected.to contain_concat__fragment('sshd instance sftp_server config') } 124 | it { is_expected.to contain_systemd__unit_file('sftp_server.service').with_enable(false).with_active(false) } 125 | it { is_expected.to contain_service('sftp_server.service').with_enable(false).with_ensure(false) } 126 | end 127 | 128 | context 'with minimal example and service stopped' do 129 | let(:params) do 130 | { 131 | 'service_ensure' => 'stopped', 132 | 'service_enable' => true, 133 | } 134 | end 135 | 136 | it { is_expected.to compile.with_all_deps } 137 | it { is_expected.to contain_concat__fragment('sshd instance sftp_server config') } 138 | it { is_expected.to contain_systemd__unit_file('sftp_server.service').with_enable(true).with_active(false) } 139 | it { is_expected.to contain_service('sftp_server.service').with_enable(true).with_ensure(false) } 140 | end 141 | 142 | context 'with minimal example and service running but not in autostart' do 143 | let(:params) do 144 | { 145 | 'ensure' => 'present', 146 | 'service_enable' => false, 147 | } 148 | end 149 | 150 | it { is_expected.to compile.with_all_deps } 151 | it { is_expected.to contain_concat__fragment('sshd instance sftp_server config') } 152 | it { is_expected.to contain_systemd__unit_file('sftp_server.service').with_enable(false).with_active(true) } 153 | it { is_expected.to contain_service('sftp_server.service').with_enable(false).with_ensure(true) } 154 | end 155 | end 156 | end 157 | end 158 | -------------------------------------------------------------------------------- /spec/defines/server/match_block_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'ssh::server::match_block' do 6 | on_supported_os.each do |os, os_facts| 7 | context "on #{os}" do 8 | let(:facts) { os_facts } 9 | let :pre_condition do 10 | 'include ssh' 11 | end 12 | 13 | context 'with *,!ssh_exempt_ldap_authkey,!sshlokey present' do 14 | let(:title) { '*,!ssh_exempt_ldap_authkey,!sshlokey' } 15 | let(:params) do 16 | { 17 | 'type' => 'group', 18 | 'options' => { 19 | 'AuthorizedKeysCommand' => '/usr/local/bin/getauthkey', 20 | 'AuthorizedKeysCommandUser' => 'nobody', 21 | 'AuthorizedKeysFile' => '/dev/null', 22 | }, 23 | 'target' => '/etc/ssh/sshd_config_sftp_server', 24 | } 25 | end 26 | 27 | it { is_expected.to compile.with_all_deps } 28 | it { is_expected.to contain_concat__fragment('match_block *,!ssh_exempt_ldap_authkey,!sshlokey') } 29 | end 30 | 31 | context 'with ssh_deny_pw_auth,sshdnypw' do 32 | let(:title) { 'ssh_deny_pw_auth,sshdnypw' } 33 | let(:params) do 34 | { 35 | 'type' => 'group', 36 | 'options' => { 37 | 'KbdInteractiveAuthentication' => 'no', 38 | 'PasswordAuthentication' => 'no', 39 | }, 40 | 'target' => '/etc/ssh/sshd_config_sftp_server', 41 | } 42 | end 43 | 44 | it { is_expected.to compile.with_all_deps } 45 | it { is_expected.to contain_concat__fragment('match_block ssh_deny_pw_auth,sshdnypw') } 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/fixtures/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except these files 4 | !.gitignore 5 | !site.pp 6 | !mock-interface-fact.json 7 | -------------------------------------------------------------------------------- /spec/fixtures/mock-interface-fact.json: -------------------------------------------------------------------------------- 1 | { 2 | "networking": { 3 | "interfaces": { 4 | "docker0": { 5 | "bindings": [ 6 | { 7 | "address": "172.17.0.1", 8 | "netmask": "255.255.0.0", 9 | "network": "172.17.0.0" 10 | } 11 | ], 12 | "bindings6": [ 13 | { 14 | "address": "fe80::42:2fff:fea3:f2b7", 15 | "netmask": "ffff:ffff:ffff:ffff::", 16 | "network": "fe80::" 17 | } 18 | ], 19 | "ip": "172.17.0.1", 20 | "ip6": "fe80::42:2fff:fea3:f2b7", 21 | "mac": "02:42:2f:a3:f2:b7", 22 | "mtu": 1500, 23 | "netmask": "255.255.0.0", 24 | "netmask6": "ffff:ffff:ffff:ffff::", 25 | "network": "172.17.0.0", 26 | "network6": "fe80::" 27 | }, 28 | "eno1": { 29 | "bindings": [ 30 | { 31 | "address": "10.13.42.61", 32 | "netmask": "255.255.255.0", 33 | "network": "10.13.42.0" 34 | }, 35 | { 36 | "address": "10.0.0.110", 37 | "netmask": "255.255.255.255", 38 | "network": "10.0.0.110" 39 | }, 40 | { 41 | "address": "10.0.0.104" 42 | }, 43 | { 44 | "address": "10.0.0.109" 45 | } 46 | ], 47 | "bindings6": [ 48 | { 49 | "address": "fe80::6544:473a:6ea4:c385", 50 | "netmask": "ffff:ffff:ffff:ffff::", 51 | "network": "fe80::" 52 | } 53 | ], 54 | "dhcp": "10.13.42.1", 55 | "ip": "10.13.42.61", 56 | "ip6": "fe80::6544:473a:6ea4:c385", 57 | "mac": "08:00:20:97:23:d1", 58 | "mtu": 1500, 59 | "netmask": "255.255.255.0", 60 | "netmask6": "ffff:ffff:ffff:ffff::", 61 | "network": "10.13.42.0", 62 | "network6": "fe80::" 63 | }, 64 | "lo": { 65 | "bindings": [ 66 | { 67 | "address": "127.0.0.1", 68 | "netmask": "255.0.0.0", 69 | "network": "127.0.0.0" 70 | } 71 | ], 72 | "bindings6": [ 73 | { 74 | "address": "::1", 75 | "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 76 | "network": "::1" 77 | } 78 | ], 79 | "ip": "127.0.0.1", 80 | "ip6": "::1", 81 | "mtu": 65536, 82 | "netmask": "255.0.0.0", 83 | "netmask6": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 84 | "network": "127.0.0.0", 85 | "network6": "::1" 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /spec/functions/ssh/ipaddresses_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'ssh::ipaddresses', type: :puppet_function do 6 | it 'exists' do 7 | is_expected.not_to be_nil 8 | end 9 | 10 | context 'with dummy structured fact data' do 11 | let(:facts) do 12 | JSON.parse File.read(File.join(File.dirname(__FILE__), '../../fixtures/mock-interface-fact.json')) 13 | end 14 | 15 | describe 'without parameters' do 16 | it 'returns all IPs other than localhost' do 17 | is_expected.to run.with_params([], []).and_return(['172.17.0.1', '10.13.42.61', '10.0.0.110', '10.0.0.104', '10.0.0.109']) 18 | end 19 | end 20 | 21 | describe 'with excluded interface' do 22 | it 'doesn\'t return the IPs of excluded interface' do 23 | is_expected.to run.with_params(['docker0'], []).and_return(['10.13.42.61', '10.0.0.110', '10.0.0.104', '10.0.0.109']) 24 | end 25 | end 26 | 27 | describe 'with excluded interfaces' do 28 | it 'doesn\'t return the IPs of those interfaces' do 29 | is_expected.to run.with_params(%w[docker0 eno1], []).and_return([]) 30 | end 31 | end 32 | 33 | describe 'with excluded re interface' do 34 | it 'doesn\'t return the IPs of excluded interface' do 35 | is_expected.to run.with_params([], [%r{^docker}]).and_return(['10.13.42.61', '10.0.0.110', '10.0.0.104', '10.0.0.109']) 36 | end 37 | end 38 | 39 | describe 'with excluded re interfaces' do 40 | it 'doesn\'t return the IPs of those interfaces' do 41 | is_expected.to run.with_params([], [%r{docker0}, %r{no1$}]).and_return([]) 42 | end 43 | end 44 | end 45 | 46 | context 'with dummy legacy fact data' do 47 | let(:facts) do 48 | { 49 | networking: {}, 50 | interfaces: 'lo,docker0,eno1', 51 | ipaddress_lo: '127.0.0.1', 52 | ipaddress_eno1: '10.13.42.61', 53 | ipaddress_docker0: '172.17.0.1' 54 | } 55 | end 56 | 57 | describe 'without parameters' do 58 | it 'returns all IPs other than localhost' do 59 | is_expected.to run.with_params([], []).and_return(['172.17.0.1', '10.13.42.61']) 60 | end 61 | end 62 | 63 | describe 'with excluded interface' do 64 | it 'doesn\'t return the IPs of excluded interface' do 65 | is_expected.to run.with_params(['docker0'], []).and_return(['10.13.42.61']) 66 | end 67 | end 68 | 69 | describe 'with excluded interfaces' do 70 | it 'doesn\'t return the IPs of those interfaces' do 71 | is_expected.to run.with_params(%w[docker0 eno1], []).and_return([]) 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/setup_acceptance_node.pp: -------------------------------------------------------------------------------- 1 | if fact('os.name') == 'Debian' and !fact('aio_agent_version') { 2 | package { ['puppet-module-puppetlabs-sshkeys-core']: 3 | ensure => present, 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Managed by modulesync - DO NOT EDIT 4 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 5 | 6 | # puppetlabs_spec_helper will set up coverage if the env variable is set. 7 | # We want to do this if lib exists and it hasn't been explicitly set. 8 | ENV['COVERAGE'] ||= 'yes' if Dir.exist?(File.expand_path('../lib', __dir__)) 9 | 10 | require 'voxpupuli/test/spec_helper' 11 | 12 | RSpec.configure do |c| 13 | c.facterdb_string_keys = false 14 | end 15 | 16 | add_mocked_facts! 17 | 18 | if File.exist?(File.join(__dir__, 'default_module_facts.yml')) 19 | facts = YAML.safe_load(File.read(File.join(__dir__, 'default_module_facts.yml'))) 20 | facts&.each do |name, value| 21 | add_custom_fact name.to_sym, value 22 | end 23 | end 24 | Dir['./spec/support/spec/**/*.rb'].sort.each { |f| require f } 25 | -------------------------------------------------------------------------------- /spec/spec_helper_acceptance.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'voxpupuli/acceptance/spec_helper_acceptance' 4 | 5 | configure_beaker 6 | 7 | Dir['./spec/support/acceptance/**/*.rb'].sort.each { |f| require f } 8 | -------------------------------------------------------------------------------- /spec/type_aliases/sshclientmatch_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Ssh::ClientMatch' do 6 | known_criteria = %w[ 7 | !all 8 | all 9 | !canonical 10 | canonical 11 | !exec 12 | exec 13 | !final 14 | final 15 | !host 16 | host 17 | !localuser 18 | localuser 19 | !originalhost 20 | originalhost 21 | !user 22 | user 23 | ] 24 | it { is_expected.to allow_values(*known_criteria) } 25 | it { is_expected.not_to allow_value(nil) } 26 | it { is_expected.not_to allow_value('foo') } 27 | end 28 | -------------------------------------------------------------------------------- /spec/unit/facter/util/fact_ssh_client_version_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'ssh_client_version_full' do 6 | before { Facter.clear } 7 | 8 | after { Facter.clear } 9 | 10 | context 'when on a Linux host' do 11 | before do 12 | allow(Facter.fact(:kernel)).to receive(:value).and_return('linux') 13 | allow(Facter::Util::Resolution).to receive(:which).with('ssh').and_return('/usr/bin/ssh') 14 | allow(Facter::Util::Resolution).to receive(:exec).with('ssh -V 2>&1').and_return('OpenSSH_6.6.1p1 Ubuntu-2ubuntu2.8, OpenSSL 1.0.1f 6 Jan 2014') 15 | end 16 | 17 | it 'execs ssh -V and returns full version number' do 18 | expect(Facter.fact(:ssh_client_version_full).value).to eq('6.6.1p1') 19 | end 20 | end 21 | 22 | context 'when on a SunOS host' do 23 | before do 24 | allow(Facter.fact(:kernel)).to receive(:value).and_return('SunOS') 25 | allow(Facter::Util::Resolution).to receive(:which).with('ssh').and_return('/usr/bin/ssh') 26 | allow(Facter::Util::Resolution).to receive(:exec).with('ssh -V 2>&1').and_return('Sun_SSH_2.4, SSH protocols 1.5/2.0, OpenSSL 0x100020bf') 27 | end 28 | 29 | it 'execs ssh -V and returns full version number' do 30 | expect(Facter.fact(:ssh_client_version_full).value).to eq('2.4') 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/unit/facter/util/fact_ssh_server_version_major_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Facter::Util::Fact do 6 | before do 7 | Facter.clear 8 | allow(Facter.fact(:kernel)).to receive(:value).and_return('linux') 9 | end 10 | 11 | describe 'ssh_server_version_major' do 12 | context 'with 3 point semver syntax (6.6.1p1)' do 13 | context 'with ssh_server_version_full fact present returns major version' do 14 | before do 15 | allow(Facter.fact(:ssh_server_version_full)).to receive(:value).and_return('6.6.1p1') 16 | end 17 | 18 | it do 19 | expect(Facter.fact(:ssh_server_version_major).value).to eq('6') 20 | end 21 | end 22 | end 23 | 24 | context 'with 2 point semver syntax (7.2p2)' do 25 | context 'with ssh_server_version_full fact present returns major version' do 26 | before do 27 | allow(Facter.fact(:ssh_server_version_full)).to receive(:value).and_return('7.2p2') 28 | end 29 | 30 | it do 31 | expect(Facter.fact(:ssh_server_version_major).value).to eq('7') 32 | end 33 | end 34 | end 35 | 36 | context 'without ssh_server_version_full fact present returns nil' do 37 | before do 38 | allow(Facter.fact(:ssh_server_version_full)).to receive(:value).and_return(nil) 39 | end 40 | 41 | it do 42 | expect(Facter.fact(:ssh_server_version_major).value).to be_nil 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/unit/facter/util/fact_ssh_server_version_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'ssh_server_version_full' do 6 | before { Facter.clear } 7 | 8 | after { Facter.clear } 9 | 10 | context 'when on a Linux host' do 11 | before do 12 | allow(Facter.fact(:kernel)).to receive(:value).and_return('linux') 13 | allow(Facter::Util::Resolution).to receive(:which).with('sshd').and_return('/usr/bin/sshd') 14 | allow(Facter::Util::Resolution).to receive(:exec).with('sshd -V 2>&1').and_return('OpenSSH_6.6.1p1 Ubuntu-2ubuntu2.8, OpenSSL 1.0.1f 6 Jan 2014') 15 | end 16 | 17 | it 'execs sshd -V and returns full version number' do 18 | expect(Facter.fact(:ssh_server_version_full).value).to eq('6.6.1p1') 19 | end 20 | end 21 | 22 | context 'when on a SunOS host' do 23 | before do 24 | allow(Facter.fact(:kernel)).to receive(:value).and_return('SunOS') 25 | allow(Facter::Util::Resolution).to receive(:which).with('sshd').and_return('/usr/bin/sshd') 26 | allow(Facter::Util::Resolution).to receive(:exec).with('sshd -V 2>&1').and_return('Sun_SSH_2.4, SSH protocols 1.5/2.0, OpenSSL 0x100020bf') 27 | end 28 | 29 | it 'execs sshd -V and returns full version number' do 30 | expect(Facter.fact(:ssh_server_version_full).value).to eq('2.4') 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /templates/issue.net.erb: -------------------------------------------------------------------------------- 1 | <%= @facts['networking']['hostname'] %> 2 | ********* ****************************************** 3 | * This system is a restricted resource and property. * 4 | * Use your administrator assigned user-id to access. * 5 | * Unauthorized use and/or misuse of this system and services may constitute * 6 | * a breach of International criminal law, and could lead to legal * 7 | * disciplinary actions. * 8 | * * 9 | * Individuals using this system without authority or in excess of their * 10 | * authority are subject to having all their activities on this system * 11 | * monitored and recorded or examined by any authorized person, including law * 12 | * enforcement, as system personnel deem appropriate. * 13 | * * 14 | * LOG OFF IMMEDIATELY * 15 | * * 16 | ******************************************************************************* 17 | -------------------------------------------------------------------------------- /templates/options.erb: -------------------------------------------------------------------------------- 1 | <%- 2 | def bool2str(v) 3 | case v 4 | when true 5 | 'yes' 6 | when false 7 | 'no' 8 | else 9 | v 10 | end 11 | end 12 | -%> 13 | <%- @options.keys.sort_by{ |sk| (sk.to_s.downcase.include? "match") ? 'zzz' + sk.to_s : sk.to_s }.each do |k| -%> 14 | <%- v = @options[k] -%> 15 | <%- if v.is_a?(Hash) -%> 16 | <%= k %> 17 | <%- v.keys.sort.each do |key| -%> 18 | <%- value = v[key] -%> 19 | <%- if value.is_a?(Array) -%> 20 | <%- value.each do |a| -%> 21 | <%- if a != '' && a != nil -%> 22 | <%= key %> <%= bool2str(a) %> 23 | <%- end -%> 24 | <%- end -%> 25 | <%- elsif value != '' && value != nil -%> 26 | <%= key %> <%= bool2str(value) %> 27 | <%- end -%> 28 | <%- end -%> 29 | <%- else -%> 30 | <%- if v.is_a?(Array) -%> 31 | <%- v.each do |a| -%> 32 | <%- if a != '' && a != nil -%> 33 | <%= k %> <%= bool2str(a) %> 34 | <%- end -%> 35 | <%- end -%> 36 | <%- elsif v != nil and v != '' -%> 37 | <%= k %> <%= bool2str(v) %> 38 | <%- end -%> 39 | <%- end -%> 40 | <%- end -%> 41 | -------------------------------------------------------------------------------- /templates/ssh_config.erb: -------------------------------------------------------------------------------- 1 | # File managed by Puppet 2 | <%- 3 | def bool2str(v) 4 | case v 5 | when true 6 | 'yes' 7 | when false 8 | 'no' 9 | else 10 | v 11 | end 12 | end 13 | -%> 14 | 15 | <%- @options.each do |k, v| -%> 16 | <%- if v.is_a?(Hash) -%> 17 | <%- if k.length > 1024 -%> 18 | <%- fail("Line exceeds 1024 characters: #{k}") -%> 19 | <%- end -%> 20 | <%= k %> 21 | <%- v.sort.each do |key, value| -%> 22 | <%- if value.is_a?(Array) -%> 23 | <%- value.each do |a| -%> 24 | <%- if a != '' && a != nil -%> 25 | <%- line_content = "#{key} #{bool2str(a)}" -%> 26 | <%- if line_content.length > 1020 -%> 27 | <%- fail("Line exceeds 1024 characters: #{line_content}") -%> 28 | <%- else -%> 29 | <%= line_content %> 30 | <%- end -%> 31 | <%- end -%> 32 | <%- end -%> 33 | <%- elsif value != '' && value != nil -%> 34 | <%- line_content = "#{key} #{bool2str(value)}" -%> 35 | <%- if line_content.length > 1020 -%> 36 | <%- fail("Line exceeds 1024 characters: #{line_content}") -%> 37 | <%- end -%> 38 | <%= line_content %> 39 | <%- end -%> 40 | <%- end -%> 41 | <%- else -%> 42 | <%- if v.is_a?(Array) -%> 43 | <%- v.each do |a| -%> 44 | <%- if a != '' && a != nil -%> 45 | <%- line_content = "#{k} #{bool2str(a)}" -%> 46 | <%- if line_content.length > 1024 -%> 47 | <%- fail("Line exceeds 1024 characters: #{line_content}") -%> 48 | <%- end -%> 49 | <%= k %> <%= bool2str(a) %> 50 | <%- end -%> 51 | <%- end -%> 52 | <%- elsif v != :undef && v != '' && v != nil -%> 53 | <%- line_content = "#{k} #{bool2str(v)}" -%> 54 | <%- if line_content.length > 1024 -%> 55 | <%- fail("Line exceeds 1024 characters: #{line_content}") -%> 56 | <%- end -%> 57 | <%= k %> <%= bool2str(v) %> 58 | <%- end -%> 59 | <%- end -%> 60 | <%- end -%> 61 | -------------------------------------------------------------------------------- /templates/ssh_instance.erb: -------------------------------------------------------------------------------- 1 | # File is managed by Puppet 2 | <%- 3 | def bool2str(v) 4 | case v 5 | when true 6 | 'yes' 7 | when false 8 | 'no' 9 | else 10 | v 11 | end 12 | end 13 | -%> 14 | <%- if addressfamily = @sshd_instance_config.delete('AddressFamily') -%> 15 | AddressFamily <%= addressfamily %> 16 | <%- end -%> 17 | <%- if port = @sshd_instance_config.delete('Port') -%> 18 | <%- if port.is_a?(Array) -%> 19 | <%- port.reject{ |x| x.to_s.strip.empty? }.each do |p| -%> 20 | Port <%= p %> 21 | <%- end -%> 22 | <%- elsif not port.to_s.strip.empty? -%> 23 | Port <%= port %> 24 | <%- end -%> 25 | <%- end -%> 26 | <%- if listen = @sshd_instance_config.delete('ListenAddress') -%> 27 | <%- if listen.is_a?(Array) -%> 28 | <%- listen.reject{ |x| x.strip.empty? }.each do |l| -%> 29 | ListenAddress <%= l %> 30 | <%- end -%> 31 | <%- elsif not listen.strip.empty? -%> 32 | ListenAddress <%= listen %> 33 | <%- end -%> 34 | <%- end -%> 35 | 36 | <%- @sshd_instance_config.keys.sort_by{ |sk| (sk.to_s.downcase.include? "match") ? 'zzz' + sk.to_s : sk.to_s }.each do |k| -%> 37 | <%- v = @sshd_instance_config[k] -%> 38 | <%- if v.is_a?(Hash) -%> 39 | <%= k %> 40 | <%- v.keys.sort.each do |key| -%> 41 | <%- value = v[key] -%> 42 | <%- if value.is_a?(Array) -%> 43 | <%- if ['ciphers', 'macs', 'kexalgorithms'].include?(key.downcase) -%> 44 | <%= key %> <%= value.join(',') %> 45 | <%- else -%> 46 | <%- value.each do |a| -%> 47 | <%- if a != '' && a != nil -%> 48 | <%= key %> <%= bool2str(a) %> 49 | <%- end -%> 50 | <%- end -%> 51 | <%- end -%> 52 | <%- elsif value != '' && value != nil -%> 53 | <%= key %> <%= bool2str(value) %> 54 | <%- end -%> 55 | <%- end -%> 56 | <%- else -%> 57 | <%- if v.is_a?(Array) -%> 58 | <%- if ['ciphers', 'macs', 'kexalgorithms'].include?(k.downcase) -%> 59 | <%= k %> <%= v.join(',') %> 60 | <%- else -%> 61 | <%- v.each do |a| -%> 62 | <%- if a != '' && a != nil -%> 63 | <%= k %> <%= bool2str(a) %> 64 | <%- end -%> 65 | <%- end -%> 66 | <%- end -%> 67 | <%- elsif v != nil and v != '' -%> 68 | <%= k %> <%= bool2str(v) %> 69 | <%- end -%> 70 | <%- end -%> 71 | <%- end -%> 72 | -------------------------------------------------------------------------------- /templates/ssh_instance_service.erb: -------------------------------------------------------------------------------- 1 | # <%= @sshd_instance_service_name %> 2 | # This file is managed by Puppet. 3 | # DO NOT EDIT 4 | 5 | [Unit] 6 | Description=SSHD Instance <%= @title %> 7 | Documentation=man:sshd(8) man:sshd_config(5) 8 | After=network.target sshd-keygen.service 9 | Wants=sshd-keygen.service 10 | 11 | [Service] 12 | <%- if @sshd_additional_service_options -%> 13 | <%- @sshd_additional_service_options.each do |k,v| -%> 14 | <%- if v.is_a?(Array) -%> 15 | <%- v.each do |a| -%> 16 | <%- if a != '' && a != nil -%> 17 | <%= k %>=<%= bool2str(a) %> 18 | <%- end -%> 19 | <%- end -%> 20 | <%- elsif v != '' && v != nil -%> 21 | <%= k %>=<%= bool2str(v) %> 22 | <%- end -%> 23 | <%- end -%> 24 | <%- end -%> 25 | <% if @sshd_environments_file %> 26 | EnvironmentFile=<%= @sshd_environments_file -%> 27 | <% end %> 28 | ExecStart=<%= @sshd_binary %> -f <%= @sshd_instance_config_file %> -D $OPTIONS <%= @sshd_service_options %> 29 | ExecReload=/bin/kill -HUP $MAINPID 30 | KillMode=process 31 | Restart=on-failure 32 | RestartSec=15s 33 | 34 | [Install] 35 | WantedBy=multi-user.target 36 | -------------------------------------------------------------------------------- /templates/sshd_config.erb: -------------------------------------------------------------------------------- 1 | # File is managed by Puppet 2 | <%- 3 | def bool2str(v) 4 | case v 5 | when true 6 | 'yes' 7 | when false 8 | 'no' 9 | else 10 | v 11 | end 12 | end 13 | -%> 14 | <%- if @include_dir -%> 15 | Include <%= @include_dir %>/*.conf 16 | <%- end -%> 17 | <%- if @include -%> 18 | Include <%= @include %> 19 | <%- end -%> 20 | <%- if addressfamily = @options.delete('AddressFamily') -%> 21 | AddressFamily <%= addressfamily %> 22 | <%- end -%> 23 | <%- if port = @options.delete('Port') -%> 24 | <%- if port.is_a?(Array) -%> 25 | <%- port.reject{ |x| x.to_s.strip.empty? }.each do |p| -%> 26 | Port <%= p %> 27 | <%- end -%> 28 | <%- elsif not port.to_s.strip.empty? -%> 29 | Port <%= port %> 30 | <%- end -%> 31 | <%- end -%> 32 | <%- if listen = @options.delete('ListenAddress') -%> 33 | <%- if listen.is_a?(Array) -%> 34 | <%- listen.reject{ |x| x.strip.empty? }.each do |l| -%> 35 | ListenAddress <%= l %> 36 | <%- end -%> 37 | <%- elsif not listen.strip.empty? -%> 38 | ListenAddress <%= listen %> 39 | <%- end -%> 40 | <%- end -%> 41 | 42 | <%- @options.keys.sort_by{ |sk| (sk.to_s.downcase.include? "match") ? 'zzz' + sk.to_s : sk.to_s }.each do |k| -%> 43 | <%- v = @options[k] -%> 44 | <%- if v.is_a?(Hash) -%> 45 | <%= k %> 46 | <%- v.keys.sort.each do |key| -%> 47 | <%- value = v[key] -%> 48 | <%- if value.is_a?(Array) -%> 49 | <%- if ['ciphers', 'macs', 'kexalgorithms'].include?(key.downcase) -%> 50 | <%= key %> <%= value.join(',') %> 51 | <%- else -%> 52 | <%- value.each do |a| -%> 53 | <%- if a != '' && a != nil -%> 54 | <%= key %> <%= bool2str(a) %> 55 | <%- end -%> 56 | <%- end -%> 57 | <%- end -%> 58 | <%- elsif value != '' && value != nil -%> 59 | <%= key %> <%= bool2str(value) %> 60 | <%- end -%> 61 | <%- end -%> 62 | <%- else -%> 63 | <%- if v.is_a?(Array) -%> 64 | <%- if ['ciphers', 'macs', 'kexalgorithms'].include?(k.downcase) -%> 65 | <%= k %> <%= v.join(',') %> 66 | <%- else -%> 67 | <%- v.each do |a| -%> 68 | <%- if a != '' && a != nil -%> 69 | <%= k %> <%= bool2str(a) %> 70 | <%- end -%> 71 | <%- end -%> 72 | <%- end -%> 73 | <%- elsif v != nil and v != '' -%> 74 | <%= k %> <%= bool2str(v) %> 75 | <%- end -%> 76 | <%- end -%> 77 | <%- end -%> 78 | -------------------------------------------------------------------------------- /templates/sshd_match_block.erb: -------------------------------------------------------------------------------- 1 | <%- 2 | def bool2str(v) 3 | case v 4 | when true 5 | 'yes' 6 | when false 7 | 'no' 8 | else 9 | v 10 | end 11 | end 12 | -%> 13 | Match <%= @type %> <%= @name %> 14 | <%- @options.keys.sort.each do |k| -%> 15 | <%- v = @options[k] -%> 16 | <%- if v != :undef -%> 17 | <%= k %> <%= bool2str(v) %> 18 | <%- end -%> 19 | <%- end -%> 20 | -------------------------------------------------------------------------------- /types/clientmatch.pp: -------------------------------------------------------------------------------- 1 | # OpenSSH client `Match` criteria. See `ssh_config(5)` 2 | type Ssh::ClientMatch = Enum[ 3 | '!all', 4 | 'all', 5 | '!canonical', 6 | 'canonical', 7 | '!exec', 8 | 'exec', 9 | '!final', 10 | 'final', 11 | '!host', 12 | 'host', 13 | '!localuser', 14 | 'localuser', 15 | '!originalhost', 16 | 'originalhost', 17 | '!user', 18 | 'user', 19 | ] 20 | --------------------------------------------------------------------------------