Module simple_acme_dns.tools
DNS tools to assist ACME verification.
Classes
class DNSQuery (domain: str,
rtype: str = 'A',
nameservers: list = None,
authoritative: bool = False,
round_robin: bool = False)-
Expand source code
class DNSQuery: """A basic class to make DNS queries""" # pylint: disable=too-many-positional-arguments def __init__( self, domain: str, rtype: str = "A", nameservers: list = None, authoritative: bool = False, round_robin: bool = False ) -> None: """ Initializes and executes our DNS query. Args: domain (list): A list of fully qualified domain names to list in the certificate. rtype (str): The DNS request type (e.g. `A`, `TXT`, `CNAME`, etc.). nameservers (list): Nameservers to query when making DNS requests. authoritative (bool): Use the authoritative nameserver for each domain. round_robin (`bool): rotate between each nameserver instead of the default fail-over method. """ self.round_robin = round_robin self.type = rtype.upper() self.domain = domain self.nameservers = nameservers if nameservers else dns.resolver.Resolver().nameservers self.nameservers = self.__get_authoritative_nameservers__() if authoritative else self.nameservers self.values = [] self.answers = [] self.last_nameserver = "" def resolve(self) -> list: """ Queries the nameservers with our configured object values. Returns: list: A list of DNS resolution answers. """ # Resolve the DNS query try: self.answers = DNSQuery.__resolve__(self.domain, rtype=self.type, nameservers=self.nameservers) except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN): self.answers = [] # Rotate the nameservers if round robin mode is enabled if self.round_robin and len(self.nameservers) > 1: self.last_nameserver = self.nameservers[0] self.nameservers = self.nameservers[1:] + [self.last_nameserver] self.values = self.__parse_values__(self.answers) return self.values def __get_authoritative_nameservers__(self) -> list: """ Checks the domain's SOA record for the authoritative nameserver of this domain. Returns: list: A list of authoritative nameservers """ # Local variables nameserver = [] domain_sections = self.domain.split(".") # Loop through each level of the subdomain to find the SOA for this FQDN. while domain_sections: # Piece together the remaining domain sections to create our next target domain domain = ".".join(domain_sections) # Get our SOA record values for this domain and remove the trailing dot from each try: nameserver = self.__parse_values__(self.__resolve__(domain, rtype="SOA", nameservers=self.nameservers)) break except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN): domain_sections.pop(0) continue # Extract the authoritative nameserver's IP from the response nameserver = nameserver[0].split("SOA ") nameserver = nameserver[0].split(" ")[0] nameserver = nameserver[:-1] nameserver = self.__parse_values__(self.__resolve__(nameserver, rtype="A", nameservers=self.nameservers)) return nameserver @staticmethod def __resolve__(domain: str, rtype: str = "A", nameservers: list = None) -> list: """ Internal function-like DNS request method. Returns: list: A list of answer values from the request. """ resolver = dns.resolver.Resolver() resolver.nameservers = nameservers if nameservers else resolver.nameservers # Resolve the DNS query return DNSQuery.__filter_list__(resolver.resolve(domain, rtype).response.answer[0].to_text().split("\n")) @staticmethod def __filter_list__(data: list) -> list: """ Filters our list properties to remove blank entries. Args: data (list): The list to remove blank entries from. Returns: list: The data list stripped of any blank entries. """ return list(filter(None, data)) @staticmethod def __parse_values__(answers: list) -> list: """ Parses the value portion of the query answer into it's own list. Args: answers (list): the answers list returned by `__resolve__()` method. Returns: list: A parsed list of values for each answer. """ values = [] # Loop through each answer and parse it's value section to the values property for answer in answers: # Save the fourth space separated item as the value = answer.split(" ", 4)[-1] value = value.replace("\"", "", 1) if value.startswith("\"") else value value = value.replace("\"", "", -1) if value.endswith("\"") else value values.append(value) return DNSQuery.__filter_list__(values)
A basic class to make DNS queries
Initializes and executes our DNS query.
Args
domain
:list
- A list of fully qualified domain names to list in the certificate.
rtype
:str
- The DNS request type (e.g.
A
,TXT
,CNAME
, etc.). nameservers
:list
- Nameservers to query when making DNS requests.
authoritative
:bool
- Use the authoritative nameserver for each domain.
round_robin (`bool): rotate between each nameserver instead of the default fail-over method.
Methods
def resolve(self) ‑> list
-
Expand source code
def resolve(self) -> list: """ Queries the nameservers with our configured object values. Returns: list: A list of DNS resolution answers. """ # Resolve the DNS query try: self.answers = DNSQuery.__resolve__(self.domain, rtype=self.type, nameservers=self.nameservers) except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN): self.answers = [] # Rotate the nameservers if round robin mode is enabled if self.round_robin and len(self.nameservers) > 1: self.last_nameserver = self.nameservers[0] self.nameservers = self.nameservers[1:] + [self.last_nameserver] self.values = self.__parse_values__(self.answers) return self.values
Queries the nameservers with our configured object values.
Returns
list
- A list of DNS resolution answers.