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:
parent
1c22d82c5e
commit
d0e6889f93
5 changed files with 318 additions and 69 deletions
|
@ -449,6 +449,79 @@ or with this Ansible task:
|
|||
to set the account's password under ``ansible_become_pass`` if the
|
||||
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
|
||||
-----------
|
||||
|
||||
|
@ -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
|
||||
and Windows 7 does not work.
|
||||
|
||||
* The become user logs on with an interactive session, so it must have the
|
||||
ability to do so on the Windows host. If it does not inherit the
|
||||
* By default, the become user logs on with an interactive session, so it must
|
||||
have the right to do so on the Windows host. If it does not inherit the
|
||||
``SeAllowLogOnLocally`` privilege or inherits the ``SeDenyLogOnLocally``
|
||||
privilege, the become process will fail.
|
||||
|
||||
|
|
|
@ -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,
|
||||
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
|
||||
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["become_user"] = become_user
|
||||
exec_manifest["become_password"] = become_password
|
||||
exec_manifest['become_flags'] = become_flags
|
||||
exec_manifest["become"] = to_text(base64.b64encode(to_bytes(become_wrapper)))
|
||||
|
||||
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["become_user"] = "SYSTEM"
|
||||
exec_manifest["become_password"] = None
|
||||
exec_manifest['become_flags'] = None
|
||||
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
|
||||
|
@ -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,
|
||||
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
|
||||
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,
|
||||
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)
|
||||
|
||||
if module_style == 'binary':
|
||||
|
|
|
@ -157,6 +157,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
|
|||
become_method=self._play_context.become_method,
|
||||
become_user=self._play_context.become_user,
|
||||
become_password=self._play_context.become_pass,
|
||||
become_flags=self._play_context.become_flags,
|
||||
environment=final_environment)
|
||||
|
||||
return (module_style, module_shebang, module_data, module_path)
|
||||
|
|
|
@ -604,9 +604,14 @@ namespace Ansible
|
|||
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, 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();
|
||||
si.startupInfo.dwFlags = (int)StartupInfoFlags.USESTDHANDLES;
|
||||
|
@ -649,14 +654,14 @@ namespace Ansible
|
|||
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
|
||||
|
||||
// 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;
|
||||
foreach (IntPtr token in tokens)
|
||||
{
|
||||
if (CreateProcessWithTokenW(
|
||||
token,
|
||||
LogonFlags.LOGON_WITH_PROFILE,
|
||||
logonFlags,
|
||||
null,
|
||||
new StringBuilder(lpCommandLine),
|
||||
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<String> service_sids = new List<String>()
|
||||
|
@ -739,16 +744,20 @@ namespace Ansible
|
|||
"S-1-5-20" // NT AUTHORITY\NetworkService
|
||||
};
|
||||
|
||||
GrantAccessToWindowStationAndDesktop(account);
|
||||
string account_sid = account.ToString();
|
||||
IntPtr hSystemToken = IntPtr.Zero;
|
||||
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;
|
||||
|
||||
try
|
||||
{
|
||||
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
|
||||
|
@ -780,12 +789,11 @@ namespace Ansible
|
|||
// might get a limited token in UAC-enabled cases, but better than nothing...
|
||||
}
|
||||
|
||||
LogonType logonType;
|
||||
string domain = null;
|
||||
|
||||
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;
|
||||
domain = "NT AUTHORITY";
|
||||
password = null;
|
||||
|
@ -805,7 +813,6 @@ namespace Ansible
|
|||
else
|
||||
{
|
||||
// We are trying to become a local or domain account
|
||||
logonType = LogonType.LOGON32_LOGON_INTERACTIVE;
|
||||
if (username.Contains(@"\"))
|
||||
{
|
||||
var user_split = username.Split(Convert.ToChar(@"\"));
|
||||
|
@ -876,7 +883,6 @@ namespace Ansible
|
|||
TokenAccessLevels.AssignPrimary |
|
||||
TokenAccessLevels.Impersonate;
|
||||
|
||||
// TODO: Find out why I can't see processes from Network Service and Local Service
|
||||
if (OpenProcessToken(hProcess, desired_access, out hToken))
|
||||
{
|
||||
string sid = GetTokenUserSID(hToken);
|
||||
|
@ -1144,16 +1150,84 @@ Function Dump-Error ($excep) {
|
|||
$eo.exception = $excep | Out-String
|
||||
$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) {
|
||||
# NB: action popping handled inside subprocess wrapper
|
||||
|
||||
Add-Type -TypeDefinition $helper_def -Debug:$false
|
||||
|
||||
$username = $payload.become_user
|
||||
$password = $payload.become_password
|
||||
|
||||
Add-Type -TypeDefinition $helper_def -Debug:$false
|
||||
try {
|
||||
$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
|
||||
$temp = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName() + ".ps1")
|
||||
|
@ -1161,24 +1235,29 @@ Function Run($payload) {
|
|||
$rc = 0
|
||||
|
||||
Try {
|
||||
# allow (potentially unprivileged) target user access to the tempfile (NB: this likely won't work if traverse checking is enabled)
|
||||
$acl = Get-Acl $temp
|
||||
# do not modify the ACL if the logon_type is LOGON32_LOGON_NEW_CREDENTIALS
|
||||
# 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 {
|
||||
$acl.AddAccessRule($(New-Object System.Security.AccessControl.FileSystemAccessRule($username, "FullControl", "Allow")))
|
||||
Try {
|
||||
$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
|
||||
|
||||
$lp_command_line = New-Object System.Text.StringBuilder @("powershell.exe -NonInteractive -NoProfile -ExecutionPolicy Bypass -File $temp")
|
||||
$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
|
||||
$stderr = $result.StandardError
|
||||
$rc = $result.ExitCode
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
password: "{{ gen_pw }}"
|
||||
update_password: always
|
||||
groups: Users
|
||||
register: user_limited_result
|
||||
|
||||
- name: create a privileged user
|
||||
win_user:
|
||||
|
@ -16,13 +17,24 @@
|
|||
password: "{{ gen_pw }}"
|
||||
update_password: always
|
||||
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
|
||||
block:
|
||||
- name: ensure current user is not the become user
|
||||
win_shell: whoami
|
||||
win_whoami:
|
||||
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
|
||||
vars: &become_vars
|
||||
|
@ -54,43 +66,31 @@
|
|||
|
||||
- name: test become runas via task vars (underprivileged user)
|
||||
vars: *become_vars
|
||||
win_shell: whoami
|
||||
win_whoami:
|
||||
register: whoami_out
|
||||
|
||||
- name: verify output
|
||||
assert:
|
||||
that:
|
||||
- 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'
|
||||
- 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 become runas via task vars (privileged user)
|
||||
vars: *admin_become_vars
|
||||
win_shell: whoami
|
||||
win_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'
|
||||
- whoami_out.account.sid == user_admin_result.sid
|
||||
- whoami_out.account.account_name == become_test_admin_username
|
||||
- whoami_out.label.account_name == 'High Mandatory Level'
|
||||
- whoami_out.label.sid == 'S-1-16-12288'
|
||||
- whoami_out.logon_type == 'Interactive'
|
||||
|
||||
- name: test become runas via task keywords
|
||||
vars:
|
||||
|
@ -110,20 +110,24 @@
|
|||
vars: *become_vars
|
||||
block:
|
||||
- name: ask who the current user is
|
||||
win_shell: whoami
|
||||
win_whoami:
|
||||
register: whoami_out
|
||||
|
||||
- name: verify output
|
||||
assert:
|
||||
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)
|
||||
vars: *become_vars
|
||||
setup:
|
||||
|
||||
- name: test become with SYSTEM account
|
||||
win_command: whoami
|
||||
win_whoami:
|
||||
become: yes
|
||||
become_method: runas
|
||||
become_user: SYSTEM
|
||||
|
@ -132,10 +136,15 @@
|
|||
- name: verify output
|
||||
assert:
|
||||
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
|
||||
win_command: whoami
|
||||
win_whoami:
|
||||
become: yes
|
||||
become_method: runas
|
||||
become_user: NetworkService
|
||||
|
@ -144,10 +153,15 @@
|
|||
- name: verify output
|
||||
assert:
|
||||
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
|
||||
win_command: whoami
|
||||
win_whoami:
|
||||
become: yes
|
||||
become_method: runas
|
||||
become_user: LocalService
|
||||
|
@ -156,11 +170,24 @@
|
|||
- name: verify output
|
||||
assert:
|
||||
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+
|
||||
- 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
|
||||
|
||||
- name: test become + async on older hosts
|
||||
|
@ -174,18 +201,85 @@
|
|||
assert:
|
||||
that:
|
||||
- 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
|
||||
assert:
|
||||
that:
|
||||
- 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: add standalone playbook tests to include password prompting and play become keywords
|
||||
|
||||
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
|
||||
win_user:
|
||||
name: "{{ become_test_username }}"
|
||||
|
|
Loading…
Reference in a new issue