From e76bd24dcb33ab71440633303124ea99d59b7783 Mon Sep 17 00:00:00 2001 From: Enis Inan Date: Thu, 13 Dec 2018 02:10:54 -0800 Subject: (maint) Add more crontab provider tests This commit adds the following tests to the cron resource: * A test to ensure that the crontab provider writes the crontab file of a new user * A test to ensure that the crontab provider fails to write the crontab file of a nonexistent user * A test to ensure that the crontab provider writes an originally unauthorized user's crontab file _if_ Puppet authorizes them in the middle of the run These tests are part of the crontab provider's specifications and should have been added before. --- ...il_to_write_a_nonexistent_users_crontab_spec.rb | 20 ++++ ...users_crontab_if_puppet_authorizes_them_spec.rb | 111 +++++++++++++++++++++ ...users_crontab_after_puppet_creates_them_spec.rb | 59 +++++++++++ spec/spec_helper_acceptance.rb | 65 ++++++++++++ 4 files changed, 255 insertions(+) create mode 100644 spec/acceptance/tests/resource/cron/should_fail_to_write_a_nonexistent_users_crontab_spec.rb create mode 100644 spec/acceptance/tests/resource/cron/should_write_an_originally_unauthorized_users_crontab_if_puppet_authorizes_them_spec.rb create mode 100644 spec/acceptance/tests/resource/cron/should_write_new_users_crontab_after_puppet_creates_them_spec.rb diff --git a/spec/acceptance/tests/resource/cron/should_fail_to_write_a_nonexistent_users_crontab_spec.rb b/spec/acceptance/tests/resource/cron/should_fail_to_write_a_nonexistent_users_crontab_spec.rb new file mode 100644 index 0000000..40fbbd9 --- /dev/null +++ b/spec/acceptance/tests/resource/cron/should_fail_to_write_a_nonexistent_users_crontab_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper_acceptance' + +RSpec.context 'when Puppet attempts to write the crontab of a nonexistent user' do + let(:nonexistent_username) { "pl#{rand(999_999).to_i}" } + + before(:each) do + step 'Ensure that the nonexistent user does not exist' do + compatible_agents.each do |agent| + user_absent(agent, nonexistent_username) + end + end + end + + compatible_agents.each do |agent| + it "should fail on #{agent}" do + manifest = cron_manifest('second_entry', command: 'ls', user: nonexistent_username) + apply_manifest_on(agent, manifest, expect_failures: true) + end + end +end diff --git a/spec/acceptance/tests/resource/cron/should_write_an_originally_unauthorized_users_crontab_if_puppet_authorizes_them_spec.rb b/spec/acceptance/tests/resource/cron/should_write_an_originally_unauthorized_users_crontab_if_puppet_authorizes_them_spec.rb new file mode 100644 index 0000000..964cc17 --- /dev/null +++ b/spec/acceptance/tests/resource/cron/should_write_an_originally_unauthorized_users_crontab_if_puppet_authorizes_them_spec.rb @@ -0,0 +1,111 @@ +require 'spec_helper_acceptance' + +RSpec.context 'when Puppet authorizes a previously unauthorized user to use crontab' do + class << self + alias_method :orig_compatible_agents, :compatible_agents + + # This test is confined to AIX and Solaris agents only. + def compatible_agents + orig_compatible_agents.select do |agent| + agent['platform'].include?('aix') || agent['platform'].include?('solaris') + end + end + end + + let(:username) { "pl#{rand(999_999).to_i}" } + let(:unauthorized_username) { "pl#{rand(999_999).to_i}" } + + let(:cron_deny_path) do + {} + end + let(:cron_deny_original_contents) do + {} + end + + before(:each) do + step 'Ensure that the test users exist' do + compatible_agents.each do |agent| + user_present(agent, username) + user_present(agent, unauthorized_username) + end + end + + step 'Get the current cron.deny contents for each agent' do + compatible_agents.each do |agent| + case agent['platform'] + when %r{aix} + cron_deny_path[agent] = '/var/adm/cron/cron.deny' + when %r{solaris} + cron_deny_path[agent] = '/etc/cron.d/cron.deny' + else + fail_test "Cannot figure out the path of the cron.deny file for the #{agent['platform']} platform" + end + + cron_deny_original_contents[agent] = on(agent, "cat #{cron_deny_path[agent]}").stdout + end + end + end + + after(:each) do + step 'Teardown -- Erase the test users on the agents' do + compatible_agents.each do |agent| + run_cron_on(agent, :remove, username) + user_absent(agent, username) + + run_cron_on(agent, :remove, unauthorized_username) + user_absent(agent, unauthorized_username) + end + end + + # It is possible for the "before" hook to raise an exception while the + # cron.deny contents are being computed, so only execute the next step + # if we've managed to compute the cron.deny contents for each agent + next unless cron_deny_original_contents.keys.size == compatible_agents.size + + step "Teardown -- Reset each agent's cron.deny contents" do + compatible_agents.each do |agent| + apply_manifest_on(agent, file_manifest(cron_deny_path[agent], ensure: :present, content: cron_deny_original_contents[agent])) + end + end + end + + compatible_agents.each do |agent| + it "should write that user's crontab on #{agent}" do + step 'Add the unauthorized user to the cron.deny file' do + on(agent, "echo #{unauthorized_username} >> #{cron_deny_path[agent]}") + end + + step 'Verify that the unauthorized user was added to the cron.deny file' do + cron_deny_contents = on(agent, "cat #{cron_deny_path[agent]}").stdout + assert_match(%r{^#{unauthorized_username}$}, cron_deny_contents, 'Failed to add the unauthorized user to the cron.deny file') + end + + step "Modify the unauthorized user's crontab with Puppet" do + # The scenario we're testing here is: + # * An unrelated cron resource triggers the prefetch step, which will also + # prefetch the crontab of our unauthorized user. The latter prefetch should + # fail, instead returning an empty crontab file. + # + # * Puppet authorizes our unauthorized user by removing them from the cron.deny + # file. + # + # * A cron resource linked to our (originally) unauthorized user should now be able + # to write to that user's crontab file (assuming it requires the resource updating + # the cron.deny file) + # + # The following manifest replicates the above scenario. Note that we test this specific + # scenario to ensure that the changes in PUP-9217 enforce backwards compatibility. + manifest = [ + cron_manifest('first_entry', command: 'ls', user: username), + file_manifest(cron_deny_path[agent], ensure: :present, content: cron_deny_original_contents[agent]), + cron_manifest('second_entry', command: 'ls', user: unauthorized_username), + ].join("\n\n") + apply_manifest_on(agent, manifest) + end + + step "Verify that Puppet did modify the unauthorized user's crontab" do + assert_matching_arrays(['* * * * * ls'], crontab_entries_of(agent, unauthorized_username), "Puppet did not modify the unauthorized user's crontab file") + end + end + end +end diff --git a/spec/acceptance/tests/resource/cron/should_write_new_users_crontab_after_puppet_creates_them_spec.rb b/spec/acceptance/tests/resource/cron/should_write_new_users_crontab_after_puppet_creates_them_spec.rb new file mode 100644 index 0000000..12506ad --- /dev/null +++ b/spec/acceptance/tests/resource/cron/should_write_new_users_crontab_after_puppet_creates_them_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper_acceptance' + +RSpec.context 'when Puppet creates a user in the middle of its run' do + let(:known_username) { "pl#{rand(999_999).to_i}" } + let(:new_username) { "pl#{rand(999_999).to_i}" } + + before(:each) do + step 'Ensure that the known user exists on all of the agents' do + compatible_agents.each do |agent| + user_present(agent, known_username) + end + end + + step 'Ensure that the new user does not exist on all of the agents' do + compatible_agents.each do |agent| + user_absent(agent, new_username) + end + end + end + + after(:each) do + step 'Teardown -- Ensure that both users are deleted on all of the agents' do + compatible_agents.each do |agent| + run_cron_on(agent, :remove, known_username) + user_absent(agent, known_username) + + run_cron_on(agent, :remove, new_username) + user_absent(agent, new_username) + end + end + end + + compatible_agents.each do |agent| + it "should be able to write their crontab on #{agent}" do + puppet_result = nil + step "Create the new user, and the known + new user's crontab entries with Puppet" do + # Placing Cron[first_entry] before creating the new user + # triggers a prefetch of all the Cron resources on the + # system. This lets us test that the prefetch step marks + # the crontab of an unknown user as empty instead of marking + # it as a failure. + manifest = [ + cron_manifest('first_entry', command: 'ls', user: known_username), + user_manifest(new_username, ensure: :present), + cron_manifest('second_entry', command: 'ls', user: new_username), + ].join("\n\n") + puppet_result = apply_manifest_on(agent, manifest) + end + + step 'Verify that Puppet successfully evaluates a Cron resource associated with the new user' do + assert_match(%r{Cron.*second_entry}, puppet_result.stdout, 'Puppet fails to evaluate a Cron resource associated with a new user') + end + + step "Verify that Puppet did create the new user's crontab file" do + assert_matching_arrays(['* * * * * ls'], crontab_entries_of(agent, new_username), "Puppet did not create the new user's crontab file") + end + end + end +end diff --git a/spec/spec_helper_acceptance.rb b/spec/spec_helper_acceptance.rb index e4f0182..57c5b21 100644 --- a/spec/spec_helper_acceptance.rb +++ b/spec/spec_helper_acceptance.rb @@ -4,6 +4,34 @@ require 'beaker/puppet_install_helper' $LOAD_PATH << File.join(__dir__, 'acceptance/lib') +# TODO: This should be added to Beaker +def assert_matching_arrays(expected, actual, message = '') + assert_equal(expected.sort, actual.sort, message) +end + +# TODO: Remove the wrappers to user_present +# and user_absent if Beaker::Host's user_present +# and user_absent functions are fixed to work with +# Arista (EOS). + +def user_present(host, username) + case host['platform'] + when %r{eos} + on(host, "useradd #{username}") + else + host.user_present(username) + end +end + +def user_absent(host, username) + case host['platform'] + when %r{eos} + on(host, "userdel #{username}", acceptable_exit_codes: [0, 1]) + else + host.user_absent(username) + end +end + def beaker_opts { debug: true, trace: true, expect_failures: true, acceptable_exit_codes: (0...256) } # { expect_failures: true, acceptable_exit_codes: (0...256) } @@ -29,6 +57,43 @@ def setup(agent, o = {}) package {'cron': name=> $cron, ensure=>present, })) end +# Returns all of the lines that correspond to crontab entries +# on the agent. For simplicity, these are any lines that do +# not begin with '#'. +def crontab_entries_of(host, username) + crontab_contents = run_cron_on(host, :list, username).stdout.strip + crontab_contents.lines.map(&:chomp).reject { |line| line =~ %r{^#} } +end + +def resource_manifest(resource, title, params = {}) + params_str = params.map { |param, value| + # This is not quite correct for all parameter values, + # but it is good enough for most purposes. + value_str = value.to_s + value_str = "\"#{value_str}\"" if value.is_a?(String) + + " #{param} => #{value_str}" + }.join(",\n") + + <<-MANIFEST + #{resource} { '#{title}': + #{params_str} +} + MANIFEST +end + +def file_manifest(path, params = {}) + resource_manifest('file', path, params) +end + +def cron_manifest(entry_name, params = {}) + resource_manifest('cron', entry_name, params) +end + +def user_manifest(username, params = {}) + resource_manifest('user', username, params) +end + RSpec.configure do |c| c.before :suite do unless ENV['BEAKER_provision'] == 'no' -- cgit v1.2.3