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 2008
This commit is contained in:
parent
49e16922b2
commit
dbd082efe4
10 changed files with 1076 additions and 716 deletions
2
changelogs/fragments/win_access_token_util.yaml
Normal file
2
changelogs/fragments/win_access_token_util.yaml
Normal file
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- Added C# module util that implements various access token functions
|
|
@ -6,26 +6,29 @@ param(
|
|||
)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.AddType
|
||||
#AnsibleRequires -CSharpUtil Ansible.AccessToken
|
||||
#AnsibleRequires -CSharpUtil Ansible.Become
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-AnsibleLog "INFO - starting become_wrapper" "become_wrapper"
|
||||
|
||||
Function Get-EnumValue($enum, $flag_type, $value, $prefix) {
|
||||
$raw_enum_value = "$prefix$($value.ToUpper())"
|
||||
Function Get-EnumValue($enum, $flag_type, $value) {
|
||||
$raw_enum_value = $value.Replace('_', '')
|
||||
try {
|
||||
$enum_value = [Enum]::Parse($enum, $raw_enum_value)
|
||||
$enum_value = [Enum]::Parse($enum, $raw_enum_value, $true)
|
||||
} catch [System.ArgumentException] {
|
||||
$valid_options = [Enum]::GetNames($enum) | ForEach-Object { $_.Substring($prefix.Length).ToLower() }
|
||||
$valid_options = [Enum]::GetNames($enum) | ForEach-Object -Process {
|
||||
(($_ -creplace "(.)([A-Z][a-z]+)", '$1_$2') -creplace "([a-z0-9])([A-Z])", '$1_$2').ToString().ToLower()
|
||||
}
|
||||
throw "become_flags $flag_type value '$value' is not valid, valid values are: $($valid_options -join ", ")"
|
||||
}
|
||||
return $enum_value
|
||||
}
|
||||
|
||||
Function Get-BecomeFlags($flags) {
|
||||
$logon_type = [Ansible.Become.LogonType]::LOGON32_LOGON_INTERACTIVE
|
||||
$logon_flags = [Ansible.Become.LogonFlags]::LOGON_WITH_PROFILE
|
||||
$logon_type = [Ansible.AccessToken.LogonType]::Interactive
|
||||
$logon_flags = [Ansible.Become.LogonFlags]::WithProfile
|
||||
|
||||
if ($null -eq $flags -or $flags -eq "") {
|
||||
$flag_split = @()
|
||||
|
@ -44,10 +47,9 @@ Function Get-BecomeFlags($flags) {
|
|||
$flag_value = $split[1]
|
||||
if ($flag_key -eq "logon_type") {
|
||||
$enum_details = @{
|
||||
enum = [Ansible.Become.LogonType]
|
||||
enum = [Ansible.AccessToken.LogonType]
|
||||
flag_type = $flag_key
|
||||
value = $flag_value
|
||||
prefix = "LOGON32_LOGON_"
|
||||
}
|
||||
$logon_type = Get-EnumValue @enum_details
|
||||
} elseif ($flag_key -eq "logon_flags") {
|
||||
|
@ -61,7 +63,6 @@ Function Get-BecomeFlags($flags) {
|
|||
enum = [Ansible.Become.LogonFlags]
|
||||
flag_type = $flag_key
|
||||
value = $logon_flag_value
|
||||
prefix = "LOGON_"
|
||||
}
|
||||
$logon_flag = Get-EnumValue @enum_details
|
||||
$logon_flags = $logon_flags -bor $logon_flag
|
||||
|
@ -80,9 +81,10 @@ $add_type = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64S
|
|||
New-Module -Name Ansible.ModuleUtils.AddType -ScriptBlock ([ScriptBlock]::Create($add_type)) | Import-Module > $null
|
||||
|
||||
$new_tmp = [System.Environment]::ExpandEnvironmentVariables($Payload.module_args["_ansible_remote_tmp"])
|
||||
$access_def = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Payload.csharp_utils["Ansible.AccessToken"]))
|
||||
$become_def = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Payload.csharp_utils["Ansible.Become"]))
|
||||
$process_def = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Payload.csharp_utils["Ansible.Process"]))
|
||||
Add-CSharpType -References $become_def, $process_def -TempPath $new_tmp -IncludeDebugInfo
|
||||
Add-CSharpType -References $access_def, $become_def, $process_def -TempPath $new_tmp -IncludeDebugInfo
|
||||
|
||||
$username = $Payload.become_user
|
||||
$password = $Payload.become_password
|
||||
|
|
457
lib/ansible/module_utils/csharp/Ansible.AccessToken.cs
Normal file
457
lib/ansible/module_utils/csharp/Ansible.AccessToken.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,13 +3,14 @@ using System;
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
using Ansible.AccessToken;
|
||||
using Ansible.Process;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ansible.Become
|
||||
{
|
||||
|
@ -59,85 +60,22 @@ namespace Ansible.Become
|
|||
public IntPtr Buffer;
|
||||
}
|
||||
|
||||
[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 LUID_AND_ATTRIBUTES
|
||||
{
|
||||
public LUID Luid;
|
||||
public UInt32 Attributes;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SECURITY_LOGON_SESSION_DATA
|
||||
{
|
||||
public UInt32 Size;
|
||||
public LUID LogonId;
|
||||
public Luid LogonId;
|
||||
public LSA_UNICODE_STRING UserName;
|
||||
public LSA_UNICODE_STRING LogonDomain;
|
||||
public LSA_UNICODE_STRING AuthenticationPackage;
|
||||
public SECURITY_LOGON_TYPE LogonType;
|
||||
}
|
||||
|
||||
[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_SOURCE
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public char[] SourceName;
|
||||
public LUID SourceIdentifier;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct TOKEN_STATISTICS
|
||||
{
|
||||
public LUID TokenId;
|
||||
public LUID AuthenticationId;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct TOKEN_USER
|
||||
{
|
||||
public SID_AND_ATTRIBUTES User;
|
||||
}
|
||||
|
||||
public enum LogonProvider
|
||||
{
|
||||
LOGON32_PROVIDER_DEFAULT = 0,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ProcessAccessFlags : uint
|
||||
{
|
||||
PROCESS_QUERY_INFORMATION = 0x00000400,
|
||||
}
|
||||
|
||||
public enum SECURITY_IMPERSONATION_LEVEL
|
||||
{
|
||||
SecurityImpersonation,
|
||||
public Luid SourceIdentifier;
|
||||
}
|
||||
|
||||
public enum SECURITY_LOGON_TYPE
|
||||
|
@ -156,39 +94,13 @@ namespace Ansible.Become
|
|||
CachedRemoteInteractive,
|
||||
CachedUnlock
|
||||
}
|
||||
|
||||
public enum TOKEN_TYPE
|
||||
{
|
||||
TokenPrimary = 1,
|
||||
TokenImpersonation
|
||||
}
|
||||
|
||||
public enum TokenElevationType
|
||||
{
|
||||
TokenElevationTypeDefault = 1,
|
||||
TokenElevationTypeFull,
|
||||
TokenElevationTypeLimited
|
||||
}
|
||||
|
||||
public enum TokenInformationClass
|
||||
{
|
||||
TokenUser = 1,
|
||||
TokenPrivileges = 3,
|
||||
TokenStatistics = 10,
|
||||
TokenElevationType = 18,
|
||||
TokenLinkedToken = 19,
|
||||
}
|
||||
}
|
||||
|
||||
internal class NativeMethods
|
||||
{
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool AllocateLocallyUniqueId(
|
||||
out NativeHelpers.LUID Luid);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool CloseHandle(
|
||||
IntPtr hObject);
|
||||
out Luid Luid);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool CreateProcessWithTokenW(
|
||||
|
@ -197,20 +109,11 @@ namespace Ansible.Become
|
|||
[MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName,
|
||||
StringBuilder lpCommandLine,
|
||||
Process.NativeHelpers.ProcessCreationFlags dwCreationFlags,
|
||||
SafeMemoryBuffer lpEnvironment,
|
||||
Process.SafeMemoryBuffer lpEnvironment,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory,
|
||||
Process.NativeHelpers.STARTUPINFOEX lpStartupInfo,
|
||||
out Process.NativeHelpers.PROCESS_INFORMATION lpProcessInformation);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool DuplicateTokenEx(
|
||||
SafeNativeHandle hExistingToken,
|
||||
TokenAccessLevels dwDesiredAccess,
|
||||
IntPtr lpTokenAttributes,
|
||||
NativeHelpers.SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
|
||||
NativeHelpers.TOKEN_TYPE TokenType,
|
||||
out SafeNativeHandle phNewToken);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern UInt32 GetCurrentThreadId();
|
||||
|
||||
|
@ -221,50 +124,17 @@ namespace Ansible.Become
|
|||
public static extern NoopSafeHandle GetThreadDesktop(
|
||||
UInt32 dwThreadId);
|
||||
|
||||
[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,
|
||||
NativeHelpers.LogonProvider dwLogonProvider,
|
||||
out SafeNativeHandle phToken);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool LookupPrivilegeNameW(
|
||||
string lpSystemName,
|
||||
ref NativeHelpers.LUID lpLuid,
|
||||
StringBuilder lpName,
|
||||
ref UInt32 cchName);
|
||||
|
||||
[DllImport("secur32.dll", SetLastError = true)]
|
||||
public static extern UInt32 LsaDeregisterLogonProcess(
|
||||
IntPtr LsaHandle);
|
||||
|
||||
[DllImport("secur32.dll", SetLastError = true)]
|
||||
public static extern UInt32 LsaEnumerateLogonSessions(
|
||||
out UInt32 LogonSessionCount,
|
||||
out SafeLsaMemoryBuffer LogonSessionList);
|
||||
|
||||
[DllImport("secur32.dll", SetLastError = true)]
|
||||
public static extern UInt32 LsaFreeReturnBuffer(
|
||||
IntPtr Buffer);
|
||||
|
||||
[DllImport("secur32.dll", SetLastError = true)]
|
||||
public static extern UInt32 LsaGetLogonSessionData(
|
||||
IntPtr LogonId,
|
||||
ref Luid LogonId,
|
||||
out SafeLsaMemoryBuffer ppLogonSessionData);
|
||||
|
||||
[DllImport("secur32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
|
@ -279,7 +149,7 @@ namespace Ansible.Become
|
|||
NativeHelpers.TOKEN_SOURCE SourceContext,
|
||||
out SafeLsaMemoryBuffer ProfileBuffer,
|
||||
out UInt32 ProfileBufferLength,
|
||||
out NativeHelpers.LUID LogonId,
|
||||
out Luid LogonId,
|
||||
out SafeNativeHandle Token,
|
||||
out IntPtr Quotas,
|
||||
out UInt32 SubStatus);
|
||||
|
@ -299,21 +169,6 @@ namespace Ansible.Become
|
|||
NativeHelpers.LSA_STRING LogonProcessName,
|
||||
out SafeLsaHandle LsaHandle,
|
||||
out IntPtr SecurityMode);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern SafeNativeHandle OpenProcess(
|
||||
NativeHelpers.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 SafeLsaHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
|
@ -340,18 +195,6 @@ namespace Ansible.Become
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
internal class NoopSafeHandle : SafeHandle
|
||||
{
|
||||
public NoopSafeHandle() : base(IntPtr.Zero, false) { }
|
||||
|
@ -364,19 +207,8 @@ namespace Ansible.Become
|
|||
[Flags]
|
||||
public enum LogonFlags
|
||||
{
|
||||
LOGON_WITH_PROFILE = 0x00000001,
|
||||
LOGON_NETCREDENTIALS_ONLY = 0x00000002
|
||||
}
|
||||
|
||||
public enum LogonType
|
||||
{
|
||||
LOGON32_LOGON_INTERACTIVE = 2,
|
||||
LOGON32_LOGON_NETWORK = 3,
|
||||
LOGON32_LOGON_BATCH = 4,
|
||||
LOGON32_LOGON_SERVICE = 5,
|
||||
LOGON32_LOGON_UNLOCK = 7,
|
||||
LOGON32_LOGON_NETWORK_CLEARTEXT = 8,
|
||||
LOGON32_LOGON_NEW_CREDENTIALS = 9
|
||||
WithProfile = 0x00000001,
|
||||
NetcredentialsOnly = 0x00000002
|
||||
}
|
||||
|
||||
public class BecomeUtil
|
||||
|
@ -392,7 +224,7 @@ namespace Ansible.Become
|
|||
|
||||
public static Result CreateProcessAsUser(string username, string password, string command)
|
||||
{
|
||||
return CreateProcessAsUser(username, password, LogonFlags.LOGON_WITH_PROFILE, LogonType.LOGON32_LOGON_INTERACTIVE,
|
||||
return CreateProcessAsUser(username, password, LogonFlags.WithProfile, LogonType.Interactive,
|
||||
null, command, null, null, "");
|
||||
}
|
||||
|
||||
|
@ -450,38 +282,25 @@ namespace Ansible.Become
|
|||
if (lpCurrentDirectory == "")
|
||||
lpCurrentDirectory = null;
|
||||
|
||||
using (SafeMemoryBuffer lpEnvironment = ProcessUtil.CreateEnvironmentPointer(environment))
|
||||
using (Process.SafeMemoryBuffer lpEnvironment = ProcessUtil.CreateEnvironmentPointer(environment))
|
||||
using (SafeNativeHandle hToken = GetUserToken(username, password, logonType))
|
||||
{
|
||||
// A user may have 2 tokens, 1 limited and 1 elevated. GetUserToken will try and get both but we will
|
||||
// only find out if the elevated token is valid when running here.
|
||||
List<SafeNativeHandle> userTokens = GetUserTokens(username, password, logonType);
|
||||
|
||||
bool launchSuccess = false;
|
||||
StringBuilder commandLine = new StringBuilder(lpCommandLine);
|
||||
foreach (SafeNativeHandle token in userTokens)
|
||||
if (!NativeMethods.CreateProcessWithTokenW(hToken, logonFlags, lpApplicationName, commandLine,
|
||||
creationFlags, lpEnvironment, lpCurrentDirectory, si, out pi))
|
||||
{
|
||||
if (NativeMethods.CreateProcessWithTokenW(token, logonFlags, lpApplicationName, commandLine,
|
||||
creationFlags, lpEnvironment, lpCurrentDirectory, si, out pi))
|
||||
{
|
||||
launchSuccess = true;
|
||||
break;
|
||||
}
|
||||
throw new Process.Win32Exception("CreateProcessWithTokenW() failed");
|
||||
}
|
||||
|
||||
if (!launchSuccess)
|
||||
throw new Win32Exception("CreateProcessWithTokenW() failed");
|
||||
}
|
||||
return ProcessUtil.WaitProcess(stdoutRead, stdoutWrite, stderrRead, stderrWrite, stdinStream, stdin, pi.hProcess);
|
||||
}
|
||||
|
||||
private static List<SafeNativeHandle> GetUserTokens(string username, string password, LogonType logonType)
|
||||
private static SafeNativeHandle GetUserToken(string username, string password, LogonType logonType)
|
||||
{
|
||||
List<SafeNativeHandle> userTokens = new List<SafeNativeHandle>();
|
||||
|
||||
SafeNativeHandle systemToken = null;
|
||||
bool impersonated = false;
|
||||
string becomeSid = username;
|
||||
if (logonType != LogonType.LOGON32_LOGON_NEW_CREDENTIALS)
|
||||
if (logonType != LogonType.NewCredentials)
|
||||
{
|
||||
// If prefixed with .\, we are becoming a local account, strip the prefix
|
||||
if (username.StartsWith(".\\"))
|
||||
|
@ -497,7 +316,14 @@ namespace Ansible.Become
|
|||
// account or have administrative rights on the become access token.
|
||||
systemToken = GetPrimaryTokenForUser(new SecurityIdentifier("S-1-5-18"), new List<string>() { "SeTcbPrivilege" });
|
||||
if (systemToken != null)
|
||||
impersonated = NativeMethods.ImpersonateLoggedOnUser(systemToken);
|
||||
{
|
||||
try
|
||||
{
|
||||
TokenUtil.ImpersonateToken(systemToken);
|
||||
impersonated = true;
|
||||
}
|
||||
catch (Process.Win32Exception) {} // We tried, just rely on current user's permissions.
|
||||
}
|
||||
}
|
||||
|
||||
// We require impersonation if becoming a service sid or becoming a user without a password
|
||||
|
@ -507,25 +333,19 @@ namespace Ansible.Become
|
|||
try
|
||||
{
|
||||
if (becomeSid == "S-1-5-18")
|
||||
userTokens.Add(systemToken);
|
||||
return systemToken;
|
||||
// Cannot use String.IsEmptyOrNull() as an empty string is an account that doesn't have a pass.
|
||||
// We only use S4U if no password was defined or it was null
|
||||
else if (!SERVICE_SIDS.Contains(becomeSid) && password == null && logonType != LogonType.LOGON32_LOGON_NEW_CREDENTIALS)
|
||||
else if (!SERVICE_SIDS.Contains(becomeSid) && password == null && logonType != LogonType.NewCredentials)
|
||||
{
|
||||
// If no password was specified, try and duplicate an existing token for that user or use S4U to
|
||||
// generate one without network credentials
|
||||
SecurityIdentifier sid = new SecurityIdentifier(becomeSid);
|
||||
SafeNativeHandle becomeToken = GetPrimaryTokenForUser(sid);
|
||||
if (becomeToken != null)
|
||||
{
|
||||
userTokens.Add(GetElevatedToken(becomeToken));
|
||||
userTokens.Add(becomeToken);
|
||||
}
|
||||
return GetElevatedToken(becomeToken);
|
||||
else
|
||||
{
|
||||
becomeToken = GetS4UTokenForUser(sid, logonType);
|
||||
userTokens.Add(becomeToken);
|
||||
}
|
||||
return GetS4UTokenForUser(sid, logonType);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -533,12 +353,12 @@ namespace Ansible.Become
|
|||
switch (becomeSid)
|
||||
{
|
||||
case "S-1-5-19":
|
||||
logonType = LogonType.LOGON32_LOGON_SERVICE;
|
||||
logonType = LogonType.Service;
|
||||
domain = "NT AUTHORITY";
|
||||
username = "LocalService";
|
||||
break;
|
||||
case "S-1-5-20":
|
||||
logonType = LogonType.LOGON32_LOGON_SERVICE;
|
||||
logonType = LogonType.Service;
|
||||
domain = "NT AUTHORITY";
|
||||
username = "NetworkService";
|
||||
break;
|
||||
|
@ -555,31 +375,25 @@ namespace Ansible.Become
|
|||
break;
|
||||
}
|
||||
|
||||
SafeNativeHandle hToken;
|
||||
if (!NativeMethods.LogonUserW(username, domain, password, logonType,
|
||||
NativeHelpers.LogonProvider.LOGON32_PROVIDER_DEFAULT, out hToken))
|
||||
{
|
||||
throw new Win32Exception("LogonUserW() failed");
|
||||
}
|
||||
SafeNativeHandle hToken = TokenUtil.LogonUser(username, domain, password, logonType,
|
||||
LogonProvider.Default);
|
||||
|
||||
// Get the elevated token for a local/domain accounts only
|
||||
if (!SERVICE_SIDS.Contains(becomeSid))
|
||||
userTokens.Add(GetElevatedToken(hToken));
|
||||
userTokens.Add(hToken);
|
||||
return GetElevatedToken(hToken);
|
||||
else
|
||||
return hToken;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (impersonated)
|
||||
NativeMethods.RevertToSelf();
|
||||
TokenUtil.RevertToSelf();
|
||||
}
|
||||
|
||||
return userTokens;
|
||||
}
|
||||
|
||||
private static SafeNativeHandle GetPrimaryTokenForUser(SecurityIdentifier sid, List<string> requiredPrivileges = null)
|
||||
{
|
||||
NativeHelpers.ProcessAccessFlags accessFlags = NativeHelpers.ProcessAccessFlags.PROCESS_QUERY_INFORMATION;
|
||||
// According to CreateProcessWithTokenW we require a token with
|
||||
// TOKEN_QUERY, TOKEN_DUPLICATE and TOKEN_ASSIGN_PRIMARY
|
||||
// Also add in TOKEN_IMPERSONATE so we can get an impersonated token
|
||||
|
@ -588,51 +402,32 @@ namespace Ansible.Become
|
|||
TokenAccessLevels.AssignPrimary |
|
||||
TokenAccessLevels.Impersonate;
|
||||
|
||||
foreach (System.Diagnostics.Process process in System.Diagnostics.Process.GetProcesses())
|
||||
foreach (SafeNativeHandle hToken in TokenUtil.EnumerateUserTokens(sid, dwAccess))
|
||||
{
|
||||
using (process)
|
||||
// Filter out any Network logon tokens, using become with that is useless when S4U
|
||||
// can give us a Batch logon
|
||||
NativeHelpers.SECURITY_LOGON_TYPE tokenLogonType = GetTokenLogonType(hToken);
|
||||
if (tokenLogonType == NativeHelpers.SECURITY_LOGON_TYPE.Network)
|
||||
continue;
|
||||
|
||||
// Check that the required privileges are on the token
|
||||
if (requiredPrivileges != null)
|
||||
{
|
||||
using (SafeNativeHandle hProcess = NativeMethods.OpenProcess(accessFlags, false, (UInt32)process.Id))
|
||||
{
|
||||
if (hProcess.IsInvalid)
|
||||
continue;
|
||||
List<string> actualPrivileges = TokenUtil.GetTokenPrivileges(hToken).Select(x => x.Name).ToList();
|
||||
int missing = requiredPrivileges.Where(x => !actualPrivileges.Contains(x)).Count();
|
||||
if (missing > 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
SafeNativeHandle hToken;
|
||||
NativeMethods.OpenProcessToken(hProcess, dwAccess, out hToken);
|
||||
if (hToken.IsInvalid)
|
||||
continue;
|
||||
|
||||
using (hToken)
|
||||
{
|
||||
if (!sid.Equals(GetTokenUserSID(hToken)))
|
||||
continue;
|
||||
|
||||
// Filter out any Network logon tokens, using become with that is useless when S4U
|
||||
// can give us a Batch logon
|
||||
NativeHelpers.SECURITY_LOGON_TYPE tokenLogonType = GetTokenLogonType(hToken);
|
||||
if (tokenLogonType == NativeHelpers.SECURITY_LOGON_TYPE.Network)
|
||||
continue;
|
||||
|
||||
// Check that the required privileges are on the token
|
||||
if (requiredPrivileges != null)
|
||||
{
|
||||
List<string> actualPrivileges = GetTokenPrivileges(hToken);
|
||||
int missing = requiredPrivileges.Where(x => !actualPrivileges.Contains(x)).Count();
|
||||
if (missing > 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
SafeNativeHandle dupToken;
|
||||
if (!NativeMethods.DuplicateTokenEx(hToken, TokenAccessLevels.MaximumAllowed,
|
||||
IntPtr.Zero, NativeHelpers.SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
|
||||
NativeHelpers.TOKEN_TYPE.TokenPrimary, out dupToken))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return dupToken;
|
||||
}
|
||||
}
|
||||
// Duplicate the token to convert it to a primary token with the access level required.
|
||||
try
|
||||
{
|
||||
return TokenUtil.DuplicateToken(hToken, TokenAccessLevels.MaximumAllowed, SecurityImpersonationLevel.Anonymous,
|
||||
TokenType.Primary);
|
||||
}
|
||||
catch (Process.Win32Exception)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -652,7 +447,7 @@ namespace Ansible.Become
|
|||
IntPtr securityMode;
|
||||
UInt32 res = NativeMethods.LsaRegisterLogonProcess(logonProcessName, out lsaHandle, out securityMode);
|
||||
if (res != 0)
|
||||
throw new Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res), "LsaRegisterLogonProcess() failed");
|
||||
throw new Process.Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res), "LsaRegisterLogonProcess() failed");
|
||||
|
||||
using (lsaHandle)
|
||||
{
|
||||
|
@ -660,7 +455,7 @@ namespace Ansible.Become
|
|||
UInt32 authPackage;
|
||||
res = NativeMethods.LsaLookupAuthenticationPackage(lsaHandle, packageName, out authPackage);
|
||||
if (res != 0)
|
||||
throw new Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res),
|
||||
throw new Process.Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res),
|
||||
String.Format("LsaLookupAuthenticationPackage({0}) failed", (string)packageName));
|
||||
|
||||
int usernameLength = username.Length * sizeof(char);
|
||||
|
@ -694,9 +489,9 @@ namespace Ansible.Become
|
|||
Marshal.Copy(username.ToCharArray(), 0, usernamePtr, username.Length);
|
||||
Marshal.Copy(domainName.ToCharArray(), 0, domainPtr, domainName.Length);
|
||||
|
||||
NativeHelpers.LUID sourceLuid;
|
||||
Luid sourceLuid;
|
||||
if (!NativeMethods.AllocateLocallyUniqueId(out sourceLuid))
|
||||
throw new Win32Exception("AllocateLocallyUniqueId() failed");
|
||||
throw new Process.Win32Exception("AllocateLocallyUniqueId() failed");
|
||||
|
||||
NativeHelpers.TOKEN_SOURCE tokenSource = new NativeHelpers.TOKEN_SOURCE
|
||||
{
|
||||
|
@ -705,12 +500,12 @@ namespace Ansible.Become
|
|||
};
|
||||
|
||||
// Only Batch or Network will work with S4U, prefer Batch but use Network if asked
|
||||
LogonType lsaLogonType = logonType == LogonType.LOGON32_LOGON_NETWORK
|
||||
? LogonType.LOGON32_LOGON_NETWORK
|
||||
: LogonType.LOGON32_LOGON_BATCH;
|
||||
LogonType lsaLogonType = logonType == LogonType.Network
|
||||
? LogonType.Network
|
||||
: LogonType.Batch;
|
||||
SafeLsaMemoryBuffer profileBuffer;
|
||||
UInt32 profileBufferLength;
|
||||
NativeHelpers.LUID logonId;
|
||||
Luid logonId;
|
||||
SafeNativeHandle hToken;
|
||||
IntPtr quotas;
|
||||
UInt32 subStatus;
|
||||
|
@ -719,7 +514,7 @@ namespace Ansible.Become
|
|||
authInfo, (UInt32)authInfoLength, IntPtr.Zero, tokenSource, out profileBuffer, out profileBufferLength,
|
||||
out logonId, out hToken, out quotas, out subStatus);
|
||||
if (res != 0)
|
||||
throw new Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res),
|
||||
throw new Process.Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res),
|
||||
String.Format("LsaLogonUser() failed with substatus {0}", subStatus));
|
||||
|
||||
profileBuffer.Dispose();
|
||||
|
@ -734,121 +529,38 @@ namespace Ansible.Become
|
|||
|
||||
private static SafeNativeHandle GetElevatedToken(SafeNativeHandle hToken)
|
||||
{
|
||||
// First determine if the current token is a limited token
|
||||
using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken, NativeHelpers.TokenInformationClass.TokenElevationType))
|
||||
{
|
||||
NativeHelpers.TokenElevationType tet = (NativeHelpers.TokenElevationType)Marshal.ReadInt32(tokenInfo.DangerousGetHandle());
|
||||
// We already have the best token we can get, just use it
|
||||
if (tet != NativeHelpers.TokenElevationType.TokenElevationTypeLimited)
|
||||
return hToken;
|
||||
}
|
||||
TokenElevationType tet = TokenUtil.GetTokenElevationType(hToken);
|
||||
// We already have the best token we can get, just use it
|
||||
if (tet != TokenElevationType.Limited)
|
||||
return hToken;
|
||||
|
||||
// We have a limited token, get the linked elevated token
|
||||
using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken, NativeHelpers.TokenInformationClass.TokenLinkedToken))
|
||||
return new SafeNativeHandle(Marshal.ReadIntPtr(tokenInfo.DangerousGetHandle()));
|
||||
}
|
||||
SafeNativeHandle linkedToken = TokenUtil.GetTokenLinkedToken(hToken);
|
||||
TokenStatistics tokenStats = TokenUtil.GetTokenStatistics(linkedToken);
|
||||
|
||||
private static List<string> GetTokenPrivileges(SafeNativeHandle hToken)
|
||||
{
|
||||
using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken, NativeHelpers.TokenInformationClass.TokenPrivileges))
|
||||
{
|
||||
NativeHelpers.TOKEN_PRIVILEGES tokenPrivileges = (NativeHelpers.TOKEN_PRIVILEGES)Marshal.PtrToStructure(
|
||||
tokenInfo.DangerousGetHandle(), typeof(NativeHelpers.TOKEN_PRIVILEGES));
|
||||
|
||||
NativeHelpers.LUID_AND_ATTRIBUTES[] luidAndAttributes = new NativeHelpers.LUID_AND_ATTRIBUTES[tokenPrivileges.PrivilegeCount];
|
||||
PtrToStructureArray(luidAndAttributes, IntPtr.Add(tokenInfo.DangerousGetHandle(), Marshal.SizeOf(tokenPrivileges.PrivilegeCount)));
|
||||
|
||||
return luidAndAttributes.Select(x => GetPrivilegeName(x.Luid)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private static SecurityIdentifier GetTokenUserSID(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);
|
||||
}
|
||||
// We can only use a token if it's a primary one (we had the SeTcbPrivilege set)
|
||||
if (tokenStats.TokenType == TokenType.Primary)
|
||||
return linkedToken;
|
||||
else
|
||||
return hToken;
|
||||
}
|
||||
|
||||
private static NativeHelpers.SECURITY_LOGON_TYPE GetTokenLogonType(SafeNativeHandle hToken)
|
||||
{
|
||||
UInt64 tokenLuidId;
|
||||
using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken, NativeHelpers.TokenInformationClass.TokenStatistics))
|
||||
{
|
||||
NativeHelpers.TOKEN_STATISTICS stats = (NativeHelpers.TOKEN_STATISTICS)Marshal.PtrToStructure(
|
||||
tokenInfo.DangerousGetHandle(), typeof(NativeHelpers.TOKEN_STATISTICS));
|
||||
tokenLuidId = (UInt64)stats.AuthenticationId;
|
||||
}
|
||||
TokenStatistics stats = TokenUtil.GetTokenStatistics(hToken);
|
||||
|
||||
// Default to Network, if we weren't able to get the actual type treat it as an error and assume
|
||||
// we don't want to run a process with the token
|
||||
NativeHelpers.SECURITY_LOGON_TYPE logonType = NativeHelpers.SECURITY_LOGON_TYPE.Network;
|
||||
UInt32 sessionCount;
|
||||
SafeLsaMemoryBuffer sessionPtr;
|
||||
UInt32 res = NativeMethods.LsaEnumerateLogonSessions(out sessionCount, out sessionPtr);
|
||||
SafeLsaMemoryBuffer sessionDataPtr;
|
||||
UInt32 res = NativeMethods.LsaGetLogonSessionData(ref stats.AuthenticationId, out sessionDataPtr);
|
||||
if (res != 0)
|
||||
throw new Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res), "LsaEnumerateLogonSession() failed");
|
||||
using (sessionPtr)
|
||||
// Default to Network, if we weren't able to get the actual type treat it as an error and assume
|
||||
// we don't want to run a process with the token
|
||||
return NativeHelpers.SECURITY_LOGON_TYPE.Network;
|
||||
|
||||
using (sessionDataPtr)
|
||||
{
|
||||
for (IntPtr p = sessionPtr.DangerousGetHandle();
|
||||
p != IntPtr.Add(sessionPtr.DangerousGetHandle(), (int)(Marshal.SizeOf(typeof(NativeHelpers.LUID)) * sessionCount));
|
||||
p = IntPtr.Add(p, Marshal.SizeOf(typeof(NativeHelpers.LUID))))
|
||||
{
|
||||
SafeLsaMemoryBuffer sessionDataPtr;
|
||||
res = NativeMethods.LsaGetLogonSessionData(p, out sessionDataPtr);
|
||||
if (res != 0)
|
||||
continue;
|
||||
|
||||
using (sessionDataPtr)
|
||||
{
|
||||
NativeHelpers.SECURITY_LOGON_SESSION_DATA sessionData = (NativeHelpers.SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure(
|
||||
sessionDataPtr.DangerousGetHandle(), typeof(NativeHelpers.SECURITY_LOGON_SESSION_DATA));
|
||||
UInt64 sessionId = (UInt64)sessionData.LogonId;
|
||||
if (sessionId == tokenLuidId)
|
||||
{
|
||||
logonType = sessionData.LogonType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
NativeHelpers.SECURITY_LOGON_SESSION_DATA sessionData = (NativeHelpers.SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure(
|
||||
sessionDataPtr.DangerousGetHandle(), typeof(NativeHelpers.SECURITY_LOGON_SESSION_DATA));
|
||||
return sessionData.LogonType;
|
||||
}
|
||||
|
||||
return logonType;
|
||||
}
|
||||
|
||||
private static SafeMemoryBuffer GetTokenInformation(SafeNativeHandle hToken, NativeHelpers.TokenInformationClass tokenClass)
|
||||
{
|
||||
UInt32 tokenLength;
|
||||
bool res = NativeMethods.GetTokenInformation(hToken, tokenClass, new SafeMemoryBuffer(IntPtr.Zero), 0, out tokenLength);
|
||||
if (!res && tokenLength == 0) // res will be false due to insufficient buffer size, we ignore if we got the buffer length
|
||||
throw new Win32Exception(String.Format("GetTokenInformation({0}) failed to get buffer length", tokenClass.ToString()));
|
||||
|
||||
SafeMemoryBuffer tokenInfo = new SafeMemoryBuffer((int)tokenLength);
|
||||
if (!NativeMethods.GetTokenInformation(hToken, tokenClass, tokenInfo, tokenLength, out tokenLength))
|
||||
throw new Win32Exception(String.Format("GetTokenInformation({0}) failed", tokenClass.ToString()));
|
||||
|
||||
return tokenInfo;
|
||||
}
|
||||
|
||||
private static string GetPrivilegeName(NativeHelpers.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("LookupPrivilegeNameW() 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 void GrantAccessToWindowStationAndDesktop(IdentityReference account)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# Copyright: (c) 2017, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.AccessToken
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
#Requires -Module Ansible.ModuleUtils.AddType
|
||||
|
||||
|
@ -40,11 +41,8 @@ Add-CSharpType -AnsibleModule $module -References @'
|
|||
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.MappedDrive
|
||||
{
|
||||
|
@ -90,40 +88,6 @@ namespace Ansible.MappedDrive
|
|||
CredReset = 0x00002000,
|
||||
}
|
||||
|
||||
public enum TokenElevationType
|
||||
{
|
||||
TokenElevationTypeDefault = 1,
|
||||
TokenElevationTypeFull,
|
||||
TokenElevationTypeLimited
|
||||
}
|
||||
|
||||
public enum TokenInformationClass
|
||||
{
|
||||
TokenUser = 1,
|
||||
TokenPrivileges = 3,
|
||||
TokenElevationType = 18,
|
||||
TokenLinkedToken = 19,
|
||||
}
|
||||
|
||||
[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 LUID_AND_ATTRIBUTES
|
||||
{
|
||||
public LUID Luid;
|
||||
public UInt32 Attributes;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct NETRESOURCEW
|
||||
{
|
||||
|
@ -136,27 +100,6 @@ namespace Ansible.MappedDrive
|
|||
[MarshalAs(UnmanagedType.LPWStr)] public string lpComment;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string lpProvider;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SID_AND_ATTRIBUTES
|
||||
{
|
||||
public IntPtr Sid;
|
||||
public UInt32 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;
|
||||
}
|
||||
}
|
||||
|
||||
internal class NativeMethods
|
||||
|
@ -165,39 +108,9 @@ namespace Ansible.MappedDrive
|
|||
public static extern bool CloseHandle(
|
||||
IntPtr hObject);
|
||||
|
||||
[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("kernel32.dll")]
|
||||
public static extern SafeNativeHandle GetCurrentProcess();
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool LookupPrivilegeNameW(
|
||||
string lpSystemName,
|
||||
ref NativeHelpers.LUID lpLuid,
|
||||
StringBuilder lpName,
|
||||
ref UInt32 cchName);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern SafeNativeHandle OpenProcess(
|
||||
UInt32 dwDesiredAccess,
|
||||
bool bInheritHandle,
|
||||
UInt32 dwProcessId);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool OpenProcessToken(
|
||||
SafeNativeHandle ProcessHandle,
|
||||
TokenAccessLevels DesiredAccess,
|
||||
out SafeNativeHandle TokenHandle);
|
||||
IntPtr hToken);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool RevertToSelf();
|
||||
|
@ -257,12 +170,12 @@ namespace Ansible.MappedDrive
|
|||
|
||||
internal class Impersonation : IDisposable
|
||||
{
|
||||
private SafeNativeHandle hToken = null;
|
||||
private IntPtr hToken = IntPtr.Zero;
|
||||
|
||||
public Impersonation(SafeNativeHandle token)
|
||||
public Impersonation(IntPtr token)
|
||||
{
|
||||
hToken = token;
|
||||
if (token != null)
|
||||
if (token != IntPtr.Zero)
|
||||
if (!NativeMethods.ImpersonateLoggedOnUser(hToken))
|
||||
throw new Win32Exception("Failed to impersonate token with ImpersonateLoggedOnUser()");
|
||||
}
|
||||
|
@ -282,18 +195,6 @@ namespace Ansible.MappedDrive
|
|||
public string Path;
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -308,11 +209,10 @@ namespace Ansible.MappedDrive
|
|||
|
||||
public class Utils
|
||||
{
|
||||
private const TokenAccessLevels IMPERSONATE_ACCESS = TokenAccessLevels.Query | TokenAccessLevels.Duplicate;
|
||||
private const UInt32 ERROR_SUCCESS = 0x00000000;
|
||||
private const UInt32 ERROR_NO_MORE_ITEMS = 0x0000103;
|
||||
|
||||
public static void AddMappedDrive(string drive, string path, SafeNativeHandle iToken, string username = null, string password = null)
|
||||
public static void AddMappedDrive(string drive, string path, IntPtr iToken, string username = null, string password = null)
|
||||
{
|
||||
NativeHelpers.NETRESOURCEW resource = new NativeHelpers.NETRESOURCEW
|
||||
{
|
||||
|
@ -332,7 +232,7 @@ namespace Ansible.MappedDrive
|
|||
}
|
||||
}
|
||||
|
||||
public static List<DriveInfo> GetMappedDrives(SafeNativeHandle iToken)
|
||||
public static List<DriveInfo> GetMappedDrives(IntPtr iToken)
|
||||
{
|
||||
using (Impersonation imp = new Impersonation(iToken))
|
||||
{
|
||||
|
@ -385,7 +285,7 @@ namespace Ansible.MappedDrive
|
|||
}
|
||||
}
|
||||
|
||||
public static void RemoveMappedDrive(string drive, SafeNativeHandle iToken)
|
||||
public static void RemoveMappedDrive(string drive, IntPtr iToken)
|
||||
{
|
||||
using (Impersonation imp = new Impersonation(iToken))
|
||||
{
|
||||
|
@ -395,117 +295,6 @@ namespace Ansible.MappedDrive
|
|||
}
|
||||
}
|
||||
|
||||
public static SafeNativeHandle GetLimitedToken()
|
||||
{
|
||||
SafeNativeHandle hToken = null;
|
||||
if (!NativeMethods.OpenProcessToken(NativeMethods.GetCurrentProcess(), IMPERSONATE_ACCESS, out hToken))
|
||||
throw new Win32Exception("Failed to open current process token with OpenProcessToken()");
|
||||
|
||||
using (hToken)
|
||||
{
|
||||
// Check the elevation type of the current token, only need to impersonate if it's a Full token
|
||||
using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken, NativeHelpers.TokenInformationClass.TokenElevationType))
|
||||
{
|
||||
NativeHelpers.TokenElevationType tet = (NativeHelpers.TokenElevationType)Marshal.ReadInt32(tokenInfo.DangerousGetHandle());
|
||||
|
||||
// If we don't have a Full token, we don't need to get the limited one to set a mapped drive
|
||||
if (tet != NativeHelpers.TokenElevationType.TokenElevationTypeFull)
|
||||
return null;
|
||||
}
|
||||
|
||||
// We have a full token, need to get the TokenLinkedToken, this requires the SeTcbPrivilege privilege
|
||||
// and we can get that from impersonating a SYSTEM account token. Without this privilege we only get
|
||||
// an SecurityIdentification token which won't work for what we need
|
||||
using (SafeNativeHandle systemToken = GetSystemToken())
|
||||
using (Impersonation systemImpersonation = new Impersonation(systemToken))
|
||||
using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken, NativeHelpers.TokenInformationClass.TokenLinkedToken))
|
||||
return new SafeNativeHandle(Marshal.ReadIntPtr(tokenInfo.DangerousGetHandle()));
|
||||
}
|
||||
}
|
||||
|
||||
private static SafeNativeHandle GetSystemToken()
|
||||
{
|
||||
foreach (System.Diagnostics.Process process in System.Diagnostics.Process.GetProcesses())
|
||||
{
|
||||
using (process)
|
||||
{
|
||||
// 0x00000400 == PROCESS_QUERY_INFORMATION
|
||||
using (SafeNativeHandle hProcess = NativeMethods.OpenProcess(0x00000400, false, (UInt32)process.Id))
|
||||
{
|
||||
if (hProcess.IsInvalid)
|
||||
continue;
|
||||
|
||||
SafeNativeHandle hToken;
|
||||
NativeMethods.OpenProcessToken(hProcess, IMPERSONATE_ACCESS, out hToken);
|
||||
if (hToken.IsInvalid)
|
||||
continue;
|
||||
|
||||
if ("S-1-5-18" == GetTokenUserSID(hToken))
|
||||
{
|
||||
// To get the TokenLinkedToken we need the SeTcbPrivilege, not all SYSTEM tokens have this
|
||||
// assigned so we check before trying again
|
||||
List<string> actualPrivileges = GetTokenPrivileges(hToken);
|
||||
if (actualPrivileges.Contains("SeTcbPrivilege"))
|
||||
return hToken;
|
||||
}
|
||||
|
||||
hToken.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new InvalidOperationException("Failed to get a copy of the SYSTEM token required to de-elevate the current user's token");
|
||||
}
|
||||
|
||||
private static List<string> GetTokenPrivileges(SafeNativeHandle hToken)
|
||||
{
|
||||
using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken, NativeHelpers.TokenInformationClass.TokenPrivileges))
|
||||
{
|
||||
NativeHelpers.TOKEN_PRIVILEGES tokenPrivileges = (NativeHelpers.TOKEN_PRIVILEGES)Marshal.PtrToStructure(
|
||||
tokenInfo.DangerousGetHandle(), typeof(NativeHelpers.TOKEN_PRIVILEGES));
|
||||
|
||||
NativeHelpers.LUID_AND_ATTRIBUTES[] luidAndAttributes = new NativeHelpers.LUID_AND_ATTRIBUTES[tokenPrivileges.PrivilegeCount];
|
||||
PtrToStructureArray(luidAndAttributes, IntPtr.Add(tokenInfo.DangerousGetHandle(), Marshal.SizeOf(tokenPrivileges.PrivilegeCount)));
|
||||
|
||||
return luidAndAttributes.Select(x => GetPrivilegeName(x.Luid)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetTokenUserSID(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).Value;
|
||||
}
|
||||
}
|
||||
|
||||
private static SafeMemoryBuffer GetTokenInformation(SafeNativeHandle hToken, NativeHelpers.TokenInformationClass tokenClass)
|
||||
{
|
||||
UInt32 tokenLength;
|
||||
bool res = NativeMethods.GetTokenInformation(hToken, tokenClass, new SafeMemoryBuffer(IntPtr.Zero), 0, out tokenLength);
|
||||
if (!res && tokenLength == 0) // res will be false due to insufficient buffer size, we ignore if we got the buffer length
|
||||
throw new Win32Exception(String.Format("GetTokenInformation({0}) failed to get buffer length", tokenClass.ToString()));
|
||||
|
||||
SafeMemoryBuffer tokenInfo = new SafeMemoryBuffer((int)tokenLength);
|
||||
if (!NativeMethods.GetTokenInformation(hToken, tokenClass, tokenInfo, tokenLength, out tokenLength))
|
||||
throw new Win32Exception(String.Format("GetTokenInformation({0}) failed", tokenClass.ToString()));
|
||||
|
||||
return tokenInfo;
|
||||
}
|
||||
|
||||
private static string GetPrivilegeName(NativeHelpers.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("LookupPrivilegeNameW() failed");
|
||||
|
||||
return name.ToString();
|
||||
}
|
||||
|
||||
private static void PtrToStructureArray<T>(T[] array, IntPtr ptr)
|
||||
{
|
||||
IntPtr ptrOffset = ptr;
|
||||
|
@ -516,6 +305,37 @@ namespace Ansible.MappedDrive
|
|||
}
|
||||
'@
|
||||
|
||||
Function Get-LimitedToken {
|
||||
$h_process = [Ansible.AccessToken.TokenUtil]::OpenProcess()
|
||||
$h_token = [Ansible.AccessToken.TokenUtil]::OpenProcessToken($h_process, "Duplicate, Query")
|
||||
|
||||
try {
|
||||
# If we don't have a Full token, we don't need to get the limited one to set a mapped drive
|
||||
$tet = [Ansible.AccessToken.TokenUtil]::GetTokenElevationType($h_token)
|
||||
if ($tet -ne [Ansible.AccessToken.TokenElevationType]::Full) {
|
||||
return
|
||||
}
|
||||
|
||||
foreach ($system_token in [Ansible.AccessToken.TokenUtil]::EnumerateUserTokens("S-1-5-18", "Duplicate")) {
|
||||
# To get the TokenLinkedToken we need the SeTcbPrivilege, not all SYSTEM tokens have this assigned so
|
||||
# we need to check before impersonating that token
|
||||
$token_privileges = [Ansible.AccessToken.TokenUtil]::GetTokenPrivileges($system_token)
|
||||
if ($null -eq ($token_privileges | Where-Object { $_.Name -eq "SeTcbPrivilege" })) {
|
||||
continue
|
||||
}
|
||||
|
||||
[Ansible.AccessToken.TokenUtil]::ImpersonateToken($system_token)
|
||||
try {
|
||||
return [Ansible.AccessToken.TokenUtil]::GetTokenLinkedToken($h_token)
|
||||
} finally {
|
||||
[Ansible.AccessToken.TokenUtil]::RevertToSelf()
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
$h_token.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
When we run with become and UAC is enabled, the become process will most likely be the Admin/Full token. This is
|
||||
an issue with the WNetConnection APIs as the Full token is unable to add/enumerate/remove connections due to
|
||||
|
@ -539,10 +359,15 @@ These are the following scenarios we have to handle;
|
|||
4. Run with become on standard user
|
||||
There's no split token, GetLimitedToken() will return $null and no impersonation is needed
|
||||
#>
|
||||
$impersonation_token = [Ansible.MappedDrive.Utils]::GetLimitedToken()
|
||||
$impersonation_token = Get-LimitedToken
|
||||
|
||||
try {
|
||||
$existing_targets = [Ansible.MappedDrive.Utils]::GetMappedDrives($impersonation_token)
|
||||
$i_token_ptr = [System.IntPtr]::Zero
|
||||
if ($null -ne $impersonation_token) {
|
||||
$i_token_ptr = $impersonation_token.DangerousGetHandle()
|
||||
}
|
||||
|
||||
$existing_targets = [Ansible.MappedDrive.Utils]::GetMappedDrives($i_token_ptr)
|
||||
$existing_target = $existing_targets | Where-Object { $_.Drive -eq $letter_root }
|
||||
|
||||
if ($existing_target) {
|
||||
|
@ -558,7 +383,7 @@ try {
|
|||
$module.FailJson("did not delete mapped drive $letter, the target path is pointing to a different location at $( $existing_target.Path )")
|
||||
}
|
||||
if (-not $module.CheckMode) {
|
||||
[Ansible.MappedDrive.Utils]::RemoveMappedDrive($letter_root, $impersonation_token)
|
||||
[Ansible.MappedDrive.Utils]::RemoveMappedDrive($letter_root, $i_token_ptr)
|
||||
}
|
||||
|
||||
$module.Result.changed = $true
|
||||
|
@ -585,14 +410,14 @@ try {
|
|||
if ($null -ne $existing_target) {
|
||||
if ($existing_target.Path -ne $path) {
|
||||
if (-not $module.CheckMode) {
|
||||
[Ansible.MappedDrive.Utils]::RemoveMappedDrive($letter_root, $impersonation_token)
|
||||
$add_method.Invoke($null, [Object[]]@($letter_root, $path, $impersonation_token, $input_username, $input_password))
|
||||
[Ansible.MappedDrive.Utils]::RemoveMappedDrive($letter_root, $i_token_ptr)
|
||||
$add_method.Invoke($null, [Object[]]@($letter_root, $path, $i_token_ptr, $input_username, $input_password))
|
||||
}
|
||||
$module.Result.changed = $true
|
||||
}
|
||||
} else {
|
||||
if (-not $module.CheckMode) {
|
||||
$add_method.Invoke($null, [Object[]]@($letter_root, $path, $impersonation_token, $input_username, $input_password))
|
||||
$add_method.Invoke($null, [Object[]]@($letter_root, $path, $i_token_ptr, $input_username, $input_password))
|
||||
}
|
||||
|
||||
$module.Result.changed = $true
|
||||
|
|
|
@ -3,13 +3,12 @@
|
|||
# Copyright: (c) 2014, Paul Durivage <paul.durivage@rackspace.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.AccessToken
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
|
||||
########
|
||||
$ADS_UF_PASSWD_CANT_CHANGE = 64
|
||||
$ADS_UF_DONT_EXPIRE_PASSWD = 65536
|
||||
$LOGON32_LOGON_NETWORK = 3
|
||||
$LOGON32_PROVIDER_DEFAULT = 0
|
||||
|
||||
$adsi = [ADSI]"WinNT://$env:COMPUTERNAME"
|
||||
|
||||
|
@ -43,49 +42,11 @@ function Get-Group($grp) {
|
|||
Function Test-LocalCredential {
|
||||
param([String]$Username, [String]$Password)
|
||||
|
||||
$platform_util = @'
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ansible
|
||||
{
|
||||
public class WinUserPInvoke
|
||||
{
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool LogonUser(
|
||||
string lpszUsername,
|
||||
string lpszDomain,
|
||||
string lpszPassword,
|
||||
UInt32 dwLogonType,
|
||||
UInt32 dwLogonProvider,
|
||||
out IntPtr phToken);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool CloseHandle(
|
||||
IntPtr hObject);
|
||||
}
|
||||
}
|
||||
'@
|
||||
|
||||
$original_tmp = $env:TMP
|
||||
$env:TMP = $_remote_tmp
|
||||
Add-Type -TypeDefinition $platform_util
|
||||
$env:TMP = $original_tmp
|
||||
|
||||
$handle = [IntPtr]::Zero
|
||||
$logon_res = [Ansible.WinUserPInvoke]::LogonUser(
|
||||
$Username,
|
||||
$null,
|
||||
$Password,
|
||||
$LOGON32_LOGON_NETWORK,
|
||||
$LOGON32_PROVIDER_DEFAULT,
|
||||
[Ref]$handle
|
||||
); $err_code = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
|
||||
|
||||
if ($logon_res) {
|
||||
try {
|
||||
$handle = [Ansible.AccessToken.TokenUtil]::LogonUser($Username, $null, $Password, "Network", "Default")
|
||||
$handle.Dispose()
|
||||
$valid_credentials = $true
|
||||
[Ansible.WinUserPInvoke]::CloseHandle($handle) > $null
|
||||
} else {
|
||||
} catch [Ansible.AccessToken.Win32Exception] {
|
||||
# following errors indicate the creds are correct but the user was
|
||||
# unable to log on for other reasons, which we don't care about
|
||||
$success_codes = @(
|
||||
|
@ -95,26 +56,22 @@ namespace Ansible
|
|||
0x00000569 # ERROR_LOGON_TYPE_GRANTED
|
||||
)
|
||||
|
||||
if ($err_code -eq 0x0000052E) {
|
||||
if ($_.Exception.NativeErrorCode -eq 0x0000052E) {
|
||||
# ERROR_LOGON_FAILURE - the user or pass was incorrect
|
||||
$valid_credentials = $false
|
||||
} elseif ($err_code -in $success_codes) {
|
||||
} elseif ($_.Exception.NativeErrorCode -in $success_codes) {
|
||||
$valid_credentials = $true
|
||||
} else {
|
||||
# an unknown failure, raise an Exception for this
|
||||
$win32_exp = New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList $err_code
|
||||
$err_msg = "LogonUserW failed: $($win32_exp.Message) (Win32ErrorCode: $err_code)"
|
||||
throw New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList $err_code, $err_msg
|
||||
# an unknown failure, reraise exception
|
||||
throw $_
|
||||
}
|
||||
}
|
||||
|
||||
return $valid_credentials
|
||||
}
|
||||
|
||||
########
|
||||
|
||||
$params = Parse-Args $args;
|
||||
$_remote_tmp = Get-AnsibleParam $params "_ansible_remote_tmp" -type "path" -default $env:TMP
|
||||
|
||||
$result = @{
|
||||
changed = $false
|
||||
|
|
|
@ -141,7 +141,7 @@
|
|||
failed_when:
|
||||
- '"Failed to become user " + become_test_username not in become_invalid_pass.msg'
|
||||
- '"LogonUser failed" not in become_invalid_pass.msg'
|
||||
- '"Win32ErrorCode 1326)" not in become_invalid_pass.msg'
|
||||
- '"Win32ErrorCode 1326 - 0x0000052E)" not in become_invalid_pass.msg'
|
||||
|
||||
- name: test become + async
|
||||
vars: *become_vars
|
||||
|
|
|
@ -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()
|
|
@ -509,7 +509,7 @@ $tests = @{
|
|||
|
||||
"Runas without working dir set" = {
|
||||
$expected = "$env:SystemRoot\system32`r`n"
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, 0, "LOGON32_LOGON_INTERACTIVE", $null,
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, 0, "Interactive", $null,
|
||||
'powershell.exe $pwd.Path', $null, $null, "")
|
||||
$actual.StandardOut | Assert-Equals -Expected $expected
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
|
@ -518,7 +518,7 @@ $tests = @{
|
|||
|
||||
"Runas with working dir set" = {
|
||||
$expected = "$env:SystemRoot`r`n"
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, 0, "LOGON32_LOGON_INTERACTIVE", $null,
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, 0, "Interactive", $null,
|
||||
'powershell.exe $pwd.Path', $env:SystemRoot, $null, "")
|
||||
$actual.StandardOut | Assert-Equals -Expected $expected
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
|
@ -527,7 +527,7 @@ $tests = @{
|
|||
|
||||
"Runas without environment set" = {
|
||||
$expected = "Windows_NT`r`n"
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, 0, "LOGON32_LOGON_INTERACTIVE", $null,
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, 0, "Interactive", $null,
|
||||
'powershell.exe $env:TEST; $env:OS', $null, $null, "")
|
||||
$actual.StandardOut | Assert-Equals -Expected $expected
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
|
@ -539,7 +539,7 @@ $tests = @{
|
|||
TEST = "tesTing"
|
||||
TEST2 = "Testing 2"
|
||||
}
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, 0, "LOGON32_LOGON_INTERACTIVE", $null,
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, 0, "Interactive", $null,
|
||||
'cmd.exe /c set', $null, $env_vars, "")
|
||||
("TEST=tesTing" -cin $actual.StandardOut.Split("`r`n")) | Assert-Equals -Expected $true
|
||||
("TEST2=Testing 2" -cin $actual.StandardOut.Split("`r`n")) | Assert-Equals -Expected $true
|
||||
|
@ -550,7 +550,7 @@ $tests = @{
|
|||
|
||||
"Runas with string stdin" = {
|
||||
$expected = "input value`r`n`r`n"
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, 0, "LOGON32_LOGON_INTERACTIVE", $null,
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, 0, "Interactive", $null,
|
||||
'powershell.exe [System.Console]::In.ReadToEnd()', $null, $null, "input value")
|
||||
$actual.StandardOut | Assert-Equals -Expected $expected
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
|
@ -559,7 +559,7 @@ $tests = @{
|
|||
|
||||
"Runas with string stdin and newline" = {
|
||||
$expected = "input value`r`n`r`n"
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, 0, "LOGON32_LOGON_INTERACTIVE", $null,
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, 0, "Interactive", $null,
|
||||
'powershell.exe [System.Console]::In.ReadToEnd()', $null, $null, "input value`r`n")
|
||||
$actual.StandardOut | Assert-Equals -Expected $expected
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
|
@ -568,7 +568,7 @@ $tests = @{
|
|||
|
||||
"Runas with byte stdin" = {
|
||||
$expected = "input value`r`n"
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, 0, "LOGON32_LOGON_INTERACTIVE", $null,
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, 0, "Interactive", $null,
|
||||
'powershell.exe [System.Console]::In.ReadToEnd()', $null, $null, [System.Text.Encoding]::UTF8.GetBytes("input value"))
|
||||
$actual.StandardOut | Assert-Equals -Expected $expected
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
|
@ -592,13 +592,13 @@ $tests = @{
|
|||
"CreateProcessAsUser with lpApplicationName" = {
|
||||
$expected = "abc`r`n"
|
||||
$full_path = "$($env:SystemRoot)\System32\WindowsPowerShell\v1.0\powershell.exe"
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("SYSTEM", $null, 0, "LOGON32_LOGON_INTERACTIVE", $full_path,
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("SYSTEM", $null, 0, "Interactive", $full_path,
|
||||
"Write-Output 'abc'", $null, $null, "")
|
||||
$actual.StandardOut | Assert-Equals -Expected $expected
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("SYSTEM", $null, 0, "LOGON32_LOGON_INTERACTIVE", $full_path,
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("SYSTEM", $null, 0, "Interactive", $full_path,
|
||||
"powershell.exe Write-Output 'abc'", $null, $null, "")
|
||||
$actual.StandardOut | Assert-Equals -Expected $expected
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
|
@ -606,7 +606,7 @@ $tests = @{
|
|||
}
|
||||
|
||||
"CreateProcessAsUser with stderr" = {
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("SYSTEM", $null, 0, "LOGON32_LOGON_INTERACTIVE", $null,
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("SYSTEM", $null, 0, "Interactive", $null,
|
||||
"powershell.exe [System.Console]::Error.WriteLine('hi')", $null, $null, "")
|
||||
$actual.StandardOut | Assert-Equals -Expected ""
|
||||
$actual.StandardError | Assert-Equals -Expected "hi`r`n"
|
||||
|
@ -614,7 +614,7 @@ $tests = @{
|
|||
}
|
||||
|
||||
"CreateProcessAsUser with exit code" = {
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("SYSTEM", $null, 0, "LOGON32_LOGON_INTERACTIVE", $null,
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("SYSTEM", $null, 0, "Interactive", $null,
|
||||
"powershell.exe exit 10", $null, $null, "")
|
||||
$actual.StandardOut | Assert-Equals -Expected ""
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
|
@ -653,7 +653,7 @@ $tests = @{
|
|||
[Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, "incorrect", "powershell.exe Write-Output abc")
|
||||
} catch {
|
||||
$failed = $true
|
||||
$_.Exception.InnerException.GetType().FullName | Assert-Equals -Expected "Ansible.Process.Win32Exception"
|
||||
$_.Exception.InnerException.GetType().FullName | Assert-Equals -Expected "Ansible.AccessToken.Win32Exception"
|
||||
# Server 2008 has a slightly different error msg, just assert we get the error 1326
|
||||
($_.Exception.Message.Contains("Win32ErrorCode 1326")) | Assert-Equals -Expected $true
|
||||
}
|
||||
|
@ -675,8 +675,8 @@ $tests = @{
|
|||
}
|
||||
|
||||
"Interactive logon with standard" = {
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, "LOGON_WITH_PROFILE",
|
||||
"LOGON32_LOGON_INTERACTIVE", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, "WithProfile",
|
||||
"Interactive", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
|
||||
|
@ -689,8 +689,8 @@ $tests = @{
|
|||
}
|
||||
|
||||
"Batch logon with standard" = {
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, "LOGON_WITH_PROFILE",
|
||||
"LOGON32_LOGON_BATCH", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, "WithProfile",
|
||||
"Batch", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
|
||||
|
@ -707,8 +707,8 @@ $tests = @{
|
|||
if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") {
|
||||
continue
|
||||
}
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, "LOGON_WITH_PROFILE",
|
||||
"LOGON32_LOGON_NETWORK", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, "WithProfile",
|
||||
"Network", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
|
||||
|
@ -725,8 +725,8 @@ $tests = @{
|
|||
if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") {
|
||||
continue
|
||||
}
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, "LOGON_WITH_PROFILE",
|
||||
"LOGON32_LOGON_NETWORK_CLEARTEXT", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, "WithProfile",
|
||||
"NetworkCleartext", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
|
||||
|
@ -739,8 +739,8 @@ $tests = @{
|
|||
}
|
||||
|
||||
"Logon without password with standard" = {
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, [NullString]::Value, "LOGON_WITH_PROFILE",
|
||||
"LOGON32_LOGON_INTERACTIVE", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, [NullString]::Value, "WithProfile",
|
||||
"Interactive", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
|
||||
|
@ -759,8 +759,8 @@ $tests = @{
|
|||
if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") {
|
||||
continue
|
||||
}
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, [NullString]::Value, "LOGON_WITH_PROFILE",
|
||||
"LOGON32_LOGON_NETWORK", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, [NullString]::Value, "WithProfile",
|
||||
"Network", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
|
||||
|
@ -775,8 +775,8 @@ $tests = @{
|
|||
}
|
||||
|
||||
"Interactive logon with admin" = {
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, "LOGON_WITH_PROFILE",
|
||||
"LOGON32_LOGON_INTERACTIVE", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, "WithProfile",
|
||||
"Interactive", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
|
||||
|
@ -789,8 +789,8 @@ $tests = @{
|
|||
}
|
||||
|
||||
"Batch logon with admin" = {
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, "LOGON_WITH_PROFILE",
|
||||
"LOGON32_LOGON_BATCH", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, "WithProfile",
|
||||
"Batch", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
|
||||
|
@ -807,8 +807,8 @@ $tests = @{
|
|||
if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") {
|
||||
continue
|
||||
}
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, "LOGON_WITH_PROFILE",
|
||||
"LOGON32_LOGON_NETWORK", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, "WithProfile",
|
||||
"Network", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
|
||||
|
@ -825,8 +825,8 @@ $tests = @{
|
|||
if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") {
|
||||
continue
|
||||
}
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, "LOGON_WITH_PROFILE",
|
||||
"LOGON32_LOGON_NETWORK_CLEARTEXT", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, "WithProfile",
|
||||
"NetworkCleartext", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
|
||||
|
@ -845,11 +845,11 @@ $tests = @{
|
|||
# become without setting the password. This is confusing as $null gets converted to "" and we need to
|
||||
# use [NullString]::Value instead if we want that behaviour. This just tests to see that an empty
|
||||
# string won't go the S4U route.
|
||||
[Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $null, "LOGON_WITH_PROFILE",
|
||||
"LOGON32_LOGON_INTERACTIVE", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
[Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $null, "WithProfile",
|
||||
"Interactive", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
} catch {
|
||||
$failed = $true
|
||||
$_.Exception.InnerException.GetType().FullName | Assert-Equals -Expected "Ansible.Process.Win32Exception"
|
||||
$_.Exception.InnerException.GetType().FullName | Assert-Equals -Expected "Ansible.AccessToken.Win32Exception"
|
||||
# Server 2008 has a slightly different error msg, just assert we get the error 1326
|
||||
($_.Exception.Message.Contains("Win32ErrorCode 1326")) | Assert-Equals -Expected $true
|
||||
}
|
||||
|
@ -857,8 +857,8 @@ $tests = @{
|
|||
}
|
||||
|
||||
"Logon without password with admin" = {
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, [NullString]::Value, "LOGON_WITH_PROFILE",
|
||||
"LOGON32_LOGON_INTERACTIVE", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, [NullString]::Value, "WithProfile",
|
||||
"Interactive", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
|
||||
|
@ -877,8 +877,8 @@ $tests = @{
|
|||
if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") {
|
||||
continue
|
||||
}
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, [NullString]::Value, "LOGON_WITH_PROFILE",
|
||||
"LOGON32_LOGON_NETWORK", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, [NullString]::Value, "WithProfile",
|
||||
"Network", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
|
||||
|
@ -899,7 +899,7 @@ $tests = @{
|
|||
}
|
||||
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, 0,
|
||||
"LOGON32_LOGON_INTERACTIVE", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
"Interactive", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
|
||||
|
@ -912,8 +912,8 @@ $tests = @{
|
|||
}
|
||||
|
||||
"Logon with network credentials and no profile" = {
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("fakeuser", "fakepassword", "LOGON_NETCREDENTIALS_ONLY",
|
||||
"LOGON32_LOGON_NEW_CREDENTIALS", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("fakeuser", "fakepassword", "NetcredentialsOnly",
|
||||
"NewCredentials", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
|
||||
|
@ -921,15 +921,15 @@ $tests = @{
|
|||
$stdout.LogonType | Assert-Equals -Expected "NewCredentials"
|
||||
$stdout.MandatoryLabelSid.Value | Assert-Equals -Expected $current_user.MandatoryLabelSid.Value
|
||||
|
||||
# while we didn't set LOGON_WITH_PROFILE, the new process is based on the current process
|
||||
# while we didn't set WithProfile, the new process is based on the current process
|
||||
$stdout.ProfileLoaded | Assert-Equals -Expected $current_user.ProfileLoaded
|
||||
$stdout.SourceName | Assert-Equals -Expected "Advapi"
|
||||
$stdout.UserSid.Value | Assert-Equals -Expected $current_user.UserSid.Value
|
||||
}
|
||||
|
||||
"Logon with network credentials and with profile" = {
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("fakeuser", "fakepassword", "LOGON_NETCREDENTIALS_ONLY, LOGON_WITH_PROFILE",
|
||||
"LOGON32_LOGON_NEW_CREDENTIALS", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("fakeuser", "fakepassword", "NetcredentialsOnly, WithProfile",
|
||||
"NewCredentials", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
|
||||
|
@ -990,8 +990,6 @@ try {
|
|||
}
|
||||
$group_obj.Add($user_obj.Path)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
foreach ($test_impl in $tests.GetEnumerator()) {
|
||||
$test = $test_impl.Key
|
||||
|
|
|
@ -1,4 +1,33 @@
|
|||
---
|
||||
- set_fact:
|
||||
test_username: ansible-test
|
||||
test_password: Password123{{ lookup('password', '/dev/null chars=ascii_letters,digits length=8') }}
|
||||
|
||||
- name: create test Admin user
|
||||
win_user:
|
||||
name: '{{ test_username }}'
|
||||
password: '{{ test_password }}'
|
||||
state: present
|
||||
groups:
|
||||
- Administrators
|
||||
|
||||
- block:
|
||||
- name: test Ansible.AccessToken.cs
|
||||
ansible_access_token_tests:
|
||||
test_username: '{{ test_username }}'
|
||||
test_password: '{{ test_password }}'
|
||||
register: ansible_access_token_test
|
||||
|
||||
- name: assert test Ansible.AccessToken.cs
|
||||
assert:
|
||||
that:
|
||||
- ansible_access_token_test.data == "success"
|
||||
always:
|
||||
- name: remove test Admin user
|
||||
win_user:
|
||||
name: '{{ test_username }}'
|
||||
state: absent
|
||||
|
||||
- name: test Ansible.Basic.cs
|
||||
ansible_basic_tests:
|
||||
register: ansible_basic_test
|
||||
|
|
Loading…
Reference in a new issue