mirror of https://github.com/ansible/ansible.git
Ansible.AccessToken - Added shared util for managing a Windows access token (#60302)
* Ansible.AccessToken - Added shared util for managing a Windows access token * Fix tests when running in CI * More fixes for older servers * More fixes for Server 2008pull/59027/merge
parent
49e16922b2
commit
dbd082efe4
@ -0,0 +1,2 @@
|
||||
minor_changes:
|
||||
- Added C# module util that implements various access token functions
|
@ -0,0 +1,457 @@
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
|
||||
namespace Ansible.AccessToken
|
||||
{
|
||||
internal class NativeHelpers
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct LUID_AND_ATTRIBUTES
|
||||
{
|
||||
public Luid Luid;
|
||||
public UInt32 Attributes;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SID_AND_ATTRIBUTES
|
||||
{
|
||||
public IntPtr Sid;
|
||||
public int Attributes;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct TOKEN_PRIVILEGES
|
||||
{
|
||||
public UInt32 PrivilegeCount;
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
|
||||
public LUID_AND_ATTRIBUTES[] Privileges;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct TOKEN_USER
|
||||
{
|
||||
public SID_AND_ATTRIBUTES User;
|
||||
}
|
||||
|
||||
public enum TokenInformationClass : uint
|
||||
{
|
||||
TokenUser = 1,
|
||||
TokenPrivileges = 3,
|
||||
TokenStatistics = 10,
|
||||
TokenElevationType = 18,
|
||||
TokenLinkedToken = 19,
|
||||
}
|
||||
}
|
||||
|
||||
internal class NativeMethods
|
||||
{
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool CloseHandle(
|
||||
IntPtr hObject);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool DuplicateTokenEx(
|
||||
SafeNativeHandle hExistingToken,
|
||||
TokenAccessLevels dwDesiredAccess,
|
||||
IntPtr lpTokenAttributes,
|
||||
SecurityImpersonationLevel ImpersonationLevel,
|
||||
TokenType TokenType,
|
||||
out SafeNativeHandle phNewToken);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern SafeNativeHandle GetCurrentProcess();
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool GetTokenInformation(
|
||||
SafeNativeHandle TokenHandle,
|
||||
NativeHelpers.TokenInformationClass TokenInformationClass,
|
||||
SafeMemoryBuffer TokenInformation,
|
||||
UInt32 TokenInformationLength,
|
||||
out UInt32 ReturnLength);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool ImpersonateLoggedOnUser(
|
||||
SafeNativeHandle hToken);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool LogonUserW(
|
||||
string lpszUsername,
|
||||
string lpszDomain,
|
||||
string lpszPassword,
|
||||
LogonType dwLogonType,
|
||||
LogonProvider dwLogonProvider,
|
||||
out SafeNativeHandle phToken);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool LookupPrivilegeNameW(
|
||||
string lpSystemName,
|
||||
ref Luid lpLuid,
|
||||
StringBuilder lpName,
|
||||
ref UInt32 cchName);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern SafeNativeHandle OpenProcess(
|
||||
ProcessAccessFlags dwDesiredAccess,
|
||||
bool bInheritHandle,
|
||||
UInt32 dwProcessId);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool OpenProcessToken(
|
||||
SafeNativeHandle ProcessHandle,
|
||||
TokenAccessLevels DesiredAccess,
|
||||
out SafeNativeHandle TokenHandle);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool RevertToSelf();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public enum LogonProvider
|
||||
{
|
||||
Default,
|
||||
}
|
||||
|
||||
public enum LogonType
|
||||
{
|
||||
Interactive = 2,
|
||||
Network = 3,
|
||||
Batch = 4,
|
||||
Service = 5,
|
||||
Unlock = 7,
|
||||
NetworkCleartext = 8,
|
||||
NewCredentials = 9,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum PrivilegeAttributes : uint
|
||||
{
|
||||
Disabled = 0x00000000,
|
||||
EnabledByDefault = 0x00000001,
|
||||
Enabled = 0x00000002,
|
||||
Removed = 0x00000004,
|
||||
UsedForAccess = 0x80000000,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ProcessAccessFlags : uint
|
||||
{
|
||||
Terminate = 0x00000001,
|
||||
CreateThread = 0x00000002,
|
||||
VmOperation = 0x00000008,
|
||||
VmRead = 0x00000010,
|
||||
VmWrite = 0x00000020,
|
||||
DupHandle = 0x00000040,
|
||||
CreateProcess = 0x00000080,
|
||||
SetQuota = 0x00000100,
|
||||
SetInformation = 0x00000200,
|
||||
QueryInformation = 0x00000400,
|
||||
SuspendResume = 0x00000800,
|
||||
QueryLimitedInformation = 0x00001000,
|
||||
Delete = 0x00010000,
|
||||
ReadControl = 0x00020000,
|
||||
WriteDac = 0x00040000,
|
||||
WriteOwner = 0x00080000,
|
||||
Synchronize = 0x00100000,
|
||||
}
|
||||
|
||||
public enum SecurityImpersonationLevel
|
||||
{
|
||||
Anonymous,
|
||||
Identification,
|
||||
Impersonation,
|
||||
Delegation,
|
||||
}
|
||||
|
||||
public enum TokenElevationType
|
||||
{
|
||||
Default = 1,
|
||||
Full,
|
||||
Limited,
|
||||
}
|
||||
|
||||
public enum TokenType
|
||||
{
|
||||
Primary = 1,
|
||||
Impersonation,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Luid
|
||||
{
|
||||
public UInt32 LowPart;
|
||||
public Int32 HighPart;
|
||||
|
||||
public static explicit operator UInt64(Luid l)
|
||||
{
|
||||
return (UInt64)((UInt64)l.HighPart << 32) | (UInt64)l.LowPart;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct TokenStatistics
|
||||
{
|
||||
public Luid TokenId;
|
||||
public Luid AuthenticationId;
|
||||
public Int64 ExpirationTime;
|
||||
public TokenType TokenType;
|
||||
public SecurityImpersonationLevel ImpersonationLevel;
|
||||
public UInt32 DynamicCharged;
|
||||
public UInt32 DynamicAvailable;
|
||||
public UInt32 GroupCount;
|
||||
public UInt32 PrivilegeCount;
|
||||
public Luid ModifiedId;
|
||||
}
|
||||
|
||||
public class PrivilegeInfo
|
||||
{
|
||||
public string Name;
|
||||
public PrivilegeAttributes Attributes;
|
||||
|
||||
internal PrivilegeInfo(NativeHelpers.LUID_AND_ATTRIBUTES la)
|
||||
{
|
||||
Name = TokenUtil.GetPrivilegeName(la.Luid);
|
||||
Attributes = (PrivilegeAttributes)la.Attributes;
|
||||
}
|
||||
}
|
||||
|
||||
public 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} - 0x{2:X8})", message, base.Message, errorCode);
|
||||
}
|
||||
|
||||
public override string Message { get { return _msg; } }
|
||||
public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
|
||||
}
|
||||
|
||||
public class TokenUtil
|
||||
{
|
||||
public static SafeNativeHandle DuplicateToken(SafeNativeHandle hToken, TokenAccessLevels access,
|
||||
SecurityImpersonationLevel impersonationLevel, TokenType tokenType)
|
||||
{
|
||||
SafeNativeHandle dupToken;
|
||||
if (!NativeMethods.DuplicateTokenEx(hToken, access, IntPtr.Zero, impersonationLevel, tokenType, out dupToken))
|
||||
throw new Win32Exception("Failed to duplicate token");
|
||||
return dupToken;
|
||||
}
|
||||
|
||||
public static SecurityIdentifier GetTokenUser(SafeNativeHandle hToken)
|
||||
{
|
||||
using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken,
|
||||
NativeHelpers.TokenInformationClass.TokenUser))
|
||||
{
|
||||
NativeHelpers.TOKEN_USER tokenUser = (NativeHelpers.TOKEN_USER)Marshal.PtrToStructure(
|
||||
tokenInfo.DangerousGetHandle(),
|
||||
typeof(NativeHelpers.TOKEN_USER));
|
||||
return new SecurityIdentifier(tokenUser.User.Sid);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<PrivilegeInfo> GetTokenPrivileges(SafeNativeHandle hToken)
|
||||
{
|
||||
using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken,
|
||||
NativeHelpers.TokenInformationClass.TokenPrivileges))
|
||||
{
|
||||
NativeHelpers.TOKEN_PRIVILEGES tokenPrivs = (NativeHelpers.TOKEN_PRIVILEGES)Marshal.PtrToStructure(
|
||||
tokenInfo.DangerousGetHandle(),
|
||||
typeof(NativeHelpers.TOKEN_PRIVILEGES));
|
||||
|
||||
NativeHelpers.LUID_AND_ATTRIBUTES[] luidAttrs =
|
||||
new NativeHelpers.LUID_AND_ATTRIBUTES[tokenPrivs.PrivilegeCount];
|
||||
PtrToStructureArray(luidAttrs, IntPtr.Add(tokenInfo.DangerousGetHandle(),
|
||||
Marshal.SizeOf(tokenPrivs.PrivilegeCount)));
|
||||
|
||||
return luidAttrs.Select(la => new PrivilegeInfo(la)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public static TokenStatistics GetTokenStatistics(SafeNativeHandle hToken)
|
||||
{
|
||||
using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken,
|
||||
NativeHelpers.TokenInformationClass.TokenStatistics))
|
||||
{
|
||||
TokenStatistics tokenStats = (TokenStatistics)Marshal.PtrToStructure(
|
||||
tokenInfo.DangerousGetHandle(),
|
||||
typeof(TokenStatistics));
|
||||
return tokenStats;
|
||||
}
|
||||
}
|
||||
|
||||
public static TokenElevationType GetTokenElevationType(SafeNativeHandle hToken)
|
||||
{
|
||||
using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken,
|
||||
NativeHelpers.TokenInformationClass.TokenElevationType))
|
||||
{
|
||||
return (TokenElevationType)Marshal.ReadInt32(tokenInfo.DangerousGetHandle());
|
||||
}
|
||||
}
|
||||
|
||||
public static SafeNativeHandle GetTokenLinkedToken(SafeNativeHandle hToken)
|
||||
{
|
||||
using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken,
|
||||
NativeHelpers.TokenInformationClass.TokenLinkedToken))
|
||||
{
|
||||
return new SafeNativeHandle(Marshal.ReadIntPtr(tokenInfo.DangerousGetHandle()));
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<SafeNativeHandle> EnumerateUserTokens(SecurityIdentifier sid,
|
||||
TokenAccessLevels access = TokenAccessLevels.Query)
|
||||
{
|
||||
foreach (System.Diagnostics.Process process in System.Diagnostics.Process.GetProcesses())
|
||||
{
|
||||
// We always need the Query access level so we can query the TokenUser
|
||||
using (process)
|
||||
using (SafeNativeHandle hToken = TryOpenAccessToken(process, access | TokenAccessLevels.Query))
|
||||
{
|
||||
if (hToken == null)
|
||||
continue;
|
||||
|
||||
if (!sid.Equals(GetTokenUser(hToken)))
|
||||
continue;
|
||||
|
||||
yield return hToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ImpersonateToken(SafeNativeHandle hToken)
|
||||
{
|
||||
if (!NativeMethods.ImpersonateLoggedOnUser(hToken))
|
||||
throw new Win32Exception("Failed to impersonate token");
|
||||
}
|
||||
|
||||
public static SafeNativeHandle LogonUser(string username, string domain, string password, LogonType logonType,
|
||||
LogonProvider logonProvider)
|
||||
{
|
||||
SafeNativeHandle hToken;
|
||||
if (!NativeMethods.LogonUserW(username, domain, password, logonType, logonProvider, out hToken))
|
||||
throw new Win32Exception(String.Format("Failed to logon {0}",
|
||||
String.IsNullOrEmpty(domain) ? username : domain + "\\" + username));
|
||||
|
||||
return hToken;
|
||||
}
|
||||
|
||||
public static SafeNativeHandle OpenProcess()
|
||||
{
|
||||
return NativeMethods.GetCurrentProcess();
|
||||
}
|
||||
|
||||
public static SafeNativeHandle OpenProcess(Int32 pid, ProcessAccessFlags access, bool inherit)
|
||||
{
|
||||
SafeNativeHandle hProcess = NativeMethods.OpenProcess(access, inherit, (UInt32)pid);
|
||||
if (hProcess.IsInvalid)
|
||||
throw new Win32Exception(String.Format("Failed to open process {0} with access {1}",
|
||||
pid, access.ToString()));
|
||||
|
||||
return hProcess;
|
||||
}
|
||||
|
||||
public static SafeNativeHandle OpenProcessToken(SafeNativeHandle hProcess, TokenAccessLevels access)
|
||||
{
|
||||
SafeNativeHandle hToken;
|
||||
if (!NativeMethods.OpenProcessToken(hProcess, access, out hToken))
|
||||
throw new Win32Exception(String.Format("Failed to open proces token with access {0}",
|
||||
access.ToString()));
|
||||
|
||||
return hToken;
|
||||
}
|
||||
|
||||
public static void RevertToSelf()
|
||||
{
|
||||
if (!NativeMethods.RevertToSelf())
|
||||
throw new Win32Exception("Failed to revert thread impersonation");
|
||||
}
|
||||
|
||||
internal static string GetPrivilegeName(Luid luid)
|
||||
{
|
||||
UInt32 nameLen = 0;
|
||||
NativeMethods.LookupPrivilegeNameW(null, ref luid, null, ref nameLen);
|
||||
|
||||
StringBuilder name = new StringBuilder((int)(nameLen + 1));
|
||||
if (!NativeMethods.LookupPrivilegeNameW(null, ref luid, name, ref nameLen))
|
||||
throw new Win32Exception("LookupPrivilegeName() failed");
|
||||
|
||||
return name.ToString();
|
||||
}
|
||||
|
||||
private static SafeMemoryBuffer GetTokenInformation(SafeNativeHandle hToken,
|
||||
NativeHelpers.TokenInformationClass infoClass)
|
||||
{
|
||||
UInt32 tokenLength;
|
||||
bool res = NativeMethods.GetTokenInformation(hToken, infoClass, new SafeMemoryBuffer(IntPtr.Zero), 0,
|
||||
out tokenLength);
|
||||
int errCode = Marshal.GetLastWin32Error();
|
||||
if (!res && errCode != 24 && errCode != 122) // ERROR_INSUFFICIENT_BUFFER, ERROR_BAD_LENGTH
|
||||
throw new Win32Exception(errCode, String.Format("GetTokenInformation({0}) failed to get buffer length",
|
||||
infoClass.ToString()));
|
||||
|
||||
SafeMemoryBuffer tokenInfo = new SafeMemoryBuffer((int)tokenLength);
|
||||
if (!NativeMethods.GetTokenInformation(hToken, infoClass, tokenInfo, tokenLength, out tokenLength))
|
||||
throw new Win32Exception(String.Format("GetTokenInformation({0}) failed", infoClass.ToString()));
|
||||
|
||||
return tokenInfo;
|
||||
}
|
||||
|
||||
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 SafeNativeHandle TryOpenAccessToken(System.Diagnostics.Process process, TokenAccessLevels access)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (SafeNativeHandle hProcess = OpenProcess(process.Id, ProcessAccessFlags.QueryInformation, false))
|
||||
return OpenProcessToken(hProcess, access);
|
||||
}
|
||||
catch (Win32Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,378 @@
|
||||
# End of the setup code and start of the module code
|
||||
#!powershell
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.AccessToken
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
test_username = @{ type = "str"; required = $true }
|
||||
test_password = @{ type = "str"; required = $true; no_log = $true }
|
||||
}
|
||||
}
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
|
||||
$test_username = $module.Params.test_username
|
||||
$test_password = $module.Params.test_password
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
$current_user = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
|
||||
|
||||
$tests = [Ordered]@{
|
||||
"Open process token" = {
|
||||
$h_process = [Ansible.AccessToken.TokenUtil]::OpenProcess()
|
||||
|
||||
$h_token = [Ansible.AccessToken.TokenUtil]::OpenProcessToken($h_process, "Query")
|
||||
try {
|
||||
$h_token.IsClosed | Assert-Equals -Expected $false
|
||||
$h_token.IsInvalid | Assert-Equals -Expected $false
|
||||
|
||||
$actual_user = [Ansible.AccessToken.TokenUtil]::GetTokenUser($h_token)
|
||||
$actual_user | Assert-Equals -Expected $current_user
|
||||
} finally {
|
||||
$h_token.Dispose()
|
||||
}
|
||||
$h_token.IsClosed | Assert-Equals -Expected $true
|
||||
}
|
||||
|
||||
"Open process token of another process" = {
|
||||
$proc_info = Start-Process -FilePath "powershell.exe" -ArgumentList "-Command Start-Sleep -Seconds 60" -WindowStyle Hidden -PassThru
|
||||
try {
|
||||
$h_process = [Ansible.AccessToken.TokenUtil]::OpenProcess($proc_info.Id, "QueryInformation", $false)
|
||||
try {
|
||||
$h_process.IsClosed | Assert-Equals -Expected $false
|
||||
$h_process.IsInvalid | Assert-Equals -Expected $false
|
||||
|
||||
$h_token = [Ansible.AccessToken.TokenUtil]::OpenProcessToken($h_process, "Query")
|
||||
try {
|
||||
$actual_user = [Ansible.AccessToken.TokenUtil]::GetTokenUser($h_token)
|
||||
$actual_user | Assert-Equals -Expected $current_user
|
||||
} finally {
|
||||
$h_token.Dispose()
|
||||
}
|
||||
} finally {
|
||||
$h_process.Dispose()
|
||||
}
|
||||
$h_process.IsClosed | Assert-Equals -Expected $true
|
||||
} finally {
|
||||
$proc_info | Stop-Process
|
||||
}
|
||||
}
|
||||
|
||||
"Failed to open process token" = {
|
||||
$failed = $false
|
||||
try {
|
||||
$h_process = [Ansible.AccessToken.TokenUtil]::OpenProcess(4, "QueryInformation", $false)
|
||||
$h_process.Dispose() # Incase this doesn't fail, make sure we still dispose of it
|
||||
} catch [Ansible.AccessToken.Win32Exception] {
|
||||
$failed = $true
|
||||
$_.Exception.Message | Assert-Equals -Expected "Failed to open process 4 with access QueryInformation (Access is denied, Win32ErrorCode 5 - 0x00000005)"
|
||||
}
|
||||
$failed | Assert-Equals -Expected $true
|
||||
}
|
||||
|
||||
"Duplicate access token primary" = {
|
||||
$h_process = [Ansible.AccessToken.TokenUtil]::OpenProcess()
|
||||
$h_token = [Ansible.AccessToken.TokenUtil]::OpenProcessToken($h_process, "Duplicate")
|
||||
try {
|
||||
$dup_token = [Ansible.AccessToken.TokenUtil]::DuplicateToken($h_token, "Query", "Anonymous", "Primary")
|
||||
try {
|
||||
$dup_token.IsClosed | Assert-Equals -Expected $false
|
||||
$dup_token.IsInvalid | Assert-Equals -Expected $false
|
||||
|
||||
$actual_user = [Ansible.AccessToken.TokenUtil]::GetTokenUser($dup_token)
|
||||
|
||||
$actual_user | Assert-Equals -Expected $current_user
|
||||
$actual_stat = [Ansible.AccessToken.TokenUtil]::GetTokenStatistics($dup_token)
|
||||
|
||||
$actual_stat.TokenType | Assert-Equals -Expected ([Ansible.AccessToken.TokenType]::Primary)
|
||||
$actual_stat.ImpersonationLevel | Assert-Equals -Expected ([Ansible.AccessToken.SecurityImpersonationLevel]::Anonymous)
|
||||
} finally {
|
||||
$dup_token.Dispose()
|
||||
}
|
||||
|
||||
$dup_token.IsClosed | Assert-Equals -Expected $true
|
||||
} finally {
|
||||
$h_token.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
"Duplicate access token impersonation" = {
|
||||
$h_process = [Ansible.AccessToken.TokenUtil]::OpenProcess()
|
||||
$h_token = [Ansible.AccessToken.TokenUtil]::OpenProcessToken($h_process, "Duplicate")
|
||||
try {
|
||||
"Anonymous", "Identification", "Impersonation", "Delegation" | ForEach-Object -Process {
|
||||
$dup_token = [Ansible.AccessToken.TokenUtil]::DuplicateToken($h_token, "Query", $_, "Impersonation")
|
||||
try {
|
||||
$actual_user = [Ansible.AccessToken.TokenUtil]::GetTokenUser($dup_token)
|
||||
|
||||
$actual_user | Assert-Equals -Expected $current_user
|
||||
$actual_stat = [Ansible.AccessToken.TokenUtil]::GetTokenStatistics($dup_token)
|
||||
|
||||
$actual_stat.TokenType | Assert-Equals -Expected ([Ansible.AccessToken.TokenType]::Impersonation)
|
||||
$actual_stat.ImpersonationLevel | Assert-Equals -Expected ([Ansible.AccessToken.SecurityImpersonationLevel]"$_")
|
||||
} finally {
|
||||
$dup_token.Dispose()
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
$h_token.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
"Impersonate SYSTEM token" = {
|
||||
$system_sid = New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList @(
|
||||
[System.Security.Principal.WellKnownSidType]::LocalSystemSid,
|
||||
$null
|
||||
)
|
||||
$tested = $false
|
||||
foreach ($h_token in [Ansible.AccessToken.TokenUtil]::EnumerateUserTokens($system_sid, "Duplicate, Impersonate, Query")) {
|
||||
$actual_user = [Ansible.AccessToken.TokenUtil]::GetTokenUser($h_token)
|
||||
$actual_user | Assert-Equals -Expected $system_sid
|
||||
|
||||
[Ansible.AccessToken.TokenUtil]::ImpersonateToken($h_token)
|
||||
try {
|
||||
$current_sid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
|
||||
$current_sid | Assert-Equals -Expected $system_sid
|
||||
} finally {
|
||||
[Ansible.AccessToken.TokenUtil]::RevertToSelf()
|
||||
}
|
||||
|
||||
$current_sid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
|
||||
$current_sid | Assert-Equals -Expected $current_user
|
||||
|
||||
# Will keep on looping for each SYSTEM token it can retrieve, we only want to test 1
|
||||
$tested = $true
|
||||
break
|
||||
}
|
||||
|
||||
$tested | Assert-Equals -Expected $true
|
||||
}
|
||||
|
||||
"Get token privileges" = {
|
||||
$h_process = [Ansible.AccessToken.TokenUtil]::OpenProcess()
|
||||
$h_token = [Ansible.AccessToken.TokenUtil]::OpenProcessToken($h_process, "Query")
|
||||
try {
|
||||
$priv_info = &whoami.exe /priv | Where-Object { $_.StartsWith("Se") }
|
||||
$actual_privs = [Ansible.AccessToken.Tokenutil]::GetTokenPrivileges($h_token)
|
||||
$actual_stat = [Ansible.AccessToken.TokenUtil]::GetTokenStatistics($h_token)
|
||||
|
||||
$actual_privs.Count | Assert-Equals -Expected $priv_info.Count
|
||||
$actual_privs.Count | Assert-Equals -Expected $actual_stat.PrivilegeCount
|
||||
|
||||
foreach ($info in $priv_info) {
|
||||
$info_split = $info.Split(" ", [System.StringSplitOptions]::RemoveEmptyEntries)
|
||||
$priv_name = $info_split[0]
|
||||
$priv_enabled = $info_split[-1] -eq "Enabled"
|
||||
$actual_priv = $actual_privs | Where-Object { $_.Name -eq $priv_name }
|
||||
|
||||
$actual_priv -eq $null | Assert-Equals -Expected $false
|
||||
if ($priv_enabled) {
|
||||
$actual_priv.Attributes.HasFlag([Ansible.AccessToken.PrivilegeAttributes]::Enabled) | Assert-Equals -Expected $true
|
||||
} else {
|
||||
$actual_priv.Attributes.HasFlag([Ansible.AccessToken.PrivilegeAttributes]::Disabled) | Assert-Equals -Expected $true
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
$h_token.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
"Get token statistics" = {
|
||||
$h_process = [Ansible.AccessToken.TokenUtil]::OpenProcess()
|
||||
$h_token = [Ansible.AccessToken.TokenUtil]::OpenProcessToken($h_process, "Query")
|
||||
try {
|
||||
$actual_priv = [Ansible.AccessToken.Tokenutil]::GetTokenPrivileges($h_token)
|
||||
$actual_stat = [Ansible.AccessToken.TokenUtil]::GetTokenStatistics($h_token)
|
||||
|
||||
$actual_stat.TokenId.GetType().FullName | Assert-Equals -Expected "Ansible.AccessToken.Luid"
|
||||
$actual_stat.AuthenticationId.GetType().FullName | Assert-Equals -Expected "Ansible.AccessToken.Luid"
|
||||
$actual_stat.ExpirationTime.GetType().FullName | Assert-Equals -Expected "System.Int64"
|
||||
|
||||
$actual_stat.TokenType | Assert-Equals -Expected ([Ansible.AccessToken.TokenType]::Primary)
|
||||
|
||||
$os_version = [Version](Get-Item -LiteralPath $env:SystemRoot\System32\kernel32.dll).VersionInfo.ProductVersion
|
||||
if ($os_version -lt [Version]"6.1") {
|
||||
# While the token is a primary token, Server 2008 reports the SecurityImpersonationLevel for a primary token as Impersonation
|
||||
$actual_stat.ImpersonationLevel | Assert-Equals -Expected ([Ansible.AccessToken.SecurityImpersonationLevel]::Impersonation)
|
||||
} else {
|
||||
$actual_stat.ImpersonationLevel | Assert-Equals -Expected ([Ansible.AccessToken.SecurityImpersonationLevel]::Anonymous)
|
||||
}
|
||||
$actual_stat.DynamicCharged.GetType().FullName | Assert-Equals -Expected "System.UInt32"
|
||||
$actual_stat.DynamicAvailable.GetType().FullName | Assert-Equals -Expected "System.UInt32"
|
||||
$actual_stat.GroupCount.GetType().FullName | Assert-Equals -Expected "System.UInt32"
|
||||
$actual_stat.PrivilegeCount | Assert-Equals -Expected $actual_priv.Count
|
||||
$actual_stat.ModifiedId.GetType().FullName | Assert-Equals -Expected "Ansible.AccessToken.Luid"
|
||||
} finally {
|
||||
$h_token.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
"Get token linked token impersonation" = {
|
||||
$h_token = [Ansible.AccessToken.TokenUtil]::LogonUser($test_username, $null, $test_password, "Interactive", "Default")
|
||||
try {
|
||||
$actual_elevation_type = [Ansible.AccessToken.TokenUtil]::GetTokenElevationType($h_token)
|
||||
$actual_elevation_type | Assert-Equals -Expected ([Ansible.AccessToken.TokenElevationType]::Limited)
|
||||
|
||||
$actual_linked = [Ansible.AccessToken.TokenUtil]::GetTokenLinkedToken($h_token)
|
||||
try {
|
||||
$actual_linked.IsClosed | Assert-Equals -Expected $false
|
||||
$actual_linked.IsInvalid | Assert-Equals -Expected $false
|
||||
|
||||
$actual_elevation_type = [Ansible.AccessToken.TokenUtil]::GetTokenElevationType($actual_linked)
|
||||
$actual_elevation_type | Assert-Equals -Expected ([Ansible.AccessToken.TokenElevationType]::Full)
|
||||
|
||||
$actual_stat = [Ansible.AccessToken.TokenUtil]::GetTokenStatistics($actual_linked)
|
||||
$actual_stat.TokenType | Assert-Equals -Expected ([Ansible.AccessToken.TokenType]::Impersonation)
|
||||
} finally {
|
||||
$actual_linked.Dispose()
|
||||
}
|
||||
$actual_linked.IsClosed | Assert-Equals -Expected $true
|
||||
} finally {
|
||||
$h_token.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
"Get token linked token primary" = {
|
||||
# We need a token with the SeTcbPrivilege for this to work.
|
||||
$system_sid = New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList @(
|
||||
[System.Security.Principal.WellKnownSidType]::LocalSystemSid,
|
||||
$null
|
||||
)
|
||||
$tested = $false
|
||||
foreach ($system_token in [Ansible.AccessToken.TokenUtil]::EnumerateUserTokens($system_sid, "Duplicate, Impersonate, Query")) {
|
||||
$privileges = [Ansible.AccessToken.TokenUtil]::GetTokenPrivileges($system_token)
|
||||
if ($null -eq ($privileges | Where-Object { $_.Name -eq "SeTcbPrivilege" })) {
|
||||
continue
|
||||
}
|
||||
|
||||
$h_token = [Ansible.AccessToken.TokenUtil]::LogonUser($test_username, $null, $test_password, "Interactive", "Default")
|
||||
try {
|
||||
[Ansible.AccessToken.TokenUtil]::ImpersonateToken($system_token)
|
||||
try {
|
||||
$actual_linked = [Ansible.AccessToken.TokenUtil]::GetTokenLinkedToken($h_token)
|
||||
try {
|
||||
$actual_linked.IsClosed | Assert-Equals -Expected $false
|
||||
$actual_linked.IsInvalid | Assert-Equals -Expected $false
|
||||
|
||||
$actual_elevation_type = [Ansible.AccessToken.TokenUtil]::GetTokenElevationType($actual_linked)
|
||||
$actual_elevation_type | Assert-Equals -Expected ([Ansible.AccessToken.TokenElevationType]::Full)
|
||||
|
||||
$actual_stat = [Ansible.AccessToken.TokenUtil]::GetTokenStatistics($actual_linked)
|
||||
$actual_stat.TokenType | Assert-Equals -Expected ([Ansible.AccessToken.TokenType]::Primary)
|
||||
} finally {
|
||||
$actual_linked.Dispose()
|
||||
}
|
||||
$actual_linked.IsClosed | Assert-Equals -Expected $true
|
||||
} finally {
|
||||
[Ansible.AccessToken.TokenUtil]::RevertToSelf()
|
||||
}
|
||||
} finally {
|
||||
$h_token.Dispose()
|
||||
}
|
||||
|
||||
$tested = $true
|
||||
break
|
||||
}
|
||||
$tested | Assert-Equals -Expected $true
|
||||
}
|
||||
|
||||
"Failed to get token information" = {
|
||||
$h_process = [Ansible.AccessToken.TokenUtil]::OpenProcess()
|
||||
$h_token = [Ansible.AccessToken.TokenUtil]::OpenProcessToken($h_process, 'Duplicate') # Without Query the below will fail
|
||||
|
||||
$failed = $false
|
||||
try {
|
||||
[Ansible.AccessToken.TokenUtil]::GetTokenUser($h_token)
|
||||
} catch [Ansible.AccessToken.Win32Exception] {
|
||||
$failed = $true
|
||||
$_.Exception.Message | Assert-Equals -Expected "GetTokenInformation(TokenUser) failed to get buffer length (Access is denied, Win32ErrorCode 5 - 0x00000005)"
|
||||
} finally {
|
||||
$h_token.Dispose()
|
||||
}
|
||||
$failed | Assert-Equals -Expected $true
|
||||
}
|
||||
|
||||
"Logon with valid credentials" = {
|
||||
$expected_user = New-Object -TypeName System.Security.Principal.NTAccount -ArgumentList $test_username
|
||||
$expected_sid = $expected_user.Translate([System.Security.Principal.SecurityIdentifier])
|
||||
|
||||
$h_token = [Ansible.AccessToken.TokenUtil]::LogonUser($test_username, $null, $test_password, "Network", "Default")
|
||||
try {
|
||||
$h_token.IsClosed | Assert-Equals -Expected $false
|
||||
$h_token.IsInvalid | Assert-Equals -Expected $false
|
||||
|
||||
$actual_user = [Ansible.AccessToken.TokenUtil]::GetTokenUser($h_token)
|
||||
$actual_user | Assert-Equals -Expected $expected_sid
|
||||
} finally {
|
||||
$h_token.Dispose()
|
||||
}
|
||||
$h_token.IsClosed | Assert-Equals -Expected $true
|
||||
}
|
||||
|
||||
"Logon with invalid credentials" = {
|
||||
$failed = $false
|
||||
try {
|
||||
[Ansible.AccessToken.TokenUtil]::LogonUser("fake-user", $null, "fake-pass", "Network", "Default")
|
||||
} catch [Ansible.AccessToken.Win32Exception] {
|
||||
$failed = $true
|
||||
$_.Exception.Message.Contains("Failed to logon fake-user") | Assert-Equals -Expected $true
|
||||
$_.Exception.Message.Contains("Win32ErrorCode 1326 - 0x0000052E)") | Assert-Equals -Expected $true
|
||||
}
|
||||
$failed | Assert-Equals -Expected $true
|
||||
}
|
||||
|
||||
"Logon with invalid credential with domain account" = {
|
||||
$failed = $false
|
||||
try {
|
||||
[Ansible.AccessToken.TokenUtil]::LogonUser("fake-user", "fake-domain", "fake-pass", "Network", "Default")
|
||||
} catch [Ansible.AccessToken.Win32Exception] {
|
||||
$failed = $true
|
||||
$_.Exception.Message.Contains("Failed to logon fake-domain\fake-user") | Assert-Equals -Expected $true
|
||||
$_.Exception.Message.Contains("Win32ErrorCode 1326 - 0x0000052E)") | Assert-Equals -Expected $true
|
||||
}
|
||||
$failed | Assert-Equals -Expected $true
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($test_impl in $tests.GetEnumerator()) {
|
||||
$test = $test_impl.Key
|
||||
&$test_impl.Value
|
||||
}
|
||||
|
||||
$module.Result.data = "success"
|
||||
$module.ExitJson()
|
Loading…
Reference in New Issue