# # Author:: Serdar Sutay () # Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Shared context used by both Powershell and Batch script provider # tests. require "chef/platform/query_helpers" shared_context Chef::Resource::WindowsScript do before(:all) do @ohai_reader = Ohai::System.new @ohai_reader.all_plugins(%w{platform kernel}) new_node = Chef::Node.new new_node.consume_external_attrs(@ohai_reader.data, {}) events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(new_node, {}, events) end let(:script_output_path) do File.join(Dir.tmpdir, make_tmpname("windows_script_test")) end before(:each) do File.delete(script_output_path) if File.exist?(script_output_path) end after(:each) do File.delete(script_output_path) if File.exist?(script_output_path) end shared_examples_for "a script resource with architecture attribute" do context "with the given architecture attribute value" do let(:expected_architecture) do if resource_architecture expected_architecture = resource_architecture else expected_architecture = @ohai_reader.data["kernel"]["machine"].to_sym end end let(:expected_architecture_output) do expected_architecture == :i386 ? "X86" : "AMD64" end let(:guard_script_suffix) do "guard" end let(:guard_script_output_path) do "#{script_output_path}#{guard_script_suffix}" end let(:resource_command) do "#{architecture_command} #{output_command} #{script_output_path}" end let(:resource_guard_command) do "#{architecture_command} #{output_command} #{guard_script_output_path}" end before(:each) do resource.code resource_command (resource.architecture resource_architecture) if resource_architecture resource.returns(0) end it "creates a process with the expected architecture" do resource.run_action(:run) expect(get_process_architecture).to eq(expected_architecture_output.downcase) end it "executes guards with the same architecture as the resource" do resource.only_if resource_guard_command resource.run_action(:run) expect(get_process_architecture).to eq(expected_architecture_output.downcase) expect(get_guard_process_architecture).to eq(expected_architecture_output.downcase) expect(get_guard_process_architecture).to eq(get_process_architecture) end context "when the guard's architecture is specified as 64-bit" do let(:guard_architecture) { :x86_64 } it "executes a 64-bit guard", :windows64_only do resource.only_if resource_guard_command, architecture: guard_architecture resource.run_action(:run) expect(get_guard_process_architecture).to eq("amd64") end end context "when the guard's architecture is specified as 32-bit" do let(:guard_architecture) { :i386 } it "executes a 32-bit guard" do resource.only_if resource_guard_command, architecture: guard_architecture resource.run_action(:run) expect(get_guard_process_architecture).to eq("x86") end end end end shared_examples_for "a Windows script running on Windows" do shared_examples_for "a script that cannot be accessed by other users if they are not administrators" do include Chef::Mixin::ShellOut let(:script_provider) { resource.provider_for_action(:run) } let(:script_file) { script_provider.script_file } let(:script_file_path) { script_file.to_path } let(:read_access_denied_command) { "::File.read('#{script_file_path}')" } let(:modify_access_denied_command) { "::File.write('#{script_file_path}', 'stuff')" } let(:delete_access_denied_command) { "::File.delete('#{script_file_path}')" } let(:access_denied_sentinel) { 7334 } let(:access_allowed_sentinel) { 1586 } let(:access_command_invalid) { 0 } let(:ruby_interpreter_path) { RbConfig.ruby } let(:ruby_command_template) { "require 'FileUtils';status = 0;begin; #{ruby_access_command};rescue Exception => e; puts e; status = e.class == Errno::EACCES ? #{access_denied_sentinel} : #{access_allowed_sentinel};end;exit status" } let(:command_template) { "set BUNDLE_GEMFILE=&#{ruby_interpreter_path} -e \"#{ruby_command_template}\"" } let(:access_command) { command_template } before do expect(script_provider).to receive(:unlink_script_file) resource.code("echo hi") script_provider.action_run end after do script_file.close! if script_file ::File.delete(script_file.to_path) if script_file && ::File.exist?(script_file.to_path) end include_context "alternate user identity" shared_examples_for "a script whose file system location cannot be accessed by other non-admin users" do let(:ruby_access_command) { file_access_command } it "generates a script in the local file system that prevents read access to other non-admin users" do shell_out!(access_command, user: windows_nonadmin_user, password: windows_nonadmin_user_password, returns: [access_denied_sentinel]) end end context "when a different non-admin user attempts write (modify) to access the script" do let(:file_access_command) { modify_access_denied_command } it_behaves_like "a script whose file system location cannot be accessed by other non-admin users" end context "when a different non-admin user attempts write (delete) to access the script" do let(:file_access_command) { delete_access_denied_command } it_behaves_like "a script whose file system location cannot be accessed by other non-admin users" end end describe "when the run action is invoked on Windows" do it "executes the script code" do resource.code("whoami > \"#{script_output_path}\"") resource.returns(0) resource.run_action(:run) end context "the script is executed with the identity of the current user", :windows_service_requires_assign_token do it_behaves_like "a script that cannot be accessed by other users if they are not administrators" end context "the script is executed with an alternate non-admin identity", :windows_service_requires_assign_token do include_context "alternate user identity" before do resource.user(windows_alternate_user) resource.password(windows_alternate_user_password) end it_behaves_like "a script that cannot be accessed by other users if they are not administrators" end end context "when $env:TMP has a space" do before(:each) do @dir = Dir.mktmpdir("Jerry Smith") @original_env = ENV.to_hash.dup ENV.delete("TMP") ENV["TMP"] = @dir end after(:each) do FileUtils.remove_entry_secure(@dir) ENV.clear ENV.update(@original_env) end it "executes the script code" do resource.code("whoami > \"#{script_output_path}\"") resource.returns(0) resource.run_action(:run) end end context "when evaluating guards" do it "has a guard_interpreter attribute set to the short name of the resource" do expect(resource.guard_interpreter).to eq(resource.resource_name) resource.not_if "findstr.exe /thiscommandhasnonzeroexitstatus" expect(Chef::Resource).to receive(:resource_for_node).and_call_original expect(resource.class).to receive(:new).and_call_original expect(resource.should_skip?(:run)).to be_falsey end context "when this resource is used as a guard and it is specified with an alternate user identity" do let(:guard_interpreter_resource) { resource.resource_name } it_behaves_like "a resource with a guard specifying an alternate user identity" end end context "when the architecture attribute is not set" do let(:resource_architecture) { nil } it_behaves_like "a script resource with architecture attribute" end context "when the architecture attribute is :i386" do let(:resource_architecture) { :i386 } it_behaves_like "a script resource with architecture attribute" end context "when the architecture attribute is :x86_64" do let(:resource_architecture) { :x86_64 } it_behaves_like "a script resource with architecture attribute" end describe "when running with an alternate user identity" do let(:resource_command_property) { :code } it_behaves_like "an execute resource that supports alternate user identity" end end def get_windows_script_output(suffix = "") File.read("#{script_output_path}#{suffix}") end def source_contains_case_insensitive_content?( source, content ) source.downcase.include?(content.downcase) end def get_guard_process_architecture get_process_architecture(guard_script_suffix) end def get_process_architecture(suffix = "") get_windows_script_output(suffix).strip.downcase end end