Skip to content

Commit 936bc82

Browse files
Dan Lavudanlavu
authored andcommitted
adding dns server and zone utilities to the ad role
1 parent 97dfb35 commit 936bc82

File tree

1 file changed

+217
-1
lines changed
  • sssd_test_framework/roles

1 file changed

+217
-1
lines changed

sssd_test_framework/roles/ad.py

Lines changed: 217 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
from __future__ import annotations
44

5+
from abc import ABC
56
from datetime import datetime
67
from typing import Any, TypeAlias
78

89
from pytest_mh.cli import CLIBuilderArgs
910
from pytest_mh.conn import ProcessResult
1011

1112
from ..hosts.ad import ADHost
12-
from ..misc import attrs_include_value, attrs_parse, attrs_to_hash, seconds_to_timespan
13+
from ..misc import attrs_include_value, attrs_parse, attrs_to_hash, ip_version, seconds_to_timespan
1314
from .base import BaseObject, BaseWindowsRole, DeleteAttribute
1415
from .generic import GenericPasswordPolicy
1516
from .ldap import LDAPNetgroupMember
@@ -27,6 +28,8 @@
2728
"ADUser",
2829
"ADPasswordPolicy",
2930
"GPO",
31+
"ADDNSServer",
32+
"ADDNSZone",
3033
]
3134

3235

@@ -360,6 +363,38 @@ def test_example(client: Client, ad: AD):
360363
"""
361364
return ADComputer(self, name, basedn)
362365

366+
def dns(self) -> ADDNSServer:
367+
"""
368+
Get DNS server object.
369+
370+
Get methods use dig and is parsed by jc. The data from jc contains several nested dict,
371+
but two are returned as a tuple, ``answer, authority``.
372+
373+
.. code-block:: python
374+
:caption: Example usage
375+
376+
# Create forward zone and add forward record
377+
zone = ad.dns().zone("example.test").create()
378+
zone.add_record("client", "172.16.200.15")
379+
380+
# Create reverse zone and add reverse record
381+
zone = ad.dns().zone("10.0.10.in-addr.arpa").create()
382+
zone.add_ptr_record("client.example.test", 15)
383+
384+
# Add forward record to default domain
385+
ad.dns().zone(ad.domain).add_record("client", "1.2.3.4")
386+
387+
# Add a global forwarder
388+
ad.dns().add_forwarder("1.1.1.1")
389+
390+
# Remove a global forwarder
391+
ad.dns().remove_forwarder("1.1.1.1")
392+
393+
# Clear all forwarders
394+
ad.dns().clear_forwarders()
395+
"""
396+
return ADDNSServer(self)
397+
363398
def gpo(self, name: str) -> GPO:
364399
"""
365400
Get group policy object.
@@ -2114,4 +2149,185 @@ def lockout(self, duration: int, attempts: int) -> ADPasswordPolicy:
21142149
return self
21152150

21162151

