From 4019d4f6d175b8a68d42615a212375e950a11eae Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Fri, 14 Dec 2018 12:00:46 +1000 Subject: [PATCH] Windows Privileges - moved util code to it's own C# util (#48897) * Windows Privileges - moved util code to it's own C# util * Rename Enabler class to PrivilegeEnabler to remove ambiguity * rename Utils to PrivilegeUtil * fix missing util name changes --- changelogs/fragments/win_privileges_util.yaml | 2 + .../module_utils/csharp/Ansible.Privilege.cs | 443 ++++++++++++++++++ .../Ansible.ModuleUtils.LinkUtil.psm1 | 5 +- .../Ansible.ModuleUtils.PrivilegeUtil.psm1 | 436 +---------------- lib/ansible/modules/windows/win_acl.ps1 | 1 - lib/ansible/modules/windows/win_regedit.ps1 | 1 - .../library/ansible_privilege_tests.ps1 | 324 +++++++++++++ .../targets/win_csharp_utils/tasks/main.yml | 9 + .../library/privilege_util_test.ps1 | 78 +-- 9 files changed, 811 insertions(+), 488 deletions(-) create mode 100644 changelogs/fragments/win_privileges_util.yaml create mode 100644 lib/ansible/module_utils/csharp/Ansible.Privilege.cs create mode 100644 test/integration/targets/win_csharp_utils/library/ansible_privilege_tests.ps1 diff --git a/changelogs/fragments/win_privileges_util.yaml b/changelogs/fragments/win_privileges_util.yaml new file mode 100644 index 00000000000..097f04839d5 --- /dev/null +++ b/changelogs/fragments/win_privileges_util.yaml @@ -0,0 +1,2 @@ +minor_changes: +- Ansible.ModuleUtils.Privilege - moved C# code to it's own util called ``Ansible.Privilege`` and expanded the tests diff --git a/lib/ansible/module_utils/csharp/Ansible.Privilege.cs b/lib/ansible/module_utils/csharp/Ansible.Privilege.cs new file mode 100644 index 00000000000..2c0b266bf71 --- /dev/null +++ b/lib/ansible/module_utils/csharp/Ansible.Privilege.cs @@ -0,0 +1,443 @@ +using Microsoft.Win32.SafeHandles; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Security.Principal; +using System.Text; + +namespace Ansible.Privilege +{ + internal class NativeHelpers + { + [StructLayout(LayoutKind.Sequential)] + public struct LUID + { + public UInt32 LowPart; + public Int32 HighPart; + } + + [StructLayout(LayoutKind.Sequential)] + public struct LUID_AND_ATTRIBUTES + { + public LUID Luid; + public PrivilegeAttributes Attributes; + } + + [StructLayout(LayoutKind.Sequential)] + public struct TOKEN_PRIVILEGES + { + public UInt32 PrivilegeCount; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] + public LUID_AND_ATTRIBUTES[] Privileges; + } + } + + internal class NativeMethods + { + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool AdjustTokenPrivileges( + SafeNativeHandle TokenHandle, + [MarshalAs(UnmanagedType.Bool)] bool DisableAllPrivileges, + SafeMemoryBuffer NewState, + UInt32 BufferLength, + SafeMemoryBuffer PreviousState, + out UInt32 ReturnLength); + + [DllImport("kernel32.dll")] + public static extern bool CloseHandle( + IntPtr hObject); + + [DllImport("kernel32")] + public static extern SafeWaitHandle GetCurrentProcess(); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool GetTokenInformation( + SafeNativeHandle TokenHandle, + UInt32 TokenInformationClass, + SafeMemoryBuffer TokenInformation, + UInt32 TokenInformationLength, + out UInt32 ReturnLength); + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern bool LookupPrivilegeName( + string lpSystemName, + ref NativeHelpers.LUID lpLuid, + StringBuilder lpName, + ref UInt32 cchName); + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern bool LookupPrivilegeValue( + string lpSystemName, + string lpName, + out NativeHelpers.LUID lpLuid); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool OpenProcessToken( + SafeHandle ProcessHandle, + TokenAccessLevels DesiredAccess, + out SafeNativeHandle TokenHandle); + } + + internal class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid + { + public SafeMemoryBuffer() : base(true) { } + public SafeMemoryBuffer(int cb) : base(true) + { + base.SetHandle(Marshal.AllocHGlobal(cb)); + } + public SafeMemoryBuffer(IntPtr handle) : base(true) + { + base.SetHandle(handle); + } + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + protected override bool ReleaseHandle() + { + Marshal.FreeHGlobal(handle); + return true; + } + } + + internal class SafeNativeHandle : SafeHandleZeroOrMinusOneIsInvalid + { + public SafeNativeHandle() : base(true) { } + public SafeNativeHandle(IntPtr handle) : base(true) { this.handle = handle; } + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + protected override bool ReleaseHandle() + { + return NativeMethods.CloseHandle(handle); + } + } + + 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); } + } + + [Flags] + public enum PrivilegeAttributes : uint + { + Disabled = 0x00000000, + EnabledByDefault = 0x00000001, + Enabled = 0x00000002, + Removed = 0x00000004, + UsedForAccess = 0x80000000, + } + + public class PrivilegeEnabler : IDisposable + { + private SafeHandle process; + private Dictionary previousState; + + /// + /// Temporarily enables the privileges specified and reverts once the class is disposed. + /// + /// Whether to fail if any privilege failed to be enabled, if false then this will continue silently + /// A list of privileges to enable + public PrivilegeEnabler(bool strict, params string[] privileges) + { + if (privileges.Length > 0) + { + process = PrivilegeUtil.GetCurrentProcess(); + Dictionary newState = new Dictionary(); + for (int i = 0; i < privileges.Length; i++) + newState.Add(privileges[i], true); + try + { + previousState = PrivilegeUtil.SetTokenPrivileges(process, newState, strict); + } + catch (Win32Exception e) + { + throw new Win32Exception(e.NativeErrorCode, String.Format("Failed to enable privilege(s) {0}", String.Join(", ", privileges))); + } + } + } + + public void Dispose() + { + // disables any privileges that were enabled by this class + if (previousState != null) + PrivilegeUtil.SetTokenPrivileges(process, previousState); + GC.SuppressFinalize(this); + } + ~PrivilegeEnabler() { this.Dispose(); } + } + + public class PrivilegeUtil + { + private static readonly UInt32 TOKEN_PRIVILEGES = 3; + + /// + /// Checks if the specific privilege constant is a valid privilege name + /// + /// The privilege constant (Se*Privilege) is valid + /// true if valid, else false + 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; + } + } + + /// + /// Disables the privilege specified + /// + /// The process token to that contains the privilege to disable + /// The privilege constant to disable + /// The previous state that can be passed to SetTokenPrivileges to revert the action + public static Dictionary DisablePrivilege(SafeHandle token, string privilege) + { + return SetTokenPrivileges(token, new Dictionary() { { privilege, false } }); + } + + /// + /// Disables all the privileges + /// + /// The process token to that contains the privilege to disable + /// The previous state that can be passed to SetTokenPrivileges to revert the action + public static Dictionary DisableAllPrivileges(SafeHandle token) + { + return AdjustTokenPrivileges(token, null, false); + } + + /// + /// Enables the privilege specified + /// + /// The process token to that contains the privilege to enable + /// The privilege constant to enable + /// The previous state that can be passed to SetTokenPrivileges to revert the action + public static Dictionary EnablePrivilege(SafeHandle token, string privilege) + { + return SetTokenPrivileges(token, new Dictionary() { { privilege, true } }); + } + + /// + /// Get's the status of all the privileges on the token specified + /// + /// The process token to get the privilege status on + /// Dictionary where the key is the privilege constant and the value is the PrivilegeAttributes flags + public static Dictionary GetAllPrivilegeInfo(SafeHandle token) + { + SafeNativeHandle hToken = null; + if (!NativeMethods.OpenProcessToken(token, TokenAccessLevels.Query, out hToken)) + throw new Win32Exception("OpenProcessToken() failed"); + + using (hToken) + { + UInt32 tokenLength = 0; + NativeMethods.GetTokenInformation(hToken, TOKEN_PRIVILEGES, new SafeMemoryBuffer(0), 0, out tokenLength); + + NativeHelpers.LUID_AND_ATTRIBUTES[] privileges; + using (SafeMemoryBuffer privilegesPtr = new SafeMemoryBuffer((int)tokenLength)) + { + 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.DangerousGetHandle(), typeof(NativeHelpers.TOKEN_PRIVILEGES)); + privileges = new NativeHelpers.LUID_AND_ATTRIBUTES[privilegeInfo.PrivilegeCount]; + PtrToStructureArray(privileges, IntPtr.Add(privilegesPtr.DangerousGetHandle(), Marshal.SizeOf(privilegeInfo.PrivilegeCount))); + } + + return privileges.ToDictionary(p => GetPrivilegeName(p.Luid), p => p.Attributes); + } + } + + /// + /// Get a handle to the current process for use with the methods above + /// + /// SafeWaitHandle handle of the current process token + public static SafeWaitHandle GetCurrentProcess() + { + return NativeMethods.GetCurrentProcess(); + } + + /// + /// Removes a privilege from the token. This operation is irreversible + /// + /// The process token to that contains the privilege to remove + /// The privilege constant to remove + public static void RemovePrivilege(SafeHandle token, string privilege) + { + SetTokenPrivileges(token, new Dictionary() { { privilege, null } }); + } + + /// + /// Do a bulk set of multiple privileges + /// + /// The process token to use when setting the privilege state + /// A dictionary that contains the privileges to set, the key is the constant name and the value can be; + /// true - enable the privilege + /// false - disable the privilege + /// null - remove the privilege (this cannot be reversed) + /// + /// When true, will fail if one privilege failed to be set, otherwise it will silently continue + /// The previous state that can be passed to SetTokenPrivileges to revert the action + public static Dictionary SetTokenPrivileges(SafeHandle token, IDictionary state, bool strict = true) + { + NativeHelpers.LUID_AND_ATTRIBUTES[] privilegeAttr = new NativeHelpers.LUID_AND_ATTRIBUTES[state.Count]; + int i = 0; + + foreach (DictionaryEntry entry in state) + { + string key = (string)entry.Key; + NativeHelpers.LUID luid; + if (!NativeMethods.LookupPrivilegeValue(null, key, out luid)) + throw new Win32Exception(String.Format("LookupPrivilegeValue({0}) failed", key)); + + PrivilegeAttributes attributes; + switch ((bool?)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, strict); + } + + private static Dictionary AdjustTokenPrivileges(SafeHandle token, NativeHelpers.LUID_AND_ATTRIBUTES[] newState, bool strict) + { + bool disableAllPrivileges; + SafeMemoryBuffer newStatePtr; + NativeHelpers.LUID_AND_ATTRIBUTES[] oldStatePrivileges; + UInt32 returnLength; + + if (newState == null) + { + disableAllPrivileges = true; + newStatePtr = new SafeMemoryBuffer(0); + } + 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 = new SafeMemoryBuffer(newStateBytes.Length); + Marshal.Copy(newStateBytes, 0, newStatePtr.DangerousGetHandle(), newStateBytes.Length); + } + + using (newStatePtr) + { + SafeNativeHandle hToken; + if (!NativeMethods.OpenProcessToken(token, TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges, out hToken)) + throw new Win32Exception("OpenProcessToken() failed with Query and AdjustPrivileges"); + + using (hToken) + { + if (!NativeMethods.AdjustTokenPrivileges(hToken, disableAllPrivileges, newStatePtr, 0, new SafeMemoryBuffer(0), out returnLength)) + { + int errCode = Marshal.GetLastWin32Error(); + if (errCode != 122) // ERROR_INSUFFICIENT_BUFFER + throw new Win32Exception(errCode, "AdjustTokenPrivileges() failed to get old state size"); + } + + using (SafeMemoryBuffer oldStatePtr = new SafeMemoryBuffer((int)returnLength)) + { + 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 + // fail if we are running with strict, otherwise ignore those privileges + if (!res || ((strict && errCode != 0) || (!strict && !(errCode == 0 || errCode == 0x00000514)))) + throw new Win32Exception(errCode, "AdjustTokenPrivileges() failed"); + + // Marshal the oldStatePtr to the struct + NativeHelpers.TOKEN_PRIVILEGES oldState = (NativeHelpers.TOKEN_PRIVILEGES)Marshal.PtrToStructure( + oldStatePtr.DangerousGetHandle(), typeof(NativeHelpers.TOKEN_PRIVILEGES)); + oldStatePrivileges = new NativeHelpers.LUID_AND_ATTRIBUTES[oldState.PrivilegeCount]; + PtrToStructureArray(oldStatePrivileges, IntPtr.Add(oldStatePtr.DangerousGetHandle(), Marshal.SizeOf(oldState.PrivilegeCount))); + } + } + } + + 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, IntPtr ptr) + { + IntPtr ptrOffset = ptr; + for (int i = 0; i < array.Length; i++, ptrOffset = IntPtr.Add(ptrOffset, Marshal.SizeOf(typeof(T)))) + array[i] = (T)Marshal.PtrToStructure(ptrOffset, typeof(T)); + } + + private static int StructureToBytes(T structure, byte[] array, int offset) + { + int size = Marshal.SizeOf(structure); + using (SafeMemoryBuffer structPtr = new SafeMemoryBuffer(size)) + { + Marshal.StructureToPtr(structure, structPtr.DangerousGetHandle(), false); + Marshal.Copy(structPtr.DangerousGetHandle(), array, offset, size); + } + + return size; + } + } +} + diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.LinkUtil.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.LinkUtil.psm1 index c18adc64968..b6f4ad99557 100644 --- a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.LinkUtil.psm1 +++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.LinkUtil.psm1 @@ -1,5 +1,5 @@ - # Copyright (c) 2017 Ansible Project - # Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) +# 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 @@ -408,7 +408,6 @@ namespace Ansible Add-Type -TypeDefinition $link_util $env:TMP = $original_tmp - Import-PrivilegeUtil # enable the SeBackupPrivilege if it is disabled $state = Get-AnsiblePrivilege -Name SeBackupPrivilege if ($state -eq $false) { diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.PrivilegeUtil.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.PrivilegeUtil.psm1 index 9152d514113..03cebe75b9c 100644 --- a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.PrivilegeUtil.psm1 +++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.PrivilegeUtil.psm1 @@ -1,424 +1,24 @@ # 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, IntPtr.Add(privilegesPtr, 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, IntPtr.Add(oldStatePtr, 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, IntPtr ptr) - { - IntPtr ptrOffset = ptr; - for (int i = 0; i < array.Length; i++, ptrOffset = IntPtr.Add(ptrOffset, Marshal.SizeOf(typeof(T)))) - array[i] = (T)Marshal.PtrToStructure(ptrOffset, 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; - } - } -} -'@ +#AnsibleRequires -CSharpUtil Ansible.Privilege 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 + No-op, as the C# types are automatically loaded. #> [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) + Param() + $msg = "Import-PrivilegeUtil is deprecated and no longer needed, this cmdlet will be removed in a future version" + if ((Get-Command -Name Add-DeprecationWarning -ErrorAction SilentlyContinue) -and (Get-Variable -Name result -ErrorAction SilentlyContinue)) { + Add-DeprecationWarning -obj $result.Value -message $msg -version 2.12 + } else { + $module = Get-Variable -Name module -ErrorAction SilentlyContinue + if ($null -ne $module -and $module.Value.GetType().FullName -eq "Ansible.Basic.AnsibleModule") { + $module.Value.Deprecate($msg, "2.12") } } - - $env:TMP = $remote_tmp - Add-Type -TypeDefinition $platform_util - $env:TMP = $original_tmp } Function Get-AnsiblePrivilege { @@ -440,15 +40,15 @@ Function Get-AnsiblePrivilege { [Parameter(Mandatory=$true)][String]$Name ) - if (-not [Ansible.PrivilegeUtil.Privileges]::CheckPrivilegeName($Name)) { + if (-not [Ansible.Privilege.PrivilegeUtil]::CheckPrivilegeName($Name)) { throw [System.ArgumentException] "Invalid privilege name '$Name'" } - $process_token = [Ansible.PrivilegeUtil.Privileges]::GetCurrentProcess() - $privilege_info = [Ansible.PrivilegeUtil.Privileges]::GetAllPrivilegeInfo($process_token) + $process_token = [Ansible.Privilege.PrivilegeUtil]::GetCurrentProcess() + $privilege_info = [Ansible.Privilege.PrivilegeUtil]::GetAllPrivilegeInfo($process_token) if ($privilege_info.ContainsKey($Name)) { $status = $privilege_info.$Name - return $status.HasFlag([Ansible.PrivilegeUtil.PrivilegeAttributes]::Enabled) + return $status.HasFlag([Ansible.Privilege.PrivilegeAttributes]::Enabled) } else { return $null } @@ -487,13 +87,13 @@ Function Set-AnsiblePrivilege { throw [System.InvalidOperationException] "Cannot $($action.ToLower()) the privilege '$Name' as it has been removed from the token" } - $process_token = [Ansible.PrivilegeUtil.Privileges]::GetCurrentProcess() + $process_token = [Ansible.Privilege.PrivilegeUtil]::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 + [Ansible.Privilege.PrivilegeUtil]::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 +Export-ModuleMember -Function Import-PrivilegeUtil, Get-AnsiblePrivilege, Set-AnsiblePrivilege + diff --git a/lib/ansible/modules/windows/win_acl.ps1 b/lib/ansible/modules/windows/win_acl.ps1 index 75bd1393ab3..1715735ce66 100644 --- a/lib/ansible/modules/windows/win_acl.ps1 +++ b/lib/ansible/modules/windows/win_acl.ps1 @@ -61,7 +61,6 @@ Function SetPrivilegeTokens() { # 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 - 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. diff --git a/lib/ansible/modules/windows/win_regedit.ps1 b/lib/ansible/modules/windows/win_regedit.ps1 index 9fd0c072d50..37b7d1e09c6 100644 --- a/lib/ansible/modules/windows/win_regedit.ps1 +++ b/lib/ansible/modules/windows/win_regedit.ps1 @@ -295,7 +295,6 @@ if ($hive) { Add-Type -TypeDefinition $registry_util $env:TMP = $original_tmp - Import-PrivilegeUtil try { Set-AnsiblePrivilege -Name SeBackupPrivilege -Value $true Set-AnsiblePrivilege -Name SeRestorePrivilege -Value $true diff --git a/test/integration/targets/win_csharp_utils/library/ansible_privilege_tests.ps1 b/test/integration/targets/win_csharp_utils/library/ansible_privilege_tests.ps1 new file mode 100644 index 00000000000..7c76036a829 --- /dev/null +++ b/test/integration/targets/win_csharp_utils/library/ansible_privilege_tests.ps1 @@ -0,0 +1,324 @@ +#!powershell + +#AnsibleRequires -CSharpUtil Ansible.Basic +#Ansiblerequires -CSharpUtil Ansible.Privilege + +$module = [Ansible.Basic.AnsibleModule]::Create($args, @{}) + +Function Assert-Equals { + param( + [Parameter(Mandatory=$true, ValueFromPipeline=$true)][AllowNull()]$Actual, + [Parameter(Mandatory=$true, Position=0)][AllowNull()]$Expected + ) + + $matched = $false + if ($Actual -is [System.Collections.ArrayList] -or $Actual -is [Array]) { + $Actual.Count | Assert-Equals -Expected $Expected.Count + for ($i = 0; $i -lt $Actual.Count; $i++) { + $actual_value = $Actual[$i] + $expected_value = $Expected[$i] + Assert-Equals -Actual $actual_value -Expected $expected_value + } + $matched = $true + } else { + $matched = $Actual -ceq $Expected + } + + if (-not $matched) { + if ($Actual -is [PSObject]) { + $Actual = $Actual.ToString() + } + + $call_stack = (Get-PSCallStack)[1] + $module.Result.test = $test + $module.Result.actual = $Actual + $module.Result.expected = $Expected + $module.Result.line = $call_stack.ScriptLineNumber + $module.Result.method = $call_stack.Position.Text + $module.FailJson("AssertionError: actual != expected") + } +} + +Function Assert-DictionaryEquals { + param( + [Parameter(Mandatory=$true, ValueFromPipeline=$true)][AllowNull()]$Actual, + [Parameter(Mandatory=$true, Position=0)][AllowNull()]$Expected + ) + $actual_keys = $Actual.Keys + $expected_keys = $Expected.Keys + + $actual_keys.Count | Assert-Equals -Expected $expected_keys.Count + foreach ($actual_entry in $Actual.GetEnumerator()) { + $actual_key = $actual_entry.Key + ($actual_key -cin $expected_keys) | Assert-Equals -Expected $true + $actual_value = $actual_entry.Value + $expected_value = $Expected.$actual_key + + if ($actual_value -is [System.Collections.IDictionary]) { + $actual_value | Assert-DictionaryEquals -Expected $expected_value + } elseif ($actual_value -is [System.Collections.ArrayList]) { + for ($i = 0; $i -lt $actual_value.Count; $i++) { + $actual_entry = $actual_value[$i] + $expected_entry = $expected_value[$i] + if ($actual_entry -is [System.Collections.IDictionary]) { + $actual_entry | Assert-DictionaryEquals -Expected $expected_entry + } else { + Assert-Equals -Actual $actual_entry -Expected $expected_entry + } + } + } else { + Assert-Equals -Actual $actual_value -Expected $expected_value + } + } + foreach ($expected_key in $expected_keys) { + ($expected_key -cin $actual_keys) | Assert-Equals -Expected $true + } +} + +Function Assert-Equals { + param( + [Parameter(Mandatory=$true, ValueFromPipeline=$true)][AllowNull()]$Actual, + [Parameter(Mandatory=$true, Position=0)][AllowNull()]$Expected + ) + + $matched = $false + if ($Actual -is [System.Collections.ArrayList] -or $Actual -is [Array]) { + $Actual.Count | Assert-Equals -Expected $Expected.Count + for ($i = 0; $i -lt $Actual.Count; $i++) { + $actual_value = $Actual[$i] + $expected_value = $Expected[$i] + Assert-Equals -Actual $actual_value -Expected $expected_value + } + $matched = $true + } else { + $matched = $Actual -ceq $Expected + } + + if (-not $matched) { + if ($Actual -is [PSObject]) { + $Actual = $Actual.ToString() + } + + $call_stack = (Get-PSCallStack)[1] + $module.Result.test = $test + $module.Result.actual = $Actual + $module.Result.expected = $Expected + $module.Result.line = $call_stack.ScriptLineNumber + $module.Result.method = $call_stack.Position.Text + $module.FailJson("AssertionError: actual != expected") + } +} + +Function Assert-DictionaryEquals { + param( + [Parameter(Mandatory=$true, ValueFromPipeline=$true)][AllowNull()]$Actual, + [Parameter(Mandatory=$true, Position=0)][AllowNull()]$Expected + ) + $actual_keys = $Actual.Keys + $expected_keys = $Expected.Keys + + $actual_keys.Count | Assert-Equals -Expected $expected_keys.Count + foreach ($actual_entry in $Actual.GetEnumerator()) { + $actual_key = $actual_entry.Key + ($actual_key -cin $expected_keys) | Assert-Equals -Expected $true + $actual_value = $actual_entry.Value + $expected_value = $Expected.$actual_key + + if ($actual_value -is [System.Collections.IDictionary]) { + $actual_value | Assert-DictionaryEquals -Expected $expected_value + } elseif ($actual_value -is [System.Collections.ArrayList]) { + for ($i = 0; $i -lt $actual_value.Count; $i++) { + $actual_entry = $actual_value[$i] + $expected_entry = $expected_value[$i] + if ($actual_entry -is [System.Collections.IDictionary]) { + $actual_entry | Assert-DictionaryEquals -Expected $expected_entry + } else { + Assert-Equals -Actual $actual_entry -Expected $expected_entry + } + } + } else { + Assert-Equals -Actual $actual_value -Expected $expected_value + } + } + foreach ($expected_key in $expected_keys) { + ($expected_key -cin $actual_keys) | Assert-Equals -Expected $true + } +} + +$process = [Ansible.Privilege.PrivilegeUtil]::GetCurrentProcess() + +$tests = @{ + "Check valid privilege name" = { + $actual = [Ansible.Privilege.PrivilegeUtil]::CheckPrivilegeName("SeTcbPrivilege") + $actual | Assert-Equals -Expected $true + } + + "Check invalid privilege name" = { + $actual = [Ansible.Privilege.PrivilegeUtil]::CheckPrivilegeName("SeFake") + $actual | Assert-Equals -Expected $false + } + + "Disable a privilege" = { + # Ensure the privilege is enabled at the start + [Ansible.Privilege.PrivilegeUtil]::EnablePrivilege($process, "SeTimeZonePrivilege") > $null + + $actual = [Ansible.Privilege.PrivilegeUtil]::DisablePrivilege($process, "SeTimeZonePrivilege") + $actual.GetType().Name | Assert-Equals -Expected 'Dictionary`2' + $actual.Count | Assert-Equals -Expected 1 + $actual.SeTimeZonePrivilege | Assert-Equals -Expected $true + + # Disable again + $actual = [Ansible.Privilege.PrivilegeUtil]::DisablePrivilege($process, "SeTimeZonePrivilege") + $actual.GetType().Name | Assert-Equals -Expected 'Dictionary`2' + $actual.Count | Assert-Equals -Expected 0 + } + + "Enable a privilege" = { + # Ensure the privilege is disabled at the start + [Ansible.Privilege.PrivilegeUtil]::DisablePrivilege($process, "SeTimeZonePrivilege") > $null + + $actual = [Ansible.Privilege.PrivilegeUtil]::EnablePrivilege($process, "SeTimeZonePrivilege") + $actual.GetType().Name | Assert-Equals -Expected 'Dictionary`2' + $actual.Count | Assert-Equals -Expected 1 + $actual.SeTimeZonePrivilege | Assert-Equals -Expected $false + + # Disable again + $actual = [Ansible.Privilege.PrivilegeUtil]::EnablePrivilege($process, "SeTimeZonePrivilege") + $actual.GetType().Name | Assert-Equals -Expected 'Dictionary`2' + $actual.Count | Assert-Equals -Expected 0 + } + + "Disable and revert privileges" = { + $current_state = [Ansible.Privilege.PrivilegeUtil]::GetAllPrivilegeInfo($process) + + $previous_state = [Ansible.Privilege.PrivilegeUtil]::DisableAllPrivileges($process) + $previous_state.GetType().Name | Assert-Equals -Expected 'Dictionary`2' + foreach ($previous_state_entry in $previous_state.GetEnumerator()) { + $previous_state_entry.Value | Assert-Equals -Expected $true + } + + # Disable again + $previous_state2 = [Ansible.Privilege.PrivilegeUtil]::DisableAllPrivileges($process) + $previous_state2.Count | Assert-Equals -Expected 0 + + $actual = [Ansible.Privilege.PrivilegeUtil]::GetAllPrivilegeInfo($process) + foreach ($actual_entry in $actual.GetEnumerator()) { + $actual_entry.Value -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected 0 + } + + [Ansible.Privilege.PrivilegeUtil]::SetTokenPrivileges($process, $previous_state) > $null + $actual = [Ansible.Privilege.PrivilegeUtil]::GetAllPrivilegeInfo($process) + $actual | Assert-DictionaryEquals -Expected $current_state + } + + "Remove a privilege" = { + [Ansible.Privilege.PrivilegeUtil]::RemovePrivilege($process, "SeUndockPrivilege") > $null + $actual = [Ansible.Privilege.PrivilegeUtil]::GetAllPrivilegeInfo($process) + $actual.ContainsKey("SeUndockPrivilege") | Assert-Equals -Expected $false + } + + "Test Enabler" = { + # Disable privilege at the start + $new_state = @{ + SeTimeZonePrivilege = $false + SeShutdownPrivilege = $false + SeIncreaseWorkingSetPrivilege = $false + } + [Ansible.Privilege.PrivilegeUtil]::SetTokenPrivileges($process, $new_state) > $null + $check_state = [Ansible.Privilege.PrivilegeUtil]::GetAllPrivilegeInfo($process) + $check_state.SeTimeZonePrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected 0 + $check_state.SeShutdownPrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected 0 + $check_state.SeIncreaseWorkingSetPrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected 0 + + # Check that strict = false won't validate privileges not held but activates the ones we want + $enabler = New-Object -TypeName Ansible.Privilege.PrivilegeEnabler -ArgumentList $false, "SeTimeZonePrivilege", "SeShutdownPrivilege", "SeTcbPrivilege" + $actual = [Ansible.Privilege.PrivilegeUtil]::GetAllPrivilegeInfo($process) + $actual.SeTimeZonePrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected ([Ansible.Privilege.PrivilegeAttributes]::Enabled) + $actual.SeShutdownPrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected ([Ansible.Privilege.PrivilegeAttributes]::Enabled) + $actual.SeIncreaseWorkingSetPrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected 0 + $actual.ContainsKey("SeTcbPrivilege") | Assert-Equals -Expected $false + + # Now verify a no-op enabler will not rever back to disabled + $enabler2 = New-Object -TypeName Ansible.Privilege.PrivilegeEnabler -ArgumentList $false, "SeTimeZonePrivilege", "SeShutdownPrivilege", "SeTcbPrivilege" + $enabler2.Dispose() + $actual = [Ansible.Privilege.PrivilegeUtil]::GetAllPrivilegeInfo($process) + $actual.SeTimeZonePrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected ([Ansible.Privilege.PrivilegeAttributes]::Enabled) + $actual.SeShutdownPrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected ([Ansible.Privilege.PrivilegeAttributes]::Enabled) + + # Verify that when disposing the object the privileges are reverted + $enabler.Dispose() + $actual = [Ansible.Privilege.PrivilegeUtil]::GetAllPrivilegeInfo($process) + $actual.SeTimeZonePrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected 0 + $actual.SeShutdownPrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected 0 + } + + "Test Enabler strict" = { + # Disable privilege at the start + $new_state = @{ + SeTimeZonePrivilege = $false + SeShutdownPrivilege = $false + SeIncreaseWorkingSetPrivilege = $false + } + [Ansible.Privilege.PrivilegeUtil]::SetTokenPrivileges($process, $new_state) > $null + $check_state = [Ansible.Privilege.PrivilegeUtil]::GetAllPrivilegeInfo($process) + $check_state.SeTimeZonePrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected 0 + $check_state.SeShutdownPrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected 0 + $check_state.SeIncreaseWorkingSetPrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected 0 + + # Check that strict = false won't validate privileges not held but activates the ones we want + $enabler = New-Object -TypeName Ansible.Privilege.PrivilegeEnabler -ArgumentList $true, "SeTimeZonePrivilege", "SeShutdownPrivilege" + $actual = [Ansible.Privilege.PrivilegeUtil]::GetAllPrivilegeInfo($process) + $actual.SeTimeZonePrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected ([Ansible.Privilege.PrivilegeAttributes]::Enabled) + $actual.SeShutdownPrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected ([Ansible.Privilege.PrivilegeAttributes]::Enabled) + $actual.SeIncreaseWorkingSetPrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected 0 + + # Now verify a no-op enabler will not rever back to disabled + $enabler2 = New-Object -TypeName Ansible.Privilege.PrivilegeEnabler -ArgumentList $true, "SeTimeZonePrivilege", "SeShutdownPrivilege" + $enabler2.Dispose() + $actual = [Ansible.Privilege.PrivilegeUtil]::GetAllPrivilegeInfo($process) + $actual.SeTimeZonePrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected ([Ansible.Privilege.PrivilegeAttributes]::Enabled) + $actual.SeShutdownPrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected ([Ansible.Privilege.PrivilegeAttributes]::Enabled) + + # Verify that when disposing the object the privileges are reverted + $enabler.Dispose() + $actual = [Ansible.Privilege.PrivilegeUtil]::GetAllPrivilegeInfo($process) + $actual.SeTimeZonePrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected 0 + $actual.SeShutdownPrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected 0 + } + + "Test Enabler invalid privilege" = { + $failed = $false + try { + New-Object -TypeName Ansible.Privilege.PrivilegeEnabler -ArgumentList $false, "SeTimeZonePrivilege", "SeFake" + } catch { + $failed = $true + $_.Exception.InnerException.Message | Assert-Equals -Expected "Failed to enable privilege(s) SeTimeZonePrivilege, SeFake (A specified privilege does not exist, Win32ErrorCode 1313)" + } + $failed | Assert-Equals -Expected $true + } + + "Test Enabler strict failure" = { + # Start disabled + [Ansible.Privilege.PrivilegeUtil]::DisablePrivilege($process, "SeTimeZonePrivilege") > $null + $check_state = [Ansible.Privilege.PrivilegeUtil]::GetAllPrivilegeInfo($process) + $check_state.SeTimeZonePrivilege -band [Ansible.Privilege.PrivilegeAttributes]::Enabled | Assert-Equals -Expected 0 + + $failed = $false + try { + New-Object -TypeName Ansible.Privilege.PrivilegeEnabler -ArgumentList $true, "SeTimeZonePrivilege", "SeTcbPrivilege" + } catch { + $failed = $true + $_.Exception.InnerException.Message | Assert-Equals -Expected "Failed to enable privilege(s) SeTimeZonePrivilege, SeTcbPrivilege (Not all privileges or groups referenced are assigned to the caller, Win32ErrorCode 1300)" + } + $failed | Assert-Equals -Expected $true + } +} + +foreach ($test_impl in $tests.GetEnumerator()) { + $test = $test_impl.Key + &$test_impl.Value +} + +$module.Result.data = "success" +$module.ExitJson() + diff --git a/test/integration/targets/win_csharp_utils/tasks/main.yml b/test/integration/targets/win_csharp_utils/tasks/main.yml index 2e5506dfc07..64c7b31675d 100644 --- a/test/integration/targets/win_csharp_utils/tasks/main.yml +++ b/test/integration/targets/win_csharp_utils/tasks/main.yml @@ -44,3 +44,12 @@ assert: that: - ansible_process_tests.data == "success" + +- name: test Ansible.Privilege.cs + ansible_privilege_tests: + register: ansible_privilege_test + +- name: assert test Ansible.Privilege.cs + assert: + that: + - ansible_privilege_test.data == "success" 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 index 0bbfadb73f4..e1ca25da812 100644 --- a/test/integration/targets/win_module_utils/library/privilege_util_test.ps1 +++ b/test/integration/targets/win_module_utils/library/privilege_util_test.ps1 @@ -1,21 +1,18 @@ #!powershell -#Requires -Module Ansible.ModuleUtils.Legacy +#AnsibleRequires -CSharpUtil Ansible.Basic #Requires -Module Ansible.ModuleUtils.PrivilegeUtil -$ErrorActionPreference = "Stop" - -$result = @{ - changed = $false -} - -Import-PrivilegeUtil +$module = [Ansible.Basic.AnsibleModule]::Create($args, @{}) 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 + $module.Result.actual = $actual + $module.Result.expected = $expected + $module.Result.line = $call_stack.ScriptLineNumber + $module.Result.method = $call_stack.Position.Text + $module.FailJson("AssertionError: actual != expected") } } @@ -63,11 +60,7 @@ 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 +$process = [Ansible.Privilege.PrivilegeUtil]::GetCurrentProcess() ### Test PS cmdlets ### # test ps Get-AnsiblePrivilege @@ -81,16 +74,16 @@ foreach ($privilege in $total_privileges) { } # test c# GetAllPrivilegeInfo -$actual = [Ansible.PrivilegeUtil.Privileges]::GetAllPrivilegeInfo($process) +$actual = [Ansible.Privilege.PrivilegeUtil]::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 + Assert-Equals -actual $actual_value.HasFlag([Ansible.Privilege.PrivilegeAttributes]::Enabled) -expected $true } else { - Assert-Equals -actual $actual_value.HasFlag([Ansible.PrivilegeUtil.PrivilegeAttributes]::Enabled) -expected $false + Assert-Equals -actual $actual_value.HasFlag([Ansible.Privilege.PrivilegeAttributes]::Enabled) -expected $false } } } @@ -114,51 +107,6 @@ 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 +$module.Result.data = "success" +$module.ExitJson() -$result.data = "success" -Exit-Json -obj $result