From e401c2f8de3dbc8d0878f4ab029f520b73d62e9b Mon Sep 17 00:00:00 2001 From: Steffen Zieger Date: Fri, 22 May 2026 15:53:44 +0200 Subject: [PATCH] add client include_dir, fixes #389 --- REFERENCE.md | 87 +++++++++++++++++++++++- manifests/client.pp | 41 +++++++---- manifests/client/config.pp | 18 +++++ manifests/client/config_file.pp | 35 ++++++++++ manifests/server.pp | 4 +- spec/classes/client_spec.rb | 117 ++++++++++++++++++++++++++++++++ templates/ssh_config.erb | 6 ++ 7 files changed, 292 insertions(+), 16 deletions(-) create mode 100644 manifests/client/config_file.pp diff --git a/REFERENCE.md b/REFERENCE.md index f3de537c..d3fa85cf 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -24,6 +24,7 @@ ### Defined types * [`ssh::client::config::user`](#ssh--client--config--user): This defined type manages a users ssh config +* [`ssh::client::config_file`](#ssh--client--config_file): Resource type for managing a config file in the include dir. * [`ssh::client::match_block`](#ssh--client--match_block): Add match_block to ssh client config (concat needed) * [`ssh::server::config::setting`](#ssh--server--config--setting): Internal define to managed ssh server param * [`ssh::server::config_file`](#ssh--server--config_file): Resource type for managing a config file in the include dir. @@ -357,6 +358,10 @@ The following parameters are available in the `ssh::client` class: * [`storeconfigs_group`](#-ssh--client--storeconfigs_group) * [`config_user`](#-ssh--client--config_user) * [`config_group`](#-ssh--client--config_group) +* [`include_dir`](#-ssh--client--include_dir) +* [`include_dir_mode`](#-ssh--client--include_dir_mode) +* [`include_dir_purge`](#-ssh--client--include_dir_purge) +* [`config_files`](#-ssh--client--config_files) ##### `ssh_config` @@ -454,6 +459,38 @@ Numeric id or name of the group for the config file Default value: `0` +##### `include_dir` + +Data type: `Optional[Stdlib::Absolutepath]` + +Path to ssh include directory. + +Default value: `undef` + +##### `include_dir_mode` + +Data type: `Stdlib::Filemode` + +Mode to set on the ssh include directory. + +Default value: `'0700'` + +##### `include_dir_purge` + +Data type: `Boolean` + +Purge the ssh include directory if true. + +Default value: `true` + +##### `config_files` + +Data type: `Hash[String, Hash]` + +Hash of config files to add to the ssh include directory. + +Default value: `{}` + ### `ssh::hostkeys` This class manages hostkeys. It is intended to be called from `ssh::server`. @@ -714,7 +751,7 @@ Default value: `'0700'` Data type: `Boolean` -Purge the include directory if true. +Purge the sshd include directory if true. Default value: `true` @@ -722,7 +759,7 @@ Default value: `true` Data type: `Hash[String, Hash]` -Hash of config files to add to the ssh include directory. +Hash of config files to add to the sshd include directory. Default value: `{}` @@ -963,6 +1000,52 @@ Default mode for the ssh config file Default value: `'0600'` +### `ssh::client::config_file` + +Resource type for managing a config file in the include dir. + +#### Parameters + +The following parameters are available in the `ssh::client::config_file` defined type: + +* [`mode`](#-ssh--client--config_file--mode) +* [`include`](#-ssh--client--config_file--include) +* [`options`](#-ssh--client--config_file--options) +* [`path`](#-ssh--client--config_file--path) + +##### `mode` + +Data type: `Stdlib::Filemode` + +File mode for the config file. + +Default value: `'0644'` + +##### `include` + +Data type: `Optional[Stdlib::Absolutepath]` + +Absolute path to config file to include at the top of the config file. +This is intended for including files not managed by this module. + +Default value: `undef` + +##### `options` + +Data type: `Hash` + +Dynamic hash for openssh client option + +Default value: `{}` + +##### `path` + +Data type: `Stdlib::Absolutepath` + + + +Default value: `"${ssh::client::include_dir}/${name}.conf"` + ### `ssh::client::match_block` Add match_block to ssh client config (concat needed) diff --git a/manifests/client.pp b/manifests/client.pp index d457cf3d..9ea9a6f2 100644 --- a/manifests/client.pp +++ b/manifests/client.pp @@ -40,22 +40,39 @@ # # @param config_user # Numeric id or name of the user for the config file +# # @param config_group # Numeric id or name of the group for the config file # +# @param include_dir +# Path to ssh include directory. +# +# @param include_dir_mode +# Mode to set on the ssh include directory. +# +# @param include_dir_purge +# Purge the ssh include directory if true. +# +# @param config_files +# Hash of config files to add to the ssh include directory. +# class ssh::client ( - Stdlib::Absolutepath $ssh_config, - Hash $default_options, - Variant[Integer, String[1]] $config_user, - Variant[Integer, String[1]] $config_group, - Optional[String[1]] $client_package_name = undef, - String $ensure = present, - Boolean $storeconfigs_enabled = true, - Hash $options = {}, - Boolean $use_augeas = false, - Array $options_absent = [], - Hash $match_block = {}, - Optional[String[1]] $storeconfigs_group = undef, + Stdlib::Absolutepath $ssh_config, + Hash $default_options, + Variant[Integer, String[1]] $config_user, + Variant[Integer, String[1]] $config_group, + Optional[String[1]] $client_package_name = undef, + String $ensure = present, + Boolean $storeconfigs_enabled = true, + Hash $options = {}, + Boolean $use_augeas = false, + Array $options_absent = [], + Hash $match_block = {}, + Optional[String[1]] $storeconfigs_group = undef, + Optional[Stdlib::Absolutepath] $include_dir = undef, + Stdlib::Filemode $include_dir_mode = '0700', + Boolean $include_dir_purge = true, + Hash[String, Hash] $config_files = {}, ) { if $use_augeas { $merged_options = sshclient_options_to_augeas_ssh_config($options, $options_absent, { 'target' => $ssh_config }) diff --git a/manifests/client/config.pp b/manifests/client/config.pp index 2547add7..6c50d636 100644 --- a/manifests/client/config.pp +++ b/manifests/client/config.pp @@ -7,6 +7,7 @@ assert_private() $options = $ssh::client::merged_options + $include_dir = $ssh::client::include_dir $use_augeas = $ssh::client::use_augeas if $use_augeas { @@ -29,4 +30,21 @@ order => '00', } } + + if $ssh::client::include_dir { + file { $ssh::client::include_dir: + ensure => directory, + owner => $ssh::client::config_user, + group => $ssh::client::config_group, + mode => $ssh::client::include_dir_mode, + purge => $ssh::client::include_dir_purge, + recurse => $ssh::client::include_dir_purge, + } + + $ssh::client::config_files.each |$file, $params| { + ssh::client::config_file { $file: + * => $params, + } + } + } } diff --git a/manifests/client/config_file.pp b/manifests/client/config_file.pp new file mode 100644 index 00000000..bd16c72b --- /dev/null +++ b/manifests/client/config_file.pp @@ -0,0 +1,35 @@ +# @summary Resource type for managing a config file in the include dir. +# +# @param mode +# File mode for the config file. +# +# @param include +# Absolute path to config file to include at the top of the config file. +# This is intended for including files not managed by this module. +# +# @param options +# Dynamic hash for openssh client option +# +define ssh::client::config_file ( + Stdlib::Absolutepath $path = "${ssh::client::include_dir}/${name}.conf", + Stdlib::Filemode $mode = '0644', + Optional[Stdlib::Absolutepath] $include = undef, + Hash $options = {}, +) { + if !$ssh::client::include_dir { + fail('ssh::client::config_file() define not supported if ssh::client::include_dir not set') + } + + concat { $path: + ensure => present, + owner => $ssh::client::config_user, + group => $ssh::client::config_group, + mode => $mode, + } + + concat::fragment { "ssh_config_file ${title}": + target => $path, + content => template("${module_name}/ssh_config.erb"), + order => '00', + } +} diff --git a/manifests/server.pp b/manifests/server.pp index fb6a10bd..bc5009dc 100644 --- a/manifests/server.pp +++ b/manifests/server.pp @@ -51,10 +51,10 @@ # Mode to set on the sshd include directory. # # @param include_dir_purge -# Purge the include directory if true. +# Purge the sshd include directory if true. # # @param config_files -# Hash of config files to add to the ssh include directory. +# Hash of config files to add to the sshd include directory. # # @param storeconfigs_enabled # Host keys will be collected and distributed unless storeconfigs_enabled is false. diff --git a/spec/classes/client_spec.rb b/spec/classes/client_spec.rb index d2209019..4254df24 100644 --- a/spec/classes/client_spec.rb +++ b/spec/classes/client_spec.rb @@ -23,6 +23,123 @@ it { is_expected.to contain_concat('/etc/ssh/another_ssh_config') } end + + context 'with include_dir set' do + let :params do + { + include_dir: '/etc/ssh/ssh_config.d', + } + end + + it { is_expected.to compile.with_all_deps } + + it { + is_expected.to contain_file('/etc/ssh/ssh_config.d').with( + ensure: 'directory', + owner: 0, + group: 0, + mode: '0700', + purge: true, + recurse: true, + ) + } + end + + context 'with include_dir and include_dir_purge false' do + let :params do + { + include_dir: '/etc/ssh/ssh_config.d', + include_dir_purge: false, + } + end + + it { is_expected.to compile.with_all_deps } + + it { + is_expected.to contain_file('/etc/ssh/ssh_config.d').with( + ensure: 'directory', + purge: false, + recurse: false, + ) + } + end + + context 'with include_dir and custom mode' do + let :params do + { + include_dir: '/etc/ssh/ssh_config.d', + include_dir_mode: '0755', + } + end + + it { + is_expected.to contain_file('/etc/ssh/ssh_config.d').with( + mode: '0755', + ) + } + end + + context 'with config_files' do + let :params do + { + include_dir: '/etc/ssh/ssh_config.d', + config_files: { + 'custom' => { + 'options' => { + 'Host *.example.com' => { + 'ProxyJump' => 'bastion.example.com', + }, + }, + }, + 'work' => { + 'options' => { + 'Host *.work.internal' => { + 'User' => 'deploy', + 'IdentityFile' => '~/.ssh/work_key', + }, + }, + }, + }, + } + end + + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_ssh__client__config_file('custom') } + it { is_expected.to contain_ssh__client__config_file('work') } + + it { + is_expected.to contain_concat('/etc/ssh/ssh_config.d/custom.conf').with( + ensure: 'present', + owner: 0, + group: 0, + mode: '0644', + ) + } + + it { + is_expected.to contain_concat('/etc/ssh/ssh_config.d/work.conf').with( + ensure: 'present', + owner: 0, + group: 0, + mode: '0644', + ) + } + end + + context 'without include_dir but with config_files' do + let :params do + { + config_files: { + 'custom' => { + 'options' => {}, + }, + }, + } + end + + it { is_expected.to compile.with_all_deps } + it { is_expected.not_to contain_ssh__client__config_file('custom') } + end end end end diff --git a/templates/ssh_config.erb b/templates/ssh_config.erb index 5546a106..b2788792 100644 --- a/templates/ssh_config.erb +++ b/templates/ssh_config.erb @@ -12,6 +12,12 @@ end -%> +<%- if @include_dir -%> +Include <%= @include_dir %>/*.conf +<%- end -%> +<%- if @include -%> +Include <%= @include %> +<%- end -%> <%- @options.each do |k, v| -%> <%- if v.is_a?(Hash) -%> <%- if k.length > 1024 -%>