From d925465deb281a4c2dc9914cbcfa49635b890425 Mon Sep 17 00:00:00 2001 From: Steffen Zieger Date: Thu, 28 May 2026 17:49:19 +0200 Subject: [PATCH] add more acceptance tests --- spec/acceptance/client_config_user_spec.rb | 91 ++++++++++++ .../client_config_validation_spec.rb | 105 ++++++++++++++ spec/acceptance/server_config_file_spec.rb | 61 ++++++++ spec/acceptance/server_config_setting_spec.rb | 62 ++++++++ spec/acceptance/server_config_spec.rb | 104 +++++++++++++ spec/acceptance/server_host_key_spec.rb | 137 ++++++++++++++++++ spec/acceptance/server_issue_net_spec.rb | 42 ++++++ spec/acceptance/server_match_block_spec.rb | 89 ++++++++++++ spec/acceptance/server_options_spec.rb | 61 ++++++++ spec/acceptance/server_purge_spec.rb | 111 ++++++++++++++ spec/acceptance/server_service_spec.rb | 54 +++++++ 11 files changed, 917 insertions(+) create mode 100644 spec/acceptance/client_config_user_spec.rb create mode 100644 spec/acceptance/client_config_validation_spec.rb create mode 100644 spec/acceptance/server_config_file_spec.rb create mode 100644 spec/acceptance/server_config_setting_spec.rb create mode 100644 spec/acceptance/server_config_spec.rb create mode 100644 spec/acceptance/server_host_key_spec.rb create mode 100644 spec/acceptance/server_issue_net_spec.rb create mode 100644 spec/acceptance/server_match_block_spec.rb create mode 100644 spec/acceptance/server_options_spec.rb create mode 100644 spec/acceptance/server_purge_spec.rb create mode 100644 spec/acceptance/server_service_spec.rb diff --git a/spec/acceptance/client_config_user_spec.rb b/spec/acceptance/client_config_user_spec.rb new file mode 100644 index 00000000..6d38c3d6 --- /dev/null +++ b/spec/acceptance/client_config_user_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper_acceptance' + +describe 'ssh::client::config::user' do + context 'with per-user ssh config' do + before(:context) do + on(default, 'useradd -m testuser || true') + end + + after(:context) do + on(default, 'userdel -r testuser || true') + end + + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh': + storeconfigs_enabled => false, + users_client_options => { + 'testuser' => { + 'options' => { + 'Host *.example.com' => { + 'User' => 'deploy', + 'IdentityFile' => '~/.ssh/deploy_key', + }, + 'ServerAliveInterval' => '60', + }, + }, + }, + } + PP + end + end + + describe file('/home/testuser/.ssh') do + it { is_expected.to be_directory } + it { is_expected.to be_owned_by 'testuser' } + it { is_expected.to be_mode '700' } + end + + describe file('/home/testuser/.ssh/config') do + it { is_expected.to be_file } + it { is_expected.to be_owned_by 'testuser' } + it { is_expected.to be_mode '600' } + its(:content) { is_expected.to match(%r{Host \*\.example\.com}) } + its(:content) { is_expected.to match(%r{User deploy}) } + its(:content) { is_expected.to match(%r{IdentityFile ~/.ssh/deploy_key}) } + its(:content) { is_expected.to match(%r{ServerAliveInterval\s+60}) } + end + + # Verify OpenSSH can parse the per-user config + describe command('ssh -G -F /home/testuser/.ssh/config example.org') do + its(:exit_status) { is_expected.to eq 0 } + end + end + + context 'with manage_user_ssh_dir set to false' do + before(:context) do + on(default, 'useradd -m testuser2 || true') + on(default, 'mkdir -p /home/testuser2/.ssh && chown testuser2 /home/testuser2/.ssh') + end + + after(:context) do + on(default, 'userdel -r testuser2 || true') + end + + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh': + storeconfigs_enabled => false, + users_client_options => { + 'testuser2' => { + 'manage_user_ssh_dir' => false, + 'options' => { + 'ServerAliveInterval' => '30', + }, + }, + }, + } + PP + end + end + + describe file('/home/testuser2/.ssh/config') do + it { is_expected.to be_file } + its(:content) { is_expected.to match(%r{ServerAliveInterval\s+30}) } + end + end +end diff --git a/spec/acceptance/client_config_validation_spec.rb b/spec/acceptance/client_config_validation_spec.rb new file mode 100644 index 00000000..81bb1df5 --- /dev/null +++ b/spec/acceptance/client_config_validation_spec.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +require 'spec_helper_acceptance' + +describe 'ssh client config validation' do + context 'with custom client_options' do + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh': + storeconfigs_enabled => false, + client_options => { + 'ServerAliveInterval' => '60', + 'ServerAliveCountMax' => '3', + 'StrictHostKeyChecking' => 'ask', + }, + } + PP + end + end + + describe file('/etc/ssh/ssh_config') do + it { is_expected.to be_file } + it { is_expected.to be_owned_by 'root' } + it { is_expected.to be_mode '644' } + its(:content) { is_expected.to match(%r{ServerAliveInterval 60}) } + its(:content) { is_expected.to match(%r{ServerAliveCountMax 3}) } + its(:content) { is_expected.to match(%r{StrictHostKeyChecking ask}) } + end + + # Verify OpenSSH can parse the generated config + describe command('ssh -G -F /etc/ssh/ssh_config example.org') do + its(:exit_status) { is_expected.to eq 0 } + its(:stdout) { is_expected.to match(%r{serveraliveinterval 60}) } + its(:stdout) { is_expected.to match(%r{serveralivecountmax 3}) } + end + end + + context 'with client_options and client_match_block combined' do + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh': + storeconfigs_enabled => false, + client_options => { + 'ForwardAgent' => 'no', + }, + client_match_block => { + 'bastion.example.com' => { + 'type' => 'host', + 'options' => { + 'ForwardAgent' => 'yes', + 'ProxyJump' => 'none', + }, + }, + }, + } + PP + end + end + + describe file('/etc/ssh/ssh_config') do + its(:content) { is_expected.to match(%r{ForwardAgent no}) } + its(:content) { is_expected.to match(%r{Match host bastion\.example\.com}) } + its(:content) { is_expected.to match(%r{ForwardAgent yes}) } + its(:content) { is_expected.to match(%r{ProxyJump none}) } + end + + # Verify the generated config is parseable for both regular and matched hosts + %w[example.org bastion.example.com].each do |host| + describe command("ssh -G -F /etc/ssh/ssh_config #{host}") do + its(:exit_status) { is_expected.to eq 0 } + end + end + end + + context 'with Host wildcard blocks in default options' do + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh': + storeconfigs_enabled => false, + client_options => { + 'Host *.internal.example.com' => { + 'User' => 'deploy', + 'IdentityFile' => '~/.ssh/deploy_key', + 'StrictHostKeyChecking' => 'no', + }, + }, + } + PP + end + end + + describe file('/etc/ssh/ssh_config') do + its(:content) { is_expected.to match(%r{Host \*\.internal\.example\.com}) } + its(:content) { is_expected.to match(%r{User deploy}) } + its(:content) { is_expected.to match(%r{IdentityFile ~/.ssh/deploy_key}) } + end + + describe command('ssh -G -F /etc/ssh/ssh_config example.org') do + its(:exit_status) { is_expected.to eq 0 } + end + end +end diff --git a/spec/acceptance/server_config_file_spec.rb b/spec/acceptance/server_config_file_spec.rb new file mode 100644 index 00000000..faa21d39 --- /dev/null +++ b/spec/acceptance/server_config_file_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'spec_helper_acceptance' + +describe 'ssh server config_file and include_dir' do + service_name = case fact('os.family') + when 'Debian' + 'ssh' + else + 'sshd' + end + + context 'with include_dir and a config_file' do + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh::server': + storeconfigs_enabled => false, + include_dir => '/etc/ssh/sshd_config.d', + options => { + 'PermitRootLogin' => 'yes', + }, + config_files => { + 'hardening' => { + 'options' => { + 'MaxAuthTries' => '3', + 'MaxSessions' => '5', + }, + }, + }, + } + PP + end + end + + describe file('/etc/ssh/sshd_config.d') do + it { is_expected.to be_directory } + it { is_expected.to be_owned_by 'root' } + it { is_expected.to be_mode '700' } + end + + describe file('/etc/ssh/sshd_config.d/hardening.conf') do + it { is_expected.to be_file } + it { is_expected.to be_owned_by 'root' } + its(:content) { is_expected.to match(%r{MaxAuthTries 3}) } + its(:content) { is_expected.to match(%r{MaxSessions 5}) } + end + + describe file('/etc/ssh/sshd_config') do + its(:content) { is_expected.to match(%r{Include /etc/ssh/sshd_config\.d/\*\.conf}) } + end + + describe command('sshd -t') do + its(:exit_status) { is_expected.to eq 0 } + end + + describe service(service_name) do + it { is_expected.to be_running } + end + end +end diff --git a/spec/acceptance/server_config_setting_spec.rb b/spec/acceptance/server_config_setting_spec.rb new file mode 100644 index 00000000..23fe9cc1 --- /dev/null +++ b/spec/acceptance/server_config_setting_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'spec_helper_acceptance' + +describe 'ssh::server::config::setting' do + service_name = case fact('os.family') + when 'Debian' + 'ssh' + else + 'sshd' + end + + context 'with a string value' do + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + include ssh + + ssh::server::config::setting { 'allow_groups': + key => 'AllowGroups', + value => 'root sshusers', + } + PP + end + end + + describe file('/etc/ssh/sshd_config') do + its(:content) { is_expected.to match(%r{AllowGroups root sshusers}) } + end + + describe command('sshd -t') do + its(:exit_status) { is_expected.to eq 0 } + end + + describe service(service_name) do + it { is_expected.to be_running } + end + end + + context 'with a boolean value' do + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + include ssh + + ssh::server::config::setting { 'permit_empty_passwords': + key => 'PermitEmptyPasswords', + value => false, + } + PP + end + end + + describe file('/etc/ssh/sshd_config') do + its(:content) { is_expected.to match(%r{PermitEmptyPasswords no}) } + end + + describe command('sshd -t') do + its(:exit_status) { is_expected.to eq 0 } + end + end +end diff --git a/spec/acceptance/server_config_spec.rb b/spec/acceptance/server_config_spec.rb new file mode 100644 index 00000000..b5e76255 --- /dev/null +++ b/spec/acceptance/server_config_spec.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +require 'spec_helper_acceptance' + +describe 'ssh server config' do + service_name = case fact('os.family') + when 'Debian' + 'ssh' + else + 'sshd' + end + + context 'with custom server_options' do + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh': + storeconfigs_enabled => false, + server_options => { + 'PermitRootLogin' => 'no', + 'X11Forwarding' => 'no', + 'MaxAuthTries' => '3', + 'PasswordAuthentication' => 'no', + 'LogLevel' => 'VERBOSE', + }, + } + PP + end + end + + describe file('/etc/ssh/sshd_config') do + it { is_expected.to be_file } + it { is_expected.to be_owned_by 'root' } + it { is_expected.to be_mode '600' } + its(:content) { is_expected.to match(%r{PermitRootLogin no}) } + its(:content) { is_expected.to match(%r{X11Forwarding no}) } + its(:content) { is_expected.to match(%r{MaxAuthTries 3}) } + its(:content) { is_expected.to match(%r{PasswordAuthentication no}) } + its(:content) { is_expected.to match(%r{LogLevel VERBOSE}) } + end + + describe command('sshd -t') do + its(:exit_status) { is_expected.to eq 0 } + end + + describe service(service_name) do + it { is_expected.to be_enabled } + it { is_expected.to be_running } + end + end + + context 'with validate_sshd_file enabled' do + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh': + storeconfigs_enabled => false, + validate_sshd_file => true, + server_options => { + 'PermitRootLogin' => 'yes', + 'X11Forwarding' => 'yes', + }, + } + PP + end + end + + describe command('sshd -t') do + its(:exit_status) { is_expected.to eq 0 } + end + end + + context 'with multi-value options (Port list)' do + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh': + storeconfigs_enabled => false, + server_options => { + 'Port' => [22, 2222], + }, + } + PP + end + end + + describe file('/etc/ssh/sshd_config') do + its(:content) { is_expected.to match(%r{Port 22}) } + its(:content) { is_expected.to match(%r{Port 2222}) } + end + + describe command('sshd -t') do + its(:exit_status) { is_expected.to eq 0 } + end + + describe port(22) do + it { is_expected.to be_listening } + end + + describe service(service_name) do + it { is_expected.to be_running } + end + end +end diff --git a/spec/acceptance/server_host_key_spec.rb b/spec/acceptance/server_host_key_spec.rb new file mode 100644 index 00000000..788dded3 --- /dev/null +++ b/spec/acceptance/server_host_key_spec.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +require 'spec_helper_acceptance' + +describe 'ssh server host key management' do + service_name = case fact('os.family') + when 'Debian' + 'ssh' + else + 'sshd' + end + + context 'manage a custom RSA host key' do + # Generate a fresh RSA keypair in a pre-test step + before(:context) do + on(default, 'ssh-keygen -t rsa -b 2048 -f /tmp/test_host_rsa_key -N "" -q') + end + + after(:context) do + on(default, 'rm -f /tmp/test_host_rsa_key /tmp/test_host_rsa_key.pub') + end + + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh': + storeconfigs_enabled => false, + } + + ssh::server::host_key { 'ssh_host_rsa_custom_key': + public_key_source => '/tmp/test_host_rsa_key.pub', + private_key_source => '/tmp/test_host_rsa_key', + } + PP + end + end + + describe file('/etc/ssh/ssh_host_rsa_custom_key.pub') do + it { is_expected.to be_file } + it { is_expected.to be_owned_by 'root' } + it { is_expected.to be_mode '644' } + end + + describe file('/etc/ssh/ssh_host_rsa_custom_key') do + it { is_expected.to be_file } + it { is_expected.to be_owned_by 'root' } + it { is_expected.to be_mode '600' } + end + + describe command('ssh-keygen -l -f /etc/ssh/ssh_host_rsa_custom_key.pub') do + its(:exit_status) { is_expected.to eq 0 } + its(:stdout) { is_expected.to match(%r{2048}) } + end + + describe service(service_name) do + it { is_expected.to be_running } + end + end + + context 'manage a host key with inline content' do + # Generate a fresh Ed25519 keypair in a pre-test step + before(:context) do + on(default, 'ssh-keygen -t ed25519 -f /tmp/test_host_ed25519_key -N "" -q') + @priv_key = on(default, 'cat /tmp/test_host_ed25519_key').stdout + @pub_key = on(default, 'cat /tmp/test_host_ed25519_key.pub').stdout + end + + after(:context) do + on(default, 'rm -f /tmp/test_host_ed25519_key /tmp/test_host_ed25519_key.pub') + end + + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh': + storeconfigs_enabled => false, + } + + ssh::server::host_key { 'ssh_host_ed25519_custom_key': + public_key_content => file('/tmp/test_host_ed25519_key.pub'), + private_key_content => file('/tmp/test_host_ed25519_key'), + } + PP + end + end + + describe file('/etc/ssh/ssh_host_ed25519_custom_key.pub') do + it { is_expected.to be_file } + it { is_expected.to be_owned_by 'root' } + it { is_expected.to be_mode '644' } + end + + describe file('/etc/ssh/ssh_host_ed25519_custom_key') do + it { is_expected.to be_file } + it { is_expected.to be_owned_by 'root' } + it { is_expected.to be_mode '600' } + end + + describe command('ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_custom_key.pub') do + its(:exit_status) { is_expected.to eq 0 } + its(:stdout) { is_expected.to match(%r{ED25519}) } + end + end + + context 'remove a host key with ensure absent' do + before(:context) do + on(default, 'ssh-keygen -t ed25519 -f /tmp/test_host_absent_key -N "" -q') + # Pre-place the key files so there is something to remove + on(default, 'cp /tmp/test_host_absent_key /etc/ssh/ssh_host_absent_key') + on(default, 'cp /tmp/test_host_absent_key.pub /etc/ssh/ssh_host_absent_key.pub') + end + + after(:context) do + on(default, 'rm -f /tmp/test_host_absent_key /tmp/test_host_absent_key.pub') + end + + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh': + storeconfigs_enabled => false, + } + + ssh::server::host_key { 'ssh_host_absent_key': + ensure => 'absent', + } + PP + end + end + + %w[/etc/ssh/ssh_host_absent_key /etc/ssh/ssh_host_absent_key.pub].each do |key_file| + describe file(key_file) do + it { is_expected.not_to exist } + end + end + end +end diff --git a/spec/acceptance/server_issue_net_spec.rb b/spec/acceptance/server_issue_net_spec.rb new file mode 100644 index 00000000..c2fc22c9 --- /dev/null +++ b/spec/acceptance/server_issue_net_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper_acceptance' + +describe 'ssh server use_issue_net' do + service_name = case fact('os.family') + when 'Debian' + 'ssh' + else + 'sshd' + end + + context 'with use_issue_net enabled' do + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh': + storeconfigs_enabled => false, + use_issue_net => true, + } + PP + end + end + + describe file('/etc/issue.net') do + it { is_expected.to be_file } + it { is_expected.to be_owned_by 'root' } + end + + describe file('/etc/ssh/sshd_config') do + its(:content) { is_expected.to match(%r{Banner /etc/issue\.net}) } + end + + describe command('sshd -t') do + its(:exit_status) { is_expected.to eq 0 } + end + + describe service(service_name) do + it { is_expected.to be_running } + end + end +end diff --git a/spec/acceptance/server_match_block_spec.rb b/spec/acceptance/server_match_block_spec.rb new file mode 100644 index 00000000..30e2d2c5 --- /dev/null +++ b/spec/acceptance/server_match_block_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'spec_helper_acceptance' + +describe 'ssh server match blocks' do + service_name = case fact('os.family') + when 'Debian' + 'ssh' + else + 'sshd' + end + + context 'with a User match block' do + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh': + storeconfigs_enabled => false, + server_match_block => { + 'sftpusers' => { + 'type' => 'group', + 'options' => { + 'ChrootDirectory' => '/home/sftp', + 'ForceCommand' => 'internal-sftp', + 'AllowTcpForwarding' => 'no', + 'X11Forwarding' => 'no', + }, + }, + }, + } + PP + end + end + + describe file('/etc/ssh/sshd_config') do + its(:content) { is_expected.to match(%r{Match group sftpusers}) } + its(:content) { is_expected.to match(%r{ChrootDirectory /home/sftp}) } + its(:content) { is_expected.to match(%r{ForceCommand internal-sftp}) } + its(:content) { is_expected.to match(%r{AllowTcpForwarding no}) } + its(:content) { is_expected.to match(%r{X11Forwarding no}) } + end + + describe command('sshd -t') do + its(:exit_status) { is_expected.to eq 0 } + end + + describe service(service_name) do + it { is_expected.to be_running } + end + end + + context 'with multiple match blocks' do + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh': + storeconfigs_enabled => false, + server_match_block => { + 'admins' => { + 'type' => 'group', + 'options' => { + 'AllowTcpForwarding' => 'yes', + 'X11Forwarding' => 'yes', + }, + }, + '10.0.0.0/8' => { + 'type' => 'address', + 'options' => { + 'PasswordAuthentication' => 'yes', + }, + }, + }, + } + PP + end + end + + describe file('/etc/ssh/sshd_config') do + its(:content) { is_expected.to match(%r{Match group admins}) } + its(:content) { is_expected.to match(%r{AllowTcpForwarding yes}) } + its(:content) { is_expected.to match(%r{Match address 10\.0\.0\.0/8}) } + its(:content) { is_expected.to match(%r{PasswordAuthentication yes}) } + end + + describe command('sshd -t') do + its(:exit_status) { is_expected.to eq 0 } + end + end +end diff --git a/spec/acceptance/server_options_spec.rb b/spec/acceptance/server_options_spec.rb new file mode 100644 index 00000000..444cd230 --- /dev/null +++ b/spec/acceptance/server_options_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'spec_helper_acceptance' + +describe 'ssh::server::options' do + context 'with additional options appended to sshd_config' do + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh': + storeconfigs_enabled => false, + } + + ssh::server::options { 'extra_hardening': + options => { + 'ClientAliveInterval' => '300', + 'ClientAliveCountMax' => '2', + }, + } + PP + end + end + + describe file('/etc/ssh/sshd_config') do + its(:content) { is_expected.to match(%r{ClientAliveInterval 300}) } + its(:content) { is_expected.to match(%r{ClientAliveCountMax 2}) } + end + + describe command('sshd -t') do + its(:exit_status) { is_expected.to eq 0 } + end + end + + context 'with boolean options' do + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh': + storeconfigs_enabled => false, + } + + ssh::server::options { 'boolean_opts': + options => { + 'PrintLastLog' => true, + 'PermitEmptyPasswords' => false, + }, + } + PP + end + end + + describe file('/etc/ssh/sshd_config') do + its(:content) { is_expected.to match(%r{PrintLastLog yes}) } + its(:content) { is_expected.to match(%r{PermitEmptyPasswords no}) } + end + + describe command('sshd -t') do + its(:exit_status) { is_expected.to eq 0 } + end + end +end diff --git a/spec/acceptance/server_purge_spec.rb b/spec/acceptance/server_purge_spec.rb new file mode 100644 index 00000000..1a2d401b --- /dev/null +++ b/spec/acceptance/server_purge_spec.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require 'spec_helper_acceptance' + +describe 'ssh server instance removal' do + context 'create then remove a server instance' do + # Step 1: Create the instance so there is something to purge + context 'provision instance on port 9022' do + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh': + storeconfigs_enabled => false, + server_instances => { + 'test_cleanup' => { + 'ensure' => 'present', + 'options' => { + 'sshd_config' => { + 'Port' => 9022, + 'AddressFamily' => 'any', + 'HostKey' => '/etc/ssh/ssh_host_rsa_key', + 'PermitRootLogin' => 'no', + }, + 'sshd_service_options' => '', + 'match_blocks' => {}, + }, + }, + }, + } + PP + end + end + + describe port(9022) do + it { is_expected.to be_listening } + end + + describe service('test_cleanup') do + it { is_expected.to be_enabled } + it { is_expected.to be_running } + end + + describe file('/etc/ssh/sshd_config.test_cleanup') do + it { is_expected.to be_file } + end + end + + # Step 2: Remove the instance + context 'remove the instance' do + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh': + storeconfigs_enabled => false, + server_instances => { + 'test_cleanup' => { + 'ensure' => 'absent', + 'options' => { + 'sshd_config' => { + 'Port' => 9022, + 'AddressFamily' => 'any', + 'HostKey' => '/etc/ssh/ssh_host_rsa_key', + 'PermitRootLogin' => 'no', + }, + 'sshd_service_options' => '', + 'match_blocks' => {}, + }, + }, + }, + } + PP + end + end + + describe port(9022) do + it { is_expected.not_to be_listening } + end + + describe service('test_cleanup') do + it { is_expected.not_to be_enabled } + it { is_expected.not_to be_running } + end + + describe file('/etc/ssh/sshd_config.test_cleanup') do + it { is_expected.not_to exist } + end + end + end + + context 'primary sshd still healthy after instance removal' do + service_name = case fact('os.family') + when 'Debian' + 'ssh' + else + 'sshd' + end + + describe service(service_name) do + it { is_expected.to be_enabled } + it { is_expected.to be_running } + end + + describe port(22) do + it { is_expected.to be_listening } + end + + describe command('sshd -t') do + its(:exit_status) { is_expected.to eq 0 } + end + end +end diff --git a/spec/acceptance/server_service_spec.rb b/spec/acceptance/server_service_spec.rb new file mode 100644 index 00000000..cf251ae5 --- /dev/null +++ b/spec/acceptance/server_service_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'spec_helper_acceptance' + +describe 'ssh server service management' do + service_name = case fact('os.family') + when 'Debian' + 'ssh' + else + 'sshd' + end + + context 'with service stopped and disabled' do + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh::server': + storeconfigs_enabled => false, + service_ensure => 'stopped', + service_enable => false, + } + PP + end + end + + describe service(service_name) do + it { is_expected.not_to be_enabled } + it { is_expected.not_to be_running } + end + end + + context 'restore service to running and enabled' do + it_behaves_like 'an idempotent resource' do + let(:manifest) do + <<~PP + class { 'ssh::server': + storeconfigs_enabled => false, + service_ensure => 'running', + service_enable => true, + } + PP + end + end + + describe service(service_name) do + it { is_expected.to be_enabled } + it { is_expected.to be_running } + end + + describe port(22) do + it { is_expected.to be_listening } + end + end +end