diff options
Diffstat (limited to 'test/integration/targets/module_utils_Ansible.Become/library')
-rw-r--r-- | test/integration/targets/module_utils_Ansible.Become/library/ansible_become_tests.ps1 | 1022 |
1 files changed, 1022 insertions, 0 deletions
diff --git a/test/integration/targets/module_utils_Ansible.Become/library/ansible_become_tests.ps1 b/test/integration/targets/module_utils_Ansible.Become/library/ansible_become_tests.ps1 new file mode 100644 index 0000000..6e36321 --- /dev/null +++ b/test/integration/targets/module_utils_Ansible.Become/library/ansible_become_tests.ps1 @@ -0,0 +1,1022 @@ +#!powershell + +#AnsibleRequires -CSharpUtil Ansible.Basic +#AnsibleRequires -CSharpUtil Ansible.Become + +$module = [Ansible.Basic.AnsibleModule]::Create($args, @{}) + +Function Assert-Equal { + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)][AllowNull()]$Actual, + [Parameter(Mandatory = $true, Position = 0)][AllowNull()]$Expected + ) + + process { + $matched = $false + if ($Actual -is [System.Collections.ArrayList] -or $Actual -is [Array]) { + $Actual.Count | Assert-Equal -Expected $Expected.Count + for ($i = 0; $i -lt $Actual.Count; $i++) { + $actual_value = $Actual[$i] + $expected_value = $Expected[$i] + Assert-Equal -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") + } + } +} + +# Would be great to move win_whomai out into it's own module util and share the +# code here, for now just rely on a cut down version +$test_whoami = { + Add-Type -TypeDefinition @' +using Microsoft.Win32.SafeHandles; +using System; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Security.Principal; +using System.Text; + +namespace Ansible +{ + internal class NativeHelpers + { + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct LSA_UNICODE_STRING + { + public UInt16 Length; + public UInt16 MaximumLength; + public IntPtr Buffer; + + public override string ToString() + { + return Marshal.PtrToStringUni(Buffer, Length / sizeof(char)); + } + } + + [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 SECURITY_LOGON_SESSION_DATA + { + public UInt32 Size; + 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_MANDATORY_LABEL + { + public SID_AND_ATTRIBUTES Label; + } + + [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 SECURITY_LOGON_TYPE + { + System = 0, // Used only by the Sytem account + Interactive = 2, + Network, + Batch, + Service, + Proxy, + Unlock, + NetworkCleartext, + NewCredentials, + RemoteInteractive, + CachedInteractive, + CachedRemoteInteractive, + CachedUnlock + } + + public enum TokenInformationClass + { + TokenUser = 1, + TokenSource = 7, + TokenStatistics = 10, + TokenIntegrityLevel = 25, + } + } + + internal class NativeMethods + { + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool CloseHandle( + IntPtr hObject); + + [DllImport("kernel32.dll")] + public static extern SafeNativeHandle GetCurrentProcess(); + + [DllImport("userenv.dll", SetLastError = true)] + public static extern bool GetProfileType( + out UInt32 dwFlags); + + [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, CharSet = CharSet.Unicode)] + public static extern bool LookupAccountSid( + string lpSystemName, + IntPtr Sid, + StringBuilder lpName, + ref UInt32 cchName, + StringBuilder ReferencedDomainName, + ref UInt32 cchReferencedDomainName, + out UInt32 peUse); + + [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, + out SafeLsaMemoryBuffer ppLogonSessionData); + + [DllImport("advapi32.dll")] + public static extern UInt32 LsaNtStatusToWinError( + UInt32 Status); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool OpenProcessToken( + SafeNativeHandle ProcessHandle, + TokenAccessLevels DesiredAccess, + out SafeNativeHandle TokenHandle); + } + + internal class SafeLsaMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid + { + public SafeLsaMemoryBuffer() : base(true) { } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + protected override bool ReleaseHandle() + { + UInt32 res = NativeMethods.LsaFreeReturnBuffer(handle); + return res == 0; + } + } + + 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; + } + } + + 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); + } + } + + public class Win32Exception : System.ComponentModel.Win32Exception + { + private string _msg; + + public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { } + public Win32Exception(int errorCode, string message) : base(errorCode) + { + _msg = String.Format("{0} ({1}, Win32ErrorCode {2})", message, base.Message, errorCode); + } + + public override string Message { get { return _msg; } } + public static explicit operator Win32Exception(string message) { return new Win32Exception(message); } + } + + public class Logon + { + public string AuthenticationPackage { get; internal set; } + public string LogonType { get; internal set; } + public string MandatoryLabelName { get; internal set; } + public SecurityIdentifier MandatoryLabelSid { get; internal set; } + public bool ProfileLoaded { get; internal set; } + public string SourceName { get; internal set; } + public string UserName { get; internal set; } + public SecurityIdentifier UserSid { get; internal set; } + + public Logon() + { + using (SafeNativeHandle process = NativeMethods.GetCurrentProcess()) + { + TokenAccessLevels dwAccess = TokenAccessLevels.Query | TokenAccessLevels.QuerySource; + + SafeNativeHandle hToken; + NativeMethods.OpenProcessToken(process, dwAccess, out hToken); + using (hToken) + { + SetLogonSessionData(hToken); + SetTokenMandatoryLabel(hToken); + SetTokenSource(hToken); + SetTokenUser(hToken); + } + } + SetProfileLoaded(); + } + + private void SetLogonSessionData(SafeNativeHandle hToken) + { + NativeHelpers.TokenInformationClass tokenClass = NativeHelpers.TokenInformationClass.TokenStatistics; + UInt32 returnLength; + NativeMethods.GetTokenInformation(hToken, tokenClass, new SafeMemoryBuffer(IntPtr.Zero), 0, out returnLength); + + UInt64 tokenLuidId; + using (SafeMemoryBuffer infoPtr = new SafeMemoryBuffer((int)returnLength)) + { + if (!NativeMethods.GetTokenInformation(hToken, tokenClass, infoPtr, returnLength, out returnLength)) + throw new Win32Exception("GetTokenInformation(TokenStatistics) failed"); + + NativeHelpers.TOKEN_STATISTICS stats = (NativeHelpers.TOKEN_STATISTICS)Marshal.PtrToStructure( + infoPtr.DangerousGetHandle(), typeof(NativeHelpers.TOKEN_STATISTICS)); + tokenLuidId = (UInt64)stats.AuthenticationId; + } + + UInt32 sessionCount; + SafeLsaMemoryBuffer sessionPtr; + UInt32 res = NativeMethods.LsaEnumerateLogonSessions(out sessionCount, out sessionPtr); + if (res != 0) + throw new Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res), "LsaEnumerateLogonSession() failed"); + using (sessionPtr) + { + IntPtr currentSession = sessionPtr.DangerousGetHandle(); + for (UInt32 i = 0; i < sessionCount; i++) + { + SafeLsaMemoryBuffer sessionDataPtr; + res = NativeMethods.LsaGetLogonSessionData(currentSession, out sessionDataPtr); + if (res != 0) + { + currentSession = IntPtr.Add(currentSession, Marshal.SizeOf(typeof(NativeHelpers.LUID))); + continue; + } + using (sessionDataPtr) + { + NativeHelpers.SECURITY_LOGON_SESSION_DATA sessionData = (NativeHelpers.SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure( + sessionDataPtr.DangerousGetHandle(), typeof(NativeHelpers.SECURITY_LOGON_SESSION_DATA)); + UInt64 sessionId = (UInt64)sessionData.LogonId; + if (sessionId == tokenLuidId) + { + AuthenticationPackage = sessionData.AuthenticationPackage.ToString(); + LogonType = sessionData.LogonType.ToString(); + break; + } + } + + currentSession = IntPtr.Add(currentSession, Marshal.SizeOf(typeof(NativeHelpers.LUID))); + } + } + } + + private void SetTokenMandatoryLabel(SafeNativeHandle hToken) + { + NativeHelpers.TokenInformationClass tokenClass = NativeHelpers.TokenInformationClass.TokenIntegrityLevel; + UInt32 returnLength; + NativeMethods.GetTokenInformation(hToken, tokenClass, new SafeMemoryBuffer(IntPtr.Zero), 0, out returnLength); + using (SafeMemoryBuffer infoPtr = new SafeMemoryBuffer((int)returnLength)) + { + if (!NativeMethods.GetTokenInformation(hToken, tokenClass, infoPtr, returnLength, out returnLength)) + throw new Win32Exception("GetTokenInformation(TokenIntegrityLevel) failed"); + NativeHelpers.TOKEN_MANDATORY_LABEL label = (NativeHelpers.TOKEN_MANDATORY_LABEL)Marshal.PtrToStructure( + infoPtr.DangerousGetHandle(), typeof(NativeHelpers.TOKEN_MANDATORY_LABEL)); + MandatoryLabelName = LookupSidName(label.Label.Sid); + MandatoryLabelSid = new SecurityIdentifier(label.Label.Sid); + } + } + + private void SetTokenSource(SafeNativeHandle hToken) + { + NativeHelpers.TokenInformationClass tokenClass = NativeHelpers.TokenInformationClass.TokenSource; + UInt32 returnLength; + NativeMethods.GetTokenInformation(hToken, tokenClass, new SafeMemoryBuffer(IntPtr.Zero), 0, out returnLength); + using (SafeMemoryBuffer infoPtr = new SafeMemoryBuffer((int)returnLength)) + { + if (!NativeMethods.GetTokenInformation(hToken, tokenClass, infoPtr, returnLength, out returnLength)) + throw new Win32Exception("GetTokenInformation(TokenSource) failed"); + NativeHelpers.TOKEN_SOURCE source = (NativeHelpers.TOKEN_SOURCE)Marshal.PtrToStructure( + infoPtr.DangerousGetHandle(), typeof(NativeHelpers.TOKEN_SOURCE)); + SourceName = new string(source.SourceName).Replace('\0', ' ').TrimEnd(); + } + } + + private void SetTokenUser(SafeNativeHandle hToken) + { + NativeHelpers.TokenInformationClass tokenClass = NativeHelpers.TokenInformationClass.TokenUser; + UInt32 returnLength; + NativeMethods.GetTokenInformation(hToken, tokenClass, new SafeMemoryBuffer(IntPtr.Zero), 0, out returnLength); + using (SafeMemoryBuffer infoPtr = new SafeMemoryBuffer((int)returnLength)) + { + if (!NativeMethods.GetTokenInformation(hToken, tokenClass, infoPtr, returnLength, out returnLength)) + throw new Win32Exception("GetTokenInformation(TokenSource) failed"); + NativeHelpers.TOKEN_USER user = (NativeHelpers.TOKEN_USER)Marshal.PtrToStructure( + infoPtr.DangerousGetHandle(), typeof(NativeHelpers.TOKEN_USER)); + UserName = LookupSidName(user.User.Sid); + UserSid = new SecurityIdentifier(user.User.Sid); + } + } + + private void SetProfileLoaded() + { + UInt32 flags; + ProfileLoaded = NativeMethods.GetProfileType(out flags); + } + + private static string LookupSidName(IntPtr pSid) + { + StringBuilder name = new StringBuilder(0); + StringBuilder domain = new StringBuilder(0); + UInt32 nameLength = 0; + UInt32 domainLength = 0; + UInt32 peUse; + NativeMethods.LookupAccountSid(null, pSid, name, ref nameLength, domain, ref domainLength, out peUse); + name.EnsureCapacity((int)nameLength); + domain.EnsureCapacity((int)domainLength); + + if (!NativeMethods.LookupAccountSid(null, pSid, name, ref nameLength, domain, ref domainLength, out peUse)) + throw new Win32Exception("LookupAccountSid() failed"); + + return String.Format("{0}\\{1}", domain.ToString(), name.ToString()); + } + } +} +'@ + $logon = New-Object -TypeName Ansible.Logon + ConvertTo-Json -InputObject $logon +}.ToString() + +$current_user_raw = [Ansible.Process.ProcessUtil]::CreateProcess($null, "powershell.exe -NoProfile -", $null, $null, $test_whoami + "`r`n") +$current_user = ConvertFrom-Json -InputObject $current_user_raw.StandardOut + +$adsi = [ADSI]"WinNT://$env:COMPUTERNAME" + +$standard_user = "become_standard" +$admin_user = "become_admin" +$become_pass = "password123!$([System.IO.Path]::GetRandomFileName())" +$medium_integrity_sid = "S-1-16-8192" +$high_integrity_sid = "S-1-16-12288" +$system_integrity_sid = "S-1-16-16384" + +$tests = @{ + "Runas standard user" = { + $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, + "powershell.exe -NoProfile -ExecutionPolicy ByPass -File $tmp_script") + $actual.StandardError | Assert-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + $stdout.LogonType | Assert-Equal -Expected "Interactive" + $stdout.ProfileLoaded | Assert-Equal -Expected $true + $stdout.UserSid.Value | Assert-Equal -Expected $standard_user_sid + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $medium_integrity_sid + } + + "Runas admin user" = { + $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, + "powershell.exe -NoProfile -ExecutionPolicy ByPass -File $tmp_script") + $actual.StandardError | Assert-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + $stdout.LogonType | Assert-Equal -Expected "Interactive" + $stdout.ProfileLoaded | Assert-Equal -Expected $true + $stdout.UserSid.Value | Assert-Equal -Expected $admin_user_sid + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $high_integrity_sid + } + + "Runas SYSTEM" = { + $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("SYSTEM", $null, + "powershell.exe -NoProfile -ExecutionPolicy ByPass -File $tmp_script") + $actual.StandardError | Assert-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + $stdout.LogonType | Assert-Equal -Expected "System" + $stdout.ProfileLoaded | Assert-Equal -Expected $true + $stdout.UserSid.Value | Assert-Equal -Expected "S-1-5-18" + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $system_integrity_sid + + $with_domain = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("NT AUTHORITY\System", $null, "whoami.exe") + $with_domain.StandardOut | Assert-Equal -Expected "nt authority\system`r`n" + } + + "Runas LocalService" = { + $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("LocalService", $null, + "powershell.exe -NoProfile -ExecutionPolicy ByPass -File $tmp_script") + $actual.StandardError | Assert-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + $stdout.LogonType | Assert-Equal -Expected "Service" + $stdout.ProfileLoaded | Assert-Equal -Expected $true + $stdout.UserSid.Value | Assert-Equal -Expected "S-1-5-19" + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $system_integrity_sid + + $with_domain = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("NT AUTHORITY\LocalService", $null, "whoami.exe") + $with_domain.StandardOut | Assert-Equal -Expected "nt authority\local service`r`n" + } + + "Runas NetworkService" = { + $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("NetworkService", $null, + "powershell.exe -NoProfile -ExecutionPolicy ByPass -File $tmp_script") + $actual.StandardError | Assert-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + $stdout.LogonType | Assert-Equal -Expected "Service" + $stdout.ProfileLoaded | Assert-Equal -Expected $true + $stdout.UserSid.Value | Assert-Equal -Expected "S-1-5-20" + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $system_integrity_sid + + $with_domain = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("NT AUTHORITY\NetworkService", $null, "whoami.exe") + $with_domain.StandardOut | Assert-Equal -Expected "nt authority\network service`r`n" + } + + "Runas without working dir set" = { + $expected = "$env:SystemRoot\system32`r`n" + $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, 0, "Interactive", $null, + 'powershell.exe $pwd.Path', $null, $null, "") + $actual.StandardOut | Assert-Equal -Expected $expected + $actual.StandardError | Assert-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + } + + "Runas with working dir set" = { + $expected = "$env:SystemRoot`r`n" + $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, 0, "Interactive", $null, + 'powershell.exe $pwd.Path', $env:SystemRoot, $null, "") + $actual.StandardOut | Assert-Equal -Expected $expected + $actual.StandardError | Assert-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + } + + "Runas without environment set" = { + $expected = "Windows_NT`r`n" + $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, $become_pass, 0, "Interactive", $null, + 'powershell.exe $env:TEST; $env:OS', $null, $null, "") + $actual.StandardOut | Assert-Equal -Expected $expected + $actual.StandardError | Assert-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + } + + "Runas with environment set" = { + $env_vars = @{ + TEST = "tesTing" + TEST2 = "Testing 2" + } + $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-Equal -Expected $true + ("TEST2=Testing 2" -cin $actual.StandardOut.Split("`r`n")) | Assert-Equal -Expected $true + ("OS=Windows_NT" -cnotin $actual.StandardOut.Split("`r`n")) | Assert-Equal -Expected $true + $actual.StandardError | Assert-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + } + + "Runas with string stdin" = { + $expected = "input value`r`n`r`n" + $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-Equal -Expected $expected + $actual.StandardError | Assert-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + } + + "Runas with string stdin and newline" = { + $expected = "input value`r`n`r`n" + $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-Equal -Expected $expected + $actual.StandardError | Assert-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + } + + "Runas with byte stdin" = { + $expected = "input value`r`n" + $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-Equal -Expected $expected + $actual.StandardError | Assert-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + } + + "Missing executable" = { + $failed = $false + try { + [Ansible.Become.BecomeUtil]::CreateProcessAsUser("SYSTEM", $null, "fake.exe") + } + catch { + $failed = $true + $_.Exception.InnerException.GetType().FullName | Assert-Equal -Expected "Ansible.Process.Win32Exception" + $expected = 'Exception calling "CreateProcessAsUser" with "3" argument(s): "CreateProcessWithTokenW() failed ' + $expected += '(The system cannot find the file specified, Win32ErrorCode 2)"' + $_.Exception.Message | Assert-Equal -Expected $expected + } + $failed | Assert-Equal -Expected $true + } + + "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, "Interactive", $full_path, + "Write-Output 'abc'", $null, $null, "") + $actual.StandardOut | Assert-Equal -Expected $expected + $actual.StandardError | Assert-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("SYSTEM", $null, 0, "Interactive", $full_path, + "powershell.exe Write-Output 'abc'", $null, $null, "") + $actual.StandardOut | Assert-Equal -Expected $expected + $actual.StandardError | Assert-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + } + + "CreateProcessAsUser with stderr" = { + $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("SYSTEM", $null, 0, "Interactive", $null, + "powershell.exe [System.Console]::Error.WriteLine('hi')", $null, $null, "") + $actual.StandardOut | Assert-Equal -Expected "" + $actual.StandardError | Assert-Equal -Expected "hi`r`n" + $actual.ExitCode | Assert-Equal -Expected 0 + } + + "CreateProcessAsUser with exit code" = { + $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("SYSTEM", $null, 0, "Interactive", $null, + "powershell.exe exit 10", $null, $null, "") + $actual.StandardOut | Assert-Equal -Expected "" + $actual.StandardError | Assert-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 10 + } + + "Local account with computer name" = { + $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("$env:COMPUTERNAME\$standard_user", $become_pass, + "powershell.exe -NoProfile -ExecutionPolicy ByPass -File $tmp_script") + $actual.StandardError | Assert-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + $stdout.LogonType | Assert-Equal -Expected "Interactive" + $stdout.ProfileLoaded | Assert-Equal -Expected $true + $stdout.UserSid.Value | Assert-Equal -Expected $standard_user_sid + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $medium_integrity_sid + } + + "Local account with computer as period" = { + $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser(".\$standard_user", $become_pass, + "powershell.exe -NoProfile -ExecutionPolicy ByPass -File $tmp_script") + $actual.StandardError | Assert-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + $stdout.LogonType | Assert-Equal -Expected "Interactive" + $stdout.ProfileLoaded | Assert-Equal -Expected $true + $stdout.UserSid.Value | Assert-Equal -Expected $standard_user_sid + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $medium_integrity_sid + } + + "Local account with invalid password" = { + $failed = $false + try { + [Ansible.Become.BecomeUtil]::CreateProcessAsUser($standard_user, "incorrect", "powershell.exe Write-Output abc") + } + catch { + $failed = $true + $_.Exception.InnerException.GetType().FullName | Assert-Equal -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-Equal -Expected $true + } + $failed | Assert-Equal -Expected $true + } + + "Invalid account" = { + $failed = $false + try { + [Ansible.Become.BecomeUtil]::CreateProcessAsUser("incorrect", "incorrect", "powershell.exe Write-Output abc") + } + catch { + $failed = $true + $_.Exception.InnerException.GetType().FullName | Assert-Equal -Expected "System.Security.Principal.IdentityNotMappedException" + $expected = 'Exception calling "CreateProcessAsUser" with "3" argument(s): "Some or all ' + $expected += 'identity references could not be translated."' + $_.Exception.Message | Assert-Equal -Expected $expected + } + $failed | Assert-Equal -Expected $true + } + + "Interactive logon with standard" = { + $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-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + $stdout.LogonType | Assert-Equal -Expected "Interactive" + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $medium_integrity_sid + $stdout.ProfileLoaded | Assert-Equal -Expected $true + $stdout.SourceName | Assert-Equal -Expected "Advapi" + $stdout.UserSid.Value | Assert-Equal -Expected $standard_user_sid + } + + "Batch logon with standard" = { + $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-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + $stdout.LogonType | Assert-Equal -Expected "Batch" + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $medium_integrity_sid + $stdout.ProfileLoaded | Assert-Equal -Expected $true + $stdout.SourceName | Assert-Equal -Expected "Advapi" + $stdout.UserSid.Value | Assert-Equal -Expected $standard_user_sid + } + + "Network logon with standard" = { + # Server 2008 will not work with become to Network or Network Credentials + if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") { + continue + } + $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-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + $stdout.LogonType | Assert-Equal -Expected "Network" + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $medium_integrity_sid + $stdout.ProfileLoaded | Assert-Equal -Expected $true + $stdout.SourceName | Assert-Equal -Expected "Advapi" + $stdout.UserSid.Value | Assert-Equal -Expected $standard_user_sid + } + + "Network with cleartext logon with standard" = { + # Server 2008 will not work with become to Network or Network Cleartext + if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") { + continue + } + $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-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + $stdout.LogonType | Assert-Equal -Expected "NetworkCleartext" + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $medium_integrity_sid + $stdout.ProfileLoaded | Assert-Equal -Expected $true + $stdout.SourceName | Assert-Equal -Expected "Advapi" + $stdout.UserSid.Value | Assert-Equal -Expected $standard_user_sid + } + + "Logon without password with standard" = { + $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-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + # Too unstable, there might be another process still lingering which causes become to steal instead of using + # S4U. Just don't check the type and source to verify we can become without a password + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + # $stdout.LogonType | Assert-Equal -Expected "Batch" + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $medium_integrity_sid + $stdout.ProfileLoaded | Assert-Equal -Expected $true + # $stdout.SourceName | Assert-Equal -Expected "ansible" + $stdout.UserSid.Value | Assert-Equal -Expected $standard_user_sid + } + + "Logon without password and network type with standard" = { + # Server 2008 will not work with become to Network or Network Cleartext + if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") { + continue + } + $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-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + # Too unstable, there might be another process still lingering which causes become to steal instead of using + # S4U. Just don't check the type and source to verify we can become without a password + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + # $stdout.LogonType | Assert-Equal -Expected "Network" + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $medium_integrity_sid + $stdout.ProfileLoaded | Assert-Equal -Expected $true + # $stdout.SourceName | Assert-Equal -Expected "ansible" + $stdout.UserSid.Value | Assert-Equal -Expected $standard_user_sid + } + + "Interactive logon with admin" = { + $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-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + $stdout.LogonType | Assert-Equal -Expected "Interactive" + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $high_integrity_sid + $stdout.ProfileLoaded | Assert-Equal -Expected $true + $stdout.SourceName | Assert-Equal -Expected "Advapi" + $stdout.UserSid.Value | Assert-Equal -Expected $admin_user_sid + } + + "Batch logon with admin" = { + $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-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + $stdout.LogonType | Assert-Equal -Expected "Batch" + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $high_integrity_sid + $stdout.ProfileLoaded | Assert-Equal -Expected $true + $stdout.SourceName | Assert-Equal -Expected "Advapi" + $stdout.UserSid.Value | Assert-Equal -Expected $admin_user_sid + } + + "Network logon with admin" = { + # Server 2008 will not work with become to Network or Network Credentials + if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") { + continue + } + $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-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + $stdout.LogonType | Assert-Equal -Expected "Network" + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $high_integrity_sid + $stdout.ProfileLoaded | Assert-Equal -Expected $true + $stdout.SourceName | Assert-Equal -Expected "Advapi" + $stdout.UserSid.Value | Assert-Equal -Expected $admin_user_sid + } + + "Network with cleartext logon with admin" = { + # Server 2008 will not work with become to Network or Network Credentials + if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") { + continue + } + $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-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + $stdout.LogonType | Assert-Equal -Expected "NetworkCleartext" + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $high_integrity_sid + $stdout.ProfileLoaded | Assert-Equal -Expected $true + $stdout.SourceName | Assert-Equal -Expected "Advapi" + $stdout.UserSid.Value | Assert-Equal -Expected $admin_user_sid + } + + "Fail to logon with null or empty password" = { + $failed = $false + try { + # Having $null or an empty string means we are trying to become a user with a blank password and not + # 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, "WithProfile", + "Interactive", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n") + } + catch { + $failed = $true + $_.Exception.InnerException.GetType().FullName | Assert-Equal -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-Equal -Expected $true + } + $failed | Assert-Equal -Expected $true + } + + "Logon without password with admin" = { + $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-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + # Too unstable, there might be another process still lingering which causes become to steal instead of using + # S4U. Just don't check the type and source to verify we can become without a password + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + # $stdout.LogonType | Assert-Equal -Expected "Batch" + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $high_integrity_sid + $stdout.ProfileLoaded | Assert-Equal -Expected $true + # $stdout.SourceName | Assert-Equal -Expected "ansible" + $stdout.UserSid.Value | Assert-Equal -Expected $admin_user_sid + } + + "Logon without password and network type with admin" = { + # become network doesn't work on Server 2008 + if ([System.Environment]::OSVersion.Version -lt [Version]"6.1") { + continue + } + $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-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + # Too unstable, there might be another process still lingering which causes become to steal instead of using + # S4U. Just don't check the type and source to verify we can become without a password + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + # $stdout.LogonType | Assert-Equal -Expected "Network" + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $high_integrity_sid + $stdout.ProfileLoaded | Assert-Equal -Expected $true + # $stdout.SourceName | Assert-Equal -Expected "ansible" + $stdout.UserSid.Value | Assert-Equal -Expected $admin_user_sid + } + + "Logon without profile with admin" = { + # Server 2008 and 2008 R2 does not support running without the profile being set + if ([System.Environment]::OSVersion.Version -lt [Version]"6.2") { + continue + } + + $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($admin_user, $become_pass, 0, + "Interactive", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n") + $actual.StandardError | Assert-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + $stdout.LogonType | Assert-Equal -Expected "Interactive" + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $high_integrity_sid + $stdout.ProfileLoaded | Assert-Equal -Expected $false + $stdout.SourceName | Assert-Equal -Expected "Advapi" + $stdout.UserSid.Value | Assert-Equal -Expected $admin_user_sid + } + + "Logon with network credentials and no profile" = { + $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("fakeuser", "fakepassword", "NetcredentialsOnly", + "NewCredentials", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n") + $actual.StandardError | Assert-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + $stdout.LogonType | Assert-Equal -Expected "NewCredentials" + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $current_user.MandatoryLabelSid.Value + + # while we didn't set WithProfile, the new process is based on the current process + $stdout.ProfileLoaded | Assert-Equal -Expected $current_user.ProfileLoaded + $stdout.SourceName | Assert-Equal -Expected "Advapi" + $stdout.UserSid.Value | Assert-Equal -Expected $current_user.UserSid.Value + } + + "Logon with network credentials and with profile" = { + $actual = [Ansible.Become.BecomeUtil]::CreateProcessAsUser("fakeuser", "fakepassword", "NetcredentialsOnly, WithProfile", + "NewCredentials", $null, "powershell.exe -NoProfile -", $tmp_dir, $null, $test_whoami + "`r`n") + $actual.StandardError | Assert-Equal -Expected "" + $actual.ExitCode | Assert-Equal -Expected 0 + + $stdout = ConvertFrom-Json -InputObject $actual.StandardOut + $stdout.LogonType | Assert-Equal -Expected "NewCredentials" + $stdout.MandatoryLabelSid.Value | Assert-Equal -Expected $current_user.MandatoryLabelSid.Value + $stdout.ProfileLoaded | Assert-Equal -Expected $current_user.ProfileLoaded + $stdout.SourceName | Assert-Equal -Expected "Advapi" + $stdout.UserSid.Value | Assert-Equal -Expected $current_user.UserSid.Value + } +} + +try { + $tmp_dir = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([System.IO.Path]::GetRandomFileName()) + New-Item -Path $tmp_dir -ItemType Directory > $null + $acl = Get-Acl -Path $tmp_dir + $ace = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList @( + New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList ([System.Security.Principal.WellKnownSidType]::WorldSid, $null) + [System.Security.AccessControl.FileSystemRights]::FullControl, + [System.Security.AccessControl.InheritanceFlags]"ContainerInherit, ObjectInherit", + [System.Security.AccessControl.PropagationFlags]::None, + [System.Security.AccessControl.AccessControlType]::Allow + ) + $acl.AddAccessRule($ace) + Set-Acl -Path $tmp_dir -AclObject $acl + + $tmp_script = Join-Path -Path $tmp_dir -ChildPath "whoami.ps1" + Set-Content -LiteralPath $tmp_script -Value $test_whoami + + foreach ($user in $standard_user, $admin_user) { + $user_obj = $adsi.Children | Where-Object { $_.SchemaClassName -eq "User" -and $_.Name -eq $user } + if ($null -eq $user_obj) { + $user_obj = $adsi.Create("User", $user) + $user_obj.SetPassword($become_pass) + $user_obj.SetInfo() + } + else { + $user_obj.SetPassword($become_pass) + } + $user_obj.RefreshCache() + + if ($user -eq $standard_user) { + $standard_user_sid = (New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList @($user_obj.ObjectSid.Value, 0)).Value + $group = [System.Security.Principal.WellKnownSidType]::BuiltinUsersSid + } + else { + $admin_user_sid = (New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList @($user_obj.ObjectSid.Value, 0)).Value + $group = [System.Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid + } + $group = (New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList $group, $null).Value + [string[]]$current_groups = $user_obj.Groups() | ForEach-Object { + New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList @( + $_.GetType().InvokeMember("objectSID", "GetProperty", $null, $_, $null), + 0 + ) + } + if ($current_groups -notcontains $group) { + $group_obj = $adsi.Children | Where-Object { + if ($_.SchemaClassName -eq "Group") { + $group_sid = New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList @($_.objectSID.Value, 0) + $group_sid -eq $group + } + } + $group_obj.Add($user_obj.Path) + } + } + foreach ($test_impl in $tests.GetEnumerator()) { + $test = $test_impl.Key + &$test_impl.Value + } +} +finally { + Remove-Item -LiteralPath $tmp_dir -Force -Recurse + foreach ($user in $standard_user, $admin_user) { + $user_obj = $adsi.Children | Where-Object { $_.SchemaClassName -eq "User" -and $_.Name -eq $user } + $adsi.Delete("User", $user_obj.Name.Value) + } +} + + +$module.Result.data = "success" +$module.ExitJson() + |