summaryrefslogtreecommitdiffstats
path: root/test/integration/targets/win_become/tasks/main.yml
blob: a0759580baccab57837d9437becfe1211a915390 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
- 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 - 0x0000052E)" not in become_invalid_pass.msg'

  - name: test become password precedence
    win_whoami:
    become: yes
    become_method: runas
    become_user: '{{ become_test_username }}'
    vars:
      ansible_become_pass: broken
      ansible_runas_pass: '{{ gen_pw }}'  # should have a higher precedence than ansible_become_pass

  - 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\""

  - 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 }}" }).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]