ansible.windows icon indicating copy to clipboard operation
ansible.windows copied to clipboard

win_acl: to have /reset capability of icacls

Open jborean93 opened this issue 4 years ago • 6 comments

From @SagarKodam on Oct 05, 2018 13:09

SUMMARY

win_acl can be used to set ACL to files and folders, but cannot be used to reset the permissions.

ISSUE TYPE
  • Feature Idea
COMPONENT NAME

win_acl

ADDITIONAL INFORMATION
- name: Reset current to default permission for temp folder
  win_acl:
    path: C:\temp
    state: reset

Copied from original issue: ansible/ansible#46540

jborean93 avatar Mar 11 '20 07:03 jborean93

From @dagwieers on Oct 05, 2018 13:20

needs_contributor

jborean93 avatar Mar 11 '20 07:03 jborean93

Hello, What exactly "reset" should mean?

  • reset as remove all explicit rights and allow inheritance if not allowed,
  • reset as revert to the state of install-time

placame avatar Sep 15 '20 08:09 placame

Based on the context of icacls it would be

Replaces ACLs with default inherited ACLs for all matching files.

So your first option.

jborean93 avatar Sep 15 '20 19:09 jborean93

Well this may be dangerous when used on system folders like %SystemRoot% or %ProgramFiles% and so on...

Should the solution use the Linux mindset: "You're the root (Admin), you should know what you're doing", or the Microsoft one: "Let me protect from yourself" ?

placame avatar Sep 16 '20 08:09 placame

@jborean93 I've implemented the reset functionality in my win_acl version. See additional improvements in my issue: #110 It still needs extensive testing, and the documentation part of the new state value. Let me know how to contribute. here is the win_acl.ps1 content:

#!powershell

# Copyright: (c) 2015, Phil Schwartz <[email protected]>
# Copyright: (c) 2015, Trond Hindenes
# Copyright: (c) 2015, Hans-Joachim Kliemeck <[email protected]>
# Revorked the logic to more exact handle of rights and proper return values, 
# Added state='reset' option to reset the ACL to the inherited ACEs only.
# https://github.com/ansible-collections/ansible.windows/issues/18
# Added support of environment variables for Path
# https://github.com/ansible/ansible/issues/68597
# Laszlo Papp <[email protected]>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Module Ansible.ModuleUtils.PrivilegeUtil
#Requires -Module Ansible.ModuleUtils.SID

$ErrorActionPreference = "Stop"

# win_acl module (File/Resources Permission Additions/Removal)

