From 06e44f6fc47566fa2a472a3c176ea2e17731cdfb Mon Sep 17 00:00:00 2001 From: Jakub Vavra Date: Fri, 3 Nov 2023 08:48:28 +0100 Subject: [PATCH] Add full integration test for dyndns Modify the role so dyndns_auth can be actually configured. Add handling of client hostname not being fqdn. Add integration test for dyndns with actual AD. --- defaults/main.yml | 6 +- tasks/main.yml | 8 +- tests/tests_dyndns.yml | 1 + tests/tests_full_integration_dyndns.yml | 164 ++++++++++++++++++++++++ 4 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 tests/tests_full_integration_dyndns.yml diff --git a/defaults/main.yml b/defaults/main.yml index 04db6ba..f35c929 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -115,10 +115,12 @@ ad_dyndns_update_ptr: true # Only applicable if `ad_dyndns_update` is true ad_dyndns_force_tcp: false -# Optional. If true, GSS-TSIG authentication will be used for secure updates +# Optional. GSS-TSIG authentication is used for secure updates # with the DNS server when updating A and AAAA records. +# Valid values are "GSS-TSIG" or "none" allowing unsecure updates. +# The default value in sssd is 'GSS-TSIG'. # Only applicable if `ad_dyndns_update` is true -ad_dyndns_auth: true +ad_dyndns_auth: null # Optional. DNS server to use when performing a DNS update when autodetection # settings fail. diff --git a/tasks/main.yml b/tasks/main.yml index 265c6fe..9fb6f0e 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -221,10 +221,16 @@ - key: dyndns_force_tcp value: "{{ ad_dyndns_force_tcp | string }}" - key: dyndns_auth - value: "{{ 'GSS-TSIG' if ad_dyndns_auth else 'none' }}" + value: "{{ ad_dyndns_auth | string if ad_dyndns_auth else '' }}" - key: dyndns_server value: "{{ ad_dyndns_server | string if ad_dyndns_server is not none else '' }}" + # For dynamic dns to work the machine either needs fqdn in hostname + # or ad_hostname needs to be defined. + - key: ad_hostname + value: "{{ ansible_hostname + '.' + ad_integration_realm | lower + | string if '.' not in ansible_hostname else '' }}" + when: - ad_dyndns_update | bool - item.value is not none diff --git a/tests/tests_dyndns.yml b/tests/tests_dyndns.yml index dd3b057..c0c3827 100644 --- a/tests/tests_dyndns.yml +++ b/tests/tests_dyndns.yml @@ -28,6 +28,7 @@ vars: ad_dyndns_iface: "TESTING" ad_dyndns_server: 127.0.0.1 + ad_dyndns_auth: "GSS-TSIG" - name: Test - Verify sssd.conf options were written block: diff --git a/tests/tests_full_integration_dyndns.yml b/tests/tests_full_integration_dyndns.yml new file mode 100644 index 0000000..00845e2 --- /dev/null +++ b/tests/tests_full_integration_dyndns.yml @@ -0,0 +1,164 @@ +# SPDX-License-Identifier: MIT +--- + +# To run this test, AD (ad1) needs to be present in the inventory +# AD should be pre-configured as a dns for the client machine. +# AD's DNS service should be configured with both forward and reverse zones +# and the DNS records from any previous runs removed. +# Note that both for client and ad the ansible_host should +# contain the ip4 address to make this work properly. +# There is expectation that both client and ad are on the +# same network with mask 255.255.255.0. + +# Example inventory: +# [client] +# client1 ansible_host=\ +# ad_integration_password=Secret123 ad_integration_realm="domain.com" \ +# ad_dyndns_update=true +# [ad] +# ad1 ansible_host= ansible_connection=winrm ansible_password=Secret123\ +# ansible_port=5986 ansible_user=Administrator \ +# ansible_winrm_server_cert_validation=ignore + +- name: Set variables + hosts: client + tasks: + - name: Set ad_integration_realm if it is not defined or is empty + set_fact: + ad_integration_realm: domain.com + when: ad_integration_realm is not defined or not ad_integration_realm + +- name: Configure DynDNS on AD + hosts: ad + gather_facts: true + tasks: + - name: Get AD address + set_fact: + ad_address: "{{ hostvars[inventory_hostname].ansible_host }}" + - name: Get network for zone + set_fact: + network_ad: "{{ ad_address.split('.')[-2::-1] | join('.') }}.in-addr.arpa" + - name: show network + debug: + msg: "AD network: {{ network_ad }}" + - name: Create reverse zone + win_command: dnscmd.exe /zoneadd {{ network_ad }} /primary + register: res + failed_when: + - 'res.rc != 0 and "DNS_ERROR_ZONE_ALREADY_EXISTS" not in res.stdout' + - name: Allow updates in the zone + win_command: dnscmd.exe /config {{ hostvars[groups['client'][0]].ad_integration_realm }} /allowupdate 1 + - name: Allow updates in the reverse zone + win_command: dnscmd.exe /config {{ network_ad }} /allowupdate 1 + - name: Disable dns forwarders + win_command: dnscmd.exe /config /norecursion 1 + +- name: Ensure that the role configures dynamic dns + hosts: client + tasks: + - name: Run the integration + block: + - name: Debug dyndns settings + debug: + msg: "Interface {{ _ad_dyndns_iface | default('eth0') }} and server: {{ hostvars[groups['ad'][0]].ansible_host }}" + - name: Run the system role with proper config + include_role: + name: linux-system-roles.ad_integration + vars: + ad_dyndns_server: "{{ hostvars[groups['ad'][0]].ansible_host }}" + ad_dyndns_iface: "{{ _ad_dyndns_iface | default('eth0') }}" + ad_dyndns_auth: "none" + ad_dyndns_update: true + ad_dyndns_refresh_interval: 60 + - name: Apply additional changes on sssd + ini_file: + path: /etc/sssd/sssd.conf + state: present + section: "domain/{{ ad_integration_realm | lower }}" + option: "{{ item.key }}" + value: "{{ item.value }}" + create: true + loop: + - key: debug_level + value: "{{ 9 | int }}" + - name: Clean sssd log + command: 'truncate -s 0 /var/log/sssd/sssd_{{ ad_integration_realm }}.log' + - name: Restart sssd + command: systemctl restart sssd + - name: Pause for 5 to give sssd chance to start and refresh dn record on AD + ansible.builtin.pause: + minutes: 5 + - name: Check sssd log fo dydndns update info + command: 'grep -A 20 "nsupdate" /var/log/sssd/sssd_{{ ad_integration_realm }}.log' + - name: Grab sssd.conf contents + command: cat /etc/sssd/sssd.conf + register: command_output + - name: Print sssd.conf + debug: + msg: "{{command_output.stdout}}" + - name: Get IP for host's FQDN + command: "dig +short {{ ansible_fqdn }}.{{ hostvars[groups['client'][0]].ad_integration_realm }} A" + register: dig_hostname + changed_when: false + failed_when: false + - name: Get hostname for host's IP address + command: "dig +short -x {{ ansible_default_ipv4.address }} PTR" + register: dig_ip + changed_when: false + failed_when: false + - name: Print to console dig outputs + debug: + msg: + - "Dig hostname: {{ dig_hostname.stdout }}" + - "Dig ip: {{ dig_ip.stdout }}" + when: "'ad' in groups and groups['ad']" + +- name: Check hosts on AD + hosts: ad + gather_facts: true + tasks: + - name: AD checks + block: + - name: Get network for zone + set_fact: + network_ad: "{{ ad_address.split('.')[-2::-1] | join('.') }}.in-addr.arpa" + client_ip_part: "{{ hostvars[groups['client'][0]].ansible_host.split('.')[-1] }}" + - name: Show network information + debug: + msg: "AD network: {{ network_ad }}, Client IP: {{ hostvars[groups['client'][0]].ansible_host }}" + - name: List all AD zones + win_command: dnscmd.exe /EnumZones + register: zones + failed_when: false + - name: Grab AD zone + # noqa: command-instead-of-shell + win_shell: 'dnscmd.exe /zoneprint {{ hostvars[groups["client"][0]].ad_integration_realm }} | findstr /c:"client"' + register: zone_ad + failed_when: false + - name: Grab AD reverse zone + # noqa: command-instead-of-shell + win_shell: 'dnscmd.exe /zoneprint {{ network_ad }} | findstr /c:"client"' + register: reverse_zone_ad + failed_when: false + - name: Show AD zones + debug: + msg: + - "Zones:" + - "{{ zones }}" + - "Zone:" + - "{{ zone_ad }}" + - "Reverse zone:" + - "{{ reverse_zone_ad }}" + - name: Test - client is in AD both in forward and reverse zones + assert: + that: + - 'hostvars[groups["client"][0]].ansible_host in zone_ad.stdout' + - 'client_ip_part in reverse_zone_ad.stdout' + +- name: Leave realm + hosts: client + tasks: + - name: Teardown - Leave realm + command: realm leave + ignore_errors: true # noqa ignore-errors + changed_when: false