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