From 9259f31fee03e75a99a23b9fde5106b43f0cc437 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Tue, 31 Jul 2018 07:48:54 +1000 Subject: [PATCH] Add Ansible.ModuleUtils.PrivilegeUtil and converted code to use it (#43179) * Add Ansible.ModuleUtils.PrivilegeUtil and converted code to use it * Changed namespace and class to be a better standard and fixed some typos * Changes from review * changes to avoid out of bound mem of server 2008 * changes to detect failure when setting a privileged not allowed --- changelogs/fragments/win_privilege_util.yaml | 2 + .../Ansible.ModuleUtils.CommandUtil.psm1 | 3 - .../Ansible.ModuleUtils.LinkUtil.psm1 | 86 +-- .../Ansible.ModuleUtils.PrivilegeUtil.psm1 | 499 ++++++++++++++++++ lib/ansible/modules/windows/win_acl.ps1 | 110 +--- lib/ansible/modules/windows/win_file.ps1 | 3 - lib/ansible/modules/windows/win_find.ps1 | 3 - lib/ansible/modules/windows/win_get_url.ps1 | 3 - lib/ansible/modules/windows/win_regedit.ps1 | 104 +--- lib/ansible/modules/windows/win_region.ps1 | 3 - .../modules/windows/win_scheduled_task.ps1 | 3 - .../windows/win_scheduled_task_stat.ps1 | 3 - lib/ansible/modules/windows/win_user.ps1 | 3 - .../modules/windows/win_user_right.ps1 | 3 - lib/ansible/modules/windows/win_whoami.ps1 | 3 - lib/ansible/plugins/shell/powershell.py | 3 - .../library/privilege_util_test.ps1 | 164 ++++++ .../targets/win_module_utils/tasks/main.yml | 8 + 18 files changed, 708 insertions(+), 298 deletions(-) create mode 100644 changelogs/fragments/win_privilege_util.yaml create mode 100644 lib/ansible/module_utils/powershell/Ansible.ModuleUtils.PrivilegeUtil.psm1 create mode 100644 test/integration/targets/win_module_utils/library/privilege_util_test.ps1 diff --git a/changelogs/fragments/win_privilege_util.yaml b/changelogs/fragments/win_privilege_util.yaml new file mode 100644 index 0000000000..6df471ea31 --- /dev/null +++ b/changelogs/fragments/win_privilege_util.yaml @@ -0,0 +1,2 @@ +minor_changes: +- Added PrivilegeUtil PowerShell module util to easily control Windows Privileges in a process diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 index 88e0cd095d..e952a7ab21 100644 --- a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 +++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 @@ -338,7 +338,6 @@ Function Load-CommandUtils { # FUTURE: find a better way to get the _ansible_remote_tmp variable $original_tmp = $env:TMP - $original_temp = $env:TEMP $remote_tmp = $original_tmp $module_params = Get-Variable -Name complex_args -ErrorAction SilentlyContinue @@ -350,10 +349,8 @@ Function Load-CommandUtils { } $env:TMP = $remote_tmp - $env:TEMP = $remote_tmp Add-Type -TypeDefinition $process_util $env:TMP = $original_tmp - $env:TEMP = $original_temp } Function Get-ExecutablePath($executable, $directory) { diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.LinkUtil.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.LinkUtil.psm1 index fcef6733a4..c18adc6496 100644 --- a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.LinkUtil.psm1 +++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.LinkUtil.psm1 @@ -1,6 +1,8 @@ # Copyright (c) 2017 Ansible Project # Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) +#Requires -Module Ansible.ModuleUtils.PrivilegeUtil + Function Load-LinkUtils() { $link_util = @' using Microsoft.Win32.SafeHandles; @@ -44,21 +46,6 @@ namespace Ansible public string[] HardTargets { get; internal set; } } - [StructLayout(LayoutKind.Sequential)] - public struct LUID - { - public UInt32 LowPart; - public Int32 HighPart; - } - - [StructLayout(LayoutKind.Sequential)] - public struct TOKEN_PRIVILEGES - { - public UInt32 PrivilegeCount; - public LUID Luid; - public UInt32 Attributes; - } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct REPARSE_DATA_BUFFER { @@ -78,10 +65,6 @@ namespace Ansible { public const int MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 1024 * 16; - private const int TOKEN_ADJUST_PRIVILEGES = 0x00000020; - private const int TOKEN_QUERY = 0x00000008; - private const int SE_PRIVILEGE_ENABLED = 0x00000002; - private const UInt32 FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; private const UInt32 FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000; @@ -101,34 +84,6 @@ namespace Ansible private const UInt32 SYMBOLIC_LINK_FLAG_FILE = 0x00000000; private const UInt32 SYMBOLIC_LINK_FLAG_DIRECTORY = 0x00000001; - [DllImport("kernel32.dll")] - private static extern IntPtr GetCurrentProcess(); - - [DllImport("kernel32.dll")] - private static extern bool CloseHandle( - IntPtr hObject); - - [DllImport("advapi32.dll")] - private static extern bool OpenProcessToken( - IntPtr ProcessHandle, - UInt32 DesiredAccess, - out IntPtr TokenHandle); - - [DllImport("advapi32.dll", CharSet = CharSet.Auto)] - private static extern bool LookupPrivilegeValue( - string lpSystemName, - string lpName, - [MarshalAs(UnmanagedType.Struct)] out LUID lpLuid); - - [DllImport("advapi32.dll")] - private static extern bool AdjustTokenPrivileges( - IntPtr TokenHandle, - [MarshalAs(UnmanagedType.Bool)] bool DisableAllPrivileges, - ref TOKEN_PRIVILEGES NewState, - UInt32 BufferLength, - IntPtr PreviousState, - IntPtr ReturnLength); - [DllImport("kernel32.dll", CharSet = CharSet.Auto)] private static extern SafeFileHandle CreateFile( string lpFileName, @@ -206,33 +161,6 @@ namespace Ansible string lpExistingFileName, IntPtr lpSecurityAttributes); - public static void EnablePrivilege(string privilege) - { - TOKEN_PRIVILEGES tkpPrivileges; - - IntPtr hToken; - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out hToken)) - throw new LinkUtilWin32Exception("OpenProcessToken failed"); - - try - { - LUID luid; - if (!LookupPrivilegeValue(null, privilege, out luid)) - throw new LinkUtilWin32Exception(String.Format("LookupPrivilegeValue({0}) failed", privilege)); - - tkpPrivileges.PrivilegeCount = 1; - tkpPrivileges.Luid = luid; - tkpPrivileges.Attributes = SE_PRIVILEGE_ENABLED; - - if (!AdjustTokenPrivileges(hToken, false, ref tkpPrivileges, 0, IntPtr.Zero, IntPtr.Zero)) - throw new LinkUtilWin32Exception(String.Format("AdjustTokenPrivileges({0}) failed", privilege)); - } - finally - { - CloseHandle(hToken); - } - } - public static LinkInfo GetLinkInfo(string linkPath) { FileAttributes attr = File.GetAttributes(linkPath); @@ -466,7 +394,6 @@ namespace Ansible # FUTURE: find a better way to get the _ansible_remote_tmp variable $original_tmp = $env:TMP - $original_temp = $env:TEMP $remote_tmp = $original_tmp $module_params = Get-Variable -Name complex_args -ErrorAction SilentlyContinue @@ -478,12 +405,15 @@ namespace Ansible } $env:TMP = $remote_tmp - $env:TEMP = $remote_tmp Add-Type -TypeDefinition $link_util $env:TMP = $original_tmp - $env:TEMP = $original_temp - [Ansible.LinkUtil]::EnablePrivilege("SeBackupPrivilege") + Import-PrivilegeUtil + # enable the SeBackupPrivilege if it is disabled + $state = Get-AnsiblePrivilege -Name SeBackupPrivilege + if ($state -eq $false) { + Set-AnsiblePrivilege -Name SeBackupPrivilege -Value $true + } } Function Get-Link($link_path) { diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.PrivilegeUtil.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.PrivilegeUtil.psm1 new file mode 100644 index 0000000000..97eb44d4bf --- /dev/null +++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.PrivilegeUtil.psm1 @@ -0,0 +1,499 @@ +# Copyright (c) 2018 Ansible Project +# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) + +# store in separate variables to make it easier for other module_utils to +# share this code in their own c# code +$ansible_privilege_util_namespaces = @( + "Microsoft.Win32.SafeHandles", + "System", + "System.Collections.Generic", + "System.Linq", + "System.Runtime.InteropServices", + "System.Security.Principal", + "System.Text" +) + +$ansible_privilege_util_code = @' +namespace Ansible.PrivilegeUtil +{ + [Flags] + public enum PrivilegeAttributes : uint + { + Disabled = 0x00000000, + EnabledByDefault = 0x00000001, + Enabled = 0x00000002, + Removed = 0x00000004, + UsedForAccess = 0x80000000, + } + + internal class NativeHelpers + { + [StructLayout(LayoutKind.Sequential)] + internal struct LUID + { + public UInt32 LowPart; + public Int32 HighPart; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct LUID_AND_ATTRIBUTES + { + public LUID Luid; + public PrivilegeAttributes Attributes; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct TOKEN_PRIVILEGES + { + public UInt32 PrivilegeCount; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] + public LUID_AND_ATTRIBUTES[] Privileges; + } + } + + internal class NativeMethods + { + [DllImport("advapi32.dll", SetLastError = true)] + internal static extern bool AdjustTokenPrivileges( + IntPtr TokenHandle, + [MarshalAs(UnmanagedType.Bool)] bool DisableAllPrivileges, + IntPtr NewState, + UInt32 BufferLength, + IntPtr PreviousState, + out UInt32 ReturnLength); + + [DllImport("kernel32.dll")] + internal static extern bool CloseHandle( + IntPtr hObject); + + [DllImport("kernel32")] + internal static extern SafeWaitHandle GetCurrentProcess(); + + [DllImport("advapi32.dll", SetLastError = true)] + internal static extern bool GetTokenInformation( + IntPtr TokenHandle, + UInt32 TokenInformationClass, + IntPtr TokenInformation, + UInt32 TokenInformationLength, + out UInt32 ReturnLength); + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + internal static extern bool LookupPrivilegeName( + string lpSystemName, + ref NativeHelpers.LUID lpLuid, + StringBuilder lpName, + ref UInt32 cchName); + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + internal static extern bool LookupPrivilegeValue( + string lpSystemName, + string lpName, + out NativeHelpers.LUID lpLuid); + + [DllImport("advapi32.dll", SetLastError = true)] + internal static extern bool OpenProcessToken( + SafeHandle ProcessHandle, + TokenAccessLevels DesiredAccess, + out IntPtr TokenHandle); + } + + public class Win32Exception : System.ComponentModel.Win32Exception + { + private string _msg; + public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { } + public Win32Exception(int errorCode, string message) : base(errorCode) + { + _msg = String.Format("{0} ({1}, Win32ErrorCode {2})", message, base.Message, errorCode); + } + public override string Message { get { return _msg; } } + public static explicit operator Win32Exception(string message) { return new Win32Exception(message); } + } + + public class Privileges + { + private static readonly UInt32 TOKEN_PRIVILEGES = 3; + + + public static bool CheckPrivilegeName(string name) + { + NativeHelpers.LUID luid; + if (!NativeMethods.LookupPrivilegeValue(null, name, out luid)) + { + int errCode = Marshal.GetLastWin32Error(); + if (errCode != 1313) // ERROR_NO_SUCH_PRIVILEGE + throw new Win32Exception(errCode, String.Format("LookupPrivilegeValue({0}) failed", name)); + return false; + } + else + { + return true; + } + } + + public static Dictionary DisablePrivilege(SafeHandle token, string privilege) + { + return SetTokenPrivileges(token, new Dictionary() { { privilege, false } }); + } + + public static Dictionary DisableAllPrivileges(SafeHandle token) + { + return AdjustTokenPrivileges(token, null); + } + + public static Dictionary EnablePrivilege(SafeHandle token, string privilege) + { + return SetTokenPrivileges(token, new Dictionary() { { privilege, true } }); + } + + public static Dictionary GetAllPrivilegeInfo(SafeHandle token) + { + IntPtr hToken = IntPtr.Zero; + if (!NativeMethods.OpenProcessToken(token, TokenAccessLevels.Query, out hToken)) + throw new Win32Exception("OpenProcessToken() failed"); + + Dictionary info = new Dictionary(); + try + { + UInt32 tokenLength = 0; + NativeMethods.GetTokenInformation(hToken, TOKEN_PRIVILEGES, IntPtr.Zero, 0, out tokenLength); + + NativeHelpers.LUID_AND_ATTRIBUTES[] privileges; + IntPtr privilegesPtr = Marshal.AllocHGlobal((int)tokenLength); + try + { + if (!NativeMethods.GetTokenInformation(hToken, TOKEN_PRIVILEGES, privilegesPtr, tokenLength, out tokenLength)) + throw new Win32Exception("GetTokenInformation() for TOKEN_PRIVILEGES failed"); + + NativeHelpers.TOKEN_PRIVILEGES privilegeInfo = (NativeHelpers.TOKEN_PRIVILEGES)Marshal.PtrToStructure(privilegesPtr, typeof(NativeHelpers.TOKEN_PRIVILEGES)); + privileges = new NativeHelpers.LUID_AND_ATTRIBUTES[privilegeInfo.PrivilegeCount]; + PtrToStructureArray(privileges, privilegesPtr.ToInt64() + Marshal.SizeOf(privilegeInfo.PrivilegeCount)); + } + finally + { + Marshal.FreeHGlobal(privilegesPtr); + } + + info = privileges.ToDictionary(p => GetPrivilegeName(p.Luid), p => p.Attributes); + } + finally + { + NativeMethods.CloseHandle(hToken); + } + return info; + } + + public static SafeWaitHandle GetCurrentProcess() + { + return NativeMethods.GetCurrentProcess(); + } + + public static void RemovePrivilege(SafeHandle token, string privilege) + { + SetTokenPrivileges(token, new Dictionary() { { privilege, null } }); + } + + public static Dictionary SetTokenPrivileges(SafeHandle token, Dictionary state) + { + NativeHelpers.LUID_AND_ATTRIBUTES[] privilegeAttr = new NativeHelpers.LUID_AND_ATTRIBUTES[state.Count]; + int i = 0; + + foreach (KeyValuePair entry in state) + { + NativeHelpers.LUID luid; + if (!NativeMethods.LookupPrivilegeValue(null, entry.Key, out luid)) + throw new Win32Exception(String.Format("LookupPrivilegeValue({0}) failed", entry.Key)); + + PrivilegeAttributes attributes; + switch (entry.Value) + { + case true: + attributes = PrivilegeAttributes.Enabled; + break; + case false: + attributes = PrivilegeAttributes.Disabled; + break; + default: + attributes = PrivilegeAttributes.Removed; + break; + } + + privilegeAttr[i].Luid = luid; + privilegeAttr[i].Attributes = attributes; + i++; + } + + return AdjustTokenPrivileges(token, privilegeAttr); + } + + private static Dictionary AdjustTokenPrivileges(SafeHandle token, NativeHelpers.LUID_AND_ATTRIBUTES[] newState) + { + bool disableAllPrivileges; + IntPtr newStatePtr; + NativeHelpers.LUID_AND_ATTRIBUTES[] oldStatePrivileges; + UInt32 returnLength; + + if (newState == null) + { + disableAllPrivileges = true; + newStatePtr = IntPtr.Zero; + } + else + { + disableAllPrivileges = false; + + // Need to manually marshal the bytes requires for newState as the constant size + // of LUID_AND_ATTRIBUTES is set to 1 and can't be overridden at runtime, TOKEN_PRIVILEGES + // always contains at least 1 entry so we need to calculate the extra size if there are + // nore than 1 LUID_AND_ATTRIBUTES entry + int tokenPrivilegesSize = Marshal.SizeOf(typeof(NativeHelpers.TOKEN_PRIVILEGES)); + int luidAttrSize = 0; + if (newState.Length > 1) + luidAttrSize = Marshal.SizeOf(typeof(NativeHelpers.LUID_AND_ATTRIBUTES)) * (newState.Length - 1); + int totalSize = tokenPrivilegesSize + luidAttrSize; + byte[] newStateBytes = new byte[totalSize]; + + // get the first entry that includes the struct details + NativeHelpers.TOKEN_PRIVILEGES tokenPrivileges = new NativeHelpers.TOKEN_PRIVILEGES() + { + PrivilegeCount = (UInt32)newState.Length, + Privileges = new NativeHelpers.LUID_AND_ATTRIBUTES[1], + }; + if (newState.Length > 0) + tokenPrivileges.Privileges[0] = newState[0]; + int offset = StructureToBytes(tokenPrivileges, newStateBytes, 0); + + // copy the remaining LUID_AND_ATTRIBUTES (if any) + for (int i = 1; i < newState.Length; i++) + offset += StructureToBytes(newState[i], newStateBytes, offset); + + // finally create the pointer to the byte array we just created + newStatePtr = Marshal.AllocHGlobal(newStateBytes.Length); + Marshal.Copy(newStateBytes, 0, newStatePtr, newStateBytes.Length); + } + + try + { + IntPtr hToken = IntPtr.Zero; + if (!NativeMethods.OpenProcessToken(token, TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges, out hToken)) + throw new Win32Exception("OpenProcessToken() failed with Query and AdjustPrivileges"); + try + { + IntPtr oldStatePtr = Marshal.AllocHGlobal(0); + if (!NativeMethods.AdjustTokenPrivileges(hToken, disableAllPrivileges, newStatePtr, 0, oldStatePtr, out returnLength)) + { + int errCode = Marshal.GetLastWin32Error(); + if (errCode != 122) // ERROR_INSUFFICIENT_BUFFER + throw new Win32Exception(errCode, "AdjustTokenPrivileges() failed to get old state size"); + } + + // resize the oldStatePtr based on the length returned from Windows + Marshal.FreeHGlobal(oldStatePtr); + oldStatePtr = Marshal.AllocHGlobal((int)returnLength); + try + { + bool res = NativeMethods.AdjustTokenPrivileges(hToken, disableAllPrivileges, newStatePtr, returnLength, oldStatePtr, out returnLength); + int errCode = Marshal.GetLastWin32Error(); + + // even when res == true, ERROR_NOT_ALL_ASSIGNED may be set as the last error code + if (!res || errCode != 0) + throw new Win32Exception(errCode, "AdjustTokenPrivileges() failed"); + + // Marshal the oldStatePtr to the struct + NativeHelpers.TOKEN_PRIVILEGES oldState = (NativeHelpers.TOKEN_PRIVILEGES)Marshal.PtrToStructure(oldStatePtr, typeof(NativeHelpers.TOKEN_PRIVILEGES)); + oldStatePrivileges = new NativeHelpers.LUID_AND_ATTRIBUTES[oldState.PrivilegeCount]; + PtrToStructureArray(oldStatePrivileges, oldStatePtr.ToInt64() + Marshal.SizeOf(oldState.PrivilegeCount)); + } + finally + { + Marshal.FreeHGlobal(oldStatePtr); + } + } + finally + { + NativeMethods.CloseHandle(hToken); + } + } + finally + { + if (newStatePtr != IntPtr.Zero) + Marshal.FreeHGlobal(newStatePtr); + } + + return oldStatePrivileges.ToDictionary(p => GetPrivilegeName(p.Luid), p => (bool?)p.Attributes.HasFlag(PrivilegeAttributes.Enabled)); + } + + private static string GetPrivilegeName(NativeHelpers.LUID luid) + { + UInt32 nameLen = 0; + NativeMethods.LookupPrivilegeName(null, ref luid, null, ref nameLen); + + StringBuilder name = new StringBuilder((int)(nameLen + 1)); + if (!NativeMethods.LookupPrivilegeName(null, ref luid, name, ref nameLen)) + throw new Win32Exception("LookupPrivilegeName() failed"); + + return name.ToString(); + } + + private static void PtrToStructureArray(T[] array, Int64 pointerAddress) + { + Int64 pointerOffset = pointerAddress; + for (int i = 0; i < array.Length; i++, pointerOffset += Marshal.SizeOf(typeof(T))) + array[i] = (T)Marshal.PtrToStructure(new IntPtr(pointerOffset), typeof(T)); + } + + private static int StructureToBytes(T structure, byte[] array, int offset) + { + int size = Marshal.SizeOf(structure); + IntPtr structPtr = Marshal.AllocHGlobal(size); + try + { + Marshal.StructureToPtr(structure, structPtr, false); + Marshal.Copy(structPtr, array, offset, size); + } + finally + { + Marshal.FreeHGlobal(structPtr); + } + + return size; + } + } +} +'@ + +Function Import-PrivilegeUtil { + <# + .SYNOPSIS + Compiles the C# code that can be used to manage Windows privileges from an + Ansible module. Once this function is called, the following PowerShell + cmdlets can be used; + + Get-AnsiblePrivilege + Set-AnsiblePrivilege + + The above cmdlets give the ability to manage permissions on the current + process token but the underlying .NET classes are also exposed for greater + control. The following functions can be used by calling the .NET class + + [Ansible.PrivilegeUtil.Privileges]::CheckPrivilegeName($name) + [Ansible.PrivilegeUtil.Privileges]::DisablePrivilege($process, $name) + [Ansible.PrivilegeUtil.Privileges]::DisableAllPrivileges($process) + [Ansible.PrivilegeUtil.Privileges]::EnablePrivilege($process, $name) + [Ansible.PrivilegeUtil.Privileges]::GetAllPrivilegeInfo($process) + [Ansible.PrivilegeUtil.Privileges]::RemovePrivilege($process, $name) + [Ansible.PrivilegeUtil.Privileges]::SetTokenPrivileges($process, $new_state) + + Here is a brief explanation of each type of arg + $process = The process handle to manipulate, use '[Ansible.PrivilegeUtils.Privileges]::GetCurrentProcess()' to get the current process handle + $name = The name of the privilege, this is the constant value from https://docs.microsoft.com/en-us/windows/desktop/SecAuthZ/privilege-constants, e.g. SeAuditPrivilege + $new_state = 'System.Collections.Generic.Dictionary`2[[System.String], [System.Nullable`1[System.Boolean]]]' + The key is the constant name as a string, the value is a ternary boolean where + true - will enable the privilege + false - will disable the privilege + null - will remove the privilege + + Each method that changes the privilege state will return a dictionary that + can be used as the $new_state arg of SetTokenPrivileges to undo and revert + back to the original state. If you remove a privilege then this is + irreversible and won't be part of the returned dict + #> + [CmdletBinding()] + # build the C# code to compile + $namespace_import = ($ansible_privilege_util_namespaces | ForEach-Object { "using $_;" }) -join "`r`n" + $platform_util = "$namespace_import`r`n`r`n$ansible_privilege_util_code" + + # FUTURE: find a better way to get the _ansible_remote_tmp variable + # this is used to force csc to compile the C# code in the remote tmp + # specified + $original_tmp = $env:TMP + + $remote_tmp = $original_tmp + $module_params = Get-Variable -Name complex_args -ErrorAction SilentlyContinue + if ($module_params) { + if ($module_params.Value.ContainsKey("_ansible_remote_tmp") ) { + $remote_tmp = $module_params.Value["_ansible_remote_tmp"] + $remote_tmp = [System.Environment]::ExpandEnvironmentVariables($remote_tmp) + } + } + + $env:TMP = $remote_tmp + Add-Type -TypeDefinition $platform_util + $env:TMP = $original_tmp +} + +Function Get-AnsiblePrivilege { + <# + .SYNOPSIS + Get the status of a privilege for the current process. This returns + $true - the privilege is enabled + $false - the privilege is disabled + $null - the privilege is removed from the token + + If Name is not a valid privilege name, this will throw an + ArgumentException. + + .EXAMPLE + Get-AnsiblePrivilege -Name SeDebugPrivilege + #> + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)][String]$Name + ) + + if (-not [Ansible.PrivilegeUtil.Privileges]::CheckPrivilegeName($Name)) { + throw [System.ArgumentException] "Invalid privilege name '$Name'" + } + + $process_token = [Ansible.PrivilegeUtil.Privileges]::GetCurrentProcess() + $privilege_info = [Ansible.PrivilegeUtil.Privileges]::GetAllPrivilegeInfo($process_token) + if ($privilege_info.ContainsKey($Name)) { + $status = $privilege_info.$Name + return $status.HasFlag([Ansible.PrivilegeUtil.PrivilegeAttributes]::Enabled) + } else { + return $null + } +} + +Function Set-AnsiblePrivilege { + <# + .SYNOPSIS + Enables/Disables a privilege on the current process' token. If a privilege + has been removed from the process token, this will throw an + InvalidOperationException. + + .EXAMPLE + # enable a privilege + Set-AnsiblePrivilege -Name SeCreateSymbolicLinkPrivilege -Value $true + + # disable a privilege + Set-AnsiblePrivilege -Name SeCreateSymbolicLinkPrivilege -Value $false + #> + [CmdletBinding(SupportsShouldProcess)] + param( + [Parameter(Mandatory=$true)][String]$Name, + [Parameter(Mandatory=$true)][bool]$Value + ) + + $action = switch($Value) { + $true { "Enable" } + $false { "Disable" } + } + + $current_state = Get-AnsiblePrivilege -Name $Name + if ($current_state -eq $Value) { + return # no change needs to occur + } elseif ($null -eq $current_state) { + # once a privilege is removed from a token we cannot do anything with it + throw [System.InvalidOperationException] "Cannot $($action.ToLower()) the privilege '$Name' as it has been removed from the token" + } + + $process_token = [Ansible.PrivilegeUtil.Privileges]::GetCurrentProcess() + if ($PSCmdlet.ShouldProcess($Name, "$action the privilege $Name")) { + $new_state = New-Object -TypeName 'System.Collections.Generic.Dictionary`2[[System.String], [System.Nullable`1[System.Boolean]]]' + $new_state.Add($Name, $Value) + [Ansible.PrivilegeUtil.Privileges]::SetTokenPrivileges($process_token, $new_state) > $null + } +} + +Export-ModuleMember -Function Import-PrivilegeUtil, Get-AnsiblePrivilege, Set-AnsiblePrivilege ` + -Variable ansible_privilege_util_namespaces, ansible_privilege_util_code \ No newline at end of file diff --git a/lib/ansible/modules/windows/win_acl.ps1 b/lib/ansible/modules/windows/win_acl.ps1 index 4db0ba718b..75bd1393ab 100644 --- a/lib/ansible/modules/windows/win_acl.ps1 +++ b/lib/ansible/modules/windows/win_acl.ps1 @@ -6,6 +6,7 @@ # 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" @@ -43,96 +44,7 @@ function Get-UserSID { return $userSID } -# 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. -$AdjustTokenPrivileges = @" -using System; -using System.Runtime.InteropServices; - -namespace Ansible { - public class TokenManipulator { - - [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] - internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, - ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen); - - [DllImport("kernel32.dll", ExactSpelling = true)] - internal static extern IntPtr GetCurrentProcess(); - - [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] - internal static extern bool OpenProcessToken(IntPtr h, int acc, - ref IntPtr phtok); - - [DllImport("advapi32.dll", SetLastError = true)] - internal static extern bool LookupPrivilegeValue(string host, string name, - ref long pluid); - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal struct TokPriv1Luid - { - public int Count; - public long Luid; - public int Attr; - } - - internal const int SE_PRIVILEGE_DISABLED = 0x00000000; - internal const int SE_PRIVILEGE_ENABLED = 0x00000002; - internal const int TOKEN_QUERY = 0x00000008; - internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020; - - public static bool AddPrivilege(string privilege) { - try { - bool retVal; - TokPriv1Luid tp; - IntPtr hproc = GetCurrentProcess(); - IntPtr htok = IntPtr.Zero; - retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok); - tp.Count = 1; - tp.Luid = 0; - tp.Attr = SE_PRIVILEGE_ENABLED; - retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid); - retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); - return retVal; - } - catch (Exception ex) { - throw ex; - } - } - - public static bool RemovePrivilege(string privilege) { - try { - bool retVal; - TokPriv1Luid tp; - IntPtr hproc = GetCurrentProcess(); - IntPtr htok = IntPtr.Zero; - retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok); - tp.Count = 1; - tp.Luid = 0; - tp.Attr = SE_PRIVILEGE_DISABLED; - retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid); - retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); - return retVal; - } - - catch (Exception ex) { - throw ex; - } - } - } -} -"@ - $params = Parse-Args $args -$_remote_tmp = Get-AnsibleParam $params "_ansible_remote_tmp" -type "path" -default $env:TMP - -$original_tmp = $env:TMP -$original_temp = $env:TEMP -$env:TMP = $_remote_tmp -$env:TEMP = $_remote_tmp -add-type $AdjustTokenPrivileges -$env:TMP = $original_tmp -$env:TEMP = $original_temp Function SetPrivilegeTokens() { # Set privilege tokens only if admin. @@ -144,13 +56,23 @@ Function SetPrivilegeTokens() { 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 - - [void][Ansible.TokenManipulator]::AddPrivilege("SeRestorePrivilege") #Grants all write access control to any file, regardless of ACL. - [void][Ansible.TokenManipulator]::AddPrivilege("SeBackupPrivilege") #Grants all read access control to any file, regardless of ACL. - [void][Ansible.TokenManipulator]::AddPrivilege("SeTakeOwnershipPrivilege") #Grants ability to take owernship of an object w/out being granted discretionary access + Import-PrivilegeUtil + $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 + } + } } } diff --git a/lib/ansible/modules/windows/win_file.ps1 b/lib/ansible/modules/windows/win_file.ps1 index 60cf3762e4..06f03494fa 100644 --- a/lib/ansible/modules/windows/win_file.ps1 +++ b/lib/ansible/modules/windows/win_file.ps1 @@ -52,12 +52,9 @@ namespace Ansible.Command { } "@ $original_tmp = $env:TMP -$original_temp = $env:TEMP $env:TMP = $_remote_tmp -$env:TEMP = $_remote_tmp Add-Type -TypeDefinition $symlink_util $env:TMP = $original_tmp -$env:TEMP = $original_temp # Used to delete directories and files with logic on handling symbolic links function Remove-File($file, $checkmode) { diff --git a/lib/ansible/modules/windows/win_find.ps1 b/lib/ansible/modules/windows/win_find.ps1 index fc8c1dc925..f2095e09bf 100644 --- a/lib/ansible/modules/windows/win_find.ps1 +++ b/lib/ansible/modules/windows/win_find.ps1 @@ -71,12 +71,9 @@ namespace Ansible.Command { } "@ $original_tmp = $env:TMP -$original_temp = $env:TEMP $env:TMP = $_remote_tmp -$env:TEMP = $_remote_tmp Add-Type -TypeDefinition $symlink_util $env:TMP = $original_tmp -$env:TEMP = $original_temp Function Assert-Age($info) { $valid_match = $true diff --git a/lib/ansible/modules/windows/win_get_url.ps1 b/lib/ansible/modules/windows/win_get_url.ps1 index c105915974..26aa1107c8 100644 --- a/lib/ansible/modules/windows/win_get_url.ps1 +++ b/lib/ansible/modules/windows/win_get_url.ps1 @@ -30,12 +30,9 @@ $webclient_util = @" } "@ $original_tmp = $env:TMP -$original_temp = $env:TEMP $env:TMP = $_remote_tmp -$env:TEMP = $_remote_tmp Add-Type -TypeDefinition $webclient_util $env:TMP = $original_tmp -$env:TEMP = $original_temp Function CheckModified-File($url, $dest, $headers, $credentials, $timeout, $use_proxy, $proxy) { diff --git a/lib/ansible/modules/windows/win_regedit.ps1 b/lib/ansible/modules/windows/win_regedit.ps1 index abeed87366..9fd0c072d5 100644 --- a/lib/ansible/modules/windows/win_regedit.ps1 +++ b/lib/ansible/modules/windows/win_regedit.ps1 @@ -6,6 +6,7 @@ # 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 $ErrorActionPreference = "Stop" @@ -39,23 +40,8 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; -namespace Ansible +namespace Ansible.RegEdit { - [StructLayout(LayoutKind.Sequential)] - public struct LUID - { - public UInt32 LowPart; - public Int32 HighPart; - } - - [StructLayout(LayoutKind.Sequential)] - public struct TOKEN_PRIVILEGES - { - public UInt32 PrivilegeCount; - public LUID Luid; - public UInt32 Attributes; - } - public enum HKEY : uint { LOCAL_MACHINE = 0x80000002, @@ -74,41 +60,8 @@ namespace Ansible public static explicit operator Win32Exception(string message) { return new Win32Exception(message); } } - public class RegistryUtil + public class Hive { - - public const int TOKEN_ADJUST_PRIVILEGES = 0x00000020; - public const int TOKEN_QUERY = 0x00000008; - public const int SE_PRIVILEGE_ENABLED = 0x00000002; - - [DllImport("kernel32.dll", CharSet = CharSet.Auto)] - private static extern IntPtr GetCurrentProcess(); - - [DllImport("kernel32.dll", CharSet = CharSet.Auto)] - private static extern bool CloseHandle( - IntPtr hObject); - - [DllImport("advapi32.dll", CharSet = CharSet.Auto)] - private static extern bool OpenProcessToken( - IntPtr ProcessHandle, - UInt32 DesiredAccess, - out IntPtr TokenHandle); - - [DllImport("advapi32.dll", CharSet = CharSet.Auto)] - private static extern bool LookupPrivilegeValue( - string lpSystemName, - string lpName, - [MarshalAs(UnmanagedType.Struct)] out LUID lpLuid); - - [DllImport("advapi32.dll", CharSet = CharSet.Auto)] - private static extern bool AdjustTokenPrivileges( - IntPtr TokenHandle, - [MarshalAs(UnmanagedType.Bool)] bool DisableAllPrivileges, - ref TOKEN_PRIVILEGES NewState, - UInt32 BufferLength, - IntPtr PreviousState, - IntPtr ReturnLength); - [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern int RegLoadKey( HKEY hKey, @@ -120,41 +73,6 @@ namespace Ansible HKEY hKey, string lpSubKey); - public static void EnablePrivileges() - { - List privileges = new List() - { - "SeRestorePrivilege", - "SeBackupPrivilege" - }; - foreach (string privilege in privileges) - { - IntPtr hToken; - LUID luid; - TOKEN_PRIVILEGES tkpPrivileges; - - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out hToken)) - throw new Win32Exception("OpenProcessToken() failed"); - - try - { - if (!LookupPrivilegeValue(null, privilege, out luid)) - throw new Win32Exception("LookupPrivilegeValue() failed"); - - tkpPrivileges.PrivilegeCount = 1; - tkpPrivileges.Luid = luid; - tkpPrivileges.Attributes = SE_PRIVILEGE_ENABLED; - - if (!AdjustTokenPrivileges(hToken, false, ref tkpPrivileges, 0, IntPtr.Zero, IntPtr.Zero)) - throw new Win32Exception(String.Format("AdjustTokenPrivileges() failed to adjust privilege {0}", privilege)); - } - finally - { - CloseHandle(hToken); - } - } - } - public static void LoadHive(string lpSubKey, string lpFile) { int ret; @@ -373,29 +291,29 @@ if ($hive) { } $original_tmp = $env:TMP - $original_temp = $env:TEMP $env:TMP = $_remote_tmp - $env:TEMP = $_remote_tmp Add-Type -TypeDefinition $registry_util $env:TMP = $original_tmp - $env:TEMP = $original_temp + + Import-PrivilegeUtil try { - [Ansible.RegistryUtil]::EnablePrivileges() + Set-AnsiblePrivilege -Name SeBackupPrivilege -Value $true + Set-AnsiblePrivilege -Name SeRestorePrivilege -Value $true } catch [System.ComponentModel.Win32Exception] { - Fail-Json -obj $result -message "failed to enable SeRestorePrivilege and SeRestorePrivilege for the current process: $($_.Exception.Message)" + Fail-Json -obj $result -message "failed to enable SeBackupPrivilege and SeRestorePrivilege for the current process: $($_.Exception.Message)" } if (Test-Path -Path HKLM:\ANSIBLE) { Add-Warning -obj $result -message "hive already loaded at HKLM:\ANSIBLE, had to unload hive for win_regedit to continue" try { - [Ansible.RegistryUtil]::UnloadHive("ANSIBLE") + [Ansible.RegEdit.Hive]::UnloadHive("ANSIBLE") } catch [System.ComponentModel.Win32Exception] { Fail-Json -obj $result -message "failed to unload registry hive HKLM:\ANSIBLE from $($hive): $($_.Exception.Message)" } } try { - [Ansible.RegistryUtil]::LoadHive("ANSIBLE", $hive) + [Ansible.RegEdit.Hive]::LoadHive("ANSIBLE", $hive) } catch [System.ComponentModel.Win32Exception] { Fail-Json -obj $result -message "failed to load registry hive from '$hive' to HKLM:\ANSIBLE: $($_.Exception.Message)" } @@ -566,7 +484,7 @@ $key_prefix[$path] [GC]::Collect() [GC]::WaitForPendingFinalizers() try { - [Ansible.RegistryUtil]::UnloadHive("ANSIBLE") + [Ansible.RegEdit.Hive]::UnloadHive("ANSIBLE") } catch [System.ComponentModel.Win32Exception] { Fail-Json -obj $result -message "failed to unload registry hive HKLM:\ANSIBLE from $($hive): $($_.Exception.Message)" } diff --git a/lib/ansible/modules/windows/win_region.ps1 b/lib/ansible/modules/windows/win_region.ps1 index df2059da5d..6dc06cda64 100644 --- a/lib/ansible/modules/windows/win_region.ps1 +++ b/lib/ansible/modules/windows/win_region.ps1 @@ -88,12 +88,9 @@ Function Set-CultureLegacy($culture) { $reg_key = 'HKCU:\Control Panel\International' $original_tmp = $env:TMP - $original_temp = $env:TEMP $env:TMP = $_remote_tmp - $env:TEMP = $_remote_tmp Add-Type -TypeDefinition $lctype_util $env:TMP = $original_tmp - $env:TEMP = $original_temp $lookup = New-Object Ansible.LocaleHelper($culture) # hex values are from http://www.pinvoke.net/default.aspx/kernel32/GetLocaleInfoEx.html diff --git a/lib/ansible/modules/windows/win_scheduled_task.ps1 b/lib/ansible/modules/windows/win_scheduled_task.ps1 index 8dd9323712..92e918ac02 100644 --- a/lib/ansible/modules/windows/win_scheduled_task.ps1 +++ b/lib/ansible/modules/windows/win_scheduled_task.ps1 @@ -125,12 +125,9 @@ public enum TASK_TRIGGER_TYPE2 // https://msdn.microsoft.com/en-us/library/windo "@ $original_tmp = $env:TMP -$original_temp = $env:TEMP $env:TMP = $_remote_tmp -$env:TEMP = $_remote_tmp Add-Type -TypeDefinition $task_enums $env:TMP = $original_tmp -$env:TEMP = $original_temp ######################## ### HELPER FUNCTIONS ### diff --git a/lib/ansible/modules/windows/win_scheduled_task_stat.ps1 b/lib/ansible/modules/windows/win_scheduled_task_stat.ps1 index 328c15dae5..71cbe7ebfe 100644 --- a/lib/ansible/modules/windows/win_scheduled_task_stat.ps1 +++ b/lib/ansible/modules/windows/win_scheduled_task_stat.ps1 @@ -70,12 +70,9 @@ public enum TASK_TRIGGER_TYPE2 "@ $original_tmp = $env:TMP -$original_temp = $env:TEMP $env:TMP = $_remote_tmp -$env:TEMP = $_remote_tmp Add-Type -TypeDefinition $task_enums $env:TMP = $original_tmp -$env:TEMP = $original_temp Function Get-PropertyValue($task_property, $com, $property) { $raw_value = $com.$property diff --git a/lib/ansible/modules/windows/win_user.ps1 b/lib/ansible/modules/windows/win_user.ps1 index 1c3964506a..715861e923 100644 --- a/lib/ansible/modules/windows/win_user.ps1 +++ b/lib/ansible/modules/windows/win_user.ps1 @@ -70,12 +70,9 @@ namespace Ansible '@ $original_tmp = $env:TMP - $original_temp = $env:TEMP $env:TMP = $_remote_tmp - $env:TEMP = $_remote_tmp Add-Type -TypeDefinition $platform_util $env:TMP = $original_tmp - $env:TEMP = $original_temp $handle = [IntPtr]::Zero $logon_res = [Ansible.WinUserPInvoke]::LogonUser($Username, $null, $Password, diff --git a/lib/ansible/modules/windows/win_user_right.ps1 b/lib/ansible/modules/windows/win_user_right.ps1 index 01d328025b..e896326e84 100644 --- a/lib/ansible/modules/windows/win_user_right.ps1 +++ b/lib/ansible/modules/windows/win_user_right.ps1 @@ -267,12 +267,9 @@ namespace Ansible "@ $original_tmp = $env:TMP -$original_temp = $env:TEMP $env:TMP = $_remote_tmp -$env:TEMP = $_remote_tmp Add-Type -TypeDefinition $sec_helper_util $env:TMP = $original_tmp -$env:TEMP = $original_temp Function Compare-UserList($existing_users, $new_users) { $added_users = [String[]]@() diff --git a/lib/ansible/modules/windows/win_whoami.ps1 b/lib/ansible/modules/windows/win_whoami.ps1 index 16988208a9..6c9965af7e 100644 --- a/lib/ansible/modules/windows/win_whoami.ps1 +++ b/lib/ansible/modules/windows/win_whoami.ps1 @@ -783,12 +783,9 @@ namespace Ansible '@ $original_tmp = $env:TMP -$original_temp = $env:TEMP $env:TMP = $_remote_tmp -$env:TEMP = $_remote_tmp Add-Type -TypeDefinition $session_util $env:TMP = $original_tmp -$env:TEMP = $original_temp $session_info = [Ansible.SessionUtil]::GetSessionInfo() diff --git a/lib/ansible/plugins/shell/powershell.py b/lib/ansible/plugins/shell/powershell.py index 77b588d8eb..4299043dca 100644 --- a/lib/ansible/plugins/shell/powershell.py +++ b/lib/ansible/plugins/shell/powershell.py @@ -1065,7 +1065,6 @@ Function Run($payload) { # NB: action popping handled inside subprocess wrapper $original_tmp = $env:TMP - $original_temp = $env:TEMP $remote_tmp = $payload["module_args"]["_ansible_remote_tmp"] $remote_tmp = [System.Environment]::ExpandEnvironmentVariables($remote_tmp) if ($null -eq $remote_tmp) { @@ -1075,10 +1074,8 @@ Function Run($payload) { # become process is run under a different console to the WinRM one so we # need to set the UTF-8 codepage again $env:TMP = $remote_tmp - $env:TEMP = $remote_tmp Add-Type -TypeDefinition $helper_def -Debug:$false $env:TMP = $original_tmp - $env:TEMP = $original_tmp $username = $payload.become_user $password = $payload.become_password diff --git a/test/integration/targets/win_module_utils/library/privilege_util_test.ps1 b/test/integration/targets/win_module_utils/library/privilege_util_test.ps1 new file mode 100644 index 0000000000..0bbfadb73f --- /dev/null +++ b/test/integration/targets/win_module_utils/library/privilege_util_test.ps1 @@ -0,0 +1,164 @@ +#!powershell + +#Requires -Module Ansible.ModuleUtils.Legacy +#Requires -Module Ansible.ModuleUtils.PrivilegeUtil + +$ErrorActionPreference = "Stop" + +$result = @{ + changed = $false +} + +Import-PrivilegeUtil + +Function Assert-Equals($actual, $expected) { + if ($actual -cne $expected) { + $call_stack = (Get-PSCallStack)[1] + $error_msg = "AssertionError:`r`nActual: `"$actual`" != Expected: `"$expected`"`r`nLine: $($call_stack.ScriptLineNumber), Method: $($call_stack.Position.Text)" + Fail-Json -obj $result -message $error_msg + } +} + +# taken from https://docs.microsoft.com/en-us/windows/desktop/SecAuthZ/privilege-constants +$total_privileges = @( + "SeAssignPrimaryTokenPrivilege", + "SeAuditPrivilege", + "SeBackupPrivilege", + "SeChangeNotifyPrivilege", + "SeCreateGlobalPrivilege", + "SeCreatePagefilePrivilege", + "SeCreatePermanentPrivilege", + "SeCreateSymbolicLinkPrivilege", + "SeCreateTokenPrivilege", + "SeDebugPrivilege", + "SeEnableDelegationPrivilege", + "SeImpersonatePrivilege", + "SeIncreaseBasePriorityPrivilege", + "SeIncreaseQuotaPrivilege", + "SeIncreaseWorkingSetPrivilege", + "SeLoadDriverPrivilege", + "SeLockMemoryPrivilege", + "SeMachineAccountPrivilege", + "SeManageVolumePrivilege", + "SeProfileSingleProcessPrivilege", + "SeRelabelPrivilege", + "SeRemoteShutdownPrivilege", + "SeRestorePrivilege", + "SeSecurityPrivilege", + "SeShutdownPrivilege", + "SeSyncAgentPrivilege", + "SeSystemEnvironmentPrivilege", + "SeSystemProfilePrivilege", + "SeSystemtimePrivilege", + "SeTakeOwnershipPrivilege", + "SeTcbPrivilege", + "SeTimeZonePrivilege", + "SeTrustedCredManAccessPrivilege", + "SeUndockPrivilege" +) + +$raw_privilege_output = &whoami /priv | Where-Object { $_.StartsWith("Se") } +$actual_privileges = @{} +foreach ($raw_privilege in $raw_privilege_output) { + $split = $raw_privilege.TrimEnd() -split " " + $actual_privileges."$($split[0])" = ($split[-1] -eq "Enabled") +} +$process = [Ansible.PrivilegeUtil.Privileges]::GetCurrentProcess() + +### Test variables ### +Assert-Equals -actual ($ansible_privilege_util_namespaces -is [array]) -expected $true +Assert-Equals -actual ($ansible_privilege_util_code -is [String]) -expected $true + +### Test PS cmdlets ### +# test ps Get-AnsiblePrivilege +foreach ($privilege in $total_privileges) { + $expected = $null + if ($actual_privileges.ContainsKey($privilege)) { + $expected = $actual_privileges.$privilege + } + $actual = Get-AnsiblePrivilege -Name $privilege + Assert-Equals -actual $actual -expected $expected +} + +# test c# GetAllPrivilegeInfo +$actual = [Ansible.PrivilegeUtil.Privileges]::GetAllPrivilegeInfo($process) +Assert-Equals -actual $actual.GetType().Name -expected 'Dictionary`2' +Assert-Equals -actual $actual.Count -expected $actual_privileges.Count +foreach ($privilege in $total_privileges) { + if ($actual_privileges.ContainsKey($privilege)) { + $actual_value = $actual.$privilege + if ($actual_privileges.$privilege) { + Assert-Equals -actual $actual_value.HasFlag([Ansible.PrivilegeUtil.PrivilegeAttributes]::Enabled) -expected $true + } else { + Assert-Equals -actual $actual_value.HasFlag([Ansible.PrivilegeUtil.PrivilegeAttributes]::Enabled) -expected $false + } + } +} + +# test Set-AnsiblePrivilege +Set-AnsiblePrivilege -Name SeUndockPrivilege -Value $false # ensure we start with a disabled privilege + +Set-AnsiblePrivilege -Name SeUndockPrivilege -Value $true -WhatIf +$actual = Get-AnsiblePrivilege -Name SeUndockPrivilege +Assert-Equals -actual $actual -expected $false + +Set-AnsiblePrivilege -Name SeUndockPrivilege -Value $true +$actual = Get-AnsiblePrivilege -Name SeUndockPrivilege +Assert-Equals -actual $actual -expected $true + +Set-AnsiblePrivilege -Name SeUndockPrivilege -Value $false -WhatIf +$actual = Get-AnsiblePrivilege -Name SeUndockPrivilege +Assert-Equals -actual $actual -expected $true + +Set-AnsiblePrivilege -Name SeUndockPrivilege -Value $false +$actual = Get-AnsiblePrivilege -Name SeUndockPrivilege +Assert-Equals -actual $actual -expected $false + +### Test C# code ### +# test CheckPrivilegeName +Assert-Equals -actual ([Ansible.PrivilegeUtil.Privileges]::CheckPrivilegeName($total_privileges[0])) -expected $true +Assert-Equals -actual ([Ansible.PrivilegeUtil.Privileges]::CheckPrivilegeName("SeFake")) -expected $false + +# test DisablePrivilege +# ensure we start in an enabled state +Set-AnsiblePrivilege -Name SeTimeZonePrivilege -Value $true +$actual = [Ansible.PrivilegeUtil.Privileges]::DisablePrivilege($process, "SeTimeZonePrivilege") +Assert-Equals -actual $actual.GetType().Name -expected 'Dictionary`2' +Assert-Equals -actual $actual.Count -expected 1 +Assert-Equals -actual $actual.SeTimeZonePrivilege -expected $true + +$actual = [Ansible.PrivilegeUtil.Privileges]::DisablePrivilege($process, "SeTimeZonePrivilege") +Assert-Equals -actual $actual.GetType().Name -expected 'Dictionary`2' +Assert-Equals -actual $actual.Count -expected 0 + +# test DisableAllPrivileges +$actual_disable_all = [Ansible.PrivilegeUtil.Privileges]::DisableAllPrivileges($process) +Assert-Equals -actual $actual_disable_all.GetType().Name -expected 'Dictionary`2' + +$actual = [Ansible.PrivilegeUtil.Privileges]::DisableAllPrivileges($process) +Assert-Equals -actual $actual.GetType().Name -expected 'Dictionary`2' +Assert-Equals -actual $actual.Count -expected 0 + +# test EnablePrivilege +$actual = [Ansible.PrivilegeUtil.Privileges]::EnablePrivilege($process, "SeTimeZonePrivilege") +Assert-Equals -actual $actual.GetType().Name -expected 'Dictionary`2' +Assert-Equals -actual $actual.Count -expected 1 +Assert-Equals -actual $actual.SeTimeZonePrivilege -expected $false + +$actual = [Ansible.PrivilegeUtil.Privileges]::EnablePrivilege($process, "SeTimeZonePrivilege") +Assert-Equals -actual $actual.GetType().Name -expected 'Dictionary`2' +Assert-Equals -actual $actual.Count -expected 0 + +# test SetTokenPrivileges +$actual = [Ansible.PrivilegeUtil.Privileges]::SetTokenPrivileges($process, $actual_disable_all) +Assert-Equals -actual $actual_disable_all.GetType().Name -expected 'Dictionary`2' +Assert-Equals -actual $actual.ContainsKey("SeTimeZonePrivilege") -expected $false +Assert-Equals -actual $actual.Count -expected $actual_disable_all.Count + +# test RemovePrivilege +[Ansible.PrivilegeUtil.Privileges]::RemovePrivilege($process, "SeTimeZonePrivilege") +$actual = Get-AnsiblePrivilege -Name SeTimeZonePrivilege +Assert-Equals -actual $actual -expected $null + +$result.data = "success" +Exit-Json -obj $result diff --git a/test/integration/targets/win_module_utils/tasks/main.yml b/test/integration/targets/win_module_utils/tasks/main.yml index d94e85ce26..05f2dffab3 100644 --- a/test/integration/targets/win_module_utils/tasks/main.yml +++ b/test/integration/targets/win_module_utils/tasks/main.yml @@ -135,3 +135,11 @@ - assert: that: - file_util_test.data == 'success' + +- name: call module with PrivilegeUtil tests + privilege_util_test: + register: privilege_util_test + +- assert: + that: + - privilege_util_test.data == 'success'