win_become: another option to support become flags for runas (#34551)

* win_become: another option to support become flags for runas

* removed uneeded entries

* fixed up whitespace issue

* Copy edit
This commit is contained in:
Jordan Borean 2018-01-20 07:58:10 +10:00 committed by Matt Davis
parent 1c22d82c5e
commit d0e6889f93
5 changed files with 318 additions and 69 deletions

View file

@ -449,6 +449,79 @@ or with this Ansible task:
to set the account's password under ``ansible_become_pass`` if the to set the account's password under ``ansible_become_pass`` if the
become_user has a password. become_user has a password.
Become Flags
------------
Ansible 2.5 adds the ``become_flags`` parameter to the ``runas`` become method. This parameter can be set using the ``become_flags`` task directive or set in Ansible's configuration using ``ansible_become_flags``. The two valid values that are initially supported for this parameter are ``logon_type`` and ``logon_flags``.
.. Note:: These flags should only be set when becoming a normal user account, not a local service account like LocalSystem.
The key ``logon_type`` sets the type of logon operation to perform. The value
can be set to one of the following:
* ``interactive``: The default logon type. The process will be run under a
context that is the same as when running a process locally. This bypasses all
WinRM restrictions and is the recommended method to use.
* ``batch``: Runs the process under a batch context that is similar to a
scheduled task with a password set. This should bypass most WinRM
restrictions and is useful if the ``become_user`` is not allowed to log on
interactively.
* ``new_credentials``: Runs under the same credentials as the calling user, but
outbound connections are run under the context of the ``become_user`` and
``become_password``, similar to ``runas.exe /netonly``. The ``logon_flags``
flag should also be set to ``netcredentials_only``. Use this flag if
the process needs to access a network resource (like an SMB share) using a
different set of credentials.
* ``network``: Runs the process under a network context without any cached
credentials. This results in the same type of logon session as running a
normal WinRM process without credential delegation, and operates under the same
restrictions.
* ``network_cleartext``: Like the ``network`` logon type, but instead caches
the credentials so it can access network resources. This is the same type of
logon session as running a normal WinRM process with credential delegation.
For more information, see
`dwLogonType <https://msdn.microsoft.com/en-au/library/windows/desktop/aa378184.aspx>`_.
The ``logon_flags`` key specifies how Windows will log the user on when creating
the new process. The value can be set to one of the following:
* ``with_profile``: The default logon flag set. The process will load the
user's profile in the ``HKEY_USERS`` registry key to ``HKEY_CURRENT_USER``.
* ``netcredentials_only``: The process will use the same token as the caller
but will use the ``become_user`` and ``become_password`` when accessing a remote
resource. This is useful in inter-domain scenarios where there is no trust
relationship, and should be used with the ``new_credentials`` ``logon_type``.
For more information, see `dwLogonFlags <https://msdn.microsoft.com/en-us/library/windows/desktop/ms682434.aspx>`_.
Here are some examples of how to use ``become_flags`` with Windows tasks:
.. code-block:: yaml
- name: copy a file from a fileshare with custom credentials
win_copy:
src: \\server\share\data\file.txt
dest: C:\temp\file.txt
remote_src: yex
vars:
ansible_become: yes
ansible_become_method: runas
ansible_become_user: DOMAIN\user
ansible_become_pass: Password01
ansible_become_flags: logon_type=new_credentials logon_flags=netcredentials_only
- name: run a command under a batch logon
win_command: whoami
become: yes
become_flags: logon_type=batch
Limitations Limitations
----------- -----------
@ -457,8 +530,8 @@ Be aware of the following limitations with ``become`` on Windows:
* Running a task with ``async`` and ``become`` on Windows Server 2008, 2008 R2 * Running a task with ``async`` and ``become`` on Windows Server 2008, 2008 R2
and Windows 7 does not work. and Windows 7 does not work.
* The become user logs on with an interactive session, so it must have the * By default, the become user logs on with an interactive session, so it must
ability to do so on the Windows host. If it does not inherit the have the right to do so on the Windows host. If it does not inherit the
``SeAllowLogOnLocally`` privilege or inherits the ``SeDenyLogOnLocally`` ``SeAllowLogOnLocally`` privilege or inherits the ``SeDenyLogOnLocally``
privilege, the become process will fail. privilege, the become process will fail.

View file

@ -599,7 +599,7 @@ def _is_binary(b_module_data):
def _find_module_utils(module_name, b_module_data, module_path, module_args, task_vars, templar, module_compression, async_timeout, become, def _find_module_utils(module_name, b_module_data, module_path, module_args, task_vars, templar, module_compression, async_timeout, become,
become_method, become_user, become_password, environment): become_method, become_user, become_password, become_flags, environment):
""" """
Given the source of the module, convert it to a Jinja2 template to insert Given the source of the module, convert it to a Jinja2 template to insert
module code and return whether it's a new or old style module. module code and return whether it's a new or old style module.
@ -787,6 +787,7 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
exec_manifest["actions"].insert(0, 'become') exec_manifest["actions"].insert(0, 'become')
exec_manifest["become_user"] = become_user exec_manifest["become_user"] = become_user
exec_manifest["become_password"] = become_password exec_manifest["become_password"] = become_password
exec_manifest['become_flags'] = become_flags
exec_manifest["become"] = to_text(base64.b64encode(to_bytes(become_wrapper))) exec_manifest["become"] = to_text(base64.b64encode(to_bytes(become_wrapper)))
lines = b_module_data.split(b'\n') lines = b_module_data.split(b'\n')
@ -842,6 +843,7 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
exec_manifest["actions"].insert(0, 'become') exec_manifest["actions"].insert(0, 'become')
exec_manifest["become_user"] = "SYSTEM" exec_manifest["become_user"] = "SYSTEM"
exec_manifest["become_password"] = None exec_manifest["become_password"] = None
exec_manifest['become_flags'] = None
exec_manifest["become"] = to_text(base64.b64encode(to_bytes(become_wrapper))) exec_manifest["become"] = to_text(base64.b64encode(to_bytes(become_wrapper)))
# FUTURE: smuggle this back as a dict instead of serializing here; the connection plugin may need to modify it # FUTURE: smuggle this back as a dict instead of serializing here; the connection plugin may need to modify it
@ -872,7 +874,7 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
def modify_module(module_name, module_path, module_args, task_vars=None, templar=None, module_compression='ZIP_STORED', async_timeout=0, become=False, def modify_module(module_name, module_path, module_args, task_vars=None, templar=None, module_compression='ZIP_STORED', async_timeout=0, become=False,
become_method=None, become_user=None, become_password=None, environment=None): become_method=None, become_user=None, become_password=None, become_flags=None, environment=None):
""" """
Used to insert chunks of code into modules before transfer rather than Used to insert chunks of code into modules before transfer rather than
doing regular python imports. This allows for more efficient transfer in doing regular python imports. This allows for more efficient transfer in
@ -903,7 +905,7 @@ def modify_module(module_name, module_path, module_args, task_vars=None, templar
(b_module_data, module_style, shebang) = _find_module_utils(module_name, b_module_data, module_path, module_args, task_vars, templar, module_compression, (b_module_data, module_style, shebang) = _find_module_utils(module_name, b_module_data, module_path, module_args, task_vars, templar, module_compression,
async_timeout=async_timeout, become=become, become_method=become_method, async_timeout=async_timeout, become=become, become_method=become_method,
become_user=become_user, become_password=become_password, become_user=become_user, become_password=become_password, become_flags=become_flags,
environment=environment) environment=environment)
if module_style == 'binary': if module_style == 'binary':

View file

@ -157,6 +157,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
become_method=self._play_context.become_method, become_method=self._play_context.become_method,
become_user=self._play_context.become_user, become_user=self._play_context.become_user,
become_password=self._play_context.become_pass, become_password=self._play_context.become_pass,
become_flags=self._play_context.become_flags,
environment=final_environment) environment=final_environment)
return (module_style, module_shebang, module_data, module_path) return (module_style, module_shebang, module_data, module_path)

