diff options
Diffstat (limited to 'lib/chef/resource/windows_share.rb')
-rw-r--r-- | lib/chef/resource/windows_share.rb | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/lib/chef/resource/windows_share.rb b/lib/chef/resource/windows_share.rb new file mode 100644 index 0000000000..ee75041012 --- /dev/null +++ b/lib/chef/resource/windows_share.rb @@ -0,0 +1,299 @@ +# +# Copyright:: 2014-2017, Sölvi Páll Ásgeirsson. +# +# 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. +# + +require "chef/resource" + +class Chef + class Resource + # @author Sölvi Páll Ásgeirsson <solvip@gmail.com> + # @author Richard Lavey <richard.lavey@calastone.com> + class WindowsShare < Chef::Resource + resource_name :windows_share + provides :windows_share, os: "windows" + + property :share_name, String, name_property: true + property :path, String + property :description, String, default: "" + property :full_users, Array, default: [] + property :change_users, Array, default: [] + property :read_users, Array, default: [] + + include Windows::Helper + include Chef::Mixin::PowershellOut + + require "win32ole" if RUBY_PLATFORM =~ /mswin|mingw32|windows/ + + ACCESS_FULL = 2_032_127 + ACCESS_CHANGE = 1_245_631 + ACCESS_READ = 1_179_817 + + action :create do + raise "No path property set" unless new_resource.path + + if different_path? + unless current_resource.path.nil? || current_resource.path.empty? + converge_by("Removing previous share") do + delete_share + end + end + converge_by("Creating share #{current_resource.share_name}") do + create_share + end + end + + if different_members?(:full_users) || + different_members?(:change_users) || + different_members?(:read_users) || + different_description? + converge_by("Setting permissions and description for #{new_resource.share_name}") do + set_share_permissions + end + end + end + + action :delete do + if !current_resource.path.nil? && !current_resource.path.empty? + converge_by("Deleting #{current_resource.share_name}") do + delete_share + end + else + Chef::Log.debug("#{current_resource.share_name} does not exist - nothing to do") + end + end + + load_current_value do |desired| + wmi = WIN32OLE.connect("winmgmts://") + shares = wmi.ExecQuery("SELECT * FROM Win32_Share WHERE name = '#{desired.share_name}'") + existing_share = shares.Count == 0 ? nil : shares.ItemIndex(0) + + description "" + unless existing_share.nil? + path existing_share.Path + description existing_share.Description + end + + perms = share_permissions name + unless perms.nil? + full_users perms[:full_users] + change_users perms[:change_users] + read_users perms[:read_users] + end + end + + def share_permissions(name) + wmi = WIN32OLE.connect("winmgmts://") + shares = wmi.ExecQuery("SELECT * FROM Win32_LogicalShareSecuritySetting WHERE name = '#{name}'") + + # The security descriptor is an output parameter + sd = nil + begin + shares.ItemIndex(0).GetSecurityDescriptor(sd) + sd = WIN32OLE::ARGV[0] + rescue WIN32OLERuntimeError + Chef::Log.warn("Failed to retrieve any security information about the share.") + end + + read = [] + change = [] + full = [] + + unless sd.nil? + sd.DACL.each do |dacl| + trustee = "#{dacl.Trustee.Domain}\\#{dacl.Trustee.Name}".downcase + case dacl.AccessMask + when ACCESS_FULL + full.push(trustee) + when ACCESS_CHANGE + change.push(trustee) + when ACCESS_READ + read.push(trustee) + else + Chef::Log.warn "Unknown access mask #{dacl.AccessMask} for user #{trustee}. This will be lost if permissions are updated" + end + end + end + + { + full_users: full, + change_users: change, + read_users: read, + } + end + + action_class do + def description_exists?(resource) + !resource.description.nil? + end + + def different_description? + if description_exists?(new_resource) && description_exists?(current_resource) + new_resource.description.casecmp(current_resource.description) != 0 + else + description_exists?(new_resource) || description_exists?(current_resource) + end + end + + def different_path? + return true if current_resource.path.nil? + win_friendly_path(new_resource.path).casecmp(win_friendly_path(current_resource.path)) != 0 + end + + def different_members?(permission_type) + !(current_resource.send(permission_type.to_sym) - new_resource.send(permission_type.to_sym).map(&:downcase)).empty? || + !(new_resource.send(permission_type.to_sym).map(&:downcase) - current_resource.send(permission_type.to_sym)).empty? + end + + def find_share_by_name(name) + wmi = WIN32OLE.connect("winmgmts://") + shares = wmi.ExecQuery("SELECT * FROM Win32_Share WHERE name = '#{name}'") + shares.Count == 0 ? nil : shares.ItemIndex(0) + end + + def delete_share + find_share_by_name(new_resource.share_name).delete + end + + def create_share + raise "#{new_resource.path} is missing or not a directory" unless ::File.directory? new_resource.path + new_share_script = <<-EOH + $share = [wmiclass]"\\\\#{ENV['COMPUTERNAME']}\\root\\CimV2:Win32_Share" + $result=$share.Create('#{new_resource.path}', + '#{new_resource.share_name}', + 0, + 16777216, + '#{new_resource.description}', + $null, + $null) + exit $result.returnValue + EOH + r = powershell_out new_share_script + message = case r.exitstatus + when 2 + "2 : Access Denied" + when 8 + "8 : Unknown Failure" + when 9 + "9 : Invalid Name" + when 10 + "10 : Invalid Level" + when 21 + "21 : Invalid Parameter" + when 22 + "22 : Duplicate Share" + when 23 + "23 : Redirected Path" + when 24 + "24 : Unknown Device or Directory" + when 25 + "25 : Net Name Not Found" + else + r.exitstatus.to_s + end + + raise "Could not create share. Win32_Share.create returned #{message}" if r.error? + end + + # set_share_permissions - Enforce the share permissions as dictated by the resource attributes + def set_share_permissions + share_permissions_script = <<-EOH + Function New-SecurityDescriptor + { + param ( + [array]$ACEs + ) + #Create SeCDesc object + $SecDesc = ([WMIClass] "\\\\$env:ComputerName\\root\\cimv2:Win32_SecurityDescriptor").CreateInstance() + + foreach ($ACE in $ACEs ) + { + $SecDesc.DACL += $ACE.psobject.baseobject + } + + #Return the security Descriptor + return $SecDesc + } + + Function New-ACE + { + param ( + [string] $Name, + [string] $Domain, + [string] $Permission = "Read" + ) + #Create the Trusteee Object + $Trustee = ([WMIClass] "\\\\$env:computername\\root\\cimv2:Win32_Trustee").CreateInstance() + $account = get-wmiobject Win32_Account -filter "Name = '$Name' and Domain = '$Domain'" + $accountSID = [WMI] "\\\\$env:ComputerName\\root\\cimv2:Win32_SID.SID='$($account.sid)'" + + $Trustee.Domain = $Domain + $Trustee.Name = $Name + $Trustee.SID = $accountSID.BinaryRepresentation + + #Create ACE (Access Control List) object. + $ACE = ([WMIClass] "\\\\$env:ComputerName\\root\\cimv2:Win32_ACE").CreateInstance() + switch ($Permission) + { + "Read" { $ACE.AccessMask = 1179817 } + "Change" { $ACE.AccessMask = 1245631 } + "Full" { $ACE.AccessMask = 2032127 } + default { throw "$Permission is not a supported permission value. Possible values are 'Read','Change','Full'" } + } + + $ACE.AceFlags = 3 + $ACE.AceType = 0 + $ACE.Trustee = $Trustee + + $ACE + } + + $dacl_array = @() + + EOH + new_resource.full_users.each do |user| + share_permissions_script += user_to_ace(user, "Full") + end + + new_resource.change_users.each do |user| + share_permissions_script += user_to_ace(user, "Change") + end + + new_resource.read_users.each do |user| + share_permissions_script += user_to_ace(user, "Read") + end + + share_permissions_script += <<-EOH + + $dacl = New-SecurityDescriptor -Aces $dacl_array + + $share = get-wmiobject win32_share -filter 'Name like "#{new_resource.share_name}"' + $return = $share.SetShareInfo($null, '#{new_resource.description}', $dacl) + exit $return.returnValue + EOH + r = powershell_out(share_permissions_script) + raise "Could not set share permissions. Win32_Share.SedtShareInfo returned #{r.exitstatus}" if r.error? + end + + def user_to_ace(fully_qualified_user_name, access) + domain, user = fully_qualified_user_name.split('\\') + unless domain && user + raise "Invalid user entry #{fully_qualified_user_name}. The user names must be specified as 'DOMAIN\\user'" + end + "\n$dacl_array += new-ace -Name '#{user}' -domain '#{domain}' -permission '#{access}'" + end + end + end + end +end |