- set_fact: 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') }}" - name: create unprivileged user win_user: name: "{{ become_test_username }}" password: "{{ gen_pw }}" update_password: always groups: Users register: user_limited_result - name: create a privileged user win_user: name: "{{ become_test_admin_username }}" 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: fetch current target date/time for log filtering raw: '[datetime]::now | Out-String' register: test_starttime - 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_whoami: register: whoami_out 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 ansible_become_user: "{{ become_test_username }}" ansible_become_password: "{{ gen_pw }}" ansible_become_method: runas ansible_become: yes win_shell: $env:USERPROFILE register: profile_dir_out - name: ensure profile dir contains test username (eg, if become fails silently, prevent deletion of real user profile) assert: that: - become_test_username in profile_dir_out.stdout_lines[0] - 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 win_whoami: register: whoami_out - name: verify output assert: that: - 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_whoami: register: whoami_out - name: verify output assert: that: - 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: ansible_become_password: "{{ gen_pw }}" become: yes become_method: runas become_user: "{{ become_test_username }}" win_shell: whoami register: whoami_out - name: verify output assert: that: - whoami_out.stdout_lines[0].endswith(become_test_username) - name: test become via block vars vars: *become_vars block: - name: ask who the current user is win_whoami: register: whoami_out - name: verify output assert: that: - 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 invalid password win_whoami: vars: ansible_become_pass: '{{ gen_pw }}abc' become: yes become_method: runas become_user: '{{ become_test_username }}' register: become_invalid_pass failed_when: - '"Failed to become user " + become_test_username not in become_invalid_pass.msg' - '"LogonUser failed" not in become_invalid_pass.msg' - '"Win32ErrorCode 1326)" not in become_invalid_pass.msg' - name: test become with SYSTEM account win_whoami: become: yes become_method: runas become_user: SYSTEM register: whoami_out - name: verify output assert: that: - 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_whoami: become: yes become_method: runas become_user: NetworkService register: whoami_out - name: verify output assert: that: - 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_whoami: become: yes become_method: runas become_user: LocalService register: whoami_out - name: verify output assert: that: - 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' - name: test become + async vars: *become_vars win_command: whoami async: 10 register: whoami_out - name: verify become + async worked assert: that: - whoami_out is successful - become_test_username in whoami_out.stdout - 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 != \"internal error: failed to parse become_flags 'logon_type=batch invalid_flags=a': 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 != \"internal error: failed to parse become_flags 'logon_type=invalid': 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 != \"internal error: failed to parse become_flags 'logon_flags=with_profile,invalid': 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: check if we are running on a dinosaur, neanderthal or an OS of the modern age win_shell: | $version = [System.Environment]::OSVersion.Version if ($version -lt [Version]"6.1") { "dinosaur" } elseif ($version -lt [Version]"6.2") { "neanderthal" } else { "False" } register: os_version - 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] == "dinosaur") 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' - name: become logon_flags bitwise tests when loading the profile # Error code of 2 means no file found == no profile loaded win_shell: | Add-Type -Name "Native" -Namespace "Ansible" -MemberDefinition '[DllImport("Userenv.dll", SetLastError=true)]public static extern bool GetProfileType(out UInt32 pdwFlags);' $profile_type = $null $res = [Ansible.Native]::GetProfileType([ref]$profile_type) if (-not $res) { $last_err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() if ($last_err -eq 2) { return $false } else { throw [System.ComponentModel.Win32Exception]$last_err } } else { return $true } vars: *admin_become_vars become_flags: logon_flags={{item.flags}} register: become_logon_flags failed_when: become_logon_flags.stdout_lines[0]|bool != item.actual when: os_version.stdout_lines[0] not in ["dinosaur", "neanderthal"] # usual suspect 2008 doesn't support the no profile flags with_items: - flags: actual: False - flags: netcredentials_only actual: False - flags: with_profile,netcredentials_only actual: True - name: echo some non ascii characters win_command: cmd.exe /c echo über den Fußgängerübergang gehen vars: *become_vars register: nonascii_output - name: assert echo some non ascii characters assert: that: - nonascii_output is changed - nonascii_output.rc == 0 - nonascii_output.stdout_lines|count == 1 - nonascii_output.stdout_lines[0] == 'über den Fußgängerübergang gehen' - nonascii_output.stderr == '' - name: get PS events containing password or module args created since test start raw: | $dt=[datetime]"{{ test_starttime.stdout|trim }}" (Get-WinEvent -LogName Microsoft-Windows-Powershell/Operational | ? { $_.TimeCreated -ge $dt -and $_.Message -match "{{ gen_pw }}|whoami" }).Count register: ps_log_count - name: assert no PS events contain password or module args assert: that: - ps_log_count.stdout | int == 0 # 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 }}" state: absent - 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 win_shell: rmdir /S /Q {{ profile_dir_out.stdout_lines[0] }} args: executable: cmd.exe 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]