1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

Add Ansible.ModuleUtils.PrivilegeUtil and converted code to use it (#43179)

* Add Ansible.ModuleUtils.PrivilegeUtil and converted code to use it

* Changed namespace and class to be a better standard and fixed some typos

* Changes from review

* changes to avoid out of bound mem of server 2008

* changes to detect failure when setting a privileged not allowed
This commit is contained in:
Jordan Borean 2018-07-31 07:48:54 +10:00 committed by Matt Davis
parent d79027b77f
commit 9259f31fee
18 changed files with 708 additions and 298 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- Added PrivilegeUtil PowerShell module util to easily control Windows Privileges in a process

View file

@ -338,7 +338,6 @@ Function Load-CommandUtils {
# FUTURE: find a better way to get the _ansible_remote_tmp variable
$original_tmp = $env:TMP
$original_temp = $env:TEMP
$remote_tmp = $original_tmp
$module_params = Get-Variable -Name complex_args -ErrorAction SilentlyContinue
@ -350,10 +349,8 @@ Function Load-CommandUtils {
}
$env:TMP = $remote_tmp
$env:TEMP = $remote_tmp
Add-Type -TypeDefinition $process_util
$env:TMP = $original_tmp
$env:TEMP = $original_temp
}
Function Get-ExecutablePath($executable, $directory) {

View file

@ -1,6 +1,8 @@
# Copyright (c) 2017 Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
#Requires -Module Ansible.ModuleUtils.PrivilegeUtil
Function Load-LinkUtils() {
$link_util = @'
using Microsoft.Win32.SafeHandles;
@ -44,21 +46,6 @@ namespace Ansible
public string[] HardTargets { get; internal set; }
}
[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
public UInt32 LowPart;
public Int32 HighPart;
}
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_PRIVILEGES
{
public UInt32 PrivilegeCount;
public LUID Luid;
public UInt32 Attributes;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct REPARSE_DATA_BUFFER
{
@ -78,10 +65,6 @@ namespace Ansible
{
public const int MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 1024 * 16;
private const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
private const int TOKEN_QUERY = 0x00000008;
private const int SE_PRIVILEGE_ENABLED = 0x00000002;
private const UInt32 FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
private const UInt32 FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000;
@ -101,34 +84,6 @@ namespace Ansible
private const UInt32 SYMBOLIC_LINK_FLAG_FILE = 0x00000000;
private const UInt32 SYMBOLIC_LINK_FLAG_DIRECTORY = 0x00000001;
[DllImport("kernel32.dll")]
private static extern IntPtr GetCurrentProcess();
[DllImport("kernel32.dll")]
private static extern bool CloseHandle(
IntPtr hObject);
[DllImport("advapi32.dll")]
private static extern bool OpenProcessToken(
IntPtr ProcessHandle,
UInt32 DesiredAccess,
out IntPtr TokenHandle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
private static extern bool LookupPrivilegeValue(
string lpSystemName,
string lpName,
[MarshalAs(UnmanagedType.Struct)] out LUID lpLuid);
[DllImport("advapi32.dll")]
private static extern bool AdjustTokenPrivileges(
IntPtr TokenHandle,
[MarshalAs(UnmanagedType.Bool)] bool DisableAllPrivileges,
ref TOKEN_PRIVILEGES NewState,
UInt32 BufferLength,
IntPtr PreviousState,
IntPtr ReturnLength);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern SafeFileHandle CreateFile(
string lpFileName,
@ -206,33 +161,6 @@ namespace Ansible
string lpExistingFileName,
IntPtr lpSecurityAttributes);
public static void EnablePrivilege(string privilege)
{
TOKEN_PRIVILEGES tkpPrivileges;
IntPtr hToken;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out hToken))
throw new LinkUtilWin32Exception("OpenProcessToken failed");
try
{
LUID luid;
if (!LookupPrivilegeValue(null, privilege, out luid))
throw new LinkUtilWin32Exception(String.Format("LookupPrivilegeValue({0}) failed", privilege));
tkpPrivileges.PrivilegeCount = 1;
tkpPrivileges.Luid = luid;
tkpPrivileges.Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, false, ref tkpPrivileges, 0, IntPtr.Zero, IntPtr.Zero))
throw new LinkUtilWin32Exception(String.Format("AdjustTokenPrivileges({0}) failed", privilege));
}
finally
{
CloseHandle(hToken);
}
}
public static LinkInfo GetLinkInfo(string linkPath)
{
FileAttributes attr = File.GetAttributes(linkPath);
@ -466,7 +394,6 @@ namespace Ansible
# FUTURE: find a better way to get the _ansible_remote_tmp variable
$original_tmp = $env:TMP
$original_temp = $env:TEMP
$remote_tmp = $original_tmp
$module_params = Get-Variable -Name complex_args -ErrorAction SilentlyContinue
@ -478,12 +405,15 @@ namespace Ansible
}
$env:TMP = $remote_tmp
$env:TEMP = $remote_tmp
Add-Type -TypeDefinition $link_util
$env:TMP = $original_tmp
$env:TEMP = $original_temp
[Ansible.LinkUtil]::EnablePrivilege("SeBackupPrivilege")
Import-PrivilegeUtil
# enable the SeBackupPrivilege if it is disabled
$state = Get-AnsiblePrivilege -Name SeBackupPrivilege
if ($state -eq $false) {
Set-AnsiblePrivilege -Name SeBackupPrivilege -Value $true
}
}
Function Get-Link($link_path) {

View file

@ -0,0 +1,499 @@
# Copyright (c) 2018 Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
# store in separate variables to make it easier for other module_utils to
# share this code in their own c# code
$ansible_privilege_util_namespaces = @(
"Microsoft.Win32.SafeHandles",
"System",
"System.Collections.Generic",
"System.Linq",
"System.Runtime.InteropServices",
"System.Security.Principal",
"System.Text"
)
$ansible_privilege_util_code = @'
namespace Ansible.PrivilegeUtil
{
[Flags]
public enum PrivilegeAttributes : uint
{
Disabled = 0x00000000,
EnabledByDefault = 0x00000001,
Enabled = 0x00000002,
Removed = 0x00000004,
UsedForAccess = 0x80000000,
}
internal class NativeHelpers
{
[StructLayout(LayoutKind.Sequential)]
internal struct LUID
{
public UInt32 LowPart;
public Int32 HighPart;
}
[StructLayout(LayoutKind.Sequential)]
internal struct LUID_AND_ATTRIBUTES
{
public LUID Luid;
public PrivilegeAttributes Attributes;
}
[StructLayout(LayoutKind.Sequential)]
internal struct TOKEN_PRIVILEGES
{
public UInt32 PrivilegeCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public LUID_AND_ATTRIBUTES[] Privileges;
}
}
internal class NativeMethods
{
[DllImport("advapi32.dll", SetLastError = true)]
internal static extern bool AdjustTokenPrivileges(
IntPtr TokenHandle,
[MarshalAs(UnmanagedType.Bool)] bool DisableAllPrivileges,
IntPtr NewState,
UInt32 BufferLength,
IntPtr PreviousState,
out UInt32 ReturnLength);
[DllImport("kernel32.dll")]
internal static extern bool CloseHandle(
IntPtr hObject);
[DllImport("kernel32")]
internal static extern SafeWaitHandle GetCurrentProcess();
[DllImport("advapi32.dll", SetLastError = true)]
internal static extern bool GetTokenInformation(
IntPtr TokenHandle,
UInt32 TokenInformationClass,
IntPtr TokenInformation,
UInt32 TokenInformationLength,
out UInt32 ReturnLength);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool LookupPrivilegeName(
string lpSystemName,
ref NativeHelpers.LUID lpLuid,
StringBuilder lpName,
ref UInt32 cchName);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool LookupPrivilegeValue(
string lpSystemName,
string lpName,
out NativeHelpers.LUID lpLuid);
[DllImport("advapi32.dll", SetLastError = true)]
internal static extern bool OpenProcessToken(
SafeHandle ProcessHandle,
TokenAccessLevels DesiredAccess,
out IntPtr TokenHandle);
}
public class Win32Exception : System.ComponentModel.Win32Exception
{
private string _msg;
public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
public Win32Exception(int errorCode, string message) : base(errorCode)
{
_msg = String.Format("{0} ({1}, Win32ErrorCode {2})", message, base.Message, errorCode);
}
public override string Message { get { return _msg; } }
public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
}
public class Privileges
{
private static readonly UInt32 TOKEN_PRIVILEGES = 3;
public static bool CheckPrivilegeName(string name)
{
NativeHelpers.LUID luid;
if (!NativeMethods.LookupPrivilegeValue(null, name, out luid))
{
int errCode = Marshal.GetLastWin32Error();
if (errCode != 1313) // ERROR_NO_SUCH_PRIVILEGE
throw new Win32Exception(errCode, String.Format("LookupPrivilegeValue({0}) failed", name));
return false;
}
else
{
return true;
}
}
public static Dictionary<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, privilegesPtr.ToInt64() + Marshal.SizeOf(privilegeInfo.PrivilegeCount));
}
finally
{
Marshal.FreeHGlobal(privilegesPtr);
}
info = privileges.ToDictionary(p => GetPrivilegeName(p.Luid), p => p.Attributes);
}
finally
{
NativeMethods.CloseHandle(hToken);
}
return info;
}
public static SafeWaitHandle GetCurrentProcess()
{
return NativeMethods.GetCurrentProcess();
}
public static void RemovePrivilege(SafeHandle token, string privilege)
{
SetTokenPrivileges(token, new Dictionary<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, oldStatePtr.ToInt64() + Marshal.SizeOf(oldState.PrivilegeCount));
}
finally
{
Marshal.FreeHGlobal(oldStatePtr);
}
}
finally
{
NativeMethods.CloseHandle(hToken);
}
}
finally
{
if (newStatePtr != IntPtr.Zero)
Marshal.FreeHGlobal(newStatePtr);
}
return oldStatePrivileges.ToDictionary(p => GetPrivilegeName(p.Luid), p => (bool?)p.Attributes.HasFlag(PrivilegeAttributes.Enabled));
}
private static string GetPrivilegeName(NativeHelpers.LUID luid)
{
UInt32 nameLen = 0;
NativeMethods.LookupPrivilegeName(null, ref luid, null, ref nameLen);
StringBuilder name = new StringBuilder((int)(nameLen + 1));
if (!NativeMethods.LookupPrivilegeName(null, ref luid, name, ref nameLen))
throw new Win32Exception("LookupPrivilegeName() failed");
return name.ToString();
}
private static void PtrToStructureArray<T>(T[] array, Int64 pointerAddress)
{
Int64 pointerOffset = pointerAddress;
for (int i = 0; i < array.Length; i++, pointerOffset += Marshal.SizeOf(typeof(T)))
array[i] = (T)Marshal.PtrToStructure(new IntPtr(pointerOffset), typeof(T));
}
private static int StructureToBytes<T>(T structure, byte[] array, int offset)
{
int size = Marshal.SizeOf(structure);
IntPtr structPtr = Marshal.AllocHGlobal(size);
try
{
Marshal.StructureToPtr(structure, structPtr, false);
Marshal.Copy(structPtr, array, offset, size);
}
finally
{
Marshal.FreeHGlobal(structPtr);
}
return size;
}
}
}
'@
Function Import-PrivilegeUtil {
<#
.SYNOPSIS
Compiles the C# code that can be used to manage Windows privileges from an
Ansible module. Once this function is called, the following PowerShell
cmdlets can be used;
Get-AnsiblePrivilege
Set-AnsiblePrivilege
The above cmdlets give the ability to manage permissions on the current
process token but the underlying .NET classes are also exposed for greater
control. The following functions can be used by calling the .NET class
[Ansible.PrivilegeUtil.Privileges]::CheckPrivilegeName($name)
[Ansible.PrivilegeUtil.Privileges]::DisablePrivilege($process, $name)
[Ansible.PrivilegeUtil.Privileges]::DisableAllPrivileges($process)
[Ansible.PrivilegeUtil.Privileges]::EnablePrivilege($process, $name)
[Ansible.PrivilegeUtil.Privileges]::GetAllPrivilegeInfo($process)
[Ansible.PrivilegeUtil.Privileges]::RemovePrivilege($process, $name)
[Ansible.PrivilegeUtil.Privileges]::SetTokenPrivileges($process, $new_state)
Here is a brief explanation of each type of arg
$process = The process handle to manipulate, use '[Ansible.PrivilegeUtils.Privileges]::GetCurrentProcess()' to get the current process handle
$name = The name of the privilege, this is the constant value from https://docs.microsoft.com/en-us/windows/desktop/SecAuthZ/privilege-constants, e.g. SeAuditPrivilege
$new_state = 'System.Collections.Generic.Dictionary`2[[System.String], [System.Nullable`1[System.Boolean]]]'
The key is the constant name as a string, the value is a ternary boolean where
true - will enable the privilege
false - will disable the privilege
null - will remove the privilege
Each method that changes the privilege state will return a dictionary that
can be used as the $new_state arg of SetTokenPrivileges to undo and revert
back to the original state. If you remove a privilege then this is
irreversible and won't be part of the returned dict
#>
[CmdletBinding()]
# build the C# code to compile
$namespace_import = ($ansible_privilege_util_namespaces | ForEach-Object { "using $_;" }) -join "`r`n"
$platform_util = "$namespace_import`r`n`r`n$ansible_privilege_util_code"
# FUTURE: find a better way to get the _ansible_remote_tmp variable
# this is used to force csc to compile the C# code in the remote tmp
# specified
$original_tmp = $env:TMP
$remote_tmp = $original_tmp
$module_params = Get-Variable -Name complex_args -ErrorAction SilentlyContinue
if ($module_params) {
if ($module_params.Value.ContainsKey("_ansible_remote_tmp") ) {
$remote_tmp = $module_params.Value["_ansible_remote_tmp"]
$remote_tmp = [System.Environment]::ExpandEnvironmentVariables($remote_tmp)
}
}
$env:TMP = $remote_tmp
Add-Type -TypeDefinition $platform_util
$env:TMP = $original_tmp
}
Function Get-AnsiblePrivilege {
<#
.SYNOPSIS
Get the status of a privilege for the current process. This returns
$true - the privilege is enabled
$false - the privilege is disabled
$null - the privilege is removed from the token
If Name is not a valid privilege name, this will throw an
ArgumentException.
.EXAMPLE
Get-AnsiblePrivilege -Name SeDebugPrivilege
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][String]$Name
)
if (-not [Ansible.PrivilegeUtil.Privileges]::CheckPrivilegeName($Name)) {
throw [System.ArgumentException] "Invalid privilege name '$Name'"
}
$process_token = [Ansible.PrivilegeUtil.Privileges]::GetCurrentProcess()
$privilege_info = [Ansible.PrivilegeUtil.Privileges]::GetAllPrivilegeInfo($process_token)
if ($privilege_info.ContainsKey($Name)) {
$status = $privilege_info.$Name
return $status.HasFlag([Ansible.PrivilegeUtil.PrivilegeAttributes]::Enabled)
} else {
return $null
}
}
Function Set-AnsiblePrivilege {
<#
.SYNOPSIS
Enables/Disables a privilege on the current process' token. If a privilege
has been removed from the process token, this will throw an
InvalidOperationException.
.EXAMPLE
# enable a privilege
Set-AnsiblePrivilege -Name SeCreateSymbolicLinkPrivilege -Value $true
# disable a privilege
Set-AnsiblePrivilege -Name SeCreateSymbolicLinkPrivilege -Value $false
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory=$true)][String]$Name,
[Parameter(Mandatory=$true)][bool]$Value
)
$action = switch($Value) {
$true { "Enable" }
$false { "Disable" }
}
$current_state = Get-AnsiblePrivilege -Name $Name
if ($current_state -eq $Value) {
return # no change needs to occur
} elseif ($null -eq $current_state) {
# once a privilege is removed from a token we cannot do anything with it
throw [System.InvalidOperationException] "Cannot $($action.ToLower()) the privilege '$Name' as it has been removed from the token"
}
$process_token = [Ansible.PrivilegeUtil.Privileges]::GetCurrentProcess()
if ($PSCmdlet.ShouldProcess($Name, "$action the privilege $Name")) {
$new_state = New-Object -TypeName 'System.Collections.Generic.Dictionary`2[[System.String], [System.Nullable`1[System.Boolean]]]'
$new_state.Add($Name, $Value)
[Ansible.PrivilegeUtil.Privileges]::SetTokenPrivileges($process_token, $new_state) > $null
}
}
Export-ModuleMember -Function Import-PrivilegeUtil, Get-AnsiblePrivilege, Set-AnsiblePrivilege `
-Variable ansible_privilege_util_namespaces, ansible_privilege_util_code

View file

@ -6,6 +6,7 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Module Ansible.ModuleUtils.PrivilegeUtil
#Requires -Module Ansible.ModuleUtils.SID
$ErrorActionPreference = "Stop"
@ -43,96 +44,7 @@ function Get-UserSID {
return $userSID
}
# Need to adjust token privs when executing Set-ACL in certain cases.
# e.g. d:\testdir is owned by group in which current user is not a member and no perms are inherited from d:\
# This also sets us up for setting the owner as a feature.
$AdjustTokenPrivileges = @"
using System;
using System.Runtime.InteropServices;
namespace Ansible {
public class TokenManipulator {
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall,
ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
[DllImport("kernel32.dll", ExactSpelling = true)]
internal static extern IntPtr GetCurrentProcess();
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
internal static extern bool OpenProcessToken(IntPtr h, int acc,
ref IntPtr phtok);
[DllImport("advapi32.dll", SetLastError = true)]
internal static extern bool LookupPrivilegeValue(string host, string name,
ref long pluid);
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct TokPriv1Luid
{
public int Count;
public long Luid;
public int Attr;
}
internal const int SE_PRIVILEGE_DISABLED = 0x00000000;
internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
internal const int TOKEN_QUERY = 0x00000008;
internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
public static bool AddPrivilege(string privilege) {
try {
bool retVal;
TokPriv1Luid tp;
IntPtr hproc = GetCurrentProcess();
IntPtr htok = IntPtr.Zero;
retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
tp.Count = 1;
tp.Luid = 0;
tp.Attr = SE_PRIVILEGE_ENABLED;
retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid);
retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
return retVal;
}
catch (Exception ex) {
throw ex;
}
}
public static bool RemovePrivilege(string privilege) {
try {
bool retVal;
TokPriv1Luid tp;
IntPtr hproc = GetCurrentProcess();
IntPtr htok = IntPtr.Zero;
retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
tp.Count = 1;
tp.Luid = 0;
tp.Attr = SE_PRIVILEGE_DISABLED;
retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid);
retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
return retVal;
}
catch (Exception ex) {
throw ex;
}
}
}
}
"@
$params = Parse-Args $args
$_remote_tmp = Get-AnsibleParam $params "_ansible_remote_tmp" -type "path" -default $env:TMP
$original_tmp = $env:TMP
$original_temp = $env:TEMP
$env:TMP = $_remote_tmp
$env:TEMP = $_remote_tmp
add-type $AdjustTokenPrivileges
$env:TMP = $original_tmp
$env:TEMP = $original_temp
Function SetPrivilegeTokens() {
# Set privilege tokens only if admin.
@ -144,13 +56,23 @@ Function SetPrivilegeTokens() {
if ($myWindowsPrincipal.IsInRole($adminRole)) {
# Need to adjust token privs when executing Set-ACL in certain cases.
# e.g. d:\testdir is owned by group in which current user is not a member and no perms are inherited from d:\
# This also sets us up for setting the owner as a feature.
# See the following for details of each privilege
# https://msdn.microsoft.com/en-us/library/windows/desktop/bb530716(v=vs.85).aspx
[void][Ansible.TokenManipulator]::AddPrivilege("SeRestorePrivilege") #Grants all write access control to any file, regardless of ACL.
[void][Ansible.TokenManipulator]::AddPrivilege("SeBackupPrivilege") #Grants all read access control to any file, regardless of ACL.
[void][Ansible.TokenManipulator]::AddPrivilege("SeTakeOwnershipPrivilege") #Grants ability to take owernship of an object w/out being granted discretionary access
Import-PrivilegeUtil
$privileges = @(
"SeRestorePrivilege", # Grants all write access control to any file, regardless of ACL.
"SeBackupPrivilege", # Grants all read access control to any file, regardless of ACL.
"SeTakeOwnershipPrivilege" # Grants ability to take owernship of an object w/out being granted discretionary access
)
foreach ($privilege in $privileges) {
$state = Get-AnsiblePrivilege -Name $privilege
if ($state -eq $false) {
Set-AnsiblePrivilege -Name $privilege -Value $true
}
}
}
}

View file

@ -52,12 +52,9 @@ namespace Ansible.Command {
}
"@
$original_tmp = $env:TMP
$original_temp = $env:TEMP
$env:TMP = $_remote_tmp
$env:TEMP = $_remote_tmp
Add-Type -TypeDefinition $symlink_util
$env:TMP = $original_tmp
$env:TEMP = $original_temp
# Used to delete directories and files with logic on handling symbolic links
function Remove-File($file, $checkmode) {

View file

@ -71,12 +71,9 @@ namespace Ansible.Command {
}
"@
$original_tmp = $env:TMP
$original_temp = $env:TEMP
$env:TMP = $_remote_tmp
$env:TEMP = $_remote_tmp
Add-Type -TypeDefinition $symlink_util
$env:TMP = $original_tmp
$env:TEMP = $original_temp
Function Assert-Age($info) {
$valid_match = $true

View file

@ -30,12 +30,9 @@ $webclient_util = @"
}
"@
$original_tmp = $env:TMP
$original_temp = $env:TEMP
$env:TMP = $_remote_tmp
$env:TEMP = $_remote_tmp
Add-Type -TypeDefinition $webclient_util
$env:TMP = $original_tmp
$env:TEMP = $original_temp
Function CheckModified-File($url, $dest, $headers, $credentials, $timeout, $use_proxy, $proxy) {

View file

@ -6,6 +6,7 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Module Ansible.ModuleUtils.PrivilegeUtil
$ErrorActionPreference = "Stop"
@ -39,23 +40,8 @@ using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Ansible
namespace Ansible.RegEdit
{
[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
public UInt32 LowPart;
public Int32 HighPart;
}
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_PRIVILEGES
{
public UInt32 PrivilegeCount;
public LUID Luid;
public UInt32 Attributes;
}
public enum HKEY : uint
{
LOCAL_MACHINE = 0x80000002,
@ -74,41 +60,8 @@ namespace Ansible
public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
}
public class RegistryUtil
public class Hive
{
public const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
public const int TOKEN_QUERY = 0x00000008;
public const int SE_PRIVILEGE_ENABLED = 0x00000002;
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr GetCurrentProcess();
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool CloseHandle(
IntPtr hObject);
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
private static extern bool OpenProcessToken(
IntPtr ProcessHandle,
UInt32 DesiredAccess,
out IntPtr TokenHandle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
private static extern bool LookupPrivilegeValue(
string lpSystemName,
string lpName,
[MarshalAs(UnmanagedType.Struct)] out LUID lpLuid);
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
private static extern bool AdjustTokenPrivileges(
IntPtr TokenHandle,
[MarshalAs(UnmanagedType.Bool)] bool DisableAllPrivileges,
ref TOKEN_PRIVILEGES NewState,
UInt32 BufferLength,
IntPtr PreviousState,
IntPtr ReturnLength);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int RegLoadKey(
HKEY hKey,
@ -120,41 +73,6 @@ namespace Ansible
HKEY hKey,
string lpSubKey);
public static void EnablePrivileges()
{
List<String> privileges = new List<String>()
{
"SeRestorePrivilege",
"SeBackupPrivilege"
};
foreach (string privilege in privileges)
{
IntPtr hToken;
LUID luid;
TOKEN_PRIVILEGES tkpPrivileges;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out hToken))
throw new Win32Exception("OpenProcessToken() failed");
try
{
if (!LookupPrivilegeValue(null, privilege, out luid))
throw new Win32Exception("LookupPrivilegeValue() failed");
tkpPrivileges.PrivilegeCount = 1;
tkpPrivileges.Luid = luid;
tkpPrivileges.Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, false, ref tkpPrivileges, 0, IntPtr.Zero, IntPtr.Zero))
throw new Win32Exception(String.Format("AdjustTokenPrivileges() failed to adjust privilege {0}", privilege));
}
finally
{
CloseHandle(hToken);
}
}
}
public static void LoadHive(string lpSubKey, string lpFile)
{
int ret;
@ -373,29 +291,29 @@ if ($hive) {
}
$original_tmp = $env:TMP
$original_temp = $env:TEMP
$env:TMP = $_remote_tmp
$env:TEMP = $_remote_tmp
Add-Type -TypeDefinition $registry_util
$env:TMP = $original_tmp
$env:TEMP = $original_temp
Import-PrivilegeUtil
try {
[Ansible.RegistryUtil]::EnablePrivileges()
Set-AnsiblePrivilege -Name SeBackupPrivilege -Value $true
Set-AnsiblePrivilege -Name SeRestorePrivilege -Value $true
} catch [System.ComponentModel.Win32Exception] {
Fail-Json -obj $result -message "failed to enable SeRestorePrivilege and SeRestorePrivilege for the current process: $($_.Exception.Message)"
Fail-Json -obj $result -message "failed to enable SeBackupPrivilege and SeRestorePrivilege for the current process: $($_.Exception.Message)"
}
if (Test-Path -Path HKLM:\ANSIBLE) {
Add-Warning -obj $result -message "hive already loaded at HKLM:\ANSIBLE, had to unload hive for win_regedit to continue"
try {
[Ansible.RegistryUtil]::UnloadHive("ANSIBLE")
[Ansible.RegEdit.Hive]::UnloadHive("ANSIBLE")
} catch [System.ComponentModel.Win32Exception] {
Fail-Json -obj $result -message "failed to unload registry hive HKLM:\ANSIBLE from $($hive): $($_.Exception.Message)"
}
}
try {
[Ansible.RegistryUtil]::LoadHive("ANSIBLE", $hive)
[Ansible.RegEdit.Hive]::LoadHive("ANSIBLE", $hive)
} catch [System.ComponentModel.Win32Exception] {
Fail-Json -obj $result -message "failed to load registry hive from '$hive' to HKLM:\ANSIBLE: $($_.Exception.Message)"
}
@ -566,7 +484,7 @@ $key_prefix[$path]
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
try {
[Ansible.RegistryUtil]::UnloadHive("ANSIBLE")
[Ansible.RegEdit.Hive]::UnloadHive("ANSIBLE")
} catch [System.ComponentModel.Win32Exception] {
Fail-Json -obj $result -message "failed to unload registry hive HKLM:\ANSIBLE from $($hive): $($_.Exception.Message)"
}

View file

@ -88,12 +88,9 @@ Function Set-CultureLegacy($culture) {
$reg_key = 'HKCU:\Control Panel\International'
$original_tmp = $env:TMP
$original_temp = $env:TEMP
$env:TMP = $_remote_tmp
$env:TEMP = $_remote_tmp
Add-Type -TypeDefinition $lctype_util
$env:TMP = $original_tmp
$env:TEMP = $original_temp
$lookup = New-Object Ansible.LocaleHelper($culture)
# hex values are from http://www.pinvoke.net/default.aspx/kernel32/GetLocaleInfoEx.html

View file

@ -125,12 +125,9 @@ public enum TASK_TRIGGER_TYPE2 // https://msdn.microsoft.com/en-us/library/windo
"@
$original_tmp = $env:TMP
$original_temp = $env:TEMP
$env:TMP = $_remote_tmp
$env:TEMP = $_remote_tmp
Add-Type -TypeDefinition $task_enums
$env:TMP = $original_tmp
$env:TEMP = $original_temp
########################
### HELPER FUNCTIONS ###

View file

@ -70,12 +70,9 @@ public enum TASK_TRIGGER_TYPE2
"@
$original_tmp = $env:TMP
$original_temp = $env:TEMP
$env:TMP = $_remote_tmp
$env:TEMP = $_remote_tmp
Add-Type -TypeDefinition $task_enums
$env:TMP = $original_tmp
$env:TEMP = $original_temp
Function Get-PropertyValue($task_property, $com, $property) {
$raw_value = $com.$property

View file

@ -70,12 +70,9 @@ namespace Ansible
'@
$original_tmp = $env:TMP
$original_temp = $env:TEMP
$env:TMP = $_remote_tmp
$env:TEMP = $_remote_tmp
Add-Type -TypeDefinition $platform_util
$env:TMP = $original_tmp
$env:TEMP = $original_temp
$handle = [IntPtr]::Zero
$logon_res = [Ansible.WinUserPInvoke]::LogonUser($Username, $null, $Password,

View file

@ -267,12 +267,9 @@ namespace Ansible
"@
$original_tmp = $env:TMP
$original_temp = $env:TEMP
$env:TMP = $_remote_tmp
$env:TEMP = $_remote_tmp
Add-Type -TypeDefinition $sec_helper_util
$env:TMP = $original_tmp
$env:TEMP = $original_temp
Function Compare-UserList($existing_users, $new_users) {
$added_users = [String[]]@()

View file

@ -783,12 +783,9 @@ namespace Ansible
'@
$original_tmp = $env:TMP
$original_temp = $env:TEMP
$env:TMP = $_remote_tmp
$env:TEMP = $_remote_tmp
Add-Type -TypeDefinition $session_util
$env:TMP = $original_tmp
$env:TEMP = $original_temp
$session_info = [Ansible.SessionUtil]::GetSessionInfo()

View file

@ -1065,7 +1065,6 @@ Function Run($payload) {
# NB: action popping handled inside subprocess wrapper
$original_tmp = $env:TMP
$original_temp = $env:TEMP
$remote_tmp = $payload["module_args"]["_ansible_remote_tmp"]
$remote_tmp = [System.Environment]::ExpandEnvironmentVariables($remote_tmp)
if ($null -eq $remote_tmp) {
@ -1075,10 +1074,8 @@ Function Run($payload) {
# become process is run under a different console to the WinRM one so we
# need to set the UTF-8 codepage again
$env:TMP = $remote_tmp
$env:TEMP = $remote_tmp
Add-Type -TypeDefinition $helper_def -Debug:$false
$env:TMP = $original_tmp
$env:TEMP = $original_tmp
$username = $payload.become_user
$password = $payload.become_password

View file

@ -0,0 +1,164 @@
#!powershell
#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Module Ansible.ModuleUtils.PrivilegeUtil
$ErrorActionPreference = "Stop"
$result = @{
changed = $false
}
Import-PrivilegeUtil
Function Assert-Equals($actual, $expected) {
if ($actual -cne $expected) {
$call_stack = (Get-PSCallStack)[1]
$error_msg = "AssertionError:`r`nActual: `"$actual`" != Expected: `"$expected`"`r`nLine: $($call_stack.ScriptLineNumber), Method: $($call_stack.Position.Text)"
Fail-Json -obj $result -message $error_msg
}
}
# taken from https://docs.microsoft.com/en-us/windows/desktop/SecAuthZ/privilege-constants
$total_privileges = @(
"SeAssignPrimaryTokenPrivilege",
"SeAuditPrivilege",
"SeBackupPrivilege",
"SeChangeNotifyPrivilege",
"SeCreateGlobalPrivilege",
"SeCreatePagefilePrivilege",
"SeCreatePermanentPrivilege",
"SeCreateSymbolicLinkPrivilege",
"SeCreateTokenPrivilege",
"SeDebugPrivilege",
"SeEnableDelegationPrivilege",
"SeImpersonatePrivilege",
"SeIncreaseBasePriorityPrivilege",
"SeIncreaseQuotaPrivilege",
"SeIncreaseWorkingSetPrivilege",
"SeLoadDriverPrivilege",
"SeLockMemoryPrivilege",
"SeMachineAccountPrivilege",
"SeManageVolumePrivilege",
"SeProfileSingleProcessPrivilege",
"SeRelabelPrivilege",
"SeRemoteShutdownPrivilege",
"SeRestorePrivilege",
"SeSecurityPrivilege",
"SeShutdownPrivilege",
"SeSyncAgentPrivilege",
"SeSystemEnvironmentPrivilege",
"SeSystemProfilePrivilege",
"SeSystemtimePrivilege",
"SeTakeOwnershipPrivilege",
"SeTcbPrivilege",
"SeTimeZonePrivilege",
"SeTrustedCredManAccessPrivilege",
"SeUndockPrivilege"
)
$raw_privilege_output = &whoami /priv | Where-Object { $_.StartsWith("Se") }
$actual_privileges = @{}
foreach ($raw_privilege in $raw_privilege_output) {
$split = $raw_privilege.TrimEnd() -split " "
$actual_privileges."$($split[0])" = ($split[-1] -eq "Enabled")
}
$process = [Ansible.PrivilegeUtil.Privileges]::GetCurrentProcess()
### Test variables ###
Assert-Equals -actual ($ansible_privilege_util_namespaces -is [array]) -expected $true
Assert-Equals -actual ($ansible_privilege_util_code -is [String]) -expected $true
### Test PS cmdlets ###
# test ps Get-AnsiblePrivilege
foreach ($privilege in $total_privileges) {
$expected = $null
if ($actual_privileges.ContainsKey($privilege)) {
$expected = $actual_privileges.$privilege
}
$actual = Get-AnsiblePrivilege -Name $privilege
Assert-Equals -actual $actual -expected $expected
}
# test c# GetAllPrivilegeInfo
$actual = [Ansible.PrivilegeUtil.Privileges]::GetAllPrivilegeInfo($process)
Assert-Equals -actual $actual.GetType().Name -expected 'Dictionary`2'
Assert-Equals -actual $actual.Count -expected $actual_privileges.Count
foreach ($privilege in $total_privileges) {
if ($actual_privileges.ContainsKey($privilege)) {
$actual_value = $actual.$privilege
if ($actual_privileges.$privilege) {
Assert-Equals -actual $actual_value.HasFlag([Ansible.PrivilegeUtil.PrivilegeAttributes]::Enabled) -expected $true
} else {
Assert-Equals -actual $actual_value.HasFlag([Ansible.PrivilegeUtil.PrivilegeAttributes]::Enabled) -expected $false
}
}
}
# test Set-AnsiblePrivilege
Set-AnsiblePrivilege -Name SeUndockPrivilege -Value $false # ensure we start with a disabled privilege
Set-AnsiblePrivilege -Name SeUndockPrivilege -Value $true -WhatIf
$actual = Get-AnsiblePrivilege -Name SeUndockPrivilege
Assert-Equals -actual $actual -expected $false
Set-AnsiblePrivilege -Name SeUndockPrivilege -Value $true
$actual = Get-AnsiblePrivilege -Name SeUndockPrivilege
Assert-Equals -actual $actual -expected $true
Set-AnsiblePrivilege -Name SeUndockPrivilege -Value $false -WhatIf
$actual = Get-AnsiblePrivilege -Name SeUndockPrivilege
Assert-Equals -actual $actual -expected $true
Set-AnsiblePrivilege -Name SeUndockPrivilege -Value $false
$actual = Get-AnsiblePrivilege -Name SeUndockPrivilege
Assert-Equals -actual $actual -expected $false
### Test C# code ###
# test CheckPrivilegeName
Assert-Equals -actual ([Ansible.PrivilegeUtil.Privileges]::CheckPrivilegeName($total_privileges[0])) -expected $true
Assert-Equals -actual ([Ansible.PrivilegeUtil.Privileges]::CheckPrivilegeName("SeFake")) -expected $false
# test DisablePrivilege
# ensure we start in an enabled state
Set-AnsiblePrivilege -Name SeTimeZonePrivilege -Value $true
$actual = [Ansible.PrivilegeUtil.Privileges]::DisablePrivilege($process, "SeTimeZonePrivilege")
Assert-Equals -actual $actual.GetType().Name -expected 'Dictionary`2'
Assert-Equals -actual $actual.Count -expected 1
Assert-Equals -actual $actual.SeTimeZonePrivilege -expected $true
$actual = [Ansible.PrivilegeUtil.Privileges]::DisablePrivilege($process, "SeTimeZonePrivilege")
Assert-Equals -actual $actual.GetType().Name -expected 'Dictionary`2'
Assert-Equals -actual $actual.Count -expected 0
# test DisableAllPrivileges
$actual_disable_all = [Ansible.PrivilegeUtil.Privileges]::DisableAllPrivileges($process)
Assert-Equals -actual $actual_disable_all.GetType().Name -expected 'Dictionary`2'
$actual = [Ansible.PrivilegeUtil.Privileges]::DisableAllPrivileges($process)
Assert-Equals -actual $actual.GetType().Name -expected 'Dictionary`2'
Assert-Equals -actual $actual.Count -expected 0
# test EnablePrivilege
$actual = [Ansible.PrivilegeUtil.Privileges]::EnablePrivilege($process, "SeTimeZonePrivilege")
Assert-Equals -actual $actual.GetType().Name -expected 'Dictionary`2'
Assert-Equals -actual $actual.Count -expected 1
Assert-Equals -actual $actual.SeTimeZonePrivilege -expected $false
$actual = [Ansible.PrivilegeUtil.Privileges]::EnablePrivilege($process, "SeTimeZonePrivilege")
Assert-Equals -actual $actual.GetType().Name -expected 'Dictionary`2'
Assert-Equals -actual $actual.Count -expected 0
# test SetTokenPrivileges
$actual = [Ansible.PrivilegeUtil.Privileges]::SetTokenPrivileges($process, $actual_disable_all)
Assert-Equals -actual $actual_disable_all.GetType().Name -expected 'Dictionary`2'
Assert-Equals -actual $actual.ContainsKey("SeTimeZonePrivilege") -expected $false
Assert-Equals -actual $actual.Count -expected $actual_disable_all.Count
# test RemovePrivilege
[Ansible.PrivilegeUtil.Privileges]::RemovePrivilege($process, "SeTimeZonePrivilege")
$actual = Get-AnsiblePrivilege -Name SeTimeZonePrivilege
Assert-Equals -actual $actual -expected $null
$result.data = "success"
Exit-Json -obj $result

View file

@ -135,3 +135,11 @@
- assert:
that:
- file_util_test.data == 'success'
- name: call module with PrivilegeUtil tests
privilege_util_test:
register: privilege_util_test
- assert:
that:
- privilege_util_test.data == 'success'