From 5d3fff24f74aeb547fbdf1aad408ed013386ce88 Mon Sep 17 00:00:00 2001 From: Alarig Le Lay Date: Fri, 19 Jul 2024 00:46:09 +0200 Subject: [PATCH] Initial commit Signed-off-by: Alarig Le Lay --- README.md | 53 ++++++++++++ check_domain_expiration_rdap.py | 137 ++++++++++++++++++++++++++++++++ requirements.txt | 4 + 3 files changed, 194 insertions(+) create mode 100644 README.md create mode 100755 check_domain_expiration_rdap.py create mode 100644 requirements.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..a11052a --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +Script intented to check a domain expiration with a query to the corresponding +RDAP server. + +This script is inspired from +https://raw.githubusercontent.com/buanzo/check_expiration_rdap/main/src/nagios_check_domain_expiration_rdap/nagios_check_domain_expiration_rdap.py +and `/usr/lib/python3.11/site-packages/nagiosplugin/examples/` + +The script assumes that the TLD has only one label while looking for the RDAP +server from the IANA JSON. If it’s not the case it will fail. + +I don’t understand half of what I wrote + +Have fun. + +Here are the tested cases: +```shell +# expired domain +alarig@x280 nagios-check_domain_expiration_rdap % (master *+%) ./check_domain_expiration_rdap.py found.com.br +EXPIRATION CRITICAL - -31 days until domain expires (outside range @~:15) | daystoexpiration=-31d;@15:30;@~:15 +zsh: exit 2 ./check_domain_expiration_rdap.py found.com.br + +# not reachable rdap server +alarig@x280 nagios-check_domain_expiration_rdap % (master *+%) ./check_domain_expiration_rdap.py 美しい.世界 +EXPIRATION UNKNOWN - The connection to the RDAP server failed: HTTPSConnectionPool(host='rdap.teleinfo.cn', port=443): Max retries exceeded with url: /xn--rhqv96g/domain/xn--n8jub8754b.xn--rhqv96g (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 111] Connection refused')) +zsh: exit 3 ./check_domain_expiration_rdap.py 美しい.世界 + +# unexistant domain name +alarig@x280 nagios-check_domain_expiration_rdap % (master *+%) ./check_domain_expiration_rdap.py foundnotfound.fr +EXPIRATION UNKNOWN - The domain foundnotfound.fr has not been found +zsh: exit 3 ./check_domain_expiration_rdap.py foundnotfound.fr + +# tld without rdap server +alarig@x280 nagios-check_domain_expiration_rdap % (master *+%) ./check_domain_expiration_rdap.py c.pt +EXPIRATION UNKNOWN - The TLD pt does not have an RDAP server +zsh: exit 3 ./check_domain_expiration_rdap.py c.pt + +# domain with more than two labels +alarig@x280 nagios-check_domain_expiration_rdap % (master *+%) ./check_domain_expiration_rdap.py demarches.gouv.fr +EXPIRATION OK - 113 days until domain expires | daystoexpiration=113d;@15:30;@~:15 + +# unicode domain +alarig@x280 nagios-check_domain_expiration_rdap % (master *+%) ./check_domain_expiration_rdap.py こっち.みんな +EXPIRATION OK - 268 days until domain expires | daystoexpiration=268d;@15:30;@~:15 + +# near expiration domain +alarig@x280 nagios-check_domain_expiration_rdap % (master *+%) ./check_domain_expiration_rdap.py hostux.ninja +EXPIRATION WARNING - 17 days until domain expires (outside range @15:30) | daystoexpiration=17d;@15:30;@~:15 +zsh: exit 1 ./check_domain_expiration_rdap.py hostux.ninja + +# very far expiration domain +alarig@x280 nagios-check_domain_expiration_rdap % (master *+%) ./check_domain_expiration_rdap.py swordarmor.fr +EXPIRATION OK - 3615 days until domain expires | daystoexpiration=3615d;@15:30;@~:15 +``` diff --git a/check_domain_expiration_rdap.py b/check_domain_expiration_rdap.py new file mode 100755 index 0000000..b795152 --- /dev/null +++ b/check_domain_expiration_rdap.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python + +import argparse +import datetime +import logging +import requests + +import nagiosplugin +import pandas +import pyunycode +import requests_cache + +_log = logging.getLogger('nagiosplugin') + +list2dict = [] + +session = requests_cache.CachedSession( + '/tmp/iana_rdap_cache', + cache_control=True +) +req = session.get('https://data.iana.org/rdap/dns.json') +for list_of_list in req.json()['services']: + k,v = list_of_list + for x in k: + list2dict.append({'name':x, 'url':v[0]}) + +df = pandas.DataFrame(list2dict) + +def expiration(domain): + domain = pyunycode.convert(domain) + tld = domain.split('.')[-1] + try: + url = df[df.name == (tld)].iloc[0].url + # no rdap on tld + except IndexError: + raise nagiosplugin.CheckError( + f'The TLD {tld} does not have an RDAP server' + ) + + req_rdap = requests.get(f'{url}domain/{domain}') + + raw_expiration = [ + event.get('eventDate', False) + for event in req_rdap.json().get('events', {}) + if event.get('eventAction', {}) == 'expiration' + ] + + try: + fecha = raw_expiration[0].split('T')[0] + except IndexError: + raise nagiosplugin.CheckError( + f'The domain {domain} has not been found' + ) + + today = datetime.datetime.now() + delta = datetime.datetime.strptime(fecha, '%Y-%m-%d') - today + return(delta.days) + + +# data acquisition + +class Expiration(nagiosplugin.Resource): + """Domain model: domain expiration + + Get the expiration date from RDAP. + The RDAP server is extracted from https://data.iana.org/rdap/dns.json which + cached to avoid useless fetching; but the JSON from the registry RDAP isn’t + cached because we can’t presume of the data lifetime. + """ + + def __init__(self, domain): + self.domain = domain + + def probe(self): + try: + days_to_expiration = expiration(self.domain) + except requests.exceptions.ConnectionError as err: + raise nagiosplugin.CheckError( + f'The connection to the RDAP server failed: {err}' + ) + + return [nagiosplugin.Metric( + 'daystoexpiration', + days_to_expiration, + uom='d' + )] + + +# data presentation + +class ExpirationSummary(nagiosplugin.Summary): + """Status line conveying expiration information. + """ + + def __init__(self, domain): + self.domain = domain + + pass + + +# runtime environment and data evaluation + +@nagiosplugin.guarded +def main(): + argp = argparse.ArgumentParser(description=__doc__) + argp.add_argument( + '-w', '--warning', metavar='int', default='30', + help='warning expiration max days. Default=30' + ) + argp.add_argument( + '-c', '--critical', metavar='range', default='15', + help='critical expiration max days. Default=15' + ) + argp.add_argument( + '-v', '--verbose', action='count', default=0, help='be more verbose' + ) + argp.add_argument('domain') + args = argp.parse_args() + wrange = f'@{args.critical}:{args.warning}' + crange = f'@~:{args.critical}' + fmetric = '{value} days until domain expires' + + check = nagiosplugin.Check( + Expiration(args.domain), + nagiosplugin.ScalarContext( + 'daystoexpiration', + warning=wrange, + critical=crange, + fmt_metric=fmetric + ), + ExpirationSummary(args.domain) + ) + check.main(verbose=args.verbose) + + +if __name__ == '__main__': + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b62e088 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +nagiosplugin +pandas +pyunycode +requests_cache