- name: setup local configuration and scratch information
  hosts: localhost
  gather_facts: no

  tasks:
  - name: create cert output folder
    file:
      path: '{{ playbook_dir }}/cert_setup'
      state: directory

  - name: create generate_cert script
    template:
      src: generate_cert.sh.tmpl
      dest: '{{ playbook_dir }}/cert_setup/generate_cert.sh'
      mode: '700'

  - name: generate CA and LDAPS certificates
    shell: ./generate_cert.sh password
    args:
      creates: '{{ playbook_dir }}/cert_setup/complete.txt'
      chdir: '{{ playbook_dir }}/cert_setup'

- name: get network adapter for each Windows host
  hosts: windows
  gather_facts: no
  tasks:
  - name: get network connection for private adapter
    ansible.windows.win_powershell:
      parameters:
        IPAddress: '{{ ansible_host }}'
      script: |
        param($IPAddress)

        $Ansible.Changed = $false

        foreach ($instance in (Get-CimInstance -ClassName Win32_NetworkAdapter -Filter "Netenabled='True'")) {
            $config = Get-CimInstance -ClassName WIn32_NetworkAdapterConfiguration -Filter "Index = '$($instance.Index)'"
            if ($config.IPAddress -contains $IPAddress) {
                $instance.NetConnectionID
            }
        }
    register: network_connection_name_raw

  - name: fail if we didn't get a network connection name
    fail:
      msg: Failed to get the Windows network connection name
    when: network_connection_name_raw.output | count != 1

  - name: set fact of network connection name
    set_fact:
      network_connection_name: '{{ network_connection_name_raw.output[0] }}'

  - name: copy CA certificate
    win_copy:
      src: '{{ playbook_dir }}/cert_setup/ca.pem'
      dest: C:\Windows\TEMP\ca.pem

  - name: import CA certificate to trusted root CA
    win_certificate_store:
      path: C:\Windows\TEMP\ca.pem
      state: present
      store_location: LocalMachine
      store_name: Root

- name: create Domain Controller
  hosts: win_controller
  gather_facts: no
  tasks:
  - name: set the DNS for the specified adapter to localhost
    win_dns_client:
      adapter_name: '{{ network_connection_name }}'
      ipv4_addresses: 127.0.0.1

  - name: ensure domain exists and DC is promoted as a domain controller
    win_domain:
      dns_domain_name: '{{ domain_name }}'
      safe_mode_password: '{{ domain_password }}'
    register: domain_setup_res

  - name: reboot DC if required after install
    win_reboot:
    when: domain_setup_res.reboot_required

  - name: create domain username
    win_domain_user:
      name: '{{ domain_username }}'
      upn: '{{ domain_upn }}'
      description: '{{ domain_username }} Domain Account'
      password: '{{ domain_password }}'
      password_never_expires: yes
      update_password: on_create
      groups:
      - Domain Admins
      state: present

  - name: test out domain user that was created
    win_whoami:
    register: become_res
    failed_when: become_res.upn != domain_upn
    become: yes
    become_method: runas
    vars:
      ansible_become_user: '{{ domain_upn }}'
      ansible_become_pass: '{{ domain_password }}'

  - name: copy LDAPS certificate
    win_copy:
      src: '{{ playbook_dir }}/cert_setup/DC01.pfx'
      dest: C:\Windows\TEMP\ldaps.pfx

  - name: import LDAPS certificate
    win_certificate_store:
      path: C:\Windows\TEMP\ldaps.pfx
      password: password
      key_exportable: no
      key_storage: machine
      state: present
      store_type: service
      store_location: NTDS
      store_name: My
    register: ldaps_cert_info

  - name: register LDAPS certificate
    ansible.windows.win_powershell:
      script: |
        $dse = [adsi]'LDAP://localhost/rootDSE'
        [void]$dse.Properties['renewServerCertificate'].Add(1)
        $dse.CommitChanges()
    when: ldaps_cert_info is changed
    vars:
      ansible_become: yes
      ansible_become_method: runas
      ansible_become_user: '{{ domain_upn }}'
      ansible_become_pass: '{{ domain_password }}'

