summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Edwards <adamed@opscode.com>2015-12-12 23:08:24 -0800
committerAdam Edwards <adamed@opscode.com>2015-12-12 23:08:24 -0800
commit1476e48e3c6403f79810cb060c96999018b726ec (patch)
tree66c60c257956b2c7460f23fc9576f2f775616dde
parent52ca3abd17f1b5f696219807dcff83280ce87ec1 (diff)
downloadchef-1476e48e3c6403f79810cb060c96999018b726ec.tar.gz
Windows alternate user support for execute resources
-rw-r--r--lib/chef/provider/execute.rb13
-rw-r--r--lib/chef/provider/powershell_script.rb8
-rw-r--r--lib/chef/provider/script.rb32
-rw-r--r--lib/chef/resource/execute.rb24
-rw-r--r--spec/functional/resource/batch_spec.rb6
-rw-r--r--spec/functional/resource/execute_spec.rb17
-rw-r--r--spec/support/shared/functional/execute_resource.rb145
-rw-r--r--spec/support/shared/functional/windows_script.rb83
-rw-r--r--spec/support/shared/unit/execute_resource.rb39
-rw-r--r--spec/unit/provider/execute_spec.rb92
-rw-r--r--spec/unit/provider/script_spec.rb55
11 files changed, 500 insertions, 14 deletions
diff --git a/lib/chef/provider/execute.rb b/lib/chef/provider/execute.rb
index 200beb02ad..6fca2aadf9 100644
--- a/lib/chef/provider/execute.rb
+++ b/lib/chef/provider/execute.rb
@@ -19,15 +19,22 @@
require 'chef/log'
require 'chef/provider'
require 'forwardable'
+require 'chef/mixin/user_identity'
class Chef
class Provider
class Execute < Chef::Provider
extend Forwardable
+ include Chef::Mixin::UserIdentity
+
provides :execute
- def_delegators :@new_resource, :command, :returns, :environment, :user, :group, :cwd, :umask, :creates
+ def_delegators :@new_resource, :command, :returns, :environment, :user, :domain, :password, :group, :cwd, :umask, :creates
+
+ def initialize(new_resource, run_context)
+ super
+ end
def load_current_resource
current_resource = Chef::Resource::Execute.new(new_resource.name)
@@ -52,6 +59,7 @@ class Chef
end
def action_run
+ validate_identity(new_resource.user, new_resource.password, new_resource.domain)
if creates && sentinel_file.exist?
Chef::Log.debug("#{new_resource} sentinel file #{sentinel_file} exists - nothing to do")
return false
@@ -92,6 +100,8 @@ class Chef
opts[:returns] = returns if returns
opts[:environment] = environment if environment
opts[:user] = user if user
+ opts[:domain] = domain if domain
+ opts[:password] = password if password
opts[:group] = group if group
opts[:cwd] = cwd if cwd
opts[:umask] = umask if umask
@@ -120,6 +130,7 @@ class Chef
( cwd && creates_relative? ) ? ::File.join(cwd, creates) : creates
))
end
+
end
end
end
diff --git a/lib/chef/provider/powershell_script.rb b/lib/chef/provider/powershell_script.rb
index e04efb6b42..44eebea6a6 100644
--- a/lib/chef/provider/powershell_script.rb
+++ b/lib/chef/provider/powershell_script.rb
@@ -149,6 +149,14 @@ EOH
<<-EOH
# Chef Client wrapper for powershell_script resources
+# In rare cases, such as when PowerShell is executed
+# as an alternate user, the new-variable cmdlet is not
+# available, so import it just in case
+if ( get-module -ListAvailable Microsoft.PowerShell.Utility )
+{
+ Import-Module Microsoft.PowerShell.Utility
+}
+
# LASTEXITCODE can be uninitialized -- make it explictly 0
# to avoid incorrect detection of failure (non-zero) codes
$global:LASTEXITCODE = 0
diff --git a/lib/chef/provider/script.rb b/lib/chef/provider/script.rb
index 2f2001dbf9..abc4ca3081 100644
--- a/lib/chef/provider/script.rb
+++ b/lib/chef/provider/script.rb
@@ -18,6 +18,7 @@
require 'tempfile'
require 'chef/provider/execute'
+require 'chef/win32/security' if Chef::Platform.windows?
require 'forwardable'
class Chef
@@ -69,7 +70,36 @@ class Chef
# FileUtils itself implements a no-op if +user+ or +group+ are nil
# You can prove this by running FileUtils.chown(nil,nil,'/tmp/file')
# as an unprivileged user.
- FileUtils.chown(new_resource.user, new_resource.group, script_file.path)
+ if ! Chef::Platform.windows?
+ FileUtils.chown(new_resource.user, new_resource.group, script_file.path)
+ else
+ grant_alternate_user_read_access
+ end
+ end
+
+ def grant_alternate_user_read_access
+ return if new_resource.user.nil?
+
+ # Duplicate the script file's existing DACL
+ # so we can add an ACE later
+ securable_object = Chef::ReservedNames::Win32::Security::SecurableObject.new(script_file.path)
+ aces = securable_object.security_descriptor.dacl.reduce([]) { | result, current | result.push(current) }
+
+ username = new_resource.user
+
+ if new_resource.domain
+ username = new_resource.domain + '\\' + new_resource.user
+ end
+
+ # Create an ACE that allows the alternate user read access to the script
+ # file so it can be read and executed.
+ user_sid = Chef::ReservedNames::Win32::Security::SID.from_account(username)
+ read_ace = Chef::ReservedNames::Win32::Security::ACE.access_allowed(user_sid, Chef::ReservedNames::Win32::API::Security::GENERIC_READ | Chef::ReservedNames::Win32::API::Security::GENERIC_EXECUTE, 0)
+ aces.push(read_ace)
+ acl = Chef::ReservedNames::Win32::Security::ACL.create(aces)
+
+ # This actually applies the modified DACL to the file
+ securable_object.dacl = acl
end
def script_file
diff --git a/lib/chef/resource/execute.rb b/lib/chef/resource/execute.rb
index 4f92a7ad35..dfb4aaa400 100644
--- a/lib/chef/resource/execute.rb
+++ b/lib/chef/resource/execute.rb
@@ -143,6 +143,30 @@ class Chef
)
end
+ def domain(arg=nil)
+ set_or_return(
+ :domain,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def password(arg=nil)
+ set_or_return(
+ :password,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def sensitive(args=nil)
+ if ! password.nil?
+ true
+ else
+ super
+ end
+ end
+
def self.set_guard_inherited_attributes(*inherited_attributes)
@class_inherited_attributes = inherited_attributes
end
diff --git a/spec/functional/resource/batch_spec.rb b/spec/functional/resource/batch_spec.rb
index 39133fd40b..ef8afa1782 100644
--- a/spec/functional/resource/batch_spec.rb
+++ b/spec/functional/resource/batch_spec.rb
@@ -23,7 +23,11 @@ describe Chef::Resource::WindowsScript::Batch, :windows_only do
let(:output_command) { ' > ' }
- let (:architecture_command) { '@echo %PROCESSOR_ARCHITECTURE%' }
+ let(:architecture_command) { '@echo %PROCESSOR_ARCHITECTURE%' }
+
+ let(:resource) do
+ Chef::Resource::WindowsScript::Batch.new("Batch resource functional test", @run_context)
+ end
it_behaves_like "a Windows script running on Windows"
diff --git a/spec/functional/resource/execute_spec.rb b/spec/functional/resource/execute_spec.rb
index 692ccfb796..67f222aba9 100644
--- a/spec/functional/resource/execute_spec.rb
+++ b/spec/functional/resource/execute_spec.rb
@@ -137,6 +137,18 @@ describe Chef::Resource::Execute do
end
end
+ describe "when a guard is specified" do
+ describe "when using the default guard interpreter" do
+ let(:guard_interpreter_resource) { nil }
+ it_behaves_like "a resource with a guard specifying an alternate user identity"
+ end
+
+ describe "when using the execute resource as the guard interpreter" do
+ let(:guard_interpreter_resource) { :execute }
+ it_behaves_like "a resource with a guard specifying an alternate user identity"
+ end
+ end
+
# Ensure that CommandTimeout is raised, and is caused by resource.timeout really expiring.
# https://github.com/chef/chef/issues/2985
#
@@ -151,4 +163,9 @@ describe Chef::Resource::Execute do
expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::CommandTimeout)
end
end
+
+ describe "when running with an alternate user identity" do
+ let(:resource_command_property) { :command }
+ it_behaves_like "an execute resource that supports alternate user identity"
+ end
end
diff --git a/spec/support/shared/functional/execute_resource.rb b/spec/support/shared/functional/execute_resource.rb
new file mode 100644
index 0000000000..27813c71b6
--- /dev/null
+++ b/spec/support/shared/functional/execute_resource.rb
@@ -0,0 +1,145 @@
+#
+# Author:: Adam Edwards (<adamed@chef.io>)
+# Copyright:: Copyright (c) 2015 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 "a non-admin Windows user" do
+ include Chef::Mixin::ShellOut
+
+ let(:windows_nonadmin_user_domain) { ENV['COMPUTERNAME'] }
+ let(:windows_nonadmin_user_qualified) { "#{windows_nonadmin_user_domain}\\#{windows_nonadmin_user}" }
+
+ before do
+ shell_out!("net.exe user /delete #{windows_nonadmin_user}", returns: [0,2])
+ shell_out!("net.exe user /add #{windows_nonadmin_user} \"#{windows_nonadmin_user_password}\"")
+ end
+
+ after do
+ shell_out!("net.exe user /delete #{windows_nonadmin_user}", returns: [0,2])
+ end
+end
+
+shared_context "alternate user identity" do
+ let(:windows_alternate_user) {"chef%02d%02d%02d" %[Time.now.year % 100, Time.now.month, Time.now.day]}
+ let(:windows_alternate_user_password) { 'lj28;fx3T!x,2'}
+ let(:windows_alternate_user_qualified) { "#{ENV['COMPUTERNAME']}\\#{windows_alternate_user}" }
+
+ let(:windows_nonadmin_user) { windows_alternate_user }
+ let(:windows_nonadmin_user_password) { windows_alternate_user_password }
+
+ include_context 'a non-admin Windows user'
+end
+
+shared_context "a command that can be executed as an alternate user" do
+ include_context "alternate user identity"
+
+ let(:script_output_dir) { Dir.mktmpdir }
+ let(:script_output_path) { File.join(script_output_dir, make_tmpname("chef_execute_identity_test")) }
+ let(:script_output) { File.read(script_output_path) }
+
+ include Chef::Mixin::ShellOut
+
+ before do
+ shell_out!("icacls \"#{script_output_dir.gsub(/\//,'\\')}\" /grant \"authenticated users:(F)\"")
+ end
+
+ after do
+ File.delete(script_output_path) if File.exists?(script_output_path)
+ Dir.rmdir(script_output_dir) if Dir.exists?(script_output_dir)
+ end
+end
+
+shared_examples_for "an execute resource that supports alternate user identity" do
+ context "when running on Windows", :windows_only do
+
+ include_context "a command that can be executed as an alternate user"
+
+ let(:windows_current_user) { ENV['USERNAME'] }
+ let(:windows_current_user_qualified) { "#{ENV['USERDOMAIN'] || ENV['COMPUTERNAME']}\\#{windows_current_user}" }
+ let(:resource_identity_command) { "powershell.exe -noprofile -command \"import-module microsoft.powershell.utility;([Security.Principal.WindowsPrincipal]([Security.Principal.WindowsIdentity]::GetCurrent())).identity.name | out-file -encoding ASCII '#{script_output_path}'\"" }
+
+ let(:execute_resource) {
+ resource.user(windows_alternate_user)
+ resource.password(windows_alternate_user_password)
+ resource.send(resource_command_property, resource_identity_command)
+ resource
+ }
+
+ it "executes the process as an alternate user" do
+ expect(windows_current_user.length).to be > 0
+ expect { execute_resource.run_action(:run) }.not_to raise_error
+ expect(script_output.chomp.length).to be > 0
+ expect(script_output.chomp.downcase).to eq(windows_alternate_user_qualified.downcase)
+ expect(script_output.chomp.downcase).not_to eq(windows_current_user.downcase)
+ expect(script_output.chomp.downcase).not_to eq(windows_current_user_qualified.downcase)
+ end
+
+ let(:windows_alternate_user_password_invalid) { "#{windows_alternate_user_password}x" }
+
+ it "raises an exception if the user's password is invalid" do
+ execute_resource.password(windows_alternate_user_password_invalid)
+ expect { execute_resource.run_action(:run) }.to raise_error(SystemCallError)
+ end
+ end
+end
+
+shared_examples_for "a resource with a guard specifying an alternate user identity" do
+ context "when running on Windows", :windows_only do
+ include_context "alternate user identity"
+
+ let(:resource_command_property) { :command }
+
+ let(:powershell_equal_to_alternate_user) { '-eq' }
+ let(:powershell_not_equal_to_alternate_user) { '-ne' }
+ let(:guard_identity_command) { "powershell.exe -noprofile -command \"import-module microsoft.powershell.utility;exit @(392,0)[[int32](([Security.Principal.WindowsPrincipal]([Security.Principal.WindowsIdentity]::GetCurrent())).Identity.Name #{comparison_to_alternate_user} '#{windows_alternate_user_qualified}')]\"" }
+
+ before do
+ resource.guard_interpreter(guard_interpreter_resource)
+ end
+
+ context "when the guard expression is true if the user is alternate and false otherwise" do
+ let(:comparison_to_alternate_user) { powershell_equal_to_alternate_user }
+
+ it "causes the resource to be updated for only_if" do
+ resource.only_if(guard_identity_command, {user: windows_alternate_user, password: windows_alternate_user_password})
+ resource.run_action(:run)
+ expect(resource).to be_updated_by_last_action
+ end
+
+ it "causes the resource to not be updated for not_if" do
+ resource.not_if(guard_identity_command, {user: windows_alternate_user, password: windows_alternate_user_password})
+ resource.run_action(:run)
+ expect(resource).not_to be_updated_by_last_action
+ end
+ end
+
+ context "when the guard expression is false if the user is alternate and true otherwise" do
+ let(:comparison_to_alternate_user) { powershell_not_equal_to_alternate_user }
+
+ it "causes the resource not to be updated for only_if" do
+ resource.only_if(guard_identity_command, {user: windows_alternate_user, password: windows_alternate_user_password})
+ resource.run_action(:run)
+ expect(resource).not_to be_updated_by_last_action
+ end
+
+ it "causes the resource to be updated for not_if" do
+ resource.not_if(guard_identity_command, {user: windows_alternate_user, password: windows_alternate_user_password})
+ resource.run_action(:run)
+ expect(resource).to be_updated_by_last_action
+ end
+ end
+ end
+end
diff --git a/spec/support/shared/functional/windows_script.rb b/spec/support/shared/functional/windows_script.rb
index d84c06c86b..bf75cdbd8f 100644
--- a/spec/support/shared/functional/windows_script.rb
+++ b/spec/support/shared/functional/windows_script.rb
@@ -46,10 +46,6 @@ shared_context Chef::Resource::WindowsScript do
File.delete(script_output_path) if File.exists?(script_output_path)
end
- let!(:resource) do
- Chef::Resource::WindowsScript::Batch.new("Batch resource functional test", @run_context)
- end
-
shared_examples_for "a script resource with architecture attribute" do
context "with the given architecture attribute value" do
let(:expected_architecture) do
@@ -125,6 +121,60 @@ shared_context Chef::Resource::WindowsScript do
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.exists?(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 read to access the script" do
+ let(:file_access_command) { read_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 (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
@@ -132,6 +182,21 @@ shared_context Chef::Resource::WindowsScript do
resource.returns(0)
resource.run_action(:run)
end
+
+ context "the script is executed with the identity of the current user" 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" 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
@@ -165,6 +230,11 @@ shared_context Chef::Resource::WindowsScript do
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
@@ -181,6 +251,11 @@ shared_context Chef::Resource::WindowsScript 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 = '')
diff --git a/spec/support/shared/unit/execute_resource.rb b/spec/support/shared/unit/execute_resource.rb
index 3a88ff8890..70d36a0959 100644
--- a/spec/support/shared/unit/execute_resource.rb
+++ b/spec/support/shared/unit/execute_resource.rb
@@ -106,6 +106,16 @@ shared_examples_for "an execute resource" do
expect(@resource.user).to eql(1)
end
+ it "should accept a string for the domain" do
+ @resource.domain 'mothership'
+ expect(@resource.domain).to eql('mothership')
+ end
+
+ it "should accept a string for the password" do
+ @resource.password 'we.funk!'
+ expect(@resource.password).to eql('we.funk!')
+ end
+
it "should accept a string for creates" do
@resource.creates "something"
expect(@resource.creates).to eql("something")
@@ -116,7 +126,34 @@ shared_examples_for "an execute resource" do
expect(@resource.live_stream).to be true
end
- describe "when it has cwd, environment, group, path, return value, and a user" do
+ describe "the resource's sensitive attribute" do
+ it "should be false by default" do
+ expect(@resource.sensitive).to eq(false)
+ end
+
+ it "should be true if set to true" do
+ expect(@resource.sensitive).to eq(false)
+ @resource.sensitive true
+ expect(@resource.sensitive).to eq(true)
+ end
+
+ it "should be true if the password is non-nil" do
+ expect(@resource.sensitive).to eq(false)
+ @resource.password('we.funk!')
+ expect(@resource.sensitive).to eq(true)
+ end
+
+ it "should be true if the password is non-nil but the value is explicitly set to false" do
+ expect(@resource.sensitive).to eq(false)
+ @resource.password('we.funk!')
+ expect(@resource.sensitive).to eq(true)
+ @resource.sensitive false
+ expect(@resource.sensitive).to eq(true)
+ end
+
+ end
+
+ describe "when it has cwd, environment, group, path, return value, and a user" do
before do
@resource.command("grep")
@resource.cwd("/tmp/")
diff --git a/spec/unit/provider/execute_spec.rb b/spec/unit/provider/execute_spec.rb
index 3af35a17ca..e94a12fc6d 100644
--- a/spec/unit/provider/execute_spec.rb
+++ b/spec/unit/provider/execute_spec.rb
@@ -239,5 +239,97 @@ describe Chef::Provider::Execute do
end
end
+ describe "when an alternate user identity is specified" do
+ before do
+ allow(provider).to receive(:shell_out!).and_return(nil)
+ end
+
+ context "when running on Windows" do
+ before do
+ allow(::Chef::Platform).to receive(:windows?).and_return(true)
+ end
+
+ context "when the username is specified" do
+ before do
+ new_resource.user('starchild')
+ end
+
+ context "when the domain is specified" do
+ before do
+ new_resource.domain('mydomain')
+ end
+
+ it "should raise an error if the password is not specified" do
+ expect(new_resource).to receive(:password).at_least(1).times.and_return(nil)
+ expect { provider.run_action(:run) }.to raise_error(ArgumentError)
+ end
+
+ it "should not raise an error if the password is specified" do
+ expect(new_resource).to receive(:password).at_least(1).times.and_return('we.funk!')
+ expect { provider.run_action(:run) }.not_to raise_error
+ end
+ end
+
+ context "when the domain is not specified" do
+ before do
+ expect(new_resource).to receive(:domain).at_least(1).times.and_return(nil)
+ end
+
+ it "should raise an error if the password is not specified" do
+ expect(new_resource).to receive(:password).at_least(1).times.and_return(nil)
+ expect { provider.run_action(:run) }.to raise_error(ArgumentError)
+ end
+
+ it "should not raise an error if the password is specified" do
+ expect(new_resource).to receive(:password).at_least(1).times.and_return('we.funk!')
+ expect { provider.run_action(:run) }.not_to raise_error
+
+ end
+ end
+ end
+
+ context "when the username is not specified" do
+ before do
+ expect(new_resource).to receive(:user).at_least(1).times.and_return(nil)
+ end
+
+ it "should raise an error if the password is specified" do
+ expect(new_resource).to receive(:password).at_least(1).times.and_return('we.funk!')
+ expect { provider.run_action(:run) }.to raise_error(ArgumentError)
+ end
+
+ it "should raise an error if the domain is specified" do
+ expect(new_resource).to receive(:domain).at_least(1).times.and_return('mothership')
+ expect { provider.run_action(:run) }.to raise_error(ArgumentError)
+ end
+
+ it "should raise an error if the domain and password are specified" do
+ expect(new_resource).to receive(:password).at_least(1).times.and_return('we.funk!')
+ expect(new_resource).to receive(:domain).at_least(1).times.and_return('mothership')
+ expect { provider.run_action(:run) }.to raise_error(ArgumentError)
+ end
+ end
+ end
+
+ context "when not running on Windows" do
+ before do
+ allow(::Chef::Platform).to receive(:windows?).and_return(false)
+ end
+
+ it "should not raise an error if the user is specified" do
+ new_resource.user('starchild')
+ end
+
+ it "should raise an error if the password is specified" do
+ expect(new_resource).to receive(:password).and_return('we.funk!')
+ expect { provider.run_action(:run) }.to raise_error(Chef::Exceptions::UnsupportedPlatform)
+ end
+
+ it "should raise an error if the domain is specified" do
+ expect(new_resource).to receive(:domain).and_return('we.funk!')
+ expect { provider.run_action(:run) }.to raise_error(Chef::Exceptions::UnsupportedPlatform)
+ end
+ end
+ end
end
end
diff --git a/spec/unit/provider/script_spec.rb b/spec/unit/provider/script_spec.rb
index 7cc5abbd15..12eee2f7f3 100644
--- a/spec/unit/provider/script_spec.rb
+++ b/spec/unit/provider/script_spec.rb
@@ -56,12 +56,55 @@ describe Chef::Provider::Script, "action_run" do
end
end
- context "#set_owner_and_group" do
- it "sets the owner and group for the script file" do
- new_resource.user 'toor'
- new_resource.group 'wheel'
- expect(FileUtils).to receive(:chown).with('toor', 'wheel', tempfile.path)
- provider.set_owner_and_group
+ context "when configuring the script file's security" do
+ context 'when not running on Windows' do
+ before do
+ allow(::Chef::Platform).to receive(:windows?).and_return(false)
+ end
+ context "#set_owner_and_group" do
+ it "sets the owner and group for the script file" do
+ new_resource.user 'toor'
+ new_resource.group 'wheel'
+ expect(FileUtils).to receive(:chown).with('toor', 'wheel', tempfile.path)
+ provider.set_owner_and_group
+ end
+ end
+ end
+
+ context 'when running on Windows' do
+ before do
+ allow(::Chef::Platform).to receive(:windows?).and_return(true)
+ expect(new_resource.user).to eq(nil)
+ stub_const('Chef::ReservedNames::Win32::API::Security::GENERIC_READ', 1)
+ stub_const('Chef::ReservedNames::Win32::API::Security::GENERIC_EXECUTE', 4)
+ stub_const('Chef::ReservedNames::Win32::Security', Class.new)
+ stub_const('Chef::ReservedNames::Win32::Security::SecurableObject', Class.new)
+ stub_const('Chef::ReservedNames::Win32::Security::SID', Class.new)
+ stub_const('Chef::ReservedNames::Win32::Security::ACE', Class.new)
+ stub_const('Chef::ReservedNames::Win32::Security::ACL', Class.new)
+ end
+
+ context "when an alternate user is not specified" do
+ it "does not attempt to set the script file's security descriptor" do
+ expect(provider).to receive(:grant_alternate_user_read_access)
+ expect(Chef::ReservedNames::Win32::Security::SecurableObject).not_to receive(:new)
+ provider.set_owner_and_group
+ end
+ end
+
+ context "when an alternate user is specified" do
+ let(:security_descriptor) { instance_double('Chef::ReservedNames::Win32::Security::SecurityDescriptor', :dacl => []) }
+ let(:securable_object) { instance_double('Chef::ReservedNames::Win32::Security::SecurableObject', :security_descriptor => security_descriptor, :dacl= => nil) }
+ it "sets the script file's security descriptor" do
+ new_resource.user('toor')
+ expect(Chef::ReservedNames::Win32::Security::SecurableObject).to receive(:new).and_return(securable_object)
+ expect(Chef::ReservedNames::Win32::Security::SID).to receive(:from_account).and_return(nil)
+ expect(Chef::ReservedNames::Win32::Security::ACE).to receive(:access_allowed).and_return(nil)
+ expect(Chef::ReservedNames::Win32::Security::ACL).to receive(:create).and_return(nil)
+ expect(securable_object).to receive(:dacl=)
+ provider.set_owner_and_group
+ end
+ end
end
end