diff options
author | John Keiser <jkeiser@opscode.com> | 2011-11-21 01:02:58 -0800 |
---|---|---|
committer | Seth Chisamore <schisamo@opscode.com> | 2011-12-08 20:10:57 -0500 |
commit | 7feea09feb5bc7a095692af0a7d0b81376c95e3a (patch) | |
tree | cdacba3bb093015320bc9d1a8911235765e4f057 /chef | |
parent | 983b8796dbf71afb285ef4abbe749814386ec1b0 (diff) | |
download | chef-7feea09feb5bc7a095692af0a7d0b81376c95e3a.tar.gz |
Add Win32FileAccessControl, make security work again
Diffstat (limited to 'chef')
-rw-r--r-- | chef/lib/chef/win32/api/error.rb | 44 | ||||
-rw-r--r-- | chef/lib/chef/win32/api/security.rb | 8 | ||||
-rw-r--r-- | chef/lib/chef/win32/security.rb | 14 | ||||
-rw-r--r-- | chef/lib/chef/win32/security/ace.rb | 29 | ||||
-rw-r--r-- | chef/lib/chef/win32/security/acl.rb | 30 | ||||
-rw-r--r-- | chef/lib/chef/win32/security/securable_object.rb | 50 | ||||
-rw-r--r-- | chef/lib/chef/win32/security/security_descriptor.rb | 26 | ||||
-rw-r--r-- | chef/lib/chef/win32/security/sid.rb | 12 | ||||
-rw-r--r-- | chef/lib/chef/win32_file_access_control.rb | 118 |
9 files changed, 261 insertions, 70 deletions
diff --git a/chef/lib/chef/win32/api/error.rb b/chef/lib/chef/win32/api/error.rb index 9ab961adf6..7553a18593 100644 --- a/chef/lib/chef/win32/api/error.rb +++ b/chef/lib/chef/win32/api/error.rb @@ -870,6 +870,50 @@ class Chef SEM_NOGPFAULTERRORBOX = 0x0002 SEM_NOOPENFILEERRORBOX = 0x8000 + def IS_ERROR(status) + status >> 31 == 1 + end + + def MAKE_HRESULT(sev, fac, code) + sev << 31 | fac << 16 | code + end + + def MAKE_SCODE(sev, fac, code) + sev << 31 | fac << 16 | code + end + + def HRESULT_CODE(hr) + hr & 0xFFFF + end + + def HRESULT_FACILITY(hr) + (hr >> 16) & 0x1fff + end + + def HRESULT_FROM_NT(x) + x | 0x10000000 # FACILITY_NT_BIT + end + + def HRESULT_FROM_WIN32(x) + if x <= 0 + x + else + (x & 0x0000FFFF) | (7 << 16) | 0x80000000 + end + end + + def HRESULT_SEVERITY(hr) + (hr >> 31) & 0x1 + end + + def FAILED(status) + status < 0 + end + + def SUCCEEDED(status) + status >= 0 + end + # Functions =begin DWORD WINAPI FormatMessage( diff --git a/chef/lib/chef/win32/api/security.rb b/chef/lib/chef/win32/api/security.rb index 7a46eddac1..9d571ecf9c 100644 --- a/chef/lib/chef/win32/api/security.rb +++ b/chef/lib/chef/win32/api/security.rb @@ -225,10 +225,10 @@ class Chef # The AceTypes this structure supports def self.supports?(ace_type) [ - Win::Security::ACCESS_ALLOWED_ACE_TYPE, - Win::Security::ACCESS_DENIED_ACE_TYPE, - Win::Security::SYSTEM_AUDIT_ACE_TYPE, - Win::Security::SYSTEM_ALARM_ACE_TYPE + ACCESS_ALLOWED_ACE_TYPE, + ACCESS_DENIED_ACE_TYPE, + SYSTEM_AUDIT_ACE_TYPE, + SYSTEM_ALARM_ACE_TYPE ].include?(ace_type) end end diff --git a/chef/lib/chef/win32/security.rb b/chef/lib/chef/win32/security.rb index 6362567836..da3587e432 100644 --- a/chef/lib/chef/win32/security.rb +++ b/chef/lib/chef/win32/security.rb @@ -26,6 +26,7 @@ class Chef class Security class << self + include Chef::Win32::API::Error include Chef::Win32::API::Security # @@ -104,7 +105,7 @@ class Chef def get_named_security_info(path, type = :SE_FILE_OBJECT, info = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION) security_descriptor = FFI::MemoryPointer.new :pointer hr = GetNamedSecurityInfoW(path.to_wstring, type, info, nil, nil, nil, nil, security_descriptor) - if hr != :S_OK + if FAILED(hr) Chef::Win32::Error.raise! end @@ -177,7 +178,7 @@ class Chef def initialize_acl(acl_size) acl = FFI::MemoryPointer.new acl_size - unless Win::Security::InitializeAcl(acl, acl_size, ACL_REVISION) + unless InitializeAcl(acl, acl_size, ACL_REVISION) Chef::Win32::Error.raise! end ACL.new(acl) @@ -213,7 +214,7 @@ class Chef system_name = system_name.to_wstring if system_name if LookupAccountNameW(system_name, name.to_wstring, nil, sid_size, nil, referenced_domain_name_size, nil) raise "Expected ERROR_INSUFFICIENT_BUFFER from LookupAccountName, and got no error!" - elsif get_last_error != ERROR_INSUFFICIENT_BUFFER + elsif Chef::Win32::Error.get_last_error != ERROR_INSUFFICIENT_BUFFER Chef::Win32::Error.raise! end @@ -232,9 +233,10 @@ class Chef # Figure out how big the buffer needs to be name_size = FFI::Buffer.new(:long).write_long(0) referenced_domain_name_size = FFI::Buffer.new(:long).write_long(0) - if LookupAccountSidW(system_name.to_wstring, sid, nil, name_size, nil, referenced_domain_name_size, nil) + system_name = system_name.to_wstring if system_name + if LookupAccountSidW(system_name, sid, nil, name_size, nil, referenced_domain_name_size, nil) raise "Expected ERROR_INSUFFICIENT_BUFFER from LookupAccountSid, and got no error!" - elsif get_last_error != ERROR_INSUFFICIENT_BUFFER + elsif Chef::Win32::Error::get_last_error != ERROR_INSUFFICIENT_BUFFER Chef::Win32::Error.raise! end @@ -312,7 +314,7 @@ class Chef end hr = SetNamedSecurityInfoW(path.to_wstring, type, security_information, owner, group, dacl, sacl) - if hr != :S_OK + if FAILED(hr) Chef::Win32::Error.raise! end end diff --git a/chef/lib/chef/win32/security/ace.rb b/chef/lib/chef/win32/security/ace.rb index 6800c06ead..326b77fc45 100644 --- a/chef/lib/chef/win32/security/ace.rb +++ b/chef/lib/chef/win32/security/ace.rb @@ -24,37 +24,38 @@ require 'ffi' class Chef module Win32 - module Security + class Security class ACE - include Chef::Win32::Memory - include Chef::Win32::Security - def initialize(pointer, owner = nil) - if ACE_WITH_MASK_AND_SID.supports?(pointer.read_uchar) - @struct = ACE_WITH_MASK_AND_SID.new pointer + if Chef::Win32::API::Security::ACE_WITH_MASK_AND_SID.supports?(pointer.read_uchar) + @struct = Chef::Win32::API::Security::ACE_WITH_MASK_AND_SID.new pointer else # TODO Support ALL the things - @struct = ACE_HEADER.new pointer + @struct = Chef::Win32::API::Security::ACE_HEADER.new pointer end # Keep a reference to the actual owner of this memory so we don't get freed @owner = owner end def self.size_with_sid(sid) - ACE_WITH_MASK_AND_SID.offset_of(:SidStart) + sid.size + Chef::Win32::API::Security::ACE_WITH_MASK_AND_SID.offset_of(:SidStart) + sid.size end def self.access_allowed(sid, access_mask, flags = 0) - create_ace_with_mask_and_sid(ACCESS_ALLOWED_ACE_TYPE, flags, access_mask, sid) + create_ace_with_mask_and_sid(Chef::Win32::API::Security::ACCESS_ALLOWED_ACE_TYPE, flags, access_mask, sid) end def self.access_denied(sid, access_mask, flags = 0) - create_ace_with_mask_and_sid(ACCESS_DENIED_ACE_TYPE, flags, access_mask, sid) + create_ace_with_mask_and_sid(Chef::Win32::API::Security::ACCESS_DENIED_ACE_TYPE, flags, access_mask, sid) end attr_reader :struct + def ==(other) + type == other.type && flags == other.flags && access_mask == other.access_mask && sid == other.sid + end + def flags struct[:AceFlags] end @@ -68,7 +69,7 @@ class Chef end def inherited? - (struct[:AceFlags] & INHERITED_ACE) != 0 + (struct[:AceFlags] & Chef::Win32::API::Security::INHERITED_ACE) != 0 end def mask @@ -90,7 +91,7 @@ class Chef def sid # The SID runs off the end of the structure, starting at :SidStart. # Use pointer arithmetic to get a pointer to that location. - SID.new(struct.pointer + struct.offset_of(:SidStart)) + Chef::Win32::Security::SID.new(struct.pointer + struct.offset_of(:SidStart)) end def type @@ -102,12 +103,12 @@ class Chef def self.create_ace_with_mask_and_sid(type, flags, mask, sid) size_needed = size_with_sid(sid) pointer = FFI::MemoryPointer.new size_needed - struct = ACE_WITH_MASK_AND_SID.new pointer + struct = Chef::Win32::API::Security::ACE_WITH_MASK_AND_SID.new pointer struct[:AceType] = type struct[:AceFlags] = flags struct[:AceSize] = size_needed struct[:Mask] = mask - memcpy(struct.pointer + struct.offset_of(:SidStart), sid.pointer, sid.size) + Chef::Win32::Memory.memcpy(struct.pointer + struct.offset_of(:SidStart), sid.pointer, sid.size) ACE.new(struct.pointer) end end diff --git a/chef/lib/chef/win32/security/acl.rb b/chef/lib/chef/win32/security/acl.rb index 66cf560804..0a5d2eafd7 100644 --- a/chef/lib/chef/win32/security/acl.rb +++ b/chef/lib/chef/win32/security/acl.rb @@ -22,14 +22,12 @@ require 'ffi' class Chef module Win32 - module Security + class Security class ACL include Enumerable - include Chef::Win32::Security - def initialize(pointer, owner = nil) - @struct = :ACLStruct.new pointer + @struct = Chef::Win32::API::Security::ACLStruct.new pointer # Keep a reference to the actual owner of this memory so that it isn't freed out from under us # TODO this could be avoided if we could mark a pointer's parent manually @owner = owner @@ -37,24 +35,32 @@ class Chef def self.create(aces) aces_size = aces.inject(0) { |sum,ace| sum + ace.size } - acl_size = align_dword(ACLStruct.size + aces_size) # What the heck is 94??? - acl = initialize_acl(acl_size) - aces.each { |ace| add_ace(acl, ace) } + acl_size = align_dword(Chef::Win32::API::Security::ACLStruct.size + aces_size) # What the heck is 94??? + acl = Chef::Win32::Security.initialize_acl(acl_size) + aces.each { |ace| Chef::Win32::Security.add_ace(acl, ace) } acl end attr_reader :struct + def ==(other) + return false if length != other.length + 0.upto(length-1) do |i| + return false if self[i] != other[i] + end + return true + end + def pointer struct.pointer end def [](index) - get_ace(self, index) + Chef::Win32::Security.get_ace(self, index) end def delete_at(index) - delete_ace(self, index) + Chef::Win32::Security.delete_ace(self, index) end def each @@ -70,15 +76,15 @@ class Chef end def push(*aces) - aces.each { |ace| add_ace(self, ace) } + aces.each { |ace| Chef::Win32::Security.add_ace(self, ace) } end def unshift(*aces) - aces.each { |ace| add_ace(self, ace, 0) } + aces.each { |ace| Chef::Win32::Security.add_ace(self, ace, 0) } end def valid? - is_valid_acl(self) + Chef::Win32::Security.is_valid_acl(self) end private diff --git a/chef/lib/chef/win32/security/securable_object.rb b/chef/lib/chef/win32/security/securable_object.rb index 5a57f0291a..06744a6c7b 100644 --- a/chef/lib/chef/win32/security/securable_object.rb +++ b/chef/lib/chef/win32/security/securable_object.rb @@ -22,49 +22,73 @@ require 'chef/win32/security/sid' class Chef module Win32 - module Security + class Security class SecurableObject - include Chef::Win32::Security - def initialize(path, type = :SE_FILE_OBJECT) @path = path @type = type end - attr_reader :pointer + attr_reader :path + attr_reader :type + + SecurityConst = Chef::Win32::API::Security + + # This method predicts what the rights mask would be on an object + # if you created an ACE with the given mask. Specifically, it looks for + # generic attributes like GENERIC_READ, and figures out what specific + # attributes will be set. This is important if you want to try to + # compare an existing ACE with one you want to create. + def predict_rights_mask(generic_mask) + mask = generic_mask + #mask |= Chef::Win32::API::Security::STANDARD_RIGHTS_READ if (mask | Chef::Win32::API::Security::GENERIC_READ) != 0 + #mask |= Chef::Win32::API::Security::STANDARD_RIGHTS_WRITE if (mask | Chef::Win32::API::Security::GENERIC_WRITE) != 0 + #mask |= Chef::Win32::API::Security::STANDARD_RIGHTS_EXECUTE if (mask | Chef::Win32::API::Security::GENERIC_EXECUTE) != 0 + #mask |= Chef::Win32::API::Security::STANDARD_RIGHTS_ALL if (mask | Chef::Win32::API::Security::GENERIC_ALL) != 0 + if type == :SE_FILE_OBJECT + mask |= Chef::Win32::API::Security::FILE_GENERIC_READ if (mask & Chef::Win32::API::Security::GENERIC_READ) != 0 + mask |= Chef::Win32::API::Security::FILE_GENERIC_WRITE if (mask & Chef::Win32::API::Security::GENERIC_WRITE) != 0 + mask |= Chef::Win32::API::Security::FILE_GENERIC_EXECUTE if (mask & Chef::Win32::API::Security::GENERIC_EXECUTE) != 0 + mask |= Chef::Win32::API::Security::FILE_ALL_ACCESS if (mask & Chef::Win32::API::Security::GENERIC_ALL) != 0 + else + raise "Unimplemented object type for predict_security_mask: #{type}" + end + mask &= ~(Chef::Win32::API::Security::GENERIC_READ | Chef::Win32::API::Security::GENERIC_WRITE | Chef::Win32::API::Security::GENERIC_EXECUTE | Chef::Win32::API::Security::GENERIC_ALL) + mask + end def security_descriptor(include_sacl = false) - security_information = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION - security_information |= SACL_SECURITY_INFORMATION if include_sacl - get_named_security_info(@path, @type, security_information) + security_information = Chef::Win32::API::Security::OWNER_SECURITY_INFORMATION | Chef::Win32::API::Security::GROUP_SECURITY_INFORMATION | Chef::Win32::API::Security::DACL_SECURITY_INFORMATION + security_information |= Chef::Win32::API::Security::SACL_SECURITY_INFORMATION if include_sacl + Security.get_named_security_info(path, type, security_information) end def dacl=(val) - set_named_security_info(@path, @type, :dacl => val) + Security.set_named_security_info(path, type, :dacl => val) end # You don't set dacl_inherits without also setting dacl, # because Windows gets angry and denies you access. So # if you want to do that, you may as well do both at once. def set_dacl(dacl, dacl_inherits) - set_named_security_info(@path, @type, :dacl => dacl, :dacl_inherits => dacl_inherits) + Security.set_named_security_info(path, type, :dacl => dacl, :dacl_inherits => dacl_inherits) end def group=(val) - set_named_security_info(@path, @type, :group => val) + Security.set_named_security_info(path, type, :group => val) end def owner=(val) - set_named_security_info(@path, @type, :owner => val) + Security.set_named_security_info(path, type, :owner => val) end def sacl=(val) - set_named_security_info(@path, @type, :sacl => val) + Security.set_named_security_info(path, type, :sacl => val) end def set_sacl(sacl, sacl_inherits) - set_named_security_info(@path, @type, :sacl => sacl, :sacl_inherits => sacl_inherits) + Security.set_named_security_info(path, type, :sacl => sacl, :sacl_inherits => sacl_inherits) end end end diff --git a/chef/lib/chef/win32/security/security_descriptor.rb b/chef/lib/chef/win32/security/security_descriptor.rb index c7ac9ce035..43a9cfe04c 100644 --- a/chef/lib/chef/win32/security/security_descriptor.rb +++ b/chef/lib/chef/win32/security/security_descriptor.rb @@ -22,11 +22,9 @@ require 'chef/win32/security/sid' class Chef module Win32 - module Security + class Security class SecurityDescriptor - include Chef::Win32::Security - def initialize(pointer) @pointer = pointer end @@ -38,54 +36,54 @@ class Chef end def control - control, version = get_security_descriptor_control(self) + control, version = Chef::Win32::Security.get_security_descriptor_control(self) control end def dacl raise "DACL not present" if !dacl_present? - present, acl, defaulted = get_security_descriptor_dacl(self) + present, acl, defaulted = Chef::Win32::Security.get_security_descriptor_dacl(self) acl end def dacl_inherits? - (control & SE_DACL_PROTECTED) == 0 + (control & Chef::Win32::API::Security::SE_DACL_PROTECTED) == 0 end def dacl_present? - (control & SE_DACL_PRESENT) != 0 + (control & Chef::Win32::API::Security::SE_DACL_PRESENT) != 0 end def group - result, defaulted = get_security_descriptor_group(self) + result, defaulted = Chef::Win32::Security.get_security_descriptor_group(self) result end def owner - result, defaulted = get_security_descriptor_owner(self) + result, defaulted = Chef::Win32::Security.get_security_descriptor_owner(self) result end def sacl raise "SACL not present" if !sacl_present? - present, acl, defaulted = get_security_descriptor_sacl(self) + present, acl, defaulted = Chef::Win32::Security.get_security_descriptor_sacl(self) acl end def sacl_inherits? - (control & SE_SACL_PROTECTED) == 0 + (control & Chef::Win32::API::Security::SE_SACL_PROTECTED) == 0 end def sacl_present? - (control & SE_SACL_PRESENT) != 0 + (control & Chef::Win32::API::Security::SE_SACL_PRESENT) != 0 end def self_relative? - (control & SE_SELF_RELATIVE) != 0 + (control & Chef::Win32::API::Security::SE_SELF_RELATIVE) != 0 end def valid? - is_valid_security_descriptor(self) + Chef::Win32::Security.is_valid_security_descriptor(self) end end end diff --git a/chef/lib/chef/win32/security/sid.rb b/chef/lib/chef/win32/security/sid.rb index b0ee6f4e41..8fe54d9460 100644 --- a/chef/lib/chef/win32/security/sid.rb +++ b/chef/lib/chef/win32/security/sid.rb @@ -20,11 +20,9 @@ require 'chef/win32/security' class Chef module Win32 - module Security + class Security class SID - include Chef::Win32::Security - def initialize(pointer, owner = nil) @pointer = pointer # Keep a reference to the actual owner of this memory so we don't get freed @@ -32,14 +30,14 @@ class Chef end def self.from_account(name) - domain, sid, use = lookup_account_name(name) + domain, sid, use = Chef::Win32::Security.lookup_account_name(name) sid end attr_reader :pointer def account - lookup_account_sid(self) + Chef::Win32::Security.lookup_account_sid(self) end def account_name @@ -48,11 +46,11 @@ class Chef end def size - get_length_sid(self) + Chef::Win32::Security.get_length_sid(self) end def valid? - is_valid_sid(self) + Chef::Win32::Security.is_valid_sid(self) end end end diff --git a/chef/lib/chef/win32_file_access_control.rb b/chef/lib/chef/win32_file_access_control.rb new file mode 100644 index 0000000000..4d74b0d6ee --- /dev/null +++ b/chef/lib/chef/win32_file_access_control.rb @@ -0,0 +1,118 @@ +# +# Author:: John Keiser (<jkeiser@opscode.com>) +# Copyright:: Copyright 2011 Opscode, 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. +# + +require 'chef/win32/security' + +class Chef + class Win32FileAccessControl + include Chef::Win32::API::Security + + def self.apply_security_policy(resource, securable_object) + provider = Win32FileAccessControl.new(resource, securable_object) + provider.apply_security_policy() + end + + def apply_security_policy + existing = securable_object.security_descriptor + # Apply owner and group + if existing.owner != target_owner + puts "Changing owner from #{existing.owner.account_name} to #{target_owner.account_name}" + securable_object.owner = target_owner + end + if existing.group != target_group + puts "Changing group from #{existing.group.account_name} to #{target_group.account_name}" + securable_object.group = target_group + end + + # Apply DACL and inherits + target_dacl = build_target_dacl + if existing.dacl_inherits? != target_inherits + puts "Changing DACL and inherits" + securable_object.set_dacl(target_dacl, target_inherits) + elsif !acls_equal(target_dacl, existing.dacl) + puts "Changing DACL" + securable_object.dacl = target_dacl + end + end + + private + + def initialize(resource, securable_object) + @resource = resource + @securable_object = securable_object + end + + attr_reader :resource + attr_reader :securable_object + + Security = Chef::Win32::Security + ACE = Security::ACE + + def acls_equal(target_acl, actual_acl) + return false if target_acl.length != actual_acl.length + 0.upto(target_acl.length - 1) do |i| + target_ace = a[i] + actual_ace = b[i] + return false if target_ace.sid != actual_ace.sid + return false if target_ace.flags != actual_ace.flags + return false if securable_object.predict_rights_mask(target_ace.mask) != actual_ace.mask + end + end + + def target_inherits + resource.inherits == nil ? true : resource.inherits + end + + def target_owner + get_sid(resource.owner) + end + + def target_group + get_sid(resource.group) + end + + def build_target_dacl + acls = [] + resource.rights.each_pair do |type, users| + case type + when :deny + users.each { |user| acls.push ACE.access_denied(get_sid(user), GENERIC_ALL) } + when :read + users.each { |user| acls.push(ACE.access_allowed(get_sid(user), GENERIC_READ | GENERIC_EXECUTE)) } + when :write + users.each { |user| acls.push(ACE.access_allowed(get_sid(user), GENERIC_WRITE | GENERIC_READ | GENERIC_EXECUTE)) } + when :full + users.each { |user| acls.push(ACE.access_allowed(get_sid(user), GENERIC_ALL)) } + else + raise "Unknown rights type #{type}" + end + end + Chef::Win32::Security::ACL.create(acls) + end + + def get_sid(value) + if value.kind_of?(String) + Chef::Win32::Security::SID.from_account(value) + elsif value.kind_of?(Chef::Win32::Security::SID) + value + else + raise "Must specify username, group or SID: #{value}" + end + end + end +end
\ No newline at end of file |