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
pull/59027/merge
Jordan Borean 5 years ago committed by GitHub
parent 49e16922b2
commit dbd082efe4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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 #Requires -Module Ansible.ModuleUtils.AddType
#AnsibleRequires -CSharpUtil Ansible.AccessToken
#AnsibleRequires -CSharpUtil Ansible.Become #AnsibleRequires -CSharpUtil Ansible.Become
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
Write-AnsibleLog "INFO - starting become_wrapper" "become_wrapper" Write-AnsibleLog "INFO - starting become_wrapper" "become_wrapper"
Function Get-EnumValue($enum, $flag_type, $value, $prefix) { Function Get-EnumValue($enum, $flag_type, $value) {
$raw_enum_value = "$prefix$($value.ToUpper())" $raw_enum_value = $value.Replace('_', '')
try { try {
$enum_value = [Enum]::Parse($enum, $raw_enum_value) $enum_value = [Enum]::Parse($enum, $raw_enum_value, $true)
} catch [System.ArgumentException] { } 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 ", ")" throw "become_flags $flag_type value '$value' is not valid, valid values are: $($valid_options -join ", ")"
} }
return $enum_value return $enum_value
} }
Function Get-BecomeFlags($flags) { Function Get-BecomeFlags($flags) {
$logon_type = [Ansible.Become.LogonType]::LOGON32_LOGON_INTERACTIVE $logon_type = [Ansible.AccessToken.LogonType]::Interactive
$logon_flags = [Ansible.Become.LogonFlags]::LOGON_WITH_PROFILE $logon_flags = [Ansible.Become.LogonFlags]::WithProfile
if ($null -eq $flags -or $flags -eq "") { if ($null -eq $flags -or $flags -eq "") {
$flag_split = @() $flag_split = @()
@ -44,10 +47,9 @@ Function Get-BecomeFlags($flags) {
$flag_value = $split[1] $flag_value = $split[1]
if ($flag_key -eq "logon_type") { if ($flag_key -eq "logon_type") {
$enum_details = @{ $enum_details = @{
enum = [Ansible.Become.LogonType] enum = [Ansible.AccessToken.LogonType]
flag_type = $flag_key flag_type = $flag_key
value = $flag_value value = $flag_value
prefix = "LOGON32_LOGON_"
} }
$logon_type = Get-EnumValue @enum_details $logon_type = Get-EnumValue @enum_details
} elseif ($flag_key -eq "logon_flags") { } elseif ($flag_key -eq "logon_flags") {
@ -61,7 +63,6 @@ Function Get-BecomeFlags($flags) {
enum = [Ansible.Become.LogonFlags] enum = [Ansible.Become.LogonFlags]
flag_type = $flag_key flag_type = $flag_key
value = $logon_flag_value value = $logon_flag_value
prefix = "LOGON_"
} }
$logon_flag = Get-EnumValue @enum_details $logon_flag = Get-EnumValue @enum_details
$logon_flags = $logon_flags -bor $logon_flag $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-Module -Name Ansible.ModuleUtils.AddType -ScriptBlock ([ScriptBlock]::Create($add_type)) | Import-Module > $null
$new_tmp = [System.Environment]::ExpandEnvironmentVariables($Payload.module_args["_ansible_remote_tmp"]) $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"])) $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"])) $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 $username = $Payload.become_user
$password = $Payload.become_password $password = $Payload.become_password

@ -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;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Runtime.ConstrainedExecution; using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.AccessControl; using System.Security.AccessControl;
using System.Security.Principal; using System.Security.Principal;
using System.Text; using System.Text;
using Ansible.AccessToken;
using Ansible.Process; using Ansible.Process;
using System.Linq;
namespace Ansible.Become namespace Ansible.Become
{ {
@ -59,85 +60,22 @@ namespace Ansible.Become
public IntPtr Buffer; 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)] [StructLayout(LayoutKind.Sequential)]
public struct SECURITY_LOGON_SESSION_DATA public struct SECURITY_LOGON_SESSION_DATA
{ {
public UInt32 Size; public UInt32 Size;
public LUID LogonId; public Luid LogonId;
public LSA_UNICODE_STRING UserName; public LSA_UNICODE_STRING UserName;
public LSA_UNICODE_STRING LogonDomain; public LSA_UNICODE_STRING LogonDomain;
public LSA_UNICODE_STRING AuthenticationPackage; public LSA_UNICODE_STRING AuthenticationPackage;
public SECURITY_LOGON_TYPE LogonType; 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)] [StructLayout(LayoutKind.Sequential)]
public struct TOKEN_SOURCE public struct TOKEN_SOURCE
{ {
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public char[] SourceName; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public char[] SourceName;
public LUID SourceIdentifier; 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 enum SECURITY_LOGON_TYPE public enum SECURITY_LOGON_TYPE
@ -156,39 +94,13 @@ namespace Ansible.Become
CachedRemoteInteractive, CachedRemoteInteractive,
CachedUnlock 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 internal class NativeMethods
{ {
[DllImport("advapi32.dll", SetLastError = true)] [DllImport("advapi32.dll", SetLastError = true)]
public static extern bool AllocateLocallyUniqueId( public static extern bool AllocateLocallyUniqueId(
out NativeHelpers.LUID Luid); out Luid Luid);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(
IntPtr hObject);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CreateProcessWithTokenW( public static extern bool CreateProcessWithTokenW(
@ -197,20 +109,11 @@ namespace Ansible.Become
[MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName, [MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName,
StringBuilder lpCommandLine, StringBuilder lpCommandLine,
Process.NativeHelpers.ProcessCreationFlags dwCreationFlags, Process.NativeHelpers.ProcessCreationFlags dwCreationFlags,
SafeMemoryBuffer lpEnvironment, Process.SafeMemoryBuffer lpEnvironment,
[MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory, [MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory,
Process.NativeHelpers.STARTUPINFOEX lpStartupInfo, Process.NativeHelpers.STARTUPINFOEX lpStartupInfo,
out Process.NativeHelpers.PROCESS_INFORMATION lpProcessInformation); 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")] [DllImport("kernel32.dll")]
public static extern UInt32 GetCurrentThreadId(); public static extern UInt32 GetCurrentThreadId();
@ -221,50 +124,17 @@ namespace Ansible.Become
public static extern NoopSafeHandle GetThreadDesktop( public static extern NoopSafeHandle GetThreadDesktop(
UInt32 dwThreadId); 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)] [DllImport("secur32.dll", SetLastError = true)]
public static extern UInt32 LsaDeregisterLogonProcess( public static extern UInt32 LsaDeregisterLogonProcess(
IntPtr LsaHandle); IntPtr LsaHandle);
[DllImport("secur32.dll", SetLastError = true)]
public static extern UInt32 LsaEnumerateLogonSessions(
out UInt32 LogonSessionCount,
out SafeLsaMemoryBuffer LogonSessionList);
[DllImport("secur32.dll", SetLastError = true)] [DllImport("secur32.dll", SetLastError = true)]
public static extern UInt32 LsaFreeReturnBuffer( public static extern UInt32 LsaFreeReturnBuffer(
IntPtr Buffer); IntPtr Buffer);
[DllImport("secur32.dll", SetLastError = true)] [DllImport("secur32.dll", SetLastError = true)]
public static extern UInt32 LsaGetLogonSessionData( public static extern UInt32 LsaGetLogonSessionData(
IntPtr LogonId, ref Luid LogonId,
out SafeLsaMemoryBuffer ppLogonSessionData); out SafeLsaMemoryBuffer ppLogonSessionData);
[DllImport("secur32.dll", SetLastError = true, CharSet = CharSet.Unicode)] [DllImport("secur32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
@ -279,7 +149,7 @@ namespace Ansible.Become
NativeHelpers.TOKEN_SOURCE SourceContext, NativeHelpers.TOKEN_SOURCE SourceContext,
out SafeLsaMemoryBuffer ProfileBuffer, out SafeLsaMemoryBuffer ProfileBuffer,
out UInt32 ProfileBufferLength, out UInt32 ProfileBufferLength,
out NativeHelpers.LUID LogonId, out Luid LogonId,
out SafeNativeHandle Token, out SafeNativeHandle Token,
out IntPtr Quotas, out IntPtr Quotas,
out UInt32 SubStatus); out UInt32 SubStatus);
@ -299,21 +169,6 @@ namespace Ansible.Become
NativeHelpers.LSA_STRING LogonProcessName, NativeHelpers.LSA_STRING LogonProcessName,
out SafeLsaHandle LsaHandle, out SafeLsaHandle LsaHandle,
out IntPtr SecurityMode); 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 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 internal class NoopSafeHandle : SafeHandle
{ {
public NoopSafeHandle() : base(IntPtr.Zero, false) { } public NoopSafeHandle() : base(IntPtr.Zero, false) { }
@ -364,19 +207,8 @@ namespace Ansible.Become
[Flags] [Flags]
public enum LogonFlags public enum LogonFlags
{ {
LOGON_WITH_PROFILE = 0x00000001, WithProfile = 0x00000001,
LOGON_NETCREDENTIALS_ONLY = 0x00000002 NetcredentialsOnly = 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
} }
public class BecomeUtil public class BecomeUtil
@ -392,7 +224,7 @@ namespace Ansible.Become
public static Result CreateProcessAsUser(string username, string password, string command) 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, ""); null, command, null, null, "");
} }
@ -450,38 +282,25 @@ namespace Ansible.Become
if (lpCurrentDirectory == "") if (lpCurrentDirectory == "")
lpCurrentDirectory = null; 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); 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, throw new Process.Win32Exception("CreateProcessWithTokenW() failed");
creationFlags, lpEnvironment, lpCurrentDirectory, si, out pi))
{
launchSuccess = true;
break;
}
} }
if (!launchSuccess)
throw new Win32Exception("CreateProcessWithTokenW() failed");
} }
return ProcessUtil.WaitProcess(stdoutRead, stdoutWrite, stderrRead, stderrWrite, stdinStream, stdin, pi.hProcess); 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; SafeNativeHandle systemToken = null;
bool impersonated = false; bool impersonated = false;
string becomeSid = username; 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 prefixed with .\, we are becoming a local account, strip the prefix
if (username.StartsWith(".\\")) if (username.StartsWith(".\\"))
@ -497,7 +316,14 @@ namespace Ansible.Become
// account or have administrative rights on the become access token. // account or have administrative rights on the become access token.
systemToken = GetPrimaryTokenForUser(new SecurityIdentifier("S-1-5-18"), new List<string>() { "SeTcbPrivilege" }); systemToken = GetPrimaryTokenForUser(new SecurityIdentifier("S-1-5-18"), new List<string>() { "SeTcbPrivilege" });
if (systemToken != null) 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 // We require impersonation if becoming a service sid or becoming a user without a password
@ -507,25 +333,19 @@ namespace Ansible.Become
try try
{ {
if (becomeSid == "S-1-5-18") 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. // 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 // 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 // If no password was specified, try and duplicate an existing token for that user or use S4U to
// generate one without network credentials // generate one without network credentials
SecurityIdentifier sid = new SecurityIdentifier(becomeSid); SecurityIdentifier sid = new SecurityIdentifier(becomeSid);
SafeNativeHandle becomeToken = GetPrimaryTokenForUser(sid); SafeNativeHandle becomeToken = GetPrimaryTokenForUser(sid);
if (becomeToken != null) if (becomeToken != null)
{ return GetElevatedToken(becomeToken);
userTokens.Add(GetElevatedToken(becomeToken));
userTokens.Add(becomeToken);
}
else else
{ return GetS4UTokenForUser(sid, logonType);
becomeToken = GetS4UTokenForUser(sid, logonType);
userTokens.Add(becomeToken);
}
} }
else else
{ {
@ -533,12 +353,12 @@ namespace Ansible.Become
switch (becomeSid) switch (becomeSid)
{ {
case "S-1-5-19": case "S-1-5-19":
logonType = LogonType.LOGON32_LOGON_SERVICE; logonType = LogonType.Service;
domain = "NT AUTHORITY"; domain = "NT AUTHORITY";
username = "LocalService"; username = "LocalService";
break; break;
case "S-1-5-20": case "S-1-5-20":
logonType = LogonType.LOGON32_LOGON_SERVICE; logonType = LogonType.Service;
domain = "NT AUTHORITY"; domain = "NT AUTHORITY";
username = "NetworkService"; username = "NetworkService";
break; break;
@ -555,31 +375,25 @@ namespace Ansible.Become
break; break;
} }
SafeNativeHandle hToken; SafeNativeHandle hToken = TokenUtil.LogonUser(username, domain, password, logonType,
if (!NativeMethods.LogonUserW(username, domain, password, logonType, LogonProvider.Default);
NativeHelpers.LogonProvider.LOGON32_PROVIDER_DEFAULT, out hToken))
{
throw new Win32Exception("LogonUserW() failed");
}
// Get the elevated token for a local/domain accounts only // Get the elevated token for a local/domain accounts only
if (!SERVICE_SIDS.Contains(becomeSid)) if (!SERVICE_SIDS.Contains(becomeSid))
userTokens.Add(GetElevatedToken(hToken)); return GetElevatedToken(hToken);
userTokens.Add(hToken); else
return hToken;
} }
} }
finally finally
{ {
if (impersonated) if (impersonated)
NativeMethods.RevertToSelf(); TokenUtil.RevertToSelf();
} }
return userTokens;
} }
private static SafeNativeHandle GetPrimaryTokenForUser(SecurityIdentifier sid, List<string> requiredPrivileges = null) 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 // According to CreateProcessWithTokenW we require a token with
// TOKEN_QUERY, TOKEN_DUPLICATE and TOKEN_ASSIGN_PRIMARY // TOKEN_QUERY, TOKEN_DUPLICATE and TOKEN_ASSIGN_PRIMARY
// Also add in TOKEN_IMPERSONATE so we can get an impersonated token // Also add in TOKEN_IMPERSONATE so we can get an impersonated token
@ -588,51 +402,32 @@ namespace Ansible.Become
TokenAccessLevels.AssignPrimary | TokenAccessLevels.AssignPrimary |
TokenAccessLevels.Impersonate; 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)) List<string> actualPrivileges = TokenUtil.GetTokenPrivileges(hToken).Select(x => x.Name).ToList();
{ int missing = requiredPrivileges.Where(x => !actualPrivileges.Contains(x)).Count();
if (hProcess.IsInvalid) if (missing > 0)
continue; 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; IntPtr securityMode;
UInt32 res = NativeMethods.LsaRegisterLogonProcess(logonProcessName, out lsaHandle, out securityMode); UInt32 res = NativeMethods.LsaRegisterLogonProcess(logonProcessName, out lsaHandle, out securityMode);
if (res != 0) if (res != 0)
throw new Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res), "LsaRegisterLogonProcess() failed"); throw new Process.Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res), "LsaRegisterLogonProcess() failed");
using (lsaHandle) using (lsaHandle)
{ {
@ -660,7 +455,7 @@ namespace Ansible.Become
UInt32 authPackage; UInt32 authPackage;
res = NativeMethods.LsaLookupAuthenticationPackage(lsaHandle, packageName, out authPackage); res = NativeMethods.LsaLookupAuthenticationPackage(lsaHandle, packageName, out authPackage);
if (res != 0) 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)); String.Format("LsaLookupAuthenticationPackage({0}) failed", (string)packageName));
int usernameLength = username.Length * sizeof(char); int usernameLength = username.Length * sizeof(char);
@ -694,9 +489,9 @@ namespace Ansible.Become
Marshal.Copy(username.ToCharArray(), 0, usernamePtr, username.Length); Marshal.Copy(username.ToCharArray(), 0, usernamePtr, username.Length);
Marshal.Copy(domainName.ToCharArray(), 0, domainPtr, domainName.Length); Marshal.Copy(domainName.ToCharArray(), 0, domainPtr, domainName.Length);
NativeHelpers.LUID sourceLuid; Luid sourceLuid;
if (!NativeMethods.AllocateLocallyUniqueId(out 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 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 // Only Batch or Network will work with S4U, prefer Batch but use Network if asked
LogonType lsaLogonType = logonType == LogonType.LOGON32_LOGON_NETWORK LogonType lsaLogonType = logonType == LogonType.Network
? LogonType.LOGON32_LOGON_NETWORK ? LogonType.Network
: LogonType.LOGON32_LOGON_BATCH; : LogonType.Batch;
SafeLsaMemoryBuffer profileBuffer; SafeLsaMemoryBuffer profileBuffer;
UInt32 profileBufferLength; UInt32 profileBufferLength;
NativeHelpers.LUID logonId; Luid logonId;
SafeNativeHandle hToken; SafeNativeHandle hToken;
IntPtr quotas; IntPtr quotas;
UInt32 subStatus; UInt32 subStatus;
@ -719,7 +514,7 @@ namespace Ansible.Become
authInfo, (UInt32)authInfoLength, IntPtr.Zero, tokenSource, out profileBuffer, out profileBufferLength, authInfo, (UInt32)authInfoLength, IntPtr.Zero, tokenSource, out profileBuffer, out profileBufferLength,
out logonId, out hToken, out quotas, out subStatus); out logonId, out hToken, out quotas, out subStatus);
if (res != 0) 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)); String.Format("LsaLogonUser() failed with substatus {0}", subStatus));
profileBuffer.Dispose(); profileBuffer.Dispose();
@ -734,121 +529,38 @@ namespace Ansible.Become
private static SafeNativeHandle GetElevatedToken(SafeNativeHandle hToken) private static SafeNativeHandle GetElevatedToken(SafeNativeHandle hToken)
{ {
// First determine if the current token is a limited token TokenElevationType tet = TokenUtil.GetTokenElevationType(hToken);
using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken, NativeHelpers.TokenInformationClass.TokenElevationType)) // We already have the best token we can get, just use it
{ if (tet != TokenElevationType.Limited)
NativeHelpers.TokenElevationType tet = (NativeHelpers.TokenElevationType)Marshal.ReadInt32(tokenInfo.DangerousGetHandle()); return hToken;
// We already have the best token we can get, just use it
if (tet != NativeHelpers.TokenElevationType.TokenElevationTypeLimited)
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()));
}
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]; SafeNativeHandle linkedToken = TokenUtil.GetTokenLinkedToken(hToken);
PtrToStructureArray(luidAndAttributes, IntPtr.Add(tokenInfo.DangerousGetHandle(), Marshal.SizeOf(tokenPrivileges.PrivilegeCount))); TokenStatistics tokenStats = TokenUtil.GetTokenStatistics(linkedToken);
return luidAndAttributes.Select(x => GetPrivilegeName(x.Luid)).ToList(); // 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
private static SecurityIdentifier GetTokenUserSID(SafeNativeHandle hToken) return 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);
}
} }
private static NativeHelpers.SECURITY_LOGON_TYPE GetTokenLogonType(SafeNativeHandle hToken) private static NativeHelpers.SECURITY_LOGON_TYPE GetTokenLogonType(SafeNativeHandle hToken)
{ {
UInt64 tokenLuidId; TokenStatistics stats = TokenUtil.GetTokenStatistics(hToken);
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;
}
// Default to Network, if we weren't able to get the actual type treat it as an error and assume SafeLsaMemoryBuffer sessionDataPtr;
// we don't want to run a process with the token UInt32 res = NativeMethods.LsaGetLogonSessionData(ref stats.AuthenticationId, out sessionDataPtr);
NativeHelpers.SECURITY_LOGON_TYPE logonType = NativeHelpers.SECURITY_LOGON_TYPE.Network;
UInt32 sessionCount;
SafeLsaMemoryBuffer sessionPtr;
UInt32 res = NativeMethods.LsaEnumerateLogonSessions(out sessionCount, out sessionPtr);
if (res != 0) if (res != 0)
throw new Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res), "LsaEnumerateLogonSession() failed"); // Default to Network, if we weren't able to get the actual type treat it as an error and assume
using (sessionPtr) // we don't want to run a process with the token
{ return NativeHelpers.SECURITY_LOGON_TYPE.Network;
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) using (sessionDataPtr)
{ {
NativeHelpers.SECURITY_LOGON_SESSION_DATA sessionData = (NativeHelpers.SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure( NativeHelpers.SECURITY_LOGON_SESSION_DATA sessionData = (NativeHelpers.SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure(
sessionDataPtr.DangerousGetHandle(), typeof(NativeHelpers.SECURITY_LOGON_SESSION_DATA)); sessionDataPtr.DangerousGetHandle(), typeof(NativeHelpers.SECURITY_LOGON_SESSION_DATA));
UInt64 sessionId = (UInt64)sessionData.LogonId; return sessionData.LogonType;
if (sessionId == tokenLuidId)
{
logonType = sessionData.LogonType;
break;
}
}
}
} }
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) private static void GrantAccessToWindowStationAndDesktop(IdentityReference account)
@ -888,4 +600,4 @@ namespace Ansible.Become
{ } { }
} }
} }
} }

@ -3,6 +3,7 @@
# Copyright: (c) 2017, Ansible Project # Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 #AnsibleRequires -CSharpUtil Ansible.Basic
#Requires -Module Ansible.ModuleUtils.AddType #Requires -Module Ansible.ModuleUtils.AddType
@ -40,11 +41,8 @@ Add-CSharpType -AnsibleModule $module -References @'
using Microsoft.Win32.SafeHandles; using Microsoft.Win32.SafeHandles;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Runtime.ConstrainedExecution; using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;
namespace Ansible.MappedDrive namespace Ansible.MappedDrive
{ {
@ -90,40 +88,6 @@ namespace Ansible.MappedDrive
CredReset = 0x00002000, 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)] [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct NETRESOURCEW public struct NETRESOURCEW
{ {
@ -136,27 +100,6 @@ namespace Ansible.MappedDrive
[MarshalAs(UnmanagedType.LPWStr)] public string lpComment; [MarshalAs(UnmanagedType.LPWStr)] public string lpComment;
[MarshalAs(UnmanagedType.LPWStr)] public string lpProvider; [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 internal class NativeMethods
@ -165,39 +108,9 @@ namespace Ansible.MappedDrive
public static extern bool CloseHandle( public static extern bool CloseHandle(
IntPtr hObject); 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)] [DllImport("advapi32.dll", SetLastError = true)]
public static extern bool ImpersonateLoggedOnUser( public static extern bool ImpersonateLoggedOnUser(
SafeNativeHandle hToken); IntPtr 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);
[DllImport("advapi32.dll", SetLastError = true)] [DllImport("advapi32.dll", SetLastError = true)]
public static extern bool RevertToSelf(); public static extern bool RevertToSelf();
@ -257,12 +170,12 @@ namespace Ansible.MappedDrive
internal class Impersonation : IDisposable internal class Impersonation : IDisposable
{ {
private SafeNativeHandle hToken = null; private IntPtr hToken = IntPtr.Zero;
public Impersonation(SafeNativeHandle token) public Impersonation(IntPtr token)
{ {
hToken = token; hToken = token;
if (token != null) if (token != IntPtr.Zero)
if (!NativeMethods.ImpersonateLoggedOnUser(hToken)) if (!NativeMethods.ImpersonateLoggedOnUser(hToken))
throw new Win32Exception("Failed to impersonate token with ImpersonateLoggedOnUser()"); throw new Win32Exception("Failed to impersonate token with ImpersonateLoggedOnUser()");
} }
@ -282,18 +195,6 @@ namespace Ansible.MappedDrive
public string Path; 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 public class Win32Exception : System.ComponentModel.Win32Exception
{ {
private string _msg; private string _msg;
@ -308,11 +209,10 @@ namespace Ansible.MappedDrive
public class Utils public class Utils
{ {
private const TokenAccessLevels IMPERSONATE_ACCESS = TokenAccessLevels.Query | TokenAccessLevels.Duplicate;
private const UInt32 ERROR_SUCCESS = 0x00000000; private const UInt32 ERROR_SUCCESS = 0x00000000;
private const UInt32 ERROR_NO_MORE_ITEMS = 0x0000103; 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 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)) 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)) using (Impersonation imp = new Impersonation(iToken))
{ {
@ -395,126 +295,46 @@ namespace Ansible.MappedDrive
} }
} }
public static SafeNativeHandle GetLimitedToken() private static void PtrToStructureArray<T>(T[] array, IntPtr ptr)
{ {
SafeNativeHandle hToken = null; IntPtr ptrOffset = ptr;
if (!NativeMethods.OpenProcessToken(NativeMethods.GetCurrentProcess(), IMPERSONATE_ACCESS, out hToken)) for (int i = 0; i < array.Length; i++, ptrOffset = IntPtr.Add(ptrOffset, Marshal.SizeOf(typeof(T))))
throw new Win32Exception("Failed to open current process token with OpenProcessToken()"); array[i] = (T)Marshal.PtrToStructure(ptrOffset, typeof(T));
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() Function Get-LimitedToken {
{ $h_process = [Ansible.AccessToken.TokenUtil]::OpenProcess()
foreach (System.Diagnostics.Process process in System.Diagnostics.Process.GetProcesses()) $h_token = [Ansible.AccessToken.TokenUtil]::OpenProcessToken($h_process, "Duplicate, Query")
{
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(); 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) {
throw new InvalidOperationException("Failed to get a copy of the SYSTEM token required to de-elevate the current user's token"); return
} }
private static List<string> GetTokenPrivileges(SafeNativeHandle hToken) 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
using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken, NativeHelpers.TokenInformationClass.TokenPrivileges)) # we need to check before impersonating that token
{ $token_privileges = [Ansible.AccessToken.TokenUtil]::GetTokenPrivileges($system_token)
NativeHelpers.TOKEN_PRIVILEGES tokenPrivileges = (NativeHelpers.TOKEN_PRIVILEGES)Marshal.PtrToStructure( if ($null -eq ($token_privileges | Where-Object { $_.Name -eq "SeTcbPrivilege" })) {
tokenInfo.DangerousGetHandle(), typeof(NativeHelpers.TOKEN_PRIVILEGES)); continue
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) [Ansible.AccessToken.TokenUtil]::ImpersonateToken($system_token)
{ try {
using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken, NativeHelpers.TokenInformationClass.TokenUser)) return [Ansible.AccessToken.TokenUtil]::GetTokenLinkedToken($h_token)
{ } finally {
NativeHelpers.TOKEN_USER tokenUser = (NativeHelpers.TOKEN_USER)Marshal.PtrToStructure(tokenInfo.DangerousGetHandle(), [Ansible.AccessToken.TokenUtil]::RevertToSelf()
typeof(NativeHelpers.TOKEN_USER));
return new SecurityIdentifier(tokenUser.User.Sid).Value;
} }
} }
} finally {
private static SafeMemoryBuffer GetTokenInformation(SafeNativeHandle hToken, NativeHelpers.TokenInformationClass tokenClass) $h_token.Dispose()
{
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));
}
} }
} }
'@
<# <#
When we run with become and UAC is enabled, the become process will most likely be the Admin/Full token. This is When we run with become and UAC is enabled, the become process will most likely be the Admin/Full token. This is
@ -539,10 +359,15 @@ These are the following scenarios we have to handle;
4. Run with become on standard user 4. Run with become on standard user
There's no split token, GetLimitedToken() will return $null and no impersonation is needed 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 { 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 } $existing_target = $existing_targets | Where-Object { $_.Drive -eq $letter_root }
if ($existing_target) { 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 )") $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) { 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 $module.Result.changed = $true
@ -585,14 +410,14 @@ try {
if ($null -ne $existing_target) { if ($null -ne $existing_target) {
if ($existing_target.Path -ne $path) { if ($existing_target.Path -ne $path) {
if (-not $module.CheckMode) { if (-not $module.CheckMode) {
[Ansible.MappedDrive.Utils]::RemoveMappedDrive($letter_root, $impersonation_token) [Ansible.MappedDrive.Utils]::RemoveMappedDrive($letter_root, $i_token_ptr)
$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 $module.Result.changed = $true
} }
} else { } else {
if (-not $module.CheckMode) { 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 $module.Result.changed = $true

@ -3,13 +3,12 @@
# Copyright: (c) 2014, Paul Durivage <paul.durivage@rackspace.com> # 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) # 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 #Requires -Module Ansible.ModuleUtils.Legacy
######## ########
$ADS_UF_PASSWD_CANT_CHANGE = 64 $ADS_UF_PASSWD_CANT_CHANGE = 64
$ADS_UF_DONT_EXPIRE_PASSWD = 65536 $ADS_UF_DONT_EXPIRE_PASSWD = 65536
$LOGON32_LOGON_NETWORK = 3
$LOGON32_PROVIDER_DEFAULT = 0
$adsi = [ADSI]"WinNT://$env:COMPUTERNAME" $adsi = [ADSI]"WinNT://$env:COMPUTERNAME"
@ -43,49 +42,11 @@ function Get-Group($grp) {
Function Test-LocalCredential { Function Test-LocalCredential {
param([String]$Username, [String]$Password) param([String]$Username, [String]$Password)
$platform_util = @' try {
using System; $handle = [Ansible.AccessToken.TokenUtil]::LogonUser($Username, $null, $Password, "Network", "Default")
using System.Runtime.InteropServices; $handle.Dispose()
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) {
$valid_credentials = $true $valid_credentials = $true
[Ansible.WinUserPInvoke]::CloseHandle($handle) > $null } catch [Ansible.AccessToken.Win32Exception] {
} else {
# following errors indicate the creds are correct but the user was # following errors indicate the creds are correct but the user was
# unable to log on for other reasons, which we don't care about # unable to log on for other reasons, which we don't care about
$success_codes = @( $success_codes = @(
@ -95,26 +56,22 @@ namespace Ansible
0x00000569 # ERROR_LOGON_TYPE_GRANTED 0x00000569 # ERROR_LOGON_TYPE_GRANTED
) )
if ($err_code -eq 0x0000052E) { if ($_.Exception.NativeErrorCode -eq 0x0000052E) {
# ERROR_LOGON_FAILURE - the user or pass was incorrect # ERROR_LOGON_FAILURE - the user or pass was incorrect
$valid_credentials = $false $valid_credentials = $false
} elseif ($err_code -in $success_codes) { } elseif ($_.Exception.NativeErrorCode -in $success_codes) {
$valid_credentials = $true $valid_credentials = $true
} else { } else {
# an unknown failure, raise an Exception for this # an unknown failure, reraise exception
$win32_exp = New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList $err_code throw $_
$err_msg = "LogonUserW failed: $($win32_exp.Message) (Win32ErrorCode: $err_code)"
throw New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList $err_code, $err_msg
} }
} }
return $valid_credentials return $valid_credentials
} }
######## ########
$params = Parse-Args $args; $params = Parse-Args $args;
$_remote_tmp = Get-AnsibleParam $params "_ansible_remote_tmp" -type "path" -default $env:TMP
$result = @{ $result = @{
changed = $false changed = $false

@ -141,7 +141,7 @@
failed_when: failed_when:
- '"Failed to become user " + become_test_username not in become_invalid_pass.msg' - '"Failed to become user " + become_test_username not in become_invalid_pass.msg'
- '"LogonUser failed" 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 - name: test become + async
vars: *become_vars 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" = { "Runas without working dir set" = {
$expected = "$env:SystemRoot\system32`r`n" $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, "") 'powershell.exe $pwd.Path', $null, $null, "")
$actual.StandardOut | Assert-Equals -Expected $expected $actual.StandardOut | Assert-Equals -Expected $expected
$actual.StandardError | Assert-Equals -Expected "" $actual.StandardError | Assert-Equals -Expected ""
@ -518,7 +518,7 @@ $tests = @{
"Runas with working dir set" = { "Runas with working dir set" = {
$expected = "$env:SystemRoot`r`n" $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, "") 'powershell.exe $pwd.Path', $env:SystemRoot, $null, "")
$actual.StandardOut | Assert-Equals -Expected $expected $actual.StandardOut | Assert-Equals -Expected $expected
$actual.StandardError | Assert-Equals -Expected "" $actual.StandardError | Assert-Equals -Expected ""
@ -527,7 +527,7 @@ $tests = @{
"Runas without environment set" = { "Runas without environment set" = {
$expected = "Windows_NT`r`n" $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, "") 'powershell.exe $env:TEST; $env:OS', $null, $null, "")
$actual.StandardOut | Assert-Equals -Expected $expected $actual.StandardOut | Assert-Equals -Expected $expected
$actual.StandardError | Assert-Equals -Expected "" $actual.StandardError | Assert-Equals -Expected ""
@ -539,7 +539,7 @@ $tests = @{
TEST = "tesTing" TEST = "tesTing"
TEST2 = "Testing 2" 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, "") 'cmd.exe /c set', $null, $env_vars, "")
("TEST=tesTing" -cin $actual.StandardOut.Split("`r`n")) | Assert-Equals -Expected $true ("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 ("TEST2=Testing 2" -cin $actual.StandardOut.Split("`r`n")) | Assert-Equals -Expected $true
@ -550,7 +550,7 @@ $tests = @{
"Runas with string stdin" = { "Runas with string stdin" = {
$expected = "input value`r`n`r`n" $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") 'powershell.exe [System.Console]::In.ReadToEnd()', $null, $null, "input value")
$actual.StandardOut | Assert-Equals -Expected $expected $actual.StandardOut | Assert-Equals -Expected $expected
$actual.StandardError | Assert-Equals -Expected "" $actual.StandardError | Assert-Equals -Expected ""
@ -559,7 +559,7 @@ $tests = @{
"Runas with string stdin and newline" = { "Runas with string stdin and newline" = {
$expected = "input value`r`n`r`n" $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") 'powershell.exe [System.Console]::In.ReadToEnd()', $null, $null, "input value`r`n")
$actual.StandardOut | Assert-Equals -Expected $expected $actual.StandardOut | Assert-Equals -Expected $expected
$actual.StandardError | Assert-Equals -Expected "" $actual.StandardError | Assert-Equals -Expected ""
@ -568,7 +568,7 @@ $tests = @{
"Runas with byte stdin" = { "Runas with byte stdin" = {
$expected = "input value`r`n" $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")) 'powershell.exe [System.Console]::In.ReadToEnd()', $null, $null, [System.Text.Encoding]::UTF8.GetBytes("input value"))
$actual.StandardOut | Assert-Equals -Expected $expected $actual.StandardOut | Assert-Equals -Expected $expected
$actual.StandardError | Assert-Equals -Expected "" $actual.StandardError | Assert-Equals -Expected ""
@ -592,13 +592,13 @@ $tests = @{
"CreateProcessAsUser with lpApplicationName" = { "CreateProcessAsUser with lpApplicationName" = {
$expected = "abc`r`n" $expected = "abc`r`n"
$full_path = "$($env:SystemRoot)\System32\WindowsPowerShell\v1.0\powershell.exe" $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, "") "Write-Output 'abc'", $null, $null, "")
$actual.StandardOut | Assert-Equals -Expected $expected $actual.StandardOut | Assert-Equals -Expected $expected
$actual.StandardError | Assert-Equals -Expected "" $actual.StandardError | Assert-Equals -Expected ""
$actual.ExitCode | Assert-Equals -Expected 0 $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, "") "powershell.exe Write-Output 'abc'", $null, $null, "")
$actual.StandardOut | Assert-Equals -Expected $expected $actual.StandardOut | Assert-Equals -Expected $expected
$actual.StandardError | Assert-Equals -Expected "" $actual.StandardError | Assert-Equals -Expected ""
@ -606,7 +606,7 @@ $tests = @{
} }
"CreateProcessAsUser with stderr" = { "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, "") "powershell.exe [System.Console]::Error.WriteLine('hi')", $null, $null, "")
$actual.StandardOut | Assert-Equals -Expected "" $actual.StandardOut | Assert-Equals -Expected ""
$actual.StandardError | Assert-Equals -Expected "hi`r`n" $actual.StandardError | Assert-Equals -Expected "hi`r`n"
@ -614,7 +614,7 @@ $tests = @{
} }
"CreateProcessAsUser with exit code" = { "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, "") "powershell.exe exit 10", $null, $null, "")
$actual.StandardOut | Assert-Equals -Expected "" $actual.StandardOut | Assert-Equals -Expected ""
$actual.StandardError | 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") [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, "incorrect", "powershell.exe Write-Output abc")
} catch { } catch {
$failed = $true $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 # Server 2008 has a slightly different error msg, just assert we get the error 1326
($_.Exception.Message.Contains("Win32ErrorCode 1326")) | Assert-Equals -Expected $true ($_.Exception.Message.Contains("Win32ErrorCode 1326")) | Assert-Equals -Expected $true
} }
@ -675,8 +675,8 @@ $tests = @{
} }
"Interactive logon with standard" = { "Interactive logon with standard" = {
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, "LOGON_WITH_PROFILE", $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, "WithProfile",
"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.StandardError | Assert-Equals -Expected ""
$actual.ExitCode | Assert-Equals -Expected 0 $actual.ExitCode | Assert-Equals -Expected 0
@ -689,8 +689,8 @@ $tests = @{
} }
"Batch logon with standard" = { "Batch logon with standard" = {
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, "LOGON_WITH_PROFILE", $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, "WithProfile",
"LOGON32_LOGON_BATCH", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n") "Batch", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
$actual.StandardError | Assert-Equals -Expected "" $actual.StandardError | Assert-Equals -Expected ""
$actual.ExitCode | Assert-Equals -Expected 0 $actual.ExitCode | Assert-Equals -Expected 0
@ -707,8 +707,8 @@ $tests = @{
if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") { if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") {
continue continue
} }
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, "LOGON_WITH_PROFILE", $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, "WithProfile",
"LOGON32_LOGON_NETWORK", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n") "Network", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
$actual.StandardError | Assert-Equals -Expected "" $actual.StandardError | Assert-Equals -Expected ""
$actual.ExitCode | Assert-Equals -Expected 0 $actual.ExitCode | Assert-Equals -Expected 0
@ -725,8 +725,8 @@ $tests = @{
if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") { if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") {
continue continue
} }
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, "LOGON_WITH_PROFILE", $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, "WithProfile",
"LOGON32_LOGON_NETWORK_CLEARTEXT", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n") "NetworkCleartext", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
$actual.StandardError | Assert-Equals -Expected "" $actual.StandardError | Assert-Equals -Expected ""
$actual.ExitCode | Assert-Equals -Expected 0 $actual.ExitCode | Assert-Equals -Expected 0
@ -739,8 +739,8 @@ $tests = @{
} }
"Logon without password with standard" = { "Logon without password with standard" = {
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, [NullString]::Value, "LOGON_WITH_PROFILE", $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, [NullString]::Value, "WithProfile",
"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.StandardError | Assert-Equals -Expected ""
$actual.ExitCode | Assert-Equals -Expected 0 $actual.ExitCode | Assert-Equals -Expected 0
@ -759,8 +759,8 @@ $tests = @{
if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") { if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") {
continue continue
} }
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, [NullString]::Value, "LOGON_WITH_PROFILE", $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, [NullString]::Value, "WithProfile",
"LOGON32_LOGON_NETWORK", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n") "Network", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
$actual.StandardError | Assert-Equals -Expected "" $actual.StandardError | Assert-Equals -Expected ""
$actual.ExitCode | Assert-Equals -Expected 0 $actual.ExitCode | Assert-Equals -Expected 0
@ -775,8 +775,8 @@ $tests = @{
} }
"Interactive logon with admin" = { "Interactive logon with admin" = {
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, "LOGON_WITH_PROFILE", $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, "WithProfile",
"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.StandardError | Assert-Equals -Expected ""
$actual.ExitCode | Assert-Equals -Expected 0 $actual.ExitCode | Assert-Equals -Expected 0
@ -789,8 +789,8 @@ $tests = @{
} }
"Batch logon with admin" = { "Batch logon with admin" = {
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, "LOGON_WITH_PROFILE", $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, "WithProfile",
"LOGON32_LOGON_BATCH", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n") "Batch", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
$actual.StandardError | Assert-Equals -Expected "" $actual.StandardError | Assert-Equals -Expected ""
$actual.ExitCode | Assert-Equals -Expected 0 $actual.ExitCode | Assert-Equals -Expected 0
@ -807,8 +807,8 @@ $tests = @{
if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") { if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") {
continue continue
} }
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, "LOGON_WITH_PROFILE", $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, "WithProfile",
"LOGON32_LOGON_NETWORK", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n") "Network", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
$actual.StandardError | Assert-Equals -Expected "" $actual.StandardError | Assert-Equals -Expected ""
$actual.ExitCode | Assert-Equals -Expected 0 $actual.ExitCode | Assert-Equals -Expected 0
@ -825,8 +825,8 @@ $tests = @{
if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") { if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") {
continue continue
} }
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, "LOGON_WITH_PROFILE", $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, "WithProfile",
"LOGON32_LOGON_NETWORK_CLEARTEXT", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n") "NetworkCleartext", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
$actual.StandardError | Assert-Equals -Expected "" $actual.StandardError | Assert-Equals -Expected ""
$actual.ExitCode | Assert-Equals -Expected 0 $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 # 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 # use [NullString]::Value instead if we want that behaviour. This just tests to see that an empty
# string won't go the S4U route. # string won't go the S4U route.
[Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $null, "LOGON_WITH_PROFILE", [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $null, "WithProfile",
"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")
} catch { } catch {
$failed = $true $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 # Server 2008 has a slightly different error msg, just assert we get the error 1326
($_.Exception.Message.Contains("Win32ErrorCode 1326")) | Assert-Equals -Expected $true ($_.Exception.Message.Contains("Win32ErrorCode 1326")) | Assert-Equals -Expected $true
} }
@ -857,8 +857,8 @@ $tests = @{
} }
"Logon without password with admin" = { "Logon without password with admin" = {
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, [NullString]::Value, "LOGON_WITH_PROFILE", $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, [NullString]::Value, "WithProfile",
"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.StandardError | Assert-Equals -Expected ""
$actual.ExitCode | Assert-Equals -Expected 0 $actual.ExitCode | Assert-Equals -Expected 0
@ -877,8 +877,8 @@ $tests = @{
if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") { if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") {
continue continue
} }
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, [NullString]::Value, "LOGON_WITH_PROFILE", $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, [NullString]::Value, "WithProfile",
"LOGON32_LOGON_NETWORK", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n") "Network", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
$actual.StandardError | Assert-Equals -Expected "" $actual.StandardError | Assert-Equals -Expected ""
$actual.ExitCode | Assert-Equals -Expected 0 $actual.ExitCode | Assert-Equals -Expected 0
@ -899,7 +899,7 @@ $tests = @{
} }
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, 0, $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.StandardError | Assert-Equals -Expected ""
$actual.ExitCode | Assert-Equals -Expected 0 $actual.ExitCode | Assert-Equals -Expected 0
@ -912,8 +912,8 @@ $tests = @{
} }
"Logon with network credentials and no profile" = { "Logon with network credentials and no profile" = {
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("fakeuser", "fakepassword", "LOGON_NETCREDENTIALS_ONLY", $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("fakeuser", "fakepassword", "NetcredentialsOnly",
"LOGON32_LOGON_NEW_CREDENTIALS", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n") "NewCredentials", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
$actual.StandardError | Assert-Equals -Expected "" $actual.StandardError | Assert-Equals -Expected ""
$actual.ExitCode | Assert-Equals -Expected 0 $actual.ExitCode | Assert-Equals -Expected 0
@ -921,15 +921,15 @@ $tests = @{
$stdout.LogonType | Assert-Equals -Expected "NewCredentials" $stdout.LogonType | Assert-Equals -Expected "NewCredentials"
$stdout.MandatoryLabelSid.Value | Assert-Equals -Expected $current_user.MandatoryLabelSid.Value $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.ProfileLoaded | Assert-Equals -Expected $current_user.ProfileLoaded
$stdout.SourceName | Assert-Equals -Expected "Advapi" $stdout.SourceName | Assert-Equals -Expected "Advapi"
$stdout.UserSid.Value | Assert-Equals -Expected $current_user.UserSid.Value $stdout.UserSid.Value | Assert-Equals -Expected $current_user.UserSid.Value
} }
"Logon with network credentials and with profile" = { "Logon with network credentials and with profile" = {
$actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("fakeuser", "fakepassword", "LOGON_NETCREDENTIALS_ONLY, LOGON_WITH_PROFILE", $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("fakeuser", "fakepassword", "NetcredentialsOnly, WithProfile",
"LOGON32_LOGON_NEW_CREDENTIALS", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n") "NewCredentials", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n")
$actual.StandardError | Assert-Equals -Expected "" $actual.StandardError | Assert-Equals -Expected ""
$actual.ExitCode | Assert-Equals -Expected 0 $actual.ExitCode | Assert-Equals -Expected 0
@ -990,8 +990,6 @@ try {
} }
$group_obj.Add($user_obj.Path) $group_obj.Add($user_obj.Path)
} }
} }
foreach ($test_impl in $tests.GetEnumerator()) { foreach ($test_impl in $tests.GetEnumerator()) {
$test = $test_impl.Key $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 - name: test Ansible.Basic.cs
ansible_basic_tests: ansible_basic_tests:
register: ansible_basic_test register: ansible_basic_test

Loading…
Cancel
Save