8c1f701e6e
* prevent scriptblock logging from logging payload contents * added tests to verify no payload contents in PS Operational event log * fix script action to send split-aware wrapper * fix CLIXML error parser (return to -EncodedCommand exposed problems with it)
369 lines
13 KiB
YAML
369 lines
13 KiB
YAML
- 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]
|