- name: join Windows host to domain
  hosts: win_children
  gather_facts: no
  tasks:
  - name: set the DNS for the private adapter to point to the DC
    win_dns_client:
      adapter_names: '{{ network_connection_name }}'
      ipv4_addresses: '{{ hostvars[groups["win_controller"][0]]["ansible_host"] }}'

  - name: join host to domain
    win_domain_membership:
      dns_domain_name: '{{ domain_name }}'
      domain_admin_user: '{{ domain_upn }}'
      domain_admin_password: '{{ domain_password }}'
      state: domain
    register: domain_join_result

  - name: trust hosts for delegation in AD
    ansible.windows.win_powershell:
      parameters:
        ComputerName: '{{ inventory_hostname }}'
      script: |
        param($ComputerName)

        $ErrorActionPreference = 'Stop'
        $Ansible.Changed = $false


        $actual = (Get-ADComputer -Identity $ComputerName -Property TrustedForDelegation).TrustedForDelegation
        if (-not $actual) {
            Set-ADComputer -Identity $ComputerName -TrustedForDelegation $true
            $Ansible.Changed = $true
        }
    when: inventory_hostname == 'SERVER2022'  # We only want to have this hosted with delegation for testing
    delegate_to: '{{ groups["win_controller"][0] }}'

  - name: reboot host to finalise domain join
    win_reboot:
    when: domain_join_result.reboot_required

  - name: test out domain user logon
    win_whoami:
    register: become_res
    failed_when: become_res.upn != domain_upn
    become: yes
    become_method: runas
    vars:
      ansible_become_user: '{{ domain_upn }}'
      ansible_become_pass: '{{ domain_password }}'

# Use the following to get a snaphot of programs installed and their product_ids
# 'SOFTWARE', 'SOFTWARE\Wow6432Node' | ForEach-Object {
#       $getParams = @{
#           Path = "HKLM:\$_\Microsoft\Windows\CurrentVersion\Uninstall\*"
#           Name = 'DisplayName'
#           ErrorAction = 'SilentlyContinue'
#       }
#       Get-ItemProperty @getParams | Select-Object -Property @(
#           @{ N = 'Name'; E = { $_.DisplayName } },
#           @{ N = 'AppId'; E = { $_.PSChildName } }
#       )
#   } | Where-Object { $_.Name -like 'Python * Standard Library *' }

- name: set up Python interpreters on test Windows host
  hosts: SERVER2012R2
  gather_facts: no
  tasks:
  - name: install Python interpreters
    win_package:
      path: '{{ item.url }}'
      arguments: '{{ item.arguments }}'
      product_id: '{{ item.product_id }}'
      state: present
    with_items:
    - url: https://www.python.org/ftp/python/3.8.10/python-3.8.10.exe
      product_id: '{4196628C-AE5C-4304-B166-B7C1E93CDC25}'
      arguments: /quiet InstallAllUsers=1 Shortcuts=0
    - url: https://www.python.org/ftp/python/3.8.10/python-3.8.10-amd64.exe
      product_id: '{080E0048-853C-49FB-96ED-30DEF7AB6E34}'
      arguments: /quiet InstallAllUsers=1 Shortcuts=0
    - url: https://www.python.org/ftp/python/3.9.13/python-3.9.13.exe
      product_id: '{E23C472D-F346-4D47-A909-9D48E5D7252F}'
      arguments: /quiet InstallAllUsers=1 Shortcuts=0
    - url: https://www.python.org/ftp/python/3.9.13/python-3.9.13-amd64.exe
      product_id: '{90A30DAB-6FD8-4CF8-BB8B-C0DB21C69F20}'
      arguments: /quiet InstallAllUsers=1 Shortcuts=0
    - url: https://www.python.org/ftp/python/3.10.9/python-3.10.9.exe
      product_id: '{335CD0FB-50DC-44D2-80E3-39749356F8D6}'
      arguments: /quiet InstallAllUsers=1 Shortcuts=0
    - url: https://www.python.org/ftp/python/3.10.9/python-3.10.9-amd64.exe
      product_id: '{0CBB496F-1D15-42F1-AA45-C01C95196EC8}'
      arguments: /quiet InstallAllUsers=1 Shortcuts=0
    - url: https://www.python.org/ftp/python/3.11.1/python-3.11.1.exe
      product_id: '{E5CB3216-2C88-4E4B-ADCA-56E9BAEE7404}'
      arguments: /quiet InstallAllUsers=1 Shortcuts=0
    - url: https://www.python.org/ftp/python/3.11.1/python-3.11.1-amd64.exe
      product_id: '{21EEFB31-6A96-4CAE-9A3B-B7FD6374C155}'
      arguments: /quiet InstallAllUsers=1 Shortcuts=0

  - name: ensure virtualenv package is installed for each Python install
    win_command: '"{{ item }}\python.exe" -m pip install virtualenv'
    args:
      creates: '{{ item }}\Scripts\virtualenv.exe'
    with_items: '{{ python_interpreters }}'

  - name: create virtualenv for each Python install
    win_command: '"{{ item }}\python.exe" -m virtualenv "{{ python_venv_path }}\{{ item | win_basename }}"'
    args:
      creates: '{{ python_venv_path }}\{{ item | win_basename }}'
    with_items: '{{ python_interpreters }}'

  - name: copy across wheel artifacts
    win_copy:
      src: artifact.zip
      dest: C:\temp\wheels.zip

  - name: ensure wheel dir exists
    win_file:
      path: C:\temp\wheels
      state: directory

  - name: extract wheel from archive
    win_unzip:
      src: C:\temp\wheels.zip
      dest: C:\temp\wheels

  - name: get pyspnego artifact sdist filename
    win_find:
      paths: C:\temp\wheels
      patterns: 'pyspnego-*.tar.gz'
      use_regex: false
    register: spnego_sdist_file

  - name: verify sdist was found
    assert:
      that:
      - spnego_sdist_file.files | count == 1

  - name: get pyspnego artifact version
    set_fact:
      spnego_version: >-
        {{ spnego_sdist_file.files[0].filename | regex_replace('pyspnego-(?P<version>.*)\.tar\.gz', '\g<version>') }}

  - name: install pyspnego into virtualenv
    win_command: >-
      "{{ python_venv_path }}\{{ item | win_basename }}\Scripts\python.exe" -m pip
      install
      pyspnego=={{ spnego_version }}
      pytest
      requests
      https://github.com/jborean93/sansldap/archive/174408ab40e42f9a2d34bc493027e43cc5d31715.zip
      --find-links=C:/temp/wheels
    args:
      creates: '{{ python_venv_path }}\{{ item | win_basename }}\Lib\site-packages\spnego'
    with_items: '{{ python_interpreters }}'

  - name: template out test integration file
    win_template:
      src: test_integration.py.tmpl
      dest: C:\temp\test_integration.py
      block_start_string: '{!!'
      block_end_string: '!!}'
    tags:
    - template

- name: set up WinRM config and SMB shares on Windows hosts
  hosts: windows
  gather_facts: no
  tasks:
  - name: set WinRM Cbt value to Strict
    ansible.windows.win_powershell:
      script: |
        $ErrorActionPreference = 'Stop'
        $Ansible.Changed = $false

        $val = (Get-Item -LiteralPath WSMan:\localhost\Service\Auth\CbtHardeningLevel).Value
        if ($val -ne 'Strict') {
            Set-Item -LiteralPath WSMan:\localhost\Service\Auth\CbtHardeningLevel -Value Strict
            $Ansible.Changed = $true
        }

  - name: enable WSMan CredSSP Server
    ansible.windows.win_powershell:
      script: |
        $ErrorActionPreference = 'Stop'
        $Ansible.Changed = $false

        $val = (Get-Item -LiteralPath WSMan:\localhost\Service\Auth\CredSSP).Value
        if ($val -ne 'true') {
            $null = Enable-WSManCredSSP -Role Server
            $Ansible.Changed = $true
        }

  - name: allow SMB traffic in
    win_firewall_rule:
      name: File and Printer Sharing (SMB-In)
      state: present
      enabled: yes

- name: set up Linux host
  hosts: linux_children
  gather_facts: no
  become: yes
  handlers:
  - name: restart NetworkManager.service
    service:
      name: NetworkManager.service
      state: restarted

  tasks:
  - name: install base packages
    yum:
      name:
      - dnsmasq
      - epel-release
      - gcc
      - python39
      - python3-devel
      - unzip
      - vim
      state: present

  - name: install kerberos packages
    yum:
      name: '{{ krb_packages }}'
      state: present

  - name: ensure virtualenv is installed on base Python interpreters
    pip:
      name:
      - virtualenv
      executable: /usr/bin/pip3.9

  - name: setup NetworkManager to use dnsmasq
    copy:
      dest: /etc/NetworkManager/conf.d/dns.conf
      content: |
        [main]
        dns=dnsmasq
    notify: restart NetworkManager.service

  - name: set dnsmasq to forward requests for domain to DC
    copy:
      dest: /etc/NetworkManager/dnsmasq.d/{{ domain_name }}
      content: server=/{{ domain_name }}/{{ hostvars[groups['win_controller'][0]]["ansible_host"] }}
    notify: restart NetworkManager.service

  - name: template krb5.conf file
    template:
      src: krb5.conf.tmpl
      dest: /etc/krb5.conf

  - name: create AD principal for Linux keytabs
    win_domain_user:
      name: '{{ inventory_hostname }}_{{ item }}'
      description: Kerberos principal for {{ inventory_hostname }} {{ item }} keytab
      password: '{{ domain_password }}'
      password_never_expires: yes
      update_password: on_create
      attributes:
        msDS-SupportedEncryptionTypes: 16  # AES256_CTS_HMAC_SHA1_96
      state: present
    become: no
    delegate_to: DC01
    with_items:
    - HTTP
    - cifs

  - name: create keytab for Linux hosts
    win_command: >-
      ktpass.exe
      -out C:\temp\{{ inventory_hostname }}-{{ item }}.keytab
      -princ {{ item }}/{{ inventory_hostname }}.{{ domain_name }}@{{ domain_name | upper }}
      -mapUser {{ inventory_hostname }}_{{ item }}@{{ domain_name | upper }}
      +rndpass
      -mapOp set
      -crypto AES256-SHA1
      -ptype KRB5_NT_PRINCIPAL
    args:
      creates: C:\temp\{{ inventory_hostname }}-{{ item }}.keytab
    become: no
    delegate_to: DC01
    with_items:
    - HTTP
    - cifs

  - name: fetch the keytab
    fetch:
      src: C:\temp\{{ inventory_hostname }}-{{ item }}.keytab
      dest: '{{ inventory_hostname }}-{{ item }}.keytab'
      flat: yes
    become: no
    delegate_to: DC01
    with_items:
    - HTTP
    - cifs

  - name: copy keytabs to host
    copy:
      src: '{{ inventory_hostname }}-{{ item }}.keytab'
      dest: /etc/{{ item }}.keytab
    with_items:
    - HTTP
    - cifs

  - name: create user keytab - MIT
    command: ktutil
    args:
      chdir: ~/
      creates: ~/user.keytab
      stdin: "addent -password -p {{ domain_upn }} -k 1 -e aes256-cts\n{{ domain_password }}\nwrite_kt user.keytab"
    become: no
    when: krb_provider == 'MIT'

  - name: create user keytab - Heimdal
    command: >-
      ktutil
      --keytab=user.keytab
      add
      --principal={{ domain_upn }}
      --kvno=1
      --enctype=aes256-cts
      --password={{ domain_password }}
    args:
      chdir: ~/
      creates: ~/user.keytab
    become: no
    when: krb_provider == 'Heimdal'

  - name: copy across CA cert
    copy:
      src: cert_setup/ca.pem
      dest: /etc/pki/ca-trust/source/anchors/pyspnego.pem
    register: ca_cert_copy

  - name: register CA cert
    command: update-ca-trust
    when: ca_cert_copy is changed

  - name: ensure wheel dir exists
    file:
      path: ~/wheels
      state: directory
    become: no

  - name: extract wheel artifacts
    unarchive:
      src: artifact.zip
      dest: ~/wheels
    become: no

  - name: get pyspnego artifact sdist filename
    find:
      paths: ~/wheels
      patterns: 'pyspnego-*.tar.gz'
      recurse: no
      file_type: file
    become: no
    register: spnego_sdist_file

  - name: verify sdist was found
    assert:
      that:
      - spnego_sdist_file.files | count == 1

  - name: get pyspnego artifact version
    set_fact:
      spnego_version: >-
        {{ spnego_sdist_file.files[0].path | basename | regex_replace('pyspnego-(?P<version>.*)\.tar\.gz', '\g<version>') }}

  - name: create a virtualenv for each Python interpeter
    pip:
      name:
      - pytest
      - pytest-forked
      - requests
      - https://github.com/jborean93/sansldap/archive/174408ab40e42f9a2d34bc493027e43cc5d31715.zip
      - pyspnego[kerberos] == {{ spnego_version }}
      virtualenv: '{{ python_venv_path }}/{{ item | basename }}'
      virtualenv_python: '{{ item }}'
      extra_args: --find-links file:///{{ spnego_sdist_file.files[0].path | dirname }}
    become: no
    with_items: '{{ python_interpreters }}'

  - name: template out test integration file
    template:
      src: test_integration.py.tmpl
      dest: ~/test_integration.py
      block_start_string: '{!!'
      block_end_string: '!!}'
    tags:
    - template
    become: no
