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
This commit is contained in:
parent
695feea541
commit
4019d4f6d1
9 changed files with 811 additions and 488 deletions
2
changelogs/fragments/win_privileges_util.yaml
Normal file
2
changelogs/fragments/win_privileges_util.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
minor_changes:
|
||||||
|
- Ansible.ModuleUtils.Privilege - moved C# code to it's own util called ``Ansible.Privilege`` and expanded the tests
|
443
lib/ansible/module_utils/csharp/Ansible.Privilege.cs
Normal file
443
lib/ansible/module_utils/csharp/Ansible.Privilege.cs
Normal file
|
@ -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<string, bool?> previousState;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Temporarily enables the privileges specified and reverts once the class is disposed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="strict">Whether to fail if any privilege failed to be enabled, if false then this will continue silently</param>
|
||||||
|
/// <param name="privileges">A list of privileges to enable</param>
|
||||||
|
public PrivilegeEnabler(bool strict, params string[] privileges)
|
||||||
|
{
|
||||||
|
if (privileges.Length > 0)
|
||||||
|
{
|
||||||
|
process = PrivilegeUtil.GetCurrentProcess();
|
||||||
|
Dictionary<string, bool?> newState = new Dictionary<string, bool?>();
|
||||||
|
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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the specific privilege constant is a valid privilege name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The privilege constant (Se*Privilege) is valid</param>
|
||||||
|
/// <returns>true if valid, else false</returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disables the privilege specified
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token">The process token to that contains the privilege to disable</param>
|
||||||
|
/// <param name="privilege">The privilege constant to disable</param>
|
||||||
|
/// <returns>The previous state that can be passed to SetTokenPrivileges to revert the action</returns>
|
||||||
|
public static Dictionary<string, bool?> DisablePrivilege(SafeHandle token, string privilege)
|
||||||
|
{
|
||||||
|
return SetTokenPrivileges(token, new Dictionary<string, bool?>() { { privilege, false } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disables all the privileges
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token">The process token to that contains the privilege to disable</param>
|
||||||
|
/// <returns>The previous state that can be passed to SetTokenPrivileges to revert the action</returns>
|
||||||
|
public static Dictionary<string, bool?> DisableAllPrivileges(SafeHandle token)
|
||||||
|
{
|
||||||
|
return AdjustTokenPrivileges(token, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables the privilege specified
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token">The process token to that contains the privilege to enable</param>
|
||||||
|
/// <param name="privilege">The privilege constant to enable</param>
|
||||||
|
/// <returns>The previous state that can be passed to SetTokenPrivileges to revert the action</returns>
|
||||||
|
public static Dictionary<string, bool?> EnablePrivilege(SafeHandle token, string privilege)
|
||||||
|
{
|
||||||
|
return SetTokenPrivileges(token, new Dictionary<string, bool?>() { { privilege, true } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get's the status of all the privileges on the token specified
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token">The process token to get the privilege status on</param>
|
||||||
|
/// <returns>Dictionary where the key is the privilege constant and the value is the PrivilegeAttributes flags</returns>
|
||||||
|
public static Dictionary<String, PrivilegeAttributes> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a handle to the current process for use with the methods above
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>SafeWaitHandle handle of the current process token</returns>
|
||||||
|
public static SafeWaitHandle GetCurrentProcess()
|
||||||
|
{
|
||||||
|
return NativeMethods.GetCurrentProcess();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a privilege from the token. This operation is irreversible
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token">The process token to that contains the privilege to remove</param>
|
||||||
|
/// <param name="privilege">The privilege constant to remove</param>
|
||||||
|
public static void RemovePrivilege(SafeHandle token, string privilege)
|
||||||
|
{
|
||||||
|
SetTokenPrivileges(token, new Dictionary<string, bool?>() { { privilege, null } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Do a bulk set of multiple privileges
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token">The process token to use when setting the privilege state</param>
|
||||||
|
/// <param name="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)
|
||||||
|
/// </param>
|
||||||
|
/// <param name="strict">When true, will fail if one privilege failed to be set, otherwise it will silently continue</param>
|
||||||
|
/// <returns>The previous state that can be passed to SetTokenPrivileges to revert the action</returns>
|
||||||
|
public static Dictionary<string, bool?> 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<string, bool?> 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>(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>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -408,7 +408,6 @@ namespace Ansible
|
||||||
Add-Type -TypeDefinition $link_util
|
Add-Type -TypeDefinition $link_util
|
||||||
$env:TMP = $original_tmp
|
$env:TMP = $original_tmp
|
||||||
|
|
||||||
Import-PrivilegeUtil
|
|
||||||
# enable the SeBackupPrivilege if it is disabled
|
# enable the SeBackupPrivilege if it is disabled
|
||||||
$state = Get-AnsiblePrivilege -Name SeBackupPrivilege
|
$state = Get-AnsiblePrivilege -Name SeBackupPrivilege
|
||||||
if ($state -eq $false) {
|
if ($state -eq $false) {
|
||||||
|
|
|
@ -1,424 +1,24 @@
|
||||||
# Copyright (c) 2018 Ansible Project
|
# Copyright (c) 2018 Ansible Project
|
||||||
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
|
# 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
|
#AnsibleRequires -CSharpUtil Ansible.Privilege
|
||||||
# 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<string, bool?> DisablePrivilege(SafeHandle token, string privilege)
|
|
||||||
{
|
|
||||||
return SetTokenPrivileges(token, new Dictionary<string, bool?>() { { privilege, false } });
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Dictionary<string, bool?> DisableAllPrivileges(SafeHandle token)
|
|
||||||
{
|
|
||||||
return AdjustTokenPrivileges(token, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Dictionary<string, bool?> EnablePrivilege(SafeHandle token, string privilege)
|
|
||||||
{
|
|
||||||
return SetTokenPrivileges(token, new Dictionary<string, bool?>() { { privilege, true } });
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Dictionary<String, PrivilegeAttributes> GetAllPrivilegeInfo(SafeHandle token)
|
|
||||||
{
|
|
||||||
IntPtr hToken = IntPtr.Zero;
|
|
||||||
if (!NativeMethods.OpenProcessToken(token, TokenAccessLevels.Query, out hToken))
|
|
||||||
throw new Win32Exception("OpenProcessToken() failed");
|
|
||||||
|
|
||||||
Dictionary<String, PrivilegeAttributes> info = new Dictionary<String, PrivilegeAttributes>();
|
|
||||||
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<string, bool?>() { { privilege, null } });
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Dictionary<string, bool?> SetTokenPrivileges(SafeHandle token, Dictionary<string, bool?> state)
|
|
||||||
{
|
|
||||||
NativeHelpers.LUID_AND_ATTRIBUTES[] privilegeAttr = new NativeHelpers.LUID_AND_ATTRIBUTES[state.Count];
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
foreach (KeyValuePair<string, bool?> 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<string, bool?> 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>(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>(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 {
|
Function Import-PrivilegeUtil {
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Compiles the C# code that can be used to manage Windows privileges from an
|
No-op, as the C# types are automatically loaded.
|
||||||
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()]
|
[CmdletBinding()]
|
||||||
# build the C# code to compile
|
Param()
|
||||||
$namespace_import = ($ansible_privilege_util_namespaces | ForEach-Object { "using $_;" }) -join "`r`n"
|
$msg = "Import-PrivilegeUtil is deprecated and no longer needed, this cmdlet will be removed in a future version"
|
||||||
$platform_util = "$namespace_import`r`n`r`n$ansible_privilege_util_code"
|
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
|
||||||
# FUTURE: find a better way to get the _ansible_remote_tmp variable
|
} else {
|
||||||
# this is used to force csc to compile the C# code in the remote tmp
|
$module = Get-Variable -Name module -ErrorAction SilentlyContinue
|
||||||
# specified
|
if ($null -ne $module -and $module.Value.GetType().FullName -eq "Ansible.Basic.AnsibleModule") {
|
||||||
$original_tmp = $env:TMP
|
$module.Value.Deprecate($msg, "2.12")
|
||||||
|
|
||||||
$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 {
|
Function Get-AnsiblePrivilege {
|
||||||
|
@ -440,15 +40,15 @@ Function Get-AnsiblePrivilege {
|
||||||
[Parameter(Mandatory=$true)][String]$Name
|
[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'"
|
throw [System.ArgumentException] "Invalid privilege name '$Name'"
|
||||||
}
|
}
|
||||||
|
|
||||||
$process_token = [Ansible.PrivilegeUtil.Privileges]::GetCurrentProcess()
|
$process_token = [Ansible.Privilege.PrivilegeUtil]::GetCurrentProcess()
|
||||||
$privilege_info = [Ansible.PrivilegeUtil.Privileges]::GetAllPrivilegeInfo($process_token)
|
$privilege_info = [Ansible.Privilege.PrivilegeUtil]::GetAllPrivilegeInfo($process_token)
|
||||||
if ($privilege_info.ContainsKey($Name)) {
|
if ($privilege_info.ContainsKey($Name)) {
|
||||||
$status = $privilege_info.$Name
|
$status = $privilege_info.$Name
|
||||||
return $status.HasFlag([Ansible.PrivilegeUtil.PrivilegeAttributes]::Enabled)
|
return $status.HasFlag([Ansible.Privilege.PrivilegeAttributes]::Enabled)
|
||||||
} else {
|
} else {
|
||||||
return $null
|
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"
|
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")) {
|
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 = New-Object -TypeName 'System.Collections.Generic.Dictionary`2[[System.String], [System.Nullable`1[System.Boolean]]]'
|
||||||
$new_state.Add($Name, $Value)
|
$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 `
|
Export-ModuleMember -Function Import-PrivilegeUtil, Get-AnsiblePrivilege, Set-AnsiblePrivilege
|
||||||
-Variable ansible_privilege_util_namespaces, ansible_privilege_util_code
|
|
||||||
|
|
|
@ -61,7 +61,6 @@ Function SetPrivilegeTokens() {
|
||||||
# This also sets us up for setting the owner as a feature.
|
# This also sets us up for setting the owner as a feature.
|
||||||
# See the following for details of each privilege
|
# See the following for details of each privilege
|
||||||
# https://msdn.microsoft.com/en-us/library/windows/desktop/bb530716(v=vs.85).aspx
|
# https://msdn.microsoft.com/en-us/library/windows/desktop/bb530716(v=vs.85).aspx
|
||||||
Import-PrivilegeUtil
|
|
||||||
$privileges = @(
|
$privileges = @(
|
||||||
"SeRestorePrivilege", # Grants all write access control to any file, regardless of ACL.
|
"SeRestorePrivilege", # Grants all write access control to any file, regardless of ACL.
|
||||||
"SeBackupPrivilege", # Grants all read access control to any file, regardless of ACL.
|
"SeBackupPrivilege", # Grants all read access control to any file, regardless of ACL.
|
||||||
|
|
|
@ -295,7 +295,6 @@ if ($hive) {
|
||||||
Add-Type -TypeDefinition $registry_util
|
Add-Type -TypeDefinition $registry_util
|
||||||
$env:TMP = $original_tmp
|
$env:TMP = $original_tmp
|
||||||
|
|
||||||
Import-PrivilegeUtil
|
|
||||||
try {
|
try {
|
||||||
Set-AnsiblePrivilege -Name SeBackupPrivilege -Value $true
|
Set-AnsiblePrivilege -Name SeBackupPrivilege -Value $true
|
||||||
Set-AnsiblePrivilege -Name SeRestorePrivilege -Value $true
|
Set-AnsiblePrivilege -Name SeRestorePrivilege -Value $true
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -44,3 +44,12 @@
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- ansible_process_tests.data == "success"
|
- 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"
|
||||||
|
|
|
@ -1,21 +1,18 @@
|
||||||
#!powershell
|
#!powershell
|
||||||
|
|
||||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||||
#Requires -Module Ansible.ModuleUtils.PrivilegeUtil
|
#Requires -Module Ansible.ModuleUtils.PrivilegeUtil
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$module = [Ansible.Basic.AnsibleModule]::Create($args, @{})
|
||||||
|
|
||||||
$result = @{
|
|
||||||
changed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
Import-PrivilegeUtil
|
|
||||||
|
|
||||||
Function Assert-Equals($actual, $expected) {
|
Function Assert-Equals($actual, $expected) {
|
||||||
if ($actual -cne $expected) {
|
if ($actual -cne $expected) {
|
||||||
$call_stack = (Get-PSCallStack)[1]
|
$call_stack = (Get-PSCallStack)[1]
|
||||||
$error_msg = "AssertionError:`r`nActual: `"$actual`" != Expected: `"$expected`"`r`nLine: $($call_stack.ScriptLineNumber), Method: $($call_stack.Position.Text)"
|
$module.Result.actual = $actual
|
||||||
Fail-Json -obj $result -message $error_msg
|
$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 " "
|
$split = $raw_privilege.TrimEnd() -split " "
|
||||||
$actual_privileges."$($split[0])" = ($split[-1] -eq "Enabled")
|
$actual_privileges."$($split[0])" = ($split[-1] -eq "Enabled")
|
||||||
}
|
}
|
||||||
$process = [Ansible.PrivilegeUtil.Privileges]::GetCurrentProcess()
|
$process = [Ansible.Privilege.PrivilegeUtil]::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 cmdlets ###
|
||||||
# test ps Get-AnsiblePrivilege
|
# test ps Get-AnsiblePrivilege
|
||||||
|
@ -81,16 +74,16 @@ foreach ($privilege in $total_privileges) {
|
||||||
}
|
}
|
||||||
|
|
||||||
# test c# GetAllPrivilegeInfo
|
# 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.GetType().Name -expected 'Dictionary`2'
|
||||||
Assert-Equals -actual $actual.Count -expected $actual_privileges.Count
|
Assert-Equals -actual $actual.Count -expected $actual_privileges.Count
|
||||||
foreach ($privilege in $total_privileges) {
|
foreach ($privilege in $total_privileges) {
|
||||||
if ($actual_privileges.ContainsKey($privilege)) {
|
if ($actual_privileges.ContainsKey($privilege)) {
|
||||||
$actual_value = $actual.$privilege
|
$actual_value = $actual.$privilege
|
||||||
if ($actual_privileges.$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 {
|
} 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
|
$actual = Get-AnsiblePrivilege -Name SeUndockPrivilege
|
||||||
Assert-Equals -actual $actual -expected $false
|
Assert-Equals -actual $actual -expected $false
|
||||||
|
|
||||||
### Test C# code ###
|
$module.Result.data = "success"
|
||||||
# test CheckPrivilegeName
|
$module.ExitJson()
|
||||||
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
|
|
||||||
|
|
Loading…
Reference in a new issue