View file

@ -604,9 +604,14 @@ namespace Ansible
private static extern int ResumeThread( private static extern int ResumeThread(
SafeHandle hThread); 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, LogonFlags logonFlags, LogonType logonType)
{ {
SecurityIdentifier account = GetBecomeSid(username); SecurityIdentifier account = null;
if (logonType != LogonType.LOGON32_LOGON_NEW_CREDENTIALS)
{
account = GetBecomeSid(username);
}
STARTUPINFOEX si = new STARTUPINFOEX(); STARTUPINFOEX si = new STARTUPINFOEX();
si.startupInfo.dwFlags = (int)StartupInfoFlags.USESTDHANDLES; si.startupInfo.dwFlags = (int)StartupInfoFlags.USESTDHANDLES;
@ -649,14 +654,14 @@ namespace Ansible
PROCESS_INFORMATION pi = new PROCESS_INFORMATION(); PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
// Get the user tokens to try running processes with // Get the user tokens to try running processes with
List<IntPtr> tokens = GetUserTokens(account, username, password); List<IntPtr> tokens = GetUserTokens(account, username, password, logonType);
bool launch_success = false; bool launch_success = false;
foreach (IntPtr token in tokens) foreach (IntPtr token in tokens)
{ {
if (CreateProcessWithTokenW( if (CreateProcessWithTokenW(
token, token,
LogonFlags.LOGON_WITH_PROFILE, logonFlags,
null, null,
new StringBuilder(lpCommandLine), new StringBuilder(lpCommandLine),
startup_flags, startup_flags,
@ -729,7 +734,7 @@ namespace Ansible
} }
} }
private static List<IntPtr> GetUserTokens(SecurityIdentifier account, string username, string password) private static List<IntPtr> GetUserTokens(SecurityIdentifier account, string username, string password, LogonType logonType)
{ {
List<IntPtr> tokens = new List<IntPtr>(); List<IntPtr> tokens = new List<IntPtr>();
List<String> service_sids = new List<String>() List<String> service_sids = new List<String>()
@ -739,16 +744,20 @@ namespace Ansible
"S-1-5-20" // NT AUTHORITY\NetworkService "S-1-5-20" // NT AUTHORITY\NetworkService
}; };
GrantAccessToWindowStationAndDesktop(account); IntPtr hSystemToken = IntPtr.Zero;
string account_sid = account.ToString(); string account_sid = "";
if (logonType != LogonType.LOGON32_LOGON_NEW_CREDENTIALS)
{
GrantAccessToWindowStationAndDesktop(account);
// Try to get SYSTEM token handle so we can impersonate to get full admin token
hSystemToken = GetSystemUserHandle();
account_sid = account.ToString();
}
bool impersonated = false; bool impersonated = false;
try try
{ {
IntPtr hSystemTokenDup = IntPtr.Zero; 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)) 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 // We need the SYSTEM token if we want to become one of those accounts, fail here
@ -780,12 +789,11 @@ namespace Ansible
// might get a limited token in UAC-enabled cases, but better than nothing... // might get a limited token in UAC-enabled cases, but better than nothing...
} }
LogonType logonType;
string domain = null; string domain = null;
if (service_sids.Contains(account_sid)) if (service_sids.Contains(account_sid))
{ {
// We're using a well-known service account, do a service logon instead of interactive // We're using a well-known service account, do a service logon instead of the actual flag set
logonType = LogonType.LOGON32_LOGON_SERVICE; logonType = LogonType.LOGON32_LOGON_SERVICE;
domain = "NT AUTHORITY"; domain = "NT AUTHORITY";
password = null; password = null;
@ -805,7 +813,6 @@ namespace Ansible
else else
{ {
// We are trying to become a local or domain account // We are trying to become a local or domain account
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(@"\"));
@ -876,7 +883,6 @@ namespace Ansible
TokenAccessLevels.AssignPrimary | TokenAccessLevels.AssignPrimary |
TokenAccessLevels.Impersonate; TokenAccessLevels.Impersonate;
// TODO: Find out why I can't see processes from Network Service and Local Service
if (OpenProcessToken(hProcess, desired_access, out hToken)) if (OpenProcessToken(hProcess, desired_access, out hToken))
{ {
string sid = GetTokenUserSID(hToken); string sid = GetTokenUserSID(hToken);
@ -1144,16 +1150,84 @@ Function Dump-Error ($excep) {
$eo.exception = $excep | Out-String $eo.exception = $excep | Out-String
$host.SetShouldExit(1) $host.SetShouldExit(1)
$eo | ConvertTo-Json -Depth 10 $eo | ConvertTo-Json -Depth 10 -Compress
}
Function Parse-EnumValue($enum, $flag_type, $value, $prefix) {
$raw_enum_value = "$prefix$($value.ToUpper())"
try {
$enum_value = [Enum]::Parse($enum, $raw_enum_value)
} catch [System.ArgumentException] {
$valid_options = [Enum]::GetNames($enum) | ForEach-Object { $_.Substring($prefix.Length).ToLower() }
throw "become_flags $flag_type value '$value' is not valid, valid values are: $($valid_options -join ", ")"
}
return $enum_value
}
Function Parse-BecomeFlags($flags) {
$logon_type = [Ansible.LogonType]::LOGON32_LOGON_INTERACTIVE
$logon_flags = [Ansible.LogonFlags]::LOGON_WITH_PROFILE
if ($flags -eq $null -or $flags -eq "") {
$flag_split = @()
} elseif ($flags -is [string]) {
$flag_split = $flags.Split(" ")
} else {
throw "become_flags must be a string, was $($flags.GetType())"
}
foreach ($flag in $flag_split) {
$split = $flag.Split("=")
if ($split.Count -ne 2) {
throw "become_flags entry '$flag' is in an invalid format, must be a key=value pair"
}
$flag_key = $split[0]
$flag_value = $split[1]
if ($flag_key -eq "logon_type") {
$enum_details = @{
enum = [Ansible.LogonType]
flag_type = $flag_key
value = $flag_value
prefix = "LOGON32_LOGON_"
}
$logon_type = Parse-EnumValue @enum_details
} elseif ($flag_key -eq "logon_flags") {
$logon_flag_values = $flag_value.Split(",")
$logon_flags = 0 -as [Ansible.LogonFlags]
foreach ($logon_flag_value in $logon_flag_values) {
if ($logon_flag_value -eq "") {
continue
}
$enum_details = @{
enum = [Ansible.LogonFlags]
flag_type = $flag_key
value = $logon_flag_value
prefix = "LOGON_"
}
$logon_flag = Parse-EnumValue @enum_details
$logon_flags = $logon_flags -bor $logon_flag
}
} else {
throw "become_flags key '$flag_key' is not a valid runas flag, must be 'logon_type' or 'logon_flags'"
}
}
return $logon_type, [Ansible.LogonFlags]$logon_flags
} }
Function Run($payload) { Function Run($payload) {
# NB: action popping handled inside subprocess wrapper # NB: action popping handled inside subprocess wrapper
Add-Type -TypeDefinition $helper_def -Debug:$false
$username = $payload.become_user $username = $payload.become_user
$password = $payload.become_password $password = $payload.become_password
try {
Add-Type -TypeDefinition $helper_def -Debug:$false $logon_type, $logon_flags = Parse-BecomeFlags -flags $payload.become_flags
} catch {
Dump-Error -excep $_
return $null
}
# 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")
@ -1161,24 +1235,29 @@ Function Run($payload) {
$rc = 0 $rc = 0
Try { Try {
# allow (potentially unprivileged) target user access to the tempfile (NB: this likely won't work if traverse checking is enabled) # do not modify the ACL if the logon_type is LOGON32_LOGON_NEW_CREDENTIALS
$acl = Get-Acl $temp # as this results in the local execution running under the same user's token,
# otherwise we need to allow (potentially unprivileges) the become user access
# to the tempfile (NB: this likely won't work if traverse checking is enaabled).
if ($logon_type -ne [Ansible.LogonType]::LOGON32_LOGON_NEW_CREDENTIALS) {
$acl = Get-Acl -Path $temp
Try { Try {
$acl.AddAccessRule($(New-Object System.Security.AccessControl.FileSystemAccessRule($username, "FullControl", "Allow"))) $acl.AddAccessRule($(New-Object System.Security.AccessControl.FileSystemAccessRule($username, "FullControl", "Allow")))
} Catch [System.Security.Principal.IdentityNotMappedException] {
throw "become_user '$username' is not recognized on this host"
} Catch {
throw "failed to set ACL on temp become execution script: $($_.Exception.Message)"
}
Set-Acl -Path $temp -AclObject $acl | Out-Null
} }
Catch [System.Security.Principal.IdentityNotMappedException] {
throw "become_user '$username' is not recognized on this host"
}
Set-Acl $temp $acl | Out-Null
$payload_string = $payload | ConvertTo-Json -Depth 99 -Compress $payload_string = $payload | ConvertTo-Json -Depth 99 -Compress
$lp_command_line = New-Object System.Text.StringBuilder @("powershell.exe -NonInteractive -NoProfile -ExecutionPolicy Bypass -File $temp") $lp_command_line = New-Object System.Text.StringBuilder @("powershell.exe -NonInteractive -NoProfile -ExecutionPolicy Bypass -File $temp")
$lp_current_directory = "$env:SystemRoot" $lp_current_directory = "$env:SystemRoot"
$result = [Ansible.BecomeUtil]::RunAsUser($username, $password, $lp_command_line, $lp_current_directory, $payload_string) $result = [Ansible.BecomeUtil]::RunAsUser($username, $password, $lp_command_line, $lp_current_directory, $payload_string, $logon_flags, $logon_type)
$stdout = $result.StandardOut $stdout = $result.StandardOut
$stderr = $result.StandardError $stderr = $result.StandardError
$rc = $result.ExitCode $rc = $result.ExitCode

View file

@ -9,6 +9,7 @@
password: "{{ gen_pw }}" password: "{{ gen_pw }}"
update_password: always update_password: always
groups: Users groups: Users
register: user_limited_result
- name: create a privileged user - name: create a privileged user
win_user: win_user:
@ -16,13 +17,24 @@
password: "{{ gen_pw }}" password: "{{ gen_pw }}"
update_password: always update_password: always
groups: Administrators groups: Administrators
register: user_admin_result
- name: add requisite logon rights for test user
win_user_right:
name: '{{item}}'
users: '{{become_test_username}}'
action: add
with_items:
- SeNetworkLogonRight
- SeInteractiveLogonRight
- SeBatchLogonRight
- 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_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) failed_when: whoami_out.account.sid == user_limited_result.sid or whoami_out.account.sid == user_admin_result.sid
- 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
@ -54,43 +66,31 @@
- name: test become runas via task vars (underprivileged user) - name: test become runas via task vars (underprivileged user)
vars: *become_vars vars: *become_vars
win_shell: whoami win_whoami:
register: whoami_out register: whoami_out
- name: verify output - name: verify output
assert: assert:
that: that:
- whoami_out.stdout_lines[0].endswith(become_test_username) - whoami_out.account.sid == user_limited_result.sid
- whoami_out.account.account_name == become_test_username
- name: test become runas to ensure underprivileged user has medium integrity level - whoami_out.label.account_name == 'Medium Mandatory Level'
vars: *become_vars - whoami_out.label.sid == 'S-1-16-8192'
win_shell: whoami /groups - whoami_out.logon_type == 'Interactive'
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) - name: test become runas via task vars (privileged user)
vars: *admin_become_vars vars: *admin_become_vars
win_shell: whoami win_whoami:
register: whoami_out register: whoami_out
- name: verify output - name: verify output
assert: assert:
that: that:
- whoami_out.stdout_lines[0].endswith(become_test_admin_username) - whoami_out.account.sid == user_admin_result.sid
- whoami_out.account.account_name == become_test_admin_username
- name: test become runas to ensure privileged user has high integrity level - whoami_out.label.account_name == 'High Mandatory Level'
vars: *admin_become_vars - whoami_out.label.sid == 'S-1-16-12288'
win_shell: whoami /groups - whoami_out.logon_type == 'Interactive'
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:
@ -110,20 +110,24 @@
vars: *become_vars vars: *become_vars
block: block:
- name: ask who the current user is - name: ask who the current user is
win_shell: whoami win_whoami:
register: whoami_out register: whoami_out
- name: verify output - name: verify output
assert: assert:
that: that:
- whoami_out.stdout_lines[0].endswith(become_test_username) - whoami_out.account.sid == user_limited_result.sid
- whoami_out.account.account_name == become_test_username
- whoami_out.label.account_name == 'Medium Mandatory Level'
- whoami_out.label.sid == 'S-1-16-8192'
- whoami_out.logon_type == 'Interactive'
- name: test with module that will return non-zero exit code (https://github.com/ansible/ansible/issues/30468) - name: test with module that will return non-zero exit code (https://github.com/ansible/ansible/issues/30468)
vars: *become_vars vars: *become_vars
setup: setup:
- name: test become with SYSTEM account - name: test become with SYSTEM account
win_command: whoami win_whoami:
become: yes become: yes
become_method: runas become_method: runas
become_user: SYSTEM become_user: SYSTEM
@ -132,10 +136,15 @@
- name: verify output - name: verify output
assert: assert:
that: that:
- whoami_out.stdout_lines[0] == "nt authority\\system" - whoami_out.account.sid == "S-1-5-18"
- whoami_out.account.account_name == "SYSTEM"
- whoami_out.account.domain_name == "NT AUTHORITY"
- whoami_out.label.account_name == 'System Mandatory Level'
- whoami_out.label.sid == 'S-1-16-16384'
- whoami_out.logon_type == 'System'
- name: test become with NetworkService account - name: test become with NetworkService account
win_command: whoami win_whoami:
become: yes become: yes
become_method: runas become_method: runas
become_user: NetworkService become_user: NetworkService
@ -144,10 +153,15 @@
- name: verify output - name: verify output
assert: assert:
that: that:
- whoami_out.stdout_lines[0] == "nt authority\\network service" - whoami_out.account.sid == "S-1-5-20"
- whoami_out.account.account_name == "NETWORK SERVICE"
- whoami_out.account.domain_name == "NT AUTHORITY"
- whoami_out.label.account_name == 'System Mandatory Level'
- whoami_out.label.sid == 'S-1-16-16384'
- whoami_out.logon_type == 'Service'
- name: test become with LocalService account - name: test become with LocalService account
win_command: whoami win_whoami:
become: yes become: yes
become_method: runas become_method: runas
become_user: LocalService become_user: LocalService
@ -156,11 +170,24 @@
- name: verify output - name: verify output
assert: assert:
that: that:
- whoami_out.stdout_lines[0] == "nt authority\\local service" - whoami_out.account.sid == "S-1-5-19"
- whoami_out.account.account_name == "LOCAL SERVICE"
- whoami_out.account.domain_name == "NT AUTHORITY"
- whoami_out.label.account_name == 'System Mandatory Level'
- whoami_out.label.sid == 'S-1-16-16384'
- whoami_out.logon_type == 'Service'
# Test out Async on Windows Server 2012+ # Test out Async on Windows Server 2012+
- name: get OS version - name: get OS version
win_shell: if ([System.Environment]::OSVersion.Version -ge [Version]"6.2") { $true } else { $false } win_shell: |
$version = [System.Environment]::OSVersion.Version
if ($version -ge [Version]"6.2") {
"async"
} elseif ($version -lt [Version]"6.1") {
"old-gramps"
} else {
""
}
register: os_version register: os_version
- name: test become + async on older hosts - name: test become + async on older hosts
@ -174,18 +201,85 @@
assert: assert:
that: that:
- whoami_out is failed - whoami_out is failed
when: os_version.stdout_lines[0] == "False" when: os_version.stdout_lines[0] != "async"
- name: verify newer hosts worked with become + async - name: verify newer hosts worked with become + async
assert: assert:
that: that:
- whoami_out is successful - whoami_out is successful
when: os_version.stdout_lines[0] == "True" when: os_version.stdout_lines[0] == "async"
- name: test failure with string become invalid key
vars: *become_vars
win_whoami:
become_flags: logon_type=batch invalid_flags=a
become_method: runas
register: failed_flags_invalid_key
failed_when: failed_flags_invalid_key.msg != "become_flags key 'invalid_flags' is not a valid runas flag, must be 'logon_type' or 'logon_flags'"
- name: test failure with invalid logon_type
vars: *become_vars
win_whoami:
become_flags: logon_type=invalid
register: failed_flags_invalid_type
failed_when: "failed_flags_invalid_type.msg != \"become_flags logon_type value 'invalid' is not valid, valid values are: interactive, network, batch, service, unlock, network_cleartext, new_credentials\""
- name: test failure with invalid logon_flag
vars: *become_vars
win_whoami:
become_flags: logon_flags=with_profile,invalid
register: failed_flags_invalid_flag
failed_when: "failed_flags_invalid_flag.msg != \"become_flags logon_flags value 'invalid' is not valid, valid values are: with_profile, netcredentials_only\""
# Server 2008 doesn't work with network and network_cleartext, there isn't really a reason why you would want this anyway
- name: become different types
vars: *become_vars
win_whoami:
become_flags: logon_type={{item.type}}
register: become_logon_type
when: not ((item.type == 'network' or item.type == 'network_cleartext') and os_version.stdout_lines[0] == "old-gramps")
failed_when: become_logon_type.logon_type != item.actual and become_logon_type.sid != user_limited_result.sid
with_items:
- type: interactive
actual: Interactive
- type: batch
actual: Batch
- type: network
actual: Network
- type: network_cleartext
actual: NetworkCleartext
- name: become netcredentials with network user
vars:
ansible_become_user: fakeuser
ansible_become_password: fakepassword
ansible_become_method: runas
ansible_become: True
ansible_become_flags: logon_type=new_credentials logon_flags=netcredentials_only
win_whoami:
register: become_netcredentials
- name: assert become netcredentials with network user
assert:
that:
# new_credentials still come up as the ansible_user so we can't test that
- become_netcredentials.label.account_name == 'High Mandatory Level'
- become_netcredentials.label.sid == 'S-1-16-12288'
# 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: remove explicit logon rights for test user
win_user_right:
name: '{{item}}'
users: '{{become_test_username}}'
action: remove
with_items:
- SeNetworkLogonRight
- SeInteractiveLogonRight
- SeBatchLogonRight
- name: ensure underprivileged test user is deleted - name: ensure underprivileged test user is deleted
win_user: win_user:
name: "{{ become_test_username }}" name: "{{ become_test_username }}"