#Functions
function Get-UserSID {
    param(
        [String]$AccountName
    )

    $userSID = $null
    $searchAppPools = $false

    if ($AccountName.Split("\").Count -gt 1) {
        if ($AccountName.Split("\")[0] -eq "IIS APPPOOL") {
            $searchAppPools = $true
            $AccountName = $AccountName.Split("\")[1]
        }
    }

    if ($searchAppPools) {
        Import-Module -Name WebAdministration
        $testIISPath = Test-Path -LiteralPath "IIS:"
        if ($testIISPath) {
            $appPoolObj = Get-ItemProperty -LiteralPath "IIS:\AppPools\$AccountName"
            $userSID = $appPoolObj.applicationPoolSid
        }
    }
    else {
        $userSID = Convert-ToSID -account_name $AccountName
    }

    return $userSID
}

$params = Parse-Args $args

Function SetPrivilegeTokens() {
    # Set privilege tokens only if admin.
    # Admins would have these privs or be able to set these privs in the UI Anyway

    $adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator
    $myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
    $myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)


    if ($myWindowsPrincipal.IsInRole($adminRole)) {
        # Need to adjust token privs when executing Set-ACL in certain cases.
        # e.g. d:\testdir is owned by group in which current user is not a member and no perms are inherited from d:\
        # This also sets us up for setting the owner as a feature.
        # See the following for details of each privilege
        # https://msdn.microsoft.com/en-us/library/windows/desktop/bb530716(v=vs.85).aspx
        $privileges = @(
            "SeRestorePrivilege",  # Grants all write access control to any file, regardless of ACL.
            "SeBackupPrivilege",  # Grants all read access control to any file, regardless of ACL.
            "SeTakeOwnershipPrivilege"  # Grants ability to take owernship of an object w/out being granted discretionary access
        )
        foreach ($privilege in $privileges) {
            $state = Get-AnsiblePrivilege -Name $privilege
            if ($state -eq $false) {
                Set-AnsiblePrivilege -Name $privilege -Value $true
            }
        }
    }
}


$result = @{
    changed = $false
	msg = ""
}

$path = (New-Object -ComObject Wscript.Shell).ExpandEnvironmentStrings(Get-AnsibleParam -obj $params -name "path" -type "str" -failifempty $true)
# We mount the HKCR, HKU, and HKCC registry hives so PS can access them.
# Network paths have no qualifiers so we use -EA SilentlyContinue to ignore that
$path_qualifier = Split-Path -Path $path -Qualifier -ErrorAction SilentlyContinue
if ($path_qualifier -eq "HKCR:" -and (-not (Test-Path -LiteralPath HKCR:\))) {
    New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT > $null
}
if ($path_qualifier -eq "HKU:" -and (-not (Test-Path -LiteralPath HKU:\))) {
    New-PSDrive -Name HKU -PSProvider Registry -Root HKEY_USERS > $null
}
if ($path_qualifier -eq "HKCC:" -and (-not (Test-Path -LiteralPath HKCC:\))) {
    New-PSDrive -Name HKCC -PSProvider Registry -Root HKEY_CURRENT_CONFIG > $null
}

If (-Not (Test-Path -LiteralPath $path)) {
    Fail-Json -obj $result -message "$path does not exist on the host"
}
$path_item = Get-Item -LiteralPath $path -Force
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "absent","present","reset"
if($state -eq 'reset'){
   SetPrivilegeTokens
   if(!(Get-Item $path).PSParentPath){
      Fail-Json -obj $result -message "$path is a root folder! Cannot reset ACL!"
	  }
   try
   {
      $objACL = Get-ACL -LiteralPath $path
      if($objACL.AreAccessRulesProtected){
         $result.changed=$true
         $objacl.SetAccessRuleProtection($false,$false)
		 If ($path_item.PSProvider.Name -eq "Registry") {
             Set-ACL -LiteralPath $path -AclObject $objACL
         } else {
             (Get-Item -LiteralPath $path).SetAccessControl($objACL)
         }
         $objACL = Get-ACL -LiteralPath $path
	     }
      $changed=$false
      $objACL.Access|?{!$_.isinherited}|%{
         $result.changed=$true
	     $changed=$true
	     [void]$objACL.RemoveAccessRule($_)
         }
      if($changed){		 
	     If ($path_item.PSProvider.Name -eq "Registry") {
                Set-ACL -LiteralPath $path -AclObject $objACL
            } else {
                (Get-Item -LiteralPath $path).SetAccessControl($objACL)
            }
         }
	  Exit-Json -obj $result
   } catch {
      Fail-Json -obj $result -message "an exception occurred when resetting the ACL - $($_.Exception.Message)"
   }
}

$user = Get-AnsibleParam -obj $params -name "user" -type "str" -failifempty $true
$rights = Get-AnsibleParam -obj $params -name "rights" -type "str" -failifempty $true

$type = Get-AnsibleParam -obj $params -name "type" -type "str" -failifempty $true -validateset "allow","deny"

$inherit = Get-AnsibleParam -obj $params -name "inherit" -type "str"
$propagation = Get-AnsibleParam -obj $params -name "propagation" -type "str" -default "None" -validateset "InheritOnly","None","NoPropagateInherit"


# Reset:

# Test that the user/group is resolvable on the local machine
$sid = Get-UserSID -AccountName $user
if (!$sid) {
    Fail-Json -obj $result -message "$user is not a valid user or group on the host machine or domain"
}

If (Test-Path -LiteralPath $path -PathType Leaf) {
    $inherit = "None"
}
ElseIf ($null -eq $inherit) {
    $inherit = "ContainerInherit, ObjectInherit"
}

# Bug in Set-Acl, Get-Acl where -LiteralPath only works for the Registry provider if the location is in that root
# qualifier. We also don't have a qualifier for a network path so only change if not null
if ($null -ne $path_qualifier) {
    Push-Location -LiteralPath $path_qualifier
}
$myMessage=""
Try {
    SetPrivilegeTokens
	$PathType='FileSystem'
	If ($path_item.PSProvider.Name -eq "Registry") {$PathType='Registry'}

    $InheritanceFlag = [System.Security.AccessControl.InheritanceFlags]$inherit
    $PropagationFlag = [System.Security.AccessControl.PropagationFlags]$propagation

    If ($type -eq "deny") {
        $objType =[System.Security.AccessControl.AccessControlType]::Deny
    }
    Else {
        $objType =[System.Security.AccessControl.AccessControlType]::Allow
    }

    $objUser = New-Object System.Security.Principal.SecurityIdentifier($sid)
    $objACE = New-Object System.Security.AccessControl."$($PathType)AccessRule" ($objUser, $Rights, $InheritanceFlag, $PropagationFlag, $objType)
    $objACL = Get-ACL -LiteralPath $path
	$objOldRules=$objACL.AccessToString
    $objRights=$null
    if($PathType -eq 'Registry'){
       $objRights=[System.Security.AccessControl.RegistryRights]$rights
    }else{
       $objRights=[System.Security.AccessControl.FileSystemRights]$rights
    }
    
	Try {
		$ar=$null
        If ($state -eq "present"){
			$objACL.AddAccessRule($objACE)
		} else {
		   [void]$objACL.RemoveAccessRule($objACE)
           $objACL.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier])|?{
             ($_.IdentityReference.Value -eq $sid) -and 
             (($_.PropagationFlags -band [System.Security.AccessControl.PropagationFlags]'InheritOnly') -ne 
			     [System.Security.AccessControl.PropagationFlags]'InheritOnly') -and
			 ($_.AccessControlType -eq $objType) 
             }|%{
             if(!$ar){$ar=$_."$($PathType)Rights"}else{$ar=$ar -bor $_."$($PathType)Rights"}
             }
		}
		$myMessage="Actual rights: "
		$objACL.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier])|?{$_.IdentityReference.Value -eq $sid}|%{
		   $myMessage += "$($_.AccessControlType): $($_."$($PathType)Rights"): $(if($_.IsInherited){'Inherited,'}) $($_.InheritanceFlags); "
		   }
		if($objOldRules -eq $objACL.AccessToString){
		   $result.changed = $false
		   $result.msg=$myMessage
		} else {
		   If ($path_item.PSProvider.Name -eq "Registry") {
               Set-ACL -LiteralPath $path -AclObject $objACL
           } else {
               (Get-Item -LiteralPath $path).SetAccessControl($objACL)
           }
           $result.changed = $true
		   $myMessage=$myMessage.Replace('Actual rights: ','New rights: ')
		   $result.msg=$myMessage
		}
		if(($ar) -and (([int]($ar -band $objRights).value__) -ne 0 ) -and ($state -ne "present")){
		   $result.stderr="$user still has $ar rights!"
		   $result.msg=$myMessage
		   Fail-Json -obj $result -message "$user still has $ar rights! $myMessage"
		}
    }
    Catch {
        Fail-Json -obj $result -message "an exception occurred when adding the specified rule - $($_.Exception.Message)"
    }
}
Catch {
    Fail-Json -obj $result -message "an error occurred when attempting to $state $rights permission(s) on $path for $user - $($_.Exception.Message)"
}
Finally {
    # Make sure we revert the location stack to the original path just for cleanups sake
    if ($null -ne $path_qualifier) {
        Pop-Location
    }
}
Exit-Json -obj $result

placame avatar Oct 13 '20 13:10 placame

@placame the short answer is that you fork this repository in GitHub, make your changes, and submit a pull request.

For the longer answer and more details, see https://github.com/ansible-collections/ansible.windows#contributing-to-this-collection

briantist avatar Nov 13 '20 15:11 briantist