diff --git a/lib/puppet/util/windows/access_control_list.rb b/lib/puppet/util/windows/access_control_list.rb index 7d60b3292..14a924cd9 100644 --- a/lib/puppet/util/windows/access_control_list.rb +++ b/lib/puppet/util/windows/access_control_list.rb @@ -1,90 +1,106 @@ # Windows Access Control List # # Represents a list of access control entries (ACEs). # # @see http://msdn.microsoft.com/en-us/library/windows/desktop/aa374872(v=vs.85).aspx # @api private class Puppet::Util::Windows::AccessControlList include Enumerable ACCESS_ALLOWED_ACE_TYPE = 0x0 ACCESS_DENIED_ACE_TYPE = 0x1 # Construct an ACL. # # @param acl [Enumerable] A list of aces to copy from. def initialize(acl = nil) if acl @aces = acl.map(&:dup) else @aces = [] end end # Enumerate each ACE in the list. # # @yieldparam ace [Hash] the ace def each @aces.each {|ace| yield ace} end # Allow the +sid+ to access a resource with the specified access +mask+. # # @param sid [String] The SID that the ACE is granting access to # @param mask [int] The access mask granted to the SID # @param flags [int] The flags assigned to the ACE, e.g. +INHERIT_ONLY_ACE+ def allow(sid, mask, flags = 0) @aces << Puppet::Util::Windows::AccessControlEntry.new(sid, mask, flags, ACCESS_ALLOWED_ACE_TYPE) end # Deny the +sid+ access to a resource with the specified access +mask+. # # @param sid [String] The SID that the ACE is denying access to # @param mask [int] The access mask denied to the SID # @param flags [int] The flags assigned to the ACE, e.g. +INHERIT_ONLY_ACE+ def deny(sid, mask, flags = 0) @aces << Puppet::Util::Windows::AccessControlEntry.new(sid, mask, flags, ACCESS_DENIED_ACE_TYPE) end # Reassign all ACEs currently assigned to +old_sid+ to +new_sid+ instead. # If an ACE is inherited or is not assigned to +old_sid+, then it will # be copied as-is to the new ACL, preserving its order within the ACL. # # @param old_sid [String] The old SID, e.g. 'S-1-5-18' # @param new_sid [String] The new SID # @return [AccessControlList] The copied ACL. def reassign!(old_sid, new_sid) new_aces = [] prepend_needed = false + aces_to_prepend = [] @aces.each do |ace| new_ace = ace.dup - if ace.sid == old_sid && ! ace.inherited? - new_ace.sid = new_sid + if ace.sid == old_sid + if ace.inherited? + # create an explicit ACE granting or denying the + # new_sid the rights that the inherited ACE + # granted or denied the old_sid. We mask off all + # flags except those affecting inheritance of the + # ACE we're creating. + inherit_mask = Windows::Security::CONTAINER_INHERIT_ACE | + Windows::Security::OBJECT_INHERIT_ACE | + Windows::Security::INHERIT_ONLY_ACE + explicit_ace = Puppet::Util::Windows::AccessControlEntry.new(new_sid, ace.mask, ace.flags & inherit_mask, ace.type) + aces_to_prepend << explicit_ace + else + new_ace.sid = new_sid - prepend_needed = old_sid == Win32::Security::SID::LocalSystem + prepend_needed = old_sid == Win32::Security::SID::LocalSystem + end end - new_aces << new_ace end + @aces = [] + if prepend_needed mask = Windows::Security::STANDARD_RIGHTS_ALL | Windows::Security::SPECIFIC_RIGHTS_ALL ace = Puppet::Util::Windows::AccessControlEntry.new( Win32::Security::SID::LocalSystem, mask) - new_aces.unshift(ace) + @aces << ace end - @aces = new_aces + @aces.concat(aces_to_prepend) + @aces.concat(new_aces) end def inspect str = "" @aces.each do |ace| str << " #{ace.inspect}\n" end str end end diff --git a/spec/unit/util/windows/access_control_list_spec.rb b/spec/unit/util/windows/access_control_list_spec.rb index 8da44548f..66c917b29 100644 --- a/spec/unit/util/windows/access_control_list_spec.rb +++ b/spec/unit/util/windows/access_control_list_spec.rb @@ -1,111 +1,133 @@ #!/usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows' describe "Puppet::Util::Windows::AccessControlList", :if => Puppet.features.microsoft_windows? do let(:klass) { Puppet::Util::Windows::AccessControlList } let(:system_sid) { 'S-1-5-18' } let(:admins_sid) { 'S-1-5-544' } let(:none_sid) { 'S-1-0-0' } let(:system_ace) do Puppet::Util::Windows::AccessControlEntry.new(system_sid, 0x1) end let(:admins_ace) do Puppet::Util::Windows::AccessControlEntry.new(admins_sid, 0x2) end let(:none_ace) do Puppet::Util::Windows::AccessControlEntry.new(none_sid, 0x3) end it "constructs an empty list" do acl = klass.new acl.to_a.should be_empty end it "supports copy constructor" do aces = klass.new([system_ace]).to_a aces.to_a.should == [system_ace] end context "appending" do it "appends an allow ace" do acl = klass.new acl.allow(system_sid, 0x1, 0x2) acl.first.type.should == klass::ACCESS_ALLOWED_ACE_TYPE end it "appends a deny ace" do acl = klass.new acl.deny(system_sid, 0x1, 0x2) acl.first.type.should == klass::ACCESS_DENIED_ACE_TYPE end it "always appends, never overwrites an ACE" do acl = klass.new([system_ace]) acl.allow(admins_sid, admins_ace.mask, admins_ace.flags) aces = acl.to_a aces.size.should == 2 aces[0].should == system_ace aces[1].sid.should == admins_sid aces[1].mask.should == admins_ace.mask aces[1].flags.should == admins_ace.flags end end context "reassigning" do it "preserves the mask from the old sid when reassigning to the new sid" do dacl = klass.new([system_ace]) dacl.reassign!(system_ace.sid, admins_ace.sid) # we removed system, so ignore prepended ace ace = dacl.to_a[1] ace.sid.should == admins_sid ace.mask.should == system_ace.mask end it "matches multiple sids" do dacl = klass.new([system_ace, system_ace]) dacl.reassign!(system_ace.sid, admins_ace.sid) # we removed system, so ignore prepended ace aces = dacl.to_a aces.size.should == 3 aces.to_a[1,2].each do |ace| ace.sid.should == admins_ace.sid end end it "preserves aces for sids that don't match, in their original order" do dacl = klass.new([system_ace, admins_ace]) dacl.reassign!(system_sid, none_sid) aces = dacl.to_a aces[1].sid == admins_ace.sid end it "preserves inherited aces, even if the sids match" do flags = Puppet::Util::Windows::AccessControlEntry::INHERITED_ACE inherited_ace = Puppet::Util::Windows::AccessControlEntry.new(system_sid, 0x1, flags) dacl = klass.new([inherited_ace, system_ace]) dacl.reassign!(system_sid, none_sid) aces = dacl.to_a aces[0].sid.should == system_sid end + it "prepends an explicit ace for the new sid with the same mask and basic inheritance as the inherited ace" do + expected_flags = + Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | + Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE | + Puppet::Util::Windows::AccessControlEntry::INHERIT_ONLY_ACE + + flags = Puppet::Util::Windows::AccessControlEntry::INHERITED_ACE | expected_flags + + inherited_ace = Puppet::Util::Windows::AccessControlEntry.new(system_sid, 0x1, flags) + dacl = klass.new([inherited_ace]) + dacl.reassign!(system_sid, none_sid) + aces = dacl.to_a + + aces.size.should == 2 + aces[0].sid.should == none_sid + aces[0].should_not be_inherited + aces[0].flags.should == expected_flags + + aces[1].sid.should == system_sid + aces[1].should be_inherited + end + it "makes a copy of the ace prior to modifying it" do arr = [system_ace] acl = klass.new(arr) acl.reassign!(system_sid, none_sid) arr[0].sid.should == system_sid end end end