aboutsummaryrefslogtreecommitdiff
path: root/spec/acceptance/tests/resource/cron/should_not_overwrite_crontab_file_on_file_read_error_spec.rb
blob: 6a5b2c635c490b54afbbde513e41345235bbece9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
require 'spec_helper_acceptance'

RSpec.context 'when Puppet cannot read a crontab file' do
  # This test only makes sense for agents that shipped with PUP-9217's
  # changes, so we do not want to run it on older agents.
  def older_agent?(agent)
    puppet_version = Gem::Version.new(on(agent, puppet('--version')).stdout.chomp)
    minimum_puppet_version = if puppet_version < Gem::Version.new('6.0.0')
                               Gem::Version.new('5.5.9')
                             else
                               Gem::Version.new('6.0.5')
                             end

    puppet_version < minimum_puppet_version
  end

  let(:username) { "pl#{rand(999_999).to_i}" }
  let(:crontab_contents) { '6 6 6 6 6 /usr/bin/true' }

  before(:each) do
    compatible_agents.each do |agent|
      next if older_agent?(agent)

      step "Create the user on #{agent}" do
        user_present(agent, username)
      end

      step "Set the user's crontab on #{agent}" do
        run_cron_on(agent, :add, username, crontab_contents)
        assert_matching_arrays([crontab_contents], crontab_entries_of(agent, username), "Could not set the user's crontab for testing")
      end
    end
  end

  after(:each) do
    compatible_agents.each do |agent|
      next if older_agent?(agent)

      step "Teardown -- Erase the user on #{agent}" do
        run_cron_on(agent, :remove, username)
        user_absent(agent, username)
      end
    end
  end

  compatible_agents.each do |agent|
    it "should not overwrite it on #{agent}" do
      if older_agent?(agent)
        skip('Skipping this test since we are on an older agent that does not have the PUP-9217 changes')
      end

      crontab_exe = nil
      step 'Find the crontab executable' do
        crontab_exe = on(agent, 'which crontab').stdout.chomp
      end

      stub_crontab_bin_dir = nil
      stub_crontab_exe = nil
      step 'Create the stub crontab executable that triggers the read error' do
        stub_crontab_bin_dir = agent.tmpdir('stub_crontab_bin_dir')
        on(agent, "chown #{username} #{stub_crontab_bin_dir}")

        stub_crontab_exe = "#{stub_crontab_bin_dir}/crontab"
        stub_crontab_exe_script = <<-SCRIPT
  #!/usr/bin/env bash
  exit 1
  SCRIPT
        create_remote_file(agent, stub_crontab_exe, stub_crontab_exe_script)
        on(agent, "chown #{username} #{stub_crontab_exe}")
        on(agent, "chmod a+x #{stub_crontab_exe}")
      end

      path_env_var = nil
      step 'Get the value of the PATH environment variable' do
        path_env_var = on(agent, 'echo $PATH').stdout.chomp
      end

      step "(PUP-9217) Attempt to overwrite the user's crontab file" do
        # This manifest reproduces the issue in PUP-9217. Here's how:
        #   1. When Puppet attempts to realize Cron['first_entry'], it will prefetch
        #   all of the present cron entries on the system. To do this, it will execute
        #   the crontab command. Since we prepend stub_crontab_bindir to PATH prior to
        #   executing Puppet, executing the crontab command will really execute
        #   stub_crontab_exe, which returns an exit code of 1. This triggers our
        #   read error.
        #
        #   2. Puppet will attempt to write Cron['first_entry'] onto disk. However, it will
        #   fail to do so because it will still execute stub_crontab_exe to perform the
        #   write. Thus, Cron['first_entry'] will fail its evaluation.
        #
        #   3. Next, Puppet will modify stub_crontab_exe to now execute the actual crontab
        #   command so that any subsequent calls to crontab will succeed. Note that under
        #   the hood, Puppet will still be executing stub_crontab_exe.
        #
        #   4. Finally, Puppet will attempt to realize Cron['second_entry']. It will skip
        #   the prefetch step, instead proceeding to directly write Cron['second_entry']
        #   to disk. But note that since the prefetch failed in (1), Puppet will proceed
        #   to overwrite our user's crontab file so that it will contain Cron['first_entry']
        #   and Cron['second_entry'] (Cron['first_entry'] is there because Puppet maintains
        #   each crontab file's entries in memory so that when it writes one entry to disk,
        #   it will write all of them).
        manifest = [
          cron_manifest('first_entry', command: 'ls', user: username),
          file_manifest(stub_crontab_exe, content: "#!/usr/bin/env bash\n#{crontab_exe} $@"),
          cron_manifest('second_entry', command: 'ls', user: username),
        ].join("\n\n")
        manifest_file = agent.tmpfile('crontab_overwrite_manifest')
        create_remote_file(agent, manifest_file, manifest)

        # We need to run a script here instead of a command because:
        #   * We need to cd into a directory owned by our user. Otherwise, bash will
        #   fail to execute stub_crontab_exe on AIX and Solaris because we run crontab
        #   as the given user, and the given user does not have access to Puppet's cwd.
        #
        #   * We also need to pass-in our PATH to Puppet since it contains stub_crontab_bin_dir.
        apply_crontab_overwrite_manifest = agent.tmpfile('apply_crontab_overwrite_manifest')
        script = <<-SCRIPT
  #!/usr/bin/env bash

  cd #{stub_crontab_bin_dir} && puppet apply #{manifest_file}
  SCRIPT
        create_remote_file(agent, apply_crontab_overwrite_manifest, script)
        on(agent, "chmod a+x #{apply_crontab_overwrite_manifest}")

        on(agent, "bash #{apply_crontab_overwrite_manifest}", environment: { PATH: "#{stub_crontab_bin_dir}:#{path_env_var}" })
      end

      step "(PUP-9217) Verify that Puppet does not overwrite the user's crontab file when it fails to read it" do
        assert_matching_arrays([crontab_contents], crontab_entries_of(agent, username), "Puppet overwrote the user's crontab file even though it failed to read it")
      end
    end
  end
end