From cc839a7f6f332e3f1c4f3b90d5099384b3904766 Mon Sep 17 00:00:00 2001 From: Adam Edwards Date: Sun, 11 Oct 2015 22:43:33 -0700 Subject: remote_file credential support for Windows UNC paths --- lib/chef/provider/remote_file/network_file.rb | 37 ++++++++++++++++--- lib/chef/resource/remote_file.rb | 51 +++++++++++++++++++++++++++ lib/chef/util/windows/logon_session.rb | 22 +++++++----- 3 files changed, 97 insertions(+), 13 deletions(-) diff --git a/lib/chef/provider/remote_file/network_file.rb b/lib/chef/provider/remote_file/network_file.rb index 093a388d2a..70a8c074ad 100644 --- a/lib/chef/provider/remote_file/network_file.rb +++ b/lib/chef/provider/remote_file/network_file.rb @@ -19,6 +19,7 @@ require 'uri' require 'tempfile' require 'chef/provider/remote_file' +require 'chef/util/windows/logon_session' class Chef class Provider @@ -35,13 +36,39 @@ class Chef # Fetches the file on a network share, returning a Tempfile-like File handle # windows only def fetch - tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile - Chef::Log.debug("#{new_resource} staging #{@source} to #{tempfile.path}") - FileUtils.cp(@source, tempfile.path) - tempfile.close if tempfile + remote_user = new_resource.remote_credentials && new_resource.remote_credentials[:user] + remote_user_domain = remote_user ? new_resource.remote_credentials[:domain] : nil + remote_user_secret = remote_user ? new_resource.remote_credentials[:secret] : nil + + session = nil + + if remote_user + session = Chef::Util::Windows::LogonSession.new(remote_user, remote_user_domain, remote_user_secret) + end + + tempfile = nil + + begin + tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile + Chef::Log.debug("#{new_resource} staging #{@source} to #{tempfile.path}") + + if session + session.open + session.set_user_context + end + + ::File.open(@source, 'rb') do | remote_file | + while data = remote_file.read(1048576) + tempfile.write(data) + end + end + ensure + session.close! if session + tempfile.close if tempfile + end + tempfile end - end end end diff --git a/lib/chef/resource/remote_file.rb b/lib/chef/resource/remote_file.rb index b7a553cbe8..faf1abb0a0 100644 --- a/lib/chef/resource/remote_file.rb +++ b/lib/chef/resource/remote_file.rb @@ -23,6 +23,7 @@ require 'chef/provider/remote_file' require 'chef/mixin/securable' require 'chef/mixin/uris' + class Chef class Resource class RemoteFile < Chef::Resource::File @@ -36,6 +37,7 @@ class Chef @ftp_active_mode = false @headers = {} @provider = Chef::Provider::RemoteFile + @remote_credentials = nil end # source can take any of the following as arguments @@ -122,6 +124,24 @@ class Chef ) end + def remote_credentials(args=nil) + set_or_return( + :remote_credentials, + args, + { :kind_of => Hash, + :callbacks => { + :validate_credentials => method(:validate_credentials) + }}) + end + + def sensitive(arg=nil) + if remote_credentials && remote_credentials[:user] + true + else + super + end + end + private include Chef::Mixin::Uris @@ -138,6 +158,37 @@ class Chef true end + def validate_credentials(credentials) + if ! Chef::Platform.windows? + raise Exceptions::UnsupportedPlatform, + "The `remote_credentials` property is only supported on the Windows platform" + end + + if ! credentials.empty? + if ! credentials.has_key?(:user) + raise ArgumentError, "A non-empty `remote_credentials` property must specify `:user` key" + end + + if credentials[:user].nil? + raise ArgumentError, "A non-empty `remote_credentials` property must specify a non-nil value for the `:user` key" + end + end + + valid_keys = [:user, :domain, :secret] + invalid_arguments = credentials.keys - valid_keys + + if ! invalid_arguments.empty? + invalid_argument_list = invalid_arguments.map { | key | "`:#{key}`" }.join(', ') + raise ArgumentError, "Only the keys `:user`, `:domain`, and `:secret` are valid keys for the `remote_credentials` [Hash]. The following invalid keys were specified: #{invalid_argument_list}" + end + + credentials.keys.each do | key | + if (! credentials[key].nil?) && (! credentials[key].is_a? String) + raise ArgumentError, "The value of key :#{key} of the `remote_credentials` property must be of type [String], but it is of type [#{credentials[key].class}]" + end + end + end + def absolute_uri?(source) Chef::Provider::RemoteFile::Fetcher.network_share?(source) or (source.kind_of?(String) and as_uri(source).absolute?) rescue URI::InvalidURIError diff --git a/lib/chef/util/windows/logon_session.rb b/lib/chef/util/windows/logon_session.rb index cae7f406ff..35885cb4be 100644 --- a/lib/chef/util/windows/logon_session.rb +++ b/lib/chef/util/windows/logon_session.rb @@ -25,9 +25,10 @@ class Chef class LogonSession include Chef::Mixin::WideString - def initialize(username, password=nil) + def initialize(username, domain=nil, password=nil) @username = username @password = password + @domain = domain @token = FFI::Buffer.new(:pointer) @session_opened = false @impersonating = false @@ -40,8 +41,9 @@ class Chef username = wstring(@username) password = wstring(@password) + domain = wstring(@domain) - status = Chef::ReservedNames::Win32::API::Security.LogonUserW(username, nil, password, Chef::ReservedNames::Win32::API::Security::LOGON32_LOGON_INTERACTIVE, Chef::ReservedNames::Win32::API::Security::LOGON32_PROVIDER_DEFAULT, @token) + status = Chef::ReservedNames::Win32::API::Security.LogonUserW(username, domain, password, Chef::ReservedNames::Win32::API::Security::LOGON32_LOGON_NETWORK, Chef::ReservedNames::Win32::API::Security::LOGON32_PROVIDER_DEFAULT, @token) if ! status raise 'logon failed' @@ -50,20 +52,24 @@ class Chef @session_opened = true end - def close - if ! @session_opened - raise 'Session not open' - end - + def close! if @impersonating restore_user_context end - Chef::ReservedNames::Win32::API.CloseHandle(@token) + Chef::ReservedNames::Win32::API::System.CloseHandle(@token.read_ulong) @token = nil @session_opened = false end + def close + if ! @session_opened + raise 'Session not open' + end + + close! + end + def set_user_context if ! @session_opened raise 'Session not open' -- cgit v1.2.1