2152+
class ADDNSServer(BaseObject[ADHost, AD]):
2153+
def __init__(self, role: AD):
2154+
"""
2155+
:param role: AD host object.
2156+
:type role: ADHost
2157+
"""
2158+
super().__init__(role)
2159+
2160+
self.domain: str = role.domain
2161+
"""Domain name."""
2162+
2163+
self.server: str = role.server
2164+
"""Server name."""
2165+
2166+
def zone(self, name: str) -> ADDNSZone:
2167+
"""
2168+
Get ADDNsServerZone object.
2169+
2170+
:param name: Zone name.
2171+
:type name: str
2172+
:return: ADDNSServerZone object.
2173+
:rtype: ADDNSZone
2174+
"""
2175+
return ADDNSZone(self.role, name)
2176+
2177+
def get_forwarders(self) -> list[str] | None:
2178+
"""
2179+
Get DNS global forwarders.
2180+
2181+
:return: DNS global forwarders.
2182+
:rtype: list[str]
2183+
"""
2184+
forwarders = self.host.conn.run("Get-DnsServerForwarder").stdout_lines
2185+
if forwarders is not None:
2186+
parsed_forwarders = attrs_parse(forwarders, ["IPAddress"])
2187+
if isinstance(parsed_forwarders, dict):
2188+
ip_addresses = parsed_forwarders.get("IPAddress")
2189+
if isinstance(ip_addresses, list) and len(ip_addresses) > 0:
2190+
ip_addresses = [s.strip() for s in ip_addresses]
2191+
ip_addresses = [r.replace("...", "") for r in ip_addresses]
2192+
return ip_addresses[0].strip("{}").split(",")
2193+
else:
2194+
return None
2195+
else:
2196+
return None
2197+
2198+
def add_forwarder(self, ip_address: str) -> ADDNSServer:
2199+
"""
2200+
Add a DNS server forwarder.
2201+
2202+
:param ip_address: IP address.,
2203+
:type ip_address: str
2204+
:return: Self.
2205+
:rtype: ADDNSServer
2206+
"""
2207+
self.host.conn.run(f"Add-DnsServerForwarder -IPAddress {ip_address}")
2208+
2209+
return self
2210+
2211+
def remove_forwarder(self, ip_address: str) -> None:
2212+
"""
2213+
Remove a DNS server forwarder.
2214+
2215+
:param ip_address: IP address.
2216+
:type ip_address: str
2217+
"""
2218+
if ip_address is not None:
2219+
self.host.conn.run(f"Remove-DnsServerForwarder {ip_address} -Force")
2220+
2221+
def clear_forwarders(self) -> None:
2222+
"""
2223+
Clear all DNS server forwarders.
2224+
2225+
AD has about four global forwarders enabled by default.
2226+
"""
2227+
forwarders = self.get_forwarders()
2228+
2229+
if isinstance(forwarders, list) and not None:
2230+
for forwarder in forwarders:
2231+
self.remove_forwarder(forwarder)
2232+
2233+
def list_zones(self) -> list[str]:
2234+
"""
2235+
List zones.
2236+
:return: List of zones.
2237+
:rtype: list[str]
2238+
"""
2239+
result = self.host.conn.run("Get-DnsServerZone | Format-List -Property ZoneName").stdout_lines
2240+
result = [x for x in result if x not in ["\r", "", None]]
2241+
result = [y.replace("\r", "").strip() for y in result]
2242+
result = [z.split(":")[1].strip() for z in result]
2243+
2244+
return result
2245+
2246+
2247+
class ADDNSZone(ADDNSServer, ABC):
2248+
"""
2249+
DNS zone management.
2250+
"""
2251+
2252+
def __init__(self, role: AD, name: str):
2253+
"""
2254+
:param name: DNS zone name.
2255+
:type name: str
2256+
:param name: DNS zone name.
2257+
:type name: str
2258+
"""
2259+
super().__init__(role)
2260+
2261+
self.zone_name: str = name
2262+
"""Zone name."""
2263+
2264+
def create(self) -> ADDNSZone:
2265+
"""
2266+
Create new zone.
2267+
2268+
:return: ADDNSServer object.
2269+
:rtype: ADDNSServer
2270+
"""
2271+
self.host.conn.run(f"Add-DnsServerPrimaryZone -Name {self.zone_name} -ReplicationScope Forest -Passthru")
2272+
return self
2273+
2274+
def delete(self) -> None:
2275+
"""
2276+
Delete zone.
2277+
"""
2278+
self.host.conn.run(f"Delete-DnsServerZone -Name {self.zone_name}")
2279+
2280+
def add_record(self, name: str, data: str | int) -> ADDNSZone:
2281+
"""
2282+
Add DNS record.
2283+
2284+
If ``data`` is a str, a forward record will be added.
2285+
If an integer a reverse record will be added.
2286+
2287+
:param name: Record name.
2288+
:type name: str
2289+
:param data: Record data.
2290+
:type data: str
2291+
:return: ADDNSZone object.
2292+
:rtype: ADDNSZone
2293+
"""
2294+
args = ""
2295+
2296+
if isinstance(data, int):
2297+
args = f"-Ptr -Name {str(data)} -AllowUpdateAny -PtrDomainName {name}.{self.zone_name}"
2298+
elif isinstance(data, str) and ip_version(data) == 4:
2299+
args = f"-A -Name {name} -IPv4Address {data}"
2300+
elif isinstance(data, str) and ip_version(data) == 6:
2301+
args = f"-A -Name {name} -IPv6Address {data}"
2302+
2303+
self.host.conn.run(f"Add-DnsServerResourceRecord -ZoneName {self.zone_name} {args} ")
2304+
return self
2305+
2306+
def delete_record(self, name: str) -> None:
2307+
"""
2308+
Delete DNS record.
2309+
2310+
:param name: Name of the record.
2311+
:type name: str
2312+
"""
2313+
if "in-addr" in self.zone_name:
2314+
record_type = "PTR"
2315+
else:
2316+
data = self.host.conn.run(f"dig +short {name}").stdout.strip()
2317+
record_type = "AAAA" if ":" in data else "A"
2318+
2319+
self.host.conn.run(
2320+
f"Remove-DnsServerResourceRecord -ZoneName {self.zone_name} -Name {name} -RRType {record_type} -Force"
2321+
)
2322+
2323+
def print(self) -> str:
2324+
"""
2325+
Prints all dns records in zone as text.
2326+
2327+
:return: Print zone data.
2328+
:rtype: str
2329+
"""
2330+
return self.host.conn.run(f"Get-DnsServerResourceRecord -ZoneName {self.zone_name}").stdout
2331+
2332+
21172333
ADNetgroupMember: TypeAlias = LDAPNetgroupMember[ADUser, ADNetgroup]

0 commit comments

Comments
 (0)