win_become: get admin token and fix async (#32485)

* win_become: make it easier to become with an admin token

* Fixed up pep8 whitespace

* fix for Server 2008

* Added support for async and become on newer hosts and fix warnings
This commit is contained in:
Jordan Borean 2017-11-04 09:14:48 +10:00 committed by Matt Davis
parent 9cfd0a58b0
commit 15b492ca57
2 changed files with 387 additions and 113 deletions

View file

@ -171,9 +171,12 @@ Set-StrictMode -Version 2
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
$helper_def = @" $helper_def = @"
using Microsoft.Win32.SafeHandles;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.AccessControl; using System.Security.AccessControl;
using System.Security.Principal; using System.Security.Principal;
@ -212,9 +215,9 @@ namespace Ansible
public Int16 wShowWindow; public Int16 wShowWindow;
public Int16 cbReserved2; public Int16 cbReserved2;
public IntPtr lpReserved2; public IntPtr lpReserved2;
public IntPtr hStdInput; public SafeFileHandle hStdInput;
public IntPtr hStdOutput; public SafeFileHandle hStdOutput;
public IntPtr hStdError; public SafeFileHandle hStdError;
public STARTUPINFO() public STARTUPINFO()
{ {
cb = Marshal.SizeOf(this); cb = Marshal.SizeOf(this);
@ -254,6 +257,42 @@ namespace Ansible
public SID_AND_ATTRIBUTES User; public SID_AND_ATTRIBUTES User;
} }
[StructLayout(LayoutKind.Sequential)]
public struct IO_COUNTERS
{
public UInt64 ReadOperationCount;
public UInt64 WriteOperationCount;
public UInt64 OtherOperationCount;
public UInt64 ReadTransferCount;
public UInt64 WriteTransferCount;
public UInt64 OtherTransferCount;
}
[StructLayout(LayoutKind.Sequential)]
public struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public UInt64 PerProcessUserTimeLimit;
public UInt64 PerJobUserTimeLimit;
public LimitFlags LimitFlags;
public UIntPtr MinimumWorkingSetSize;
public UIntPtr MaximumWorkingSetSize;
public UInt32 ActiveProcessLimit;
public UIntPtr Affinity;
public UInt32 PriorityClass;
public UInt32 SchedulingClass;
}
[StructLayout(LayoutKind.Sequential)]
public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public UIntPtr ProcessMemoryLimit;
public UIntPtr JobMemoryLimit;
public UIntPtr PeakProcessMemoryUsed;
public UIntPtr PeakJobMemoryUsed;
}
[Flags] [Flags]
public enum StartupInfoFlags : uint public enum StartupInfoFlags : uint
{ {
@ -263,6 +302,7 @@ namespace Ansible
[Flags] [Flags]
public enum CreationFlags : uint public enum CreationFlags : uint
{ {
CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
CREATE_DEFAULT_ERROR_MODE = 0x04000000, CREATE_DEFAULT_ERROR_MODE = 0x04000000,
CREATE_NEW_CONSOLE = 0x00000010, CREATE_NEW_CONSOLE = 0x00000010,
CREATE_NEW_PROCESS_GROUP = 0x00000200, CREATE_NEW_PROCESS_GROUP = 0x00000200,
@ -353,11 +393,35 @@ namespace Ansible
TokenImpersonation TokenImpersonation
} }
enum JobObjectInfoType
{
AssociateCompletionPortInformation = 7,
BasicLimitInformation = 2,
BasicUIRestrictions = 4,
EndOfJobTimeInformation = 6,
ExtendedLimitInformation = 9,
SecurityLimitInformation = 5,
GroupInformation = 11
}
[Flags]
enum ThreadAccessRights : uint
{
SUSPEND_RESUME = 0x0002
}
[Flags]
public enum LimitFlags : uint
{
JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800,
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000
}
class NativeWaitHandle : WaitHandle class NativeWaitHandle : WaitHandle
{ {
public NativeWaitHandle(IntPtr handle) public NativeWaitHandle(IntPtr handle)
{ {
this.Handle = handle; this.SafeWaitHandle = new SafeWaitHandle(handle, false);
} }
} }
@ -380,6 +444,69 @@ namespace Ansible
public uint ExitCode { get; internal set; } public uint ExitCode { get; internal set; }
} }
public class Job : IDisposable
{
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr CreateJobObject(
IntPtr lpJobAttributes,
string lpName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetInformationJobObject(
IntPtr hJob,
JobObjectInfoType JobObjectInfoClass,
IntPtr lpJobObjectInfo,
UInt32 cbJobObjectInfoLength);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AssignProcessToJobObject(
IntPtr hJob,
IntPtr hProcess);
[DllImport("kernel32.dll")]
private static extern bool CloseHandle(
IntPtr hObject);
private IntPtr handle;
public Job()
{
handle = CreateJobObject(IntPtr.Zero, null);
if (handle == IntPtr.Zero)
throw new Win32Exception("CreateJobObject() failed");
JOBOBJECT_BASIC_LIMIT_INFORMATION jobInfo = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
jobInfo.LimitFlags = LimitFlags.JOB_OBJECT_LIMIT_BREAKAWAY_OK | LimitFlags.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedJobInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
extendedJobInfo.BasicLimitInformation = jobInfo;
int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
IntPtr pExtendedJobInfo = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(extendedJobInfo, pExtendedJobInfo, false);
if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, pExtendedJobInfo, (UInt32)length))
throw new Win32Exception("SetInformationJobObject() failed");
}
public void AssignProcess(IntPtr processHandle)
{
if (!AssignProcessToJobObject(handle, processHandle))
throw new Win32Exception("AssignProcessToJobObject() failed");
}
public void Dispose()
{
if (handle != IntPtr.Zero)
{
CloseHandle(handle);
handle = IntPtr.Zero;
}
GC.SuppressFinalize(this);
}
}
public class BecomeUtil public class BecomeUtil
{ {
[DllImport("advapi32.dll", SetLastError = true)] [DllImport("advapi32.dll", SetLastError = true)]
@ -407,14 +534,14 @@ namespace Ansible
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
private static extern bool CreatePipe( private static extern bool CreatePipe(
out IntPtr hReadPipe, out SafeFileHandle hReadPipe,
out IntPtr hWritePipe, out SafeFileHandle hWritePipe,
SECURITY_ATTRIBUTES lpPipeAttributes, SECURITY_ATTRIBUTES lpPipeAttributes,
uint nSize); uint nSize);
[DllImport("kernel32.dll", SetLastError = true)] [DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetHandleInformation( private static extern bool SetHandleInformation(
IntPtr hObject, SafeFileHandle hObject,
HandleFlags dwMask, HandleFlags dwMask,
int dwFlags); int dwFlags);
@ -431,7 +558,8 @@ namespace Ansible
private static extern IntPtr GetProcessWindowStation(); private static extern IntPtr GetProcessWindowStation();
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr GetThreadDesktop(int dwThreadId); private static extern IntPtr GetThreadDesktop(
int dwThreadId);
[DllImport("kernel32.dll", SetLastError = true)] [DllImport("kernel32.dll", SetLastError = true)]
private static extern int GetCurrentThreadId(); private static extern int GetCurrentThreadId();
@ -480,17 +608,27 @@ namespace Ansible
out IntPtr phNewToken); out IntPtr phNewToken);
[DllImport("advapi32.dll", SetLastError = true)] [DllImport("advapi32.dll", SetLastError = true)]
public static extern bool ImpersonateLoggedOnUser( private static extern bool ImpersonateLoggedOnUser(
IntPtr hToken); IntPtr hToken);
[DllImport("advapi32.dll", SetLastError = true)] [DllImport("advapi32.dll", SetLastError = true)]
public static extern bool RevertToSelf(); private static extern bool RevertToSelf();
[DllImport("kernel32.dll", SetLastError = true)]
private static extern SafeFileHandle OpenThread(
ThreadAccessRights dwDesiredAccess,
bool bInheritHandle,
int dwThreadId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern int ResumeThread(
SafeHandle hThread);
public static CommandResult RunAsUser(string username, string password, string lpCommandLine, string lpCurrentDirectory, string stdinInput) public static CommandResult RunAsUser(string username, string password, string lpCommandLine, string lpCurrentDirectory, string stdinInput)
{ {
SecurityIdentifier account = GetBecomeSid(username); SecurityIdentifier account = GetBecomeSid(username);
CreationFlags startup_flags = CreationFlags.CREATE_UNICODE_ENVIRONMENT; CreationFlags startup_flags = CreationFlags.CREATE_UNICODE_ENVIRONMENT | CreationFlags.CREATE_BREAKAWAY_FROM_JOB | CreationFlags.CREATE_SUSPENDED;
STARTUPINFOEX si = new STARTUPINFOEX(); STARTUPINFOEX si = new STARTUPINFOEX();
si.startupInfo.dwFlags = (int)StartupInfoFlags.USESTDHANDLES; si.startupInfo.dwFlags = (int)StartupInfoFlags.USESTDHANDLES;
@ -499,7 +637,7 @@ namespace Ansible
pipesec.bInheritHandle = true; pipesec.bInheritHandle = true;
// Create the stdout, stderr and stdin pipes used in the process and add to the startupInfo // Create the stdout, stderr and stdin pipes used in the process and add to the startupInfo
IntPtr stdout_read, stdout_write, stderr_read, stderr_write, stdin_read, stdin_write = IntPtr.Zero; SafeFileHandle stdout_read, stdout_write, stderr_read, stderr_write, stdin_read, stdin_write;
if (!CreatePipe(out stdout_read, out stdout_write, pipesec, 0)) if (!CreatePipe(out stdout_read, out stdout_write, pipesec, 0))
throw new Win32Exception("STDOUT pipe setup failed"); throw new Win32Exception("STDOUT pipe setup failed");
if (!SetHandleInformation(stdout_read, HandleFlags.INHERIT, 0)) if (!SetHandleInformation(stdout_read, HandleFlags.INHERIT, 0))
@ -521,7 +659,7 @@ namespace Ansible
// Setup the stdin buffer // Setup the stdin buffer
UTF8Encoding utf8_encoding = new UTF8Encoding(false); UTF8Encoding utf8_encoding = new UTF8Encoding(false);
FileStream stdin_fs = new FileStream(stdin_write, FileAccess.Write, true, 32768); FileStream stdin_fs = new FileStream(stdin_write, FileAccess.Write, 32768);
StreamWriter stdin = new StreamWriter(stdin_fs, utf8_encoding, 32768); StreamWriter stdin = new StreamWriter(stdin_fs, utf8_encoding, 32768);
// Create the environment block if set // Create the environment block if set
@ -554,26 +692,44 @@ namespace Ansible
if (!launch_success) if (!launch_success)
throw new Win32Exception("Failed to start become process"); throw new Win32Exception("Failed to start become process");
// Setup the output buffers and get stdout/stderr // If 2012/8+ OS, create new job with JOB_OBJECT_LIMIT_BREAKAWAY_OK
FileStream stdout_fs = new FileStream(stdout_read, FileAccess.Read, true, 4096); // so that async can work
StreamReader stdout = new StreamReader(stdout_fs, utf8_encoding, true, 4096); Job job = null;
CloseHandle(stdout_write); if (Environment.OSVersion.Version >= new Version("6.2"))
{
FileStream stderr_fs = new FileStream(stderr_read, FileAccess.Read, true, 4096); job = new Job();
StreamReader stderr = new StreamReader(stderr_fs, utf8_encoding, true, 4096); job.AssignProcess(pi.hProcess);
CloseHandle(stderr_write); }
ResumeProcessById(pi.dwProcessId);
stdin.WriteLine(stdinInput);
stdin.Close();
string stdout_str, stderr_str = null;
GetProcessOutput(stdout, stderr, out stdout_str, out stderr_str);
uint rc = GetProcessExitCode(pi.hProcess);
CommandResult result = new CommandResult(); CommandResult result = new CommandResult();
result.StandardOut = stdout_str; try
result.StandardError = stderr_str; {
result.ExitCode = rc; // Setup the output buffers and get stdout/stderr
FileStream stdout_fs = new FileStream(stdout_read, FileAccess.Read, 4096);
StreamReader stdout = new StreamReader(stdout_fs, utf8_encoding, true, 4096);
stdout_write.Close();
FileStream stderr_fs = new FileStream(stderr_read, FileAccess.Read, 4096);
StreamReader stderr = new StreamReader(stderr_fs, utf8_encoding, true, 4096);
stderr_write.Close();
stdin.WriteLine(stdinInput);
stdin.Close();
string stdout_str, stderr_str = null;
GetProcessOutput(stdout, stderr, out stdout_str, out stderr_str);
UInt32 rc = GetProcessExitCode(pi.hProcess);
result.StandardOut = stdout_str;
result.StandardError = stderr_str;
result.ExitCode = rc;
}
finally
{
if (job != null)
job.Dispose();
}
return result; return result;
} }
@ -604,71 +760,64 @@ namespace Ansible
GrantAccessToWindowStationAndDesktop(account); GrantAccessToWindowStationAndDesktop(account);
string account_sid = account.ToString(); string account_sid = account.ToString();
bool impersonated = false;
IntPtr hSystemTokenDup = IntPtr.Zero;
// Try to get SYSTEM token handle so we can impersonate to get full admin token
IntPtr hSystemToken = GetSystemUserHandle();
if (hSystemToken == IntPtr.Zero && service_sids.Contains(account_sid))
{
// We need the SYSTEM token if we want to become one of those accounts, fail here
throw new Win32Exception("Failed to get token for NT AUTHORITY\\SYSTEM");
}
else if (hSystemToken != IntPtr.Zero)
{
// We have the token, need to duplicate and impersonate
bool dupResult = DuplicateTokenEx(
hSystemToken,
TokenAccessLevels.MaximumAllowed,
IntPtr.Zero,
SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
TOKEN_TYPE.TokenPrimary,
out hSystemTokenDup);
int lastError = Marshal.GetLastWin32Error();
CloseHandle(hSystemToken);
if (!dupResult && service_sids.Contains(account_sid))
throw new Win32Exception(lastError, "Failed to duplicate token for NT AUTHORITY\\SYSTEM");
else if (dupResult && account_sid != "S-1-5-18")
{
if (ImpersonateLoggedOnUser(hSystemTokenDup))
impersonated = true;
else if (service_sids.Contains(account_sid))
throw new Win32Exception("Failed to impersonate as SYSTEM account");
}
}
LogonType logonType;
string domain = null;
if (service_sids.Contains(account_sid)) if (service_sids.Contains(account_sid))
{ {
// We are trying to become to a service account logonType = LogonType.LOGON32_LOGON_SERVICE;
IntPtr hToken = GetUserHandle(); domain = "NT AUTHORITY";
if (hToken == IntPtr.Zero) password = null;
throw new Exception("Failed to get token for NT AUTHORITY\\SYSTEM");
IntPtr hTokenDup = IntPtr.Zero;
try
{
if (!DuplicateTokenEx(
hToken,
TokenAccessLevels.MaximumAllowed,
IntPtr.Zero,
SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
TOKEN_TYPE.TokenPrimary,
out hTokenDup))
{
throw new Win32Exception("Failed to duplicate the SYSTEM account token");
}
}
finally
{
CloseHandle(hToken);
}
string lpszDomain = "NT AUTHORITY";
string lpszUsername = null;
switch (account_sid) switch (account_sid)
{ {
case "S-1-5-18": case "S-1-5-18":
tokens.Add(hTokenDup); tokens.Add(hSystemTokenDup);
return tokens; return tokens;
case "S-1-5-19": case "S-1-5-19":
lpszUsername = "LocalService"; username = "LocalService";
break; break;
case "S-1-5-20": case "S-1-5-20":
lpszUsername = "NetworkService"; username = "NetworkService";
break; break;
} }
if (!ImpersonateLoggedOnUser(hTokenDup))
throw new Win32Exception("Failed to impersonate as SYSTEM account");
IntPtr newToken = IntPtr.Zero;
if (!LogonUser(
lpszUsername,
lpszDomain,
null,
LogonType.LOGON32_LOGON_SERVICE,
LogonProvider.LOGON32_PROVIDER_DEFAULT,
out newToken))
{
throw new Win32Exception("LogonUser failed");
}
RevertToSelf();
tokens.Add(newToken);
return tokens;
} }
else else
{ {
// We are trying to become a local or domain account // We are trying to become a local or domain account
string domain = null; logonType = LogonType.LOGON32_LOGON_INTERACTIVE;
if (username.Contains(@"\")) if (username.Contains(@"\"))
{ {
var user_split = username.Split(Convert.ToChar(@"\")); var user_split = username.Split(Convert.ToChar(@"\"));
@ -679,31 +828,35 @@ namespace Ansible
domain = null; domain = null;
else else
domain = "."; domain = ".";
// Logon and get the token
IntPtr hToken = IntPtr.Zero;
if (!LogonUser(
username,
domain,
password,
LogonType.LOGON32_LOGON_INTERACTIVE,
LogonProvider.LOGON32_PROVIDER_DEFAULT,
out hToken))
{
throw new Win32Exception("LogonUser failed");
}
// Get the elevate token
IntPtr hTokenElevated = GetElevatedToken(hToken);
tokens.Add(hTokenElevated);
tokens.Add(hToken);
return tokens;
} }
IntPtr hToken = IntPtr.Zero;
if (!LogonUser(
username,
domain,
password,
logonType,
LogonProvider.LOGON32_PROVIDER_DEFAULT,
out hToken))
{
throw new Win32Exception("LogonUser failed");
}
if (!service_sids.Contains(account_sid))
{
// Try and get the elevated token for local/domain account
IntPtr hTokenElevated = GetElevatedToken(hToken);
tokens.Add(hTokenElevated);
}
tokens.Add(hToken);
if (impersonated)
RevertToSelf();
return tokens;
} }
private static IntPtr GetUserHandle() private static IntPtr GetSystemUserHandle()
{ {
uint array_byte_size = 1024 * sizeof(uint); uint array_byte_size = 1024 * sizeof(uint);
IntPtr[] pids = new IntPtr[1024]; IntPtr[] pids = new IntPtr[1024];
@ -864,6 +1017,44 @@ namespace Ansible
security.Persist(safeHandle, AccessControlSections.Access); security.Persist(safeHandle, AccessControlSections.Access);
} }
private static void ResumeThreadById(int threadId)
{
var threadHandle = OpenThread(ThreadAccessRights.SUSPEND_RESUME, false, threadId);
if (threadHandle.IsInvalid)
throw new Win32Exception(String.Format("Thread ID {0} is invalid", threadId));
try
{
if (ResumeThread(threadHandle) == -1)
throw new Win32Exception(String.Format("Thread ID {0} cannot be resumed", threadId));
}
finally
{
threadHandle.Dispose();
}
}
private static void ResumeProcessById(int pid)
{
var proc = Process.GetProcessById(pid);
// wait for at least one suspended thread in the process (this handles possible slow startup race where
// primary thread of created-suspended process has not yet become runnable)
var retryCount = 0;
while (!proc.Threads.OfType<ProcessThread>().Any(t => t.ThreadState == System.Diagnostics.ThreadState.Wait &&
t.WaitReason == ThreadWaitReason.Suspended))
{
proc.Refresh();
Thread.Sleep(50);
if (retryCount > 100)
throw new InvalidOperationException(String.Format("No threads were suspended in target PID {0} after 5s", pid));
}
foreach (var thread in proc.Threads.OfType<ProcessThread>().Where(t => t.ThreadState == System.Diagnostics.ThreadState.Wait &&
t.WaitReason == ThreadWaitReason.Suspended))
ResumeThreadById(thread.Id);
}
private class GenericSecurity : NativeObjectSecurity private class GenericSecurity : NativeObjectSecurity
{ {
public GenericSecurity(bool isContainer, ResourceType resType, SafeHandle objectHandle, AccessControlSections sectionsRequested) public GenericSecurity(bool isContainer, ResourceType resType, SafeHandle objectHandle, AccessControlSections sectionsRequested)
@ -969,8 +1160,7 @@ Function Run($payload) {
$username = $payload.become_user $username = $payload.become_user
$password = $payload.become_password $password = $payload.become_password
# FUTURE: convert to SafeHandle so we can stop ignoring warnings? Add-Type -TypeDefinition $helper_def -Debug:$false
Add-Type -TypeDefinition $helper_def -Debug:$false -IgnoreWarnings
# NB: CreateProcessWithTokenW commandline maxes out at 1024 chars, must bootstrap via filesystem # NB: CreateProcessWithTokenW commandline maxes out at 1024 chars, must bootstrap via filesystem
$temp = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName() + ".ps1") $temp = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName() + ".ps1")

View file

@ -1,5 +1,6 @@
- set_fact: - set_fact:
become_test_username: ansible_become_test become_test_username: ansible_become_test
become_test_admin_username: ansible_become_admin
gen_pw: password123! + {{ lookup('password', '/dev/null chars=ascii_letters,digits length=8') }} gen_pw: password123! + {{ lookup('password', '/dev/null chars=ascii_letters,digits length=8') }}
- name: create unprivileged user - name: create unprivileged user
@ -9,16 +10,19 @@
update_password: always update_password: always
groups: Users groups: Users
- name: create a privileged user
win_user:
name: "{{ become_test_admin_username }}"
password: "{{ gen_pw }}"
update_password: always
groups: Administrators
- name: execute tests and ensure that test user is deleted regardless of success/failure - name: execute tests and ensure that test user is deleted regardless of success/failure
block: block:
- name: ensure current user is not the become user - name: ensure current user is not the become user
win_shell: whoami win_shell: whoami
register: whoami_out register: whoami_out
failed_when: whoami_out.stdout_lines[0].endswith(become_test_username) or whoami_out.stdout_lines[0].endswith(become_test_admin_username)
- name: verify output
assert:
that:
- not whoami_out.stdout_lines[0].endswith(become_test_username)
- name: get become user profile dir so we can clean it up later - name: get become user profile dir so we can clean it up later
vars: &become_vars vars: &become_vars
@ -34,7 +38,21 @@
that: that:
- become_test_username in profile_dir_out.stdout_lines[0] - become_test_username in profile_dir_out.stdout_lines[0]
- name: test become runas via task vars - name: get become admin user profile dir so we can clean it up later
vars: &admin_become_vars
ansible_become_user: "{{ become_test_admin_username }}"
ansible_become_password: "{{ gen_pw }}"
ansible_become_method: runas
ansible_become: yes
win_shell: $env:USERPROFILE
register: admin_profile_dir_out
- name: ensure profile dir contains admin test username
assert:
that:
- become_test_admin_username in admin_profile_dir_out.stdout_lines[0]
- name: test become runas via task vars (underprivileged user)
vars: *become_vars vars: *become_vars
win_shell: whoami win_shell: whoami
register: whoami_out register: whoami_out
@ -44,6 +62,36 @@
that: that:
- whoami_out.stdout_lines[0].endswith(become_test_username) - whoami_out.stdout_lines[0].endswith(become_test_username)
- name: test become runas to ensure underprivileged user has medium integrity level
vars: *become_vars
win_shell: whoami /groups
register: whoami_out
- name: verify output
assert:
that:
- '"Mandatory Label\Medium Mandatory Level" in whoami_out.stdout'
- name: test become runas via task vars (privileged user)
vars: *admin_become_vars
win_shell: whoami
register: whoami_out
- name: verify output
assert:
that:
- whoami_out.stdout_lines[0].endswith(become_test_admin_username)
- name: test become runas to ensure privileged user has high integrity level
vars: *admin_become_vars
win_shell: whoami /groups
register: whoami_out
- name: verify output
assert:
that:
- '"Mandatory Label\High Mandatory Level" in whoami_out.stdout'
- name: test become runas via task keywords - name: test become runas via task keywords
vars: vars:
ansible_become_password: "{{ gen_pw }}" ansible_become_password: "{{ gen_pw }}"
@ -51,7 +99,6 @@
become_method: runas become_method: runas
become_user: "{{ become_test_username }}" become_user: "{{ become_test_username }}"
win_shell: whoami win_shell: whoami
register: whoami_out register: whoami_out
- name: verify output - name: verify output
@ -111,17 +158,54 @@
that: that:
- whoami_out.stdout_lines[0] == "nt authority\\local service" - whoami_out.stdout_lines[0] == "nt authority\\local service"
# Test out Async on Windows Server 2012+
- name: get OS version
win_shell: if ([System.Environment]::OSVersion.Version -ge [Version]"6.2") { $true } else { $false }
register: os_version
- name: test become + async on older hosts
vars: *become_vars
win_command: whoami
async: 10
register: whoami_out
ignore_errors: yes
- name: verify older hosts failed with become + async
assert:
that:
- whoami_out|failed
when: os_version.stdout_lines[0] == "False"
- name: verify newer hosts worked with become + async
assert:
that:
- whoami_out|success
when: os_version.stdout_lines[0] == "True"
# FUTURE: test raw + script become behavior once they're running under the exec wrapper again # FUTURE: test raw + script become behavior once they're running under the exec wrapper again
# FUTURE: add standalone playbook tests to include password prompting and play become keywords # FUTURE: add standalone playbook tests to include password prompting and play become keywords
always: always:
- name: ensure test user is deleted - name: ensure underprivileged test user is deleted
win_user: win_user:
name: "{{ become_test_username }}" name: "{{ become_test_username }}"
state: absent state: absent
- name: ensure test user profile is deleted
- name: ensure privileged test user is deleted
win_user:
name: "{{ become_test_admin_username }}"
state: absent
- name: ensure underprivileged test user profile is deleted
# NB: have to work around powershell limitation of long filenames until win_file fixes it # NB: have to work around powershell limitation of long filenames until win_file fixes it
win_shell: rmdir /S /Q {{ profile_dir_out.stdout_lines[0] }} win_shell: rmdir /S /Q {{ profile_dir_out.stdout_lines[0] }}
args: args:
executable: cmd.exe executable: cmd.exe
when: become_test_username in profile_dir_out.stdout_lines[0] when: become_test_username in profile_dir_out.stdout_lines[0]
- name: ensure privileged test user profile is deleted
# NB: have to work around powershell limitation of long filenames until win_file fixes it
win_shell: rmdir /S /Q {{ admin_profile_dir_out.stdout_lines[0] }}
args:
executable: cmd.exe
when: become_test_admin_username in admin_profile_dir_out.stdout_lines[0]