1- name: get network adapter for each Windows host
2  hosts: windows
3  gather_facts: no
4  tasks:
5  - name: get network connection for private adapter
6    win_shell: |
7      foreach ($instance in (Get-CimInstance -ClassName Win32_NetworkAdapter -Filter "Netenabled='True'")) {
8          $instance_config = Get-CimInstance -ClassName WIn32_NetworkAdapterConfiguration -Filter "Index = '$($instance.Index)'"
9          if ($instance_config.IPAddress -contains "{{ansible_host}}") {
10              $instance.NetConnectionID
11          }
12      }
13    changed_when: no
14    register: network_connection_name_raw
15
16  - name: fail if we didn't get a network connection name
17    fail:
18      msg: Failed to get the Windows network connection name
19    when: network_connection_name_raw.stdout_lines | count != 1
20
21  - name: set fact of network connection name
22    set_fact:
23      network_connection_name: '{{ network_connection_name_raw.stdout | trim }}'
24
25- name: create Domain Controller
26  hosts: win_controller
27  gather_facts: no
28  tasks:
29  - name: set the DNS for the specified adapter to localhost
30    win_dns_client:
31      adapter_name: '{{ network_connection_name }}'
32      ipv4_addresses: 127.0.0.1
33
34  - name: ensure domain exists and DC is promoted as a domain controller
35    win_domain:
36      dns_domain_name: '{{ domain_name }}'
37      safe_mode_password: '{{ domain_password }}'
38    register: domain_setup_res
39
40  - name: reboot DC if required after install
41    win_reboot:
42    when: domain_setup_res.reboot_required
43
44  - name: create domain username
45    win_domain_user:
46      name: '{{ domain_username }}'
47      upn: '{{ domain_upn }}'
48      description: '{{ domain_username }} Domain Account'
49      password: '{{ domain_password }}'
50      password_never_expires: yes
51      update_password: on_create
52      groups:
53      - Domain Admins
54      state: present
55    register: domain_user_result
56    # ADWS may not be online after first reboot, need to keep on retrying
57    retries: 30
58    delay: 15
59    until: domain_user_result is successful
60
61  - name: test out domain user that was created
62    win_whoami:
63    register: become_res
64    failed_when: become_res.upn != domain_upn
65    become: yes
66    become_method: runas
67    vars:
68      ansible_become_user: '{{ domain_upn }}'
69      ansible_become_pass: '{{ domain_password }}'
70
71- name: join Windows host to domain
72  hosts: win_children
73  gather_facts: no
74  tasks:
75  - name: set the DNS for the private adapter to point to the DC
76    win_dns_client:
77      adapter_names: '{{ network_connection_name }}'
78      ipv4_addresses: '{{ hostvars[groups["win_controller"][0]]["ansible_host"] }}'
79
80  - name: join host to domain
81    win_domain_membership:
82      dns_domain_name: '{{ domain_name }}'
83      domain_admin_user: '{{ domain_upn }}'
84      domain_admin_password: '{{ domain_password }}'
85      state: domain
86    register: domain_join_result
87
88  - name: trust hosts for delegation in AD
89    win_shell: |
90      $computerName = '{{ inventory_hostname }}'
91      $actual = (Get-ADComputer -Identity $computerName -Property TrustedForDelegation).TrustedForDelegation
92      if ($actual) {
93          $false
94      } else {
95          Set-ADComputer -Identity $computerName -TrustedForDelegation $true
96          $true
97      }
98    when: inventory_hostname == 'SERVER2022'  # We only want to have this hosted with delegation for testing
99    register: delegate_actual
100    changed_when: delegate_actual.stdout | trim | bool
101    delegate_to: '{{ groups["win_controller"][0] }}'
102
103  - name: reboot host to finalise domain join
104    win_reboot:
105    when: domain_join_result.reboot_required
106
107  - name: test out domain user logon
108    win_whoami:
109    register: become_res
110    failed_when: become_res.upn != domain_upn
111    become: yes
112    become_method: runas
113    vars:
114      ansible_become_user: '{{ domain_upn }}'
115      ansible_become_pass: '{{ domain_password }}'
116
117- name: set up Python interpreters on test Windows host
118  hosts: SERVER2012R2
119  gather_facts: no
120  tasks:
121  - name: install Python interpreters
122    win_package:
123      path: '{{ item.url }}'
124      arguments: '{{ item.arguments }}'
125      product_id: '{{ item.product_id }}'
126      state: present
127    with_items:
128    - url: https://www.python.org/ftp/python/3.6.8/python-3.6.8.exe
129      product_id: '{B56829C6-1C25-469E-B351-1467C6295566}'
130      arguments: /quiet InstallAllUsers=1 Shortcuts=0
131    - url: https://www.python.org/ftp/python/3.6.8/python-3.6.8-amd64.exe
132      product_id: '{E1155302-B578-4D8C-8431-FAE677FBC58C}'
133      arguments: /quiet InstallAllUsers=1 Shortcuts=0
134    - url: https://www.python.org/ftp/python/3.7.9/python-3.7.9.exe
135      product_id: '{65048DA1-5996-4FF9-B20A-66EB2E68D0A4}'
136      arguments: /quiet InstallAllUsers=1 Shortcuts=0
137    - url: https://www.python.org/ftp/python/3.7.9/python-3.7.9-amd64.exe
138      product_id: '{FF740026-2FC0-4F8A-A046-8B316AF4ECA6}'
139      arguments: /quiet InstallAllUsers=1 Shortcuts=0
140    - url: https://www.python.org/ftp/python/3.8.10/python-3.8.10.exe
141      product_id: '{4196628C-AE5C-4304-B166-B7C1E93CDC25}'
142      arguments: /quiet InstallAllUsers=1 Shortcuts=0
143    - url: https://www.python.org/ftp/python/3.8.10/python-3.8.10-amd64.exe
144      product_id: '{080E0048-853C-49FB-96ED-30DEF7AB6E34}'
145      arguments: /quiet InstallAllUsers=1 Shortcuts=0
146    - url: https://www.python.org/ftp/python/3.9.7/python-3.9.7.exe
147      product_id: '{E3343646-507D-4269-BF4C-68D78153FD0D}'
148      arguments: /quiet InstallAllUsers=1 Shortcuts=0
149    - url: https://www.python.org/ftp/python/3.9.7/python-3.9.7-amd64.exe
150      product_id: '{05903EEF-72A2-4C1A-AD35-41AD6C7094A8}'
151      arguments: /quiet InstallAllUsers=1 Shortcuts=0
152    - url: https://www.python.org/ftp/python/3.10.0/python-3.10.0.exe
153      product_id: '{88B95F8C-ECD7-42F1-A216-0880C9DE2F34}'
154      arguments: /quiet InstallAllUsers=1 Shortcuts=0
155    - url: https://www.python.org/ftp/python/3.10.0/python-3.10.0-amd64.exe
156      product_id: '{BB3BA776-4C84-43FB-9CE6-5A37FFC23032}'
157      arguments: /quiet InstallAllUsers=1 Shortcuts=0
158
159  - name: ensure virtualenv package is installed for each Python install
160    win_command: '"{{ item }}\python.exe" -m pip install virtualenv'
161    args:
162      creates: '{{ item }}\Scripts\virtualenv.exe'
163    with_items: '{{ python_interpreters }}'
164
165  - name: create virtualenv for each Python install
166    win_command: '"{{ item }}\python.exe" -m virtualenv "{{ python_venv_path }}\{{ item | win_basename }}"'
167    args:
168      creates: '{{ python_venv_path }}\{{ item | win_basename }}'
169    with_items: '{{ python_interpreters }}'
170
171  - name: copy across wheel artifacts
172    win_copy:
173      src: artifact.zip
174      dest: C:\temp\wheels.zip
175
176  - name: ensure wheel dir exists
177    win_file:
178      path: C:\temp\wheels
179      state: directory
180
181  - name: extract wheel from archive
182    win_unzip:
183      src: C:\temp\wheels.zip
184      dest: C:\temp\wheels
185
186  - name: install pyspnego wheel into virtualenv
187    win_shell: |
188      &'{{ python_venv_path }}\{{ item | win_basename }}\Scripts\pip.exe' install --no-index --find-links=C:/temp/wheels --no-deps pyspnego
189      &'{{ python_venv_path }}\{{ item | win_basename }}\Scripts\pip.exe' install pyspnego pytest requests
190    args:
191      creates: '{{ python_venv_path }}\{{ item | win_basename }}\Lib\site-packages\spnego'
192    with_items: '{{ python_interpreters }}'
193
194  - name: template out test integration file
195    win_template:
196      src: test_integration.py.tmpl
197      dest: C:\temp\test_integration.py
198      block_start_string: '{!!'
199      block_end_string: '!!}'
200    tags:
201    - template
202
203- name: set up WinRM config and SMB shares on Windows hosts
204  hosts: windows
205  gather_facts: no
206  tasks:
207  - name: set WinRM Cbt value to Strict
208    win_shell: |
209      $val = (Get-Item -LiteralPath WSMan:\localhost\Service\Auth\CbtHardeningLevel).Value
210      if ($val -eq 'Strict') {
211          $false
212      } else {
213          Set-Item -LiteralPath WSMan:\localhost\Service\Auth\CbtHardeningLevel -Value Strict
214          $true
215      }
216    register: cbt_res
217    changed_when: cbt_res.stdout | trim | bool
218
219  - name: enable WSMan CredSSP Server
220    win_shell: |
221      $val = (Get-Item -LiteralPath WSMan:\localhost\Service\Auth\CredSSP).Value
222      if ($val -eq 'true') {
223          $false
224      } else {
225          $null = Enable-WSManCredSSP -Role Server
226          $true
227      }
228    register: credssp_res
229    changed_when: credssp_res.stdout | trim | bool
230
231  - name: allow SMB traffic in
232    win_firewall_rule:
233      name: File and Printer Sharing (SMB-In)
234      state: present
235      enabled: yes
236
237- name: set up Linux host
238  hosts: linux_children
239  gather_facts: no
240  become: yes
241  handlers:
242  - name: restart NetworkManager.service
243    service:
244      name: NetworkManager.service
245      state: restarted
246
247  tasks:
248  - name: install base packages
249    yum:
250      name:
251      - dnsmasq
252      - epel-release
253      - gcc
254      - python3
255      - python3-devel
256      - unzip
257      - vim
258      state: present
259
260  - name: install kerberos packages
261    yum:
262      name: '{{ krb_packages }}'
263      state: present
264
265  - name: ensure virtualenv is installed on base Python interpreters
266    pip:
267      name:
268      - virtualenv
269      - wheel
270      executable: /usr/bin/pip3
271
272  - name: setup NetworkManager to use dnsmasq
273    copy:
274      dest: /etc/NetworkManager/conf.d/dns.conf
275      content: |
276        [main]
277        dns=dnsmasq
278    notify: restart NetworkManager.service
279
280  - name: set dnsmasq to forward requests for domain to DC
281    copy:
282      dest: /etc/NetworkManager/dnsmasq.d/{{ domain_name }}
283      content: server=/{{ domain_name }}/{{ hostvars[groups['win_controller'][0]]["ansible_host"] }}
284    notify: restart NetworkManager.service
285
286  - name: template krb5.conf file
287    template:
288      src: krb5.conf.tmpl
289      dest: /etc/krb5.conf
290
291  - name: create AD principal for Linux keytabs
292    win_domain_user:
293      name: '{{ inventory_hostname }}_{{ item }}'
294      description: Kerberos principal for {{ inventory_hostname }} {{ item }} keytab
295      password: '{{ domain_password }}'
296      password_never_expires: yes
297      update_password: on_create
298      attributes:
299        msDS-SupportedEncryptionTypes: 16  # AES256_CTS_HMAC_SHA1_96
300      state: present
301    become: no
302    delegate_to: DC01
303    with_items:
304    - HTTP
305    - cifs
306
307  - name: create keytab for Linux hosts
308    win_command: >-
309      ktpass.exe
310      -out C:\temp\{{ inventory_hostname }}-{{ item }}.keytab
311      -princ {{ item }}/{{ inventory_hostname }}.{{ domain_name }}@{{ domain_name | upper }}
312      -mapUser {{ inventory_hostname }}_{{ item }}@{{ domain_name | upper }}
313      +rndpass
314      -mapOp set
315      -crypto AES256-SHA1
316      -ptype KRB5_NT_PRINCIPAL
317    args:
318      creates: C:\temp\{{ inventory_hostname }}-{{ item }}.keytab
319    become: no
320    delegate_to: DC01
321    with_items:
322    - HTTP
323    - cifs
324
325  - name: fetch the keytab
326    fetch:
327      src: C:\temp\{{ inventory_hostname }}-{{ item }}.keytab
328      dest: '{{ inventory_hostname }}-{{ item }}.keytab'
329      flat: yes
330    become: no
331    delegate_to: DC01
332    with_items:
333    - HTTP
334    - cifs
335
336  - name: copy keytabs to host
337    copy:
338      src: '{{ inventory_hostname }}-{{ item }}.keytab'
339      dest: /etc/{{ item }}.keytab
340    with_items:
341    - HTTP
342    - cifs
343
344  - name: ensure wheel dir exists
345    file:
346      path: ~/wheels
347      state: directory
348    become: no
349
350  - name: extract wheel artifacts
351    unarchive:
352      src: artifact.zip
353      dest: ~/wheels
354    become: no
355
356  - name: find the universal wheel
357    find:
358      paths: ~/wheels
359      patterns: pyspnego-*-py2.py3-none-any.whl
360      recurse: no
361      file_type: file
362    become: no
363    register: wheel_artifact
364
365  - name: create a virtualenv for each Python interpeter
366    pip:
367      name:
368      - gssapi
369      - krb5
370      - pytest
371      - pytest-forked
372      - requests
373      - wheel
374      - '{{ wheel_artifact.files[0].path }}'
375      virtualenv: '{{ python_venv_path }}/{{ item | basename }}'
376      virtualenv_python: '{{ item }}'
377    become: no
378    with_items: '{{ python_interpreters }}'
379
380  - name: template out test integration file
381    template:
382      src: test_integration.py.tmpl
383      dest: ~/test_integration.py
384      block_start_string: '{!!'
385      block_end_string: '!!}'
386    tags:
387    - template
388    become: no
389