You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ansible/test/integration/targets/module_utils_Ansible.Become/library/ansible_become_tests.ps1

1023 lines
47 KiB
PowerShell

#!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()