diff --git a/src/_dns.py b/src/_dns.py index fe8d7ba70906319a3a8f6959aa936492c7ed654b..c0e6d1d5d779f6e36a3a84fe8c5e5ce2eb45cff6 100644 --- a/src/_dns.py +++ b/src/_dns.py @@ -1,92 +1,110 @@ +from functools import lru_cache import dns.resolver # pylint: disable=import-error -import utils - - -DOMAIN = "shore.co.il" -SUBDOMAINS = [DOMAIN, f"www.{DOMAIN}"] - - -def get_resolvers(): - """Return a list of resolvers for each nameserver in the DNS zone.""" - default_resolver = dns.resolver.Resolver() - resolvers = [] - for ns in default_resolver.query( # pylint: disable=invalid-name - DOMAIN, "NS" - ): - for address in default_resolver.query(ns.to_text()): - resolver = dns.resolver.Resolver(configure=False) - resolver.nameservers = [address.to_text()] - resolvers.append(resolver) - return resolvers - - -RESOLVERS = get_resolvers() - - -def cross_query(qname, rdtype="A"): - """Return all of the answers from all nameservers.""" - answers = set() - for resolver in RESOLVERS: - for address in resolver.query(qname, rdtype): - answers.add(address) - return answers +import dns.query # pylint: disable=import-error +import dns.zone # pylint: disable=import-error +from utils import Check + + +class CheckDNS(Check): # pylint: disable=abstract-method + _domain = "shore.co.il" + _subdomains = [_domain, f"www.{_domain}"] + + @lru_cache(3) + def _get_resolvers(self): + """Return a list of resolvers for each nameserver in the DNS zone.""" + default_resolver = dns.resolver.Resolver() + resolvers = [] + for ns in default_resolver.query( # pylint: disable=invalid-name + self._domain, "NS" + ): + for address in default_resolver.query(ns.to_text()): + resolver = dns.resolver.Resolver(configure=False) + resolver.nameservers = [address.to_text()] + resolvers.append(resolver) + return resolvers + + def _cross_query(self, qname, rdtype="A"): + """Return all of the answers from all nameservers.""" + answers = set() + for resolver in self._resolvers: + for address in resolver.query(qname, rdtype): + answers.add(address) + return answers + + def __init__(self): + self._resolvers = self._get_resolvers() + + +class CheckSOA(CheckDNS): + def _check(self): + """Validate the SOA record.""" + soas = self._cross_query(self._domain, "SOA") + + if len(soas) > 1: + return False, "SOA records don't match." + try: + record = soas.pop() + record.mname.to_text() + except Exception as e: # pylint: disable=broad-except,invalid-name + return False, str(e) -def validate_soa(): - """Validate the SOA record.""" - soas = cross_query(DOMAIN, "SOA") + return True, "SOA record validated." - if len(soas) > 1: - return [False, "SOA records don't match."] - try: - record = soas.pop() - record.mname.to_text() - except Exception as e: # pylint: disable=broad-except,invalid-name - print(str(e)) - return [False, "SOA record is invalid."] +class CheckMX(CheckDNS): + def _check(self): + """Validate the MX record.""" + mxs = self._cross_query(self._domain, "MX") + if len(mxs) > 1: + return False, "MX records don't match." - return [True, "SOA record validated."] + try: + record = mxs.pop() + ips = self._cross_query(record.exchange.to_text()) + if len(ips) > 1: + return False, "MX records don't match." + except Exception as e: # pylint: disable=broad-except,invalid-name + return [False, str(e)] + return True, "MX record validated." -def validate_mx(): - """Validate the MX record.""" - mxs = cross_query(DOMAIN, "MX") - if len(mxs) > 1: - return [False, "MX records don't match."] - try: - record = mxs.pop() - ips = cross_query(record.exchange.to_text()) - if len(ips) > 1: - return [False, "MX records don't match."] - except Exception as e: # pylint: disable=broad-except,invalid-name - print(str(e)) - return [False, "MX record is invalid."] +class CheckSubDomains(CheckDNS): + def _check(self): + """Validate important subdomains.""" + for domain in self._subdomains: + try: + ips = self._cross_query(domain) + if len(ips) > 1: + return [True, f"Domain {domain} records don't match."] + except Exception as e: # pylint: disable=broad-except,invalid-name + return [False, str(e)] + return True, "Subdomains validated." - return [True, "MX record validated."] +class CheckTransfer(CheckDNS): + def _check(self): + """Validate zone transfer, to check the TCP transport.""" + zones = [] + for resolver in self._resolvers: + try: + zone = dns.zone.from_xfr( + dns.query.xfr(resolver.nameservers[0], self._domain) + ) + zones.append(zone) + except Exception as e: # pylint: disable=broad-except,invalid-name + return False, str(e) -def validate_subdomains(): - """Validate important subdomains.""" - for domain in SUBDOMAINS: - try: - ips = cross_query(domain) - if len(ips) > 1: - return [True, f"Domain {domain} records don't match."] - except Exception as e: # pylint: disable=broad-except,invalid-name - print(str(e)) - return [False, "Failed to validate domain {d}."] - return [True, "Subdomains validated."] + return True, "Zone transfer validated." def handler(event, context): # pylint: disable=unused-argument """Lambda event handler.""" - for check in [validate_soa, validate_mx, validate_subdomains]: - success, message = check.__call__() - print(message) - if not success: - utils.publish(message) + CheckSOA().run() + CheckMX().run() + CheckSubDomains().run() + # CheckTransfer().run() # AXFR is blocked on ns1, need to figure out why. if __name__ == "__main__": diff --git a/src/autoconfig.py b/src/autoconfig.py index f65d48e6c28524a32272f18cb79a3ecbdf3f1106..a3404adfc4cef914ed5651ded4733dc2f78c5032 100644 --- a/src/autoconfig.py +++ b/src/autoconfig.py @@ -1,12 +1,10 @@ -from utils import check_urls +from utils import website_handler +URLS = [ + {"url": "https://autoconfig.shore.co.il/mail/config-v1.1.xml"}, +] -def handler(event, context): # pylint: disable=unused-argument - """Lambda event handler.""" - checks = [ - {"url": "https://autoconfig.shore.co.il/mail/config-v1.1.xml"}, - ] - check_urls(checks) +handler = website_handler(URLS) if __name__ == "__main__": diff --git a/src/gitlab.py b/src/gitlab.py index 0395fda283cd421db76ba4929796899309ccd9ec..d66593cf2aa56e5b6eee3ee4d198ab226ec66450 100644 --- a/src/gitlab.py +++ b/src/gitlab.py @@ -1,14 +1,12 @@ -from utils import check_urls +from utils import website_handler +URLS = [ + {"url": "http://git.shore.co.il/", "codes": [301, 302]}, + {"url": "https://git.shore.co.il/", "codes": [301, 302]}, + {"url": "https://git.shore.co.il/explore/"}, +] -def handler(event, context): # pylint: disable=unused-argument - """Lambda event handler.""" - checks = [ - {"url": "http://git.shore.co.il/", "codes": [301, 302]}, - {"url": "https://git.shore.co.il/", "codes": [301, 302]}, - {"url": "https://git.shore.co.il/explore/"}, - ] - check_urls(checks) +handler = website_handler(URLS) if __name__ == "__main__": diff --git a/src/imap.py b/src/imap.py index f967ed73de9ebab1bf87b45bbf5ecae89b5bc6b2..57fbcb1c1ba63a9e3b43e631f594380a6147b6b1 100644 --- a/src/imap.py +++ b/src/imap.py @@ -1,28 +1,26 @@ from imaplib import IMAP4_SSL -from utils import publish +from utils import Check -def check_imap(): - """Check the IMAP port.""" - try: - imap = IMAP4_SSL("imap.shore.co.il", 993) - if "AUTH=PLAIN" not in imap.capabilities: - return [False, "AUTH not in IMAP capabilities."] - if imap.noop()[0] != "OK": - return [False, "NOOP failed in IMAP connection."] - imap.logout() - except Exception as e: # pylint: disable=broad-except,invalid-name - print(str(e)) - return [False, "IMAP failure."] - return [True, "IAMP is OK."] +class CheckIMAP(Check): + def _check(self): + """Check the IMAP port.""" + try: + imap = IMAP4_SSL("imap.shore.co.il", 993) + if "AUTH=PLAIN" not in imap.capabilities: + return [False, "AUTH not in IMAP capabilities."] + if imap.noop()[0] != "OK": + return [False, "NOOP failed in IMAP connection."] + imap.logout() + except Exception as e: # pylint: disable=broad-except,invalid-name + print(str(e)) + return [False, "IMAP failure."] + return [True, "IAMP is OK."] def handler(event, context): # pylint: disable=unused-argument """Lambda event handler.""" - success, message = check_imap() - print(message) - if not success: - publish(message) + CheckIMAP().run() if __name__ == "__main__": diff --git a/src/kodi.py b/src/kodi.py index 678fb78fc94c916d1ab8993c4d32989e186f72b4..bbf3809a7f57883354fc8ae191e6bbbd1ab10b6b 100644 --- a/src/kodi.py +++ b/src/kodi.py @@ -1,13 +1,11 @@ -from utils import check_urls +from utils import website_handler +URLS = [ + {"url": "http://kodi.shore.co.il/", "codes": [301, 302]}, + {"url": "https://kodi.shore.co.il/", "codes": [401]}, +] -def handler(event, context): # pylint: disable=unused-argument - """Lambda event handler.""" - checks = [ - {"url": "http://kodi.shore.co.il/", "codes": [301, 302]}, - {"url": "https://kodi.shore.co.il/", "codes": [401]}, - ] - check_urls(checks) +handler = website_handler(URLS) if __name__ == "__main__": diff --git a/src/mta_sts.py b/src/mta_sts.py index de8875cab64d7b4111fe2471234b9a407f45e308..4d9d0737409f7c7033c61d6faa81d983478ddb93 100644 --- a/src/mta_sts.py +++ b/src/mta_sts.py @@ -1,12 +1,10 @@ -from utils import check_urls +from utils import website_handler +URLS = [ + {"url": "https://mta-sts.shore.co.il/.well-known/mta-sts.txt"}, +] -def handler(event, context): # pylint: disable=unused-argument - """Lambda event handler.""" - checks = [ - {"url": "https://mta-sts.shore.co.il/.well-known/mta-sts.txt"}, - ] - check_urls(checks) +handler = website_handler(URLS) if __name__ == "__main__": diff --git a/src/myip.py b/src/myip.py index 88427be7e4d77ae933dd73406f1a87541f9fa589..f2f90ed4e91856aaaff03b9ca38eaf7bc53dbb0c 100644 --- a/src/myip.py +++ b/src/myip.py @@ -1,13 +1,11 @@ -from utils import check_urls +from utils import website_handler +URLS = [ + {"url": "http://myip.shore.co.il/"}, + {"url": "https://myip.shore.co.il/"}, +] -def handler(event, context): # pylint: disable=unused-argument - """Lambda event handler.""" - checks = [ - {"url": "http://myip.shore.co.il/"}, - {"url": "https://myip.shore.co.il/"}, - ] - check_urls(checks) +handler = website_handler(URLS) if __name__ == "__main__": diff --git a/src/nextcloud.py b/src/nextcloud.py index ad2860e67125ca4184de33ec80123e5bdead306c..abbf4037a87d2e33a89f52e737f389cf8d2bd6e8 100644 --- a/src/nextcloud.py +++ b/src/nextcloud.py @@ -1,14 +1,12 @@ -from utils import check_urls +from utils import website_handler +URLS = [ + {"url": "http://nextcloud.shore.co.il/", "codes": [301, 302]}, + {"url": "https://nextcloud.shore.co.il/", "codes": [301, 302]}, + {"url": "https://nextcloud.shore.co.il/login"}, +] -def handler(event, context): # pylint: disable=unused-argument - """Lambda event handler.""" - checks = [ - {"url": "http://nextcloud.shore.co.il/", "codes": [301, 302]}, - {"url": "https://nextcloud.shore.co.il/", "codes": [301, 302]}, - {"url": "https://nextcloud.shore.co.il/login"}, - ] - check_urls(checks) +handler = website_handler(URLS) if __name__ == "__main__": diff --git a/src/notify.py b/src/notify.py index 9f1bca88efc246b304e5f3b7d41793e296ad7d67..86c7092c06644d3b1f3576c1957cbd04afddd1ed 100644 --- a/src/notify.py +++ b/src/notify.py @@ -1,13 +1,11 @@ -from utils import check_urls +from utils import website_handler +URLS = [ + {"url": "http://notify.shore.co.il/", "codes": [301, 302]}, + {"url": "https://notify.shore.co.il/ping"}, +] -def handler(event, context): # pylint: disable=unused-argument - """Lambda event handler.""" - checks = [ - {"url": "http://notify.shore.co.il/", "codes": [301, 302]}, - {"url": "https://notify.shore.co.il/ping"}, - ] - check_urls(checks) +handler = website_handler(URLS) if __name__ == "__main__": diff --git a/src/registry.py b/src/registry.py index 950b5acd6f92374d08003e3ecd64bb245164211b..fb66094c89404e0a6a7407ae831129bc1b946d4a 100644 --- a/src/registry.py +++ b/src/registry.py @@ -1,14 +1,12 @@ -from utils import check_urls +from utils import website_handler +URLS = [ + {"url": "http://registry.shore.co.il/", "codes": [301, 302]}, + {"url": "https://registry.shore.co.il/"}, + {"url": "https://registry.shore.co.il/v2/_catalog"}, +] -def handler(event, context): # pylint: disable=unused-argument - """Lambda event handler.""" - checks = [ - {"url": "http://registry.shore.co.il/", "codes": [301, 302]}, - {"url": "https://registry.shore.co.il/"}, - {"url": "https://registry.shore.co.il/v2/_catalog"}, - ] - check_urls(checks) +handler = website_handler(URLS) if __name__ == "__main__": diff --git a/src/smtp.py b/src/smtp.py index b8947dd7b75bb8d7ce883a1d00d6ccfc7fa4a45f..20afdf153bcf1d0f4f62b4d556dd541e7d1df197 100644 --- a/src/smtp.py +++ b/src/smtp.py @@ -1,24 +1,28 @@ from smtplib import SMTP -from utils import publish +from utils import Check -def check_smtp(port): - """Check the SMTP port.""" - try: - smtp = SMTP("smtp.shore.co.il", port) - ehlo = smtp.ehlo() - if ehlo[0] != 250 or "LOGIN" in ehlo[1].decode().split(): - return [False, f"First EHLO on port {port} failed."] - if smtp.starttls() != (220, b"TLS go ahead"): - return [False, f"STARTTLS on port {port} failed."] - ehlo = smtp.ehlo() - if ehlo[0] != 250 or "LOGIN" not in ehlo[1].decode().split(): - return [False, f"Second EHLO on port {port} failed."] - smtp.close() - except Exception as e: # pylint: disable=broad-except,invalid-name - print(str(e)) - return [False, f"SMTP failure on port {port}."] - return [True, f"SMTP on port {port} is OK."] +class CheckSMTP(Check): + def __init__(self, port): + self._port = port + + def _check(self): + """Check the SMTP port.""" + try: + smtp = SMTP("smtp.shore.co.il", self._port) + ehlo = smtp.ehlo() + if ehlo[0] != 250 or "LOGIN" in ehlo[1].decode().split(): + return [False, f"First EHLO on port {self._port} failed."] + if smtp.starttls() != (220, b"TLS go ahead"): + return [False, f"STARTTLS on port {self._port} failed."] + ehlo = smtp.ehlo() + if ehlo[0] != 250 or "LOGIN" not in ehlo[1].decode().split(): + return [False, f"Second EHLO on port {self._port} failed."] + smtp.close() + except Exception as e: # pylint: disable=broad-except,invalid-name + print(str(e)) + return [False, f"SMTP failure on port {self._port}."] + return [True, f"SMTP on port {self._port} is OK."] def handler(event, context): # pylint: disable=unused-argument @@ -28,10 +32,7 @@ def handler(event, context): # pylint: disable=unused-argument # raise the cost. For now assume that if the submission port is working, # the smtp port is working too (same gateway, same host, same processes). for port in [587]: - success, message = check_smtp(port) - print(message) - if not success: - publish(message) + CheckSMTP(port).run() if __name__ == "__main__": diff --git a/src/ssh.py b/src/ssh.py index 9d72d5b8b552c6c0c6c175e7526d1eeb8f572674..38e92d7616685a74830aed1e2bf6e3acf61ccf22 100644 --- a/src/ssh.py +++ b/src/ssh.py @@ -1,29 +1,30 @@ import socket -from utils import publish +from utils import Check -def check_ssh(host, port=22): - """Check that an SSH server is available on that host and port.""" - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - sock.connect((host, port)) - msg = sock.recv(1024) - sock.close() - return msg.startswith(b"SSH-2.0-OpenSSH") - except Exception as e: # pylint: disable=broad-except,invalid-name - print(str(e)) - return False +class CheckSSH(Check): + def __init__(self, hostname, port=22): + self._hostname = hostname + self._port = port + + def _check(self): + """Check that an SSH server is available on that host and port.""" + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + sock.connect((self._hostname, self._port)) + msg = sock.recv(1024) + sock.close() + if msg.startswith(b"SSH-2.0-OpenSSH"): + return True, f"SSH on {self._hostname}:{self._port} is OK." + return False, f"SSH on {self._hostname}:{self._port} failed." + except Exception as e: # pylint: disable=broad-except,invalid-name + return False, str(e) def handler(event, context): # pylint: disable=unused-argument """Lambda event handler.""" for host in ["ns1.shore.co.il", "ns4.shore.co.il"]: - if check_ssh(host): - print(f"SSH on {host} is OK.") - else: - message = f"SSH on {host} failed." - print(message) - publish(message) + CheckSSH(host).run() if __name__ == "__main__": diff --git a/src/transmission.py b/src/transmission.py index b4b910fa242707a678a7def1b9eb999d8f336141..5467d1dcbd12e124779f4b712dfbb538c4fb744c 100644 --- a/src/transmission.py +++ b/src/transmission.py @@ -1,13 +1,11 @@ -from utils import check_urls +from utils import website_handler +URLS = [ + {"url": "http://transmission.shore.co.il/", "codes": [301, 302]}, + {"url": "https://transmission.shore.co.il/", "codes": [401]}, +] -def handler(event, context): # pylint: disable=unused-argument - """Lambda event handler.""" - checks = [ - {"url": "http://transmission.shore.co.il/", "codes": [301, 302]}, - {"url": "https://transmission.shore.co.il/", "codes": [401]}, - ] - check_urls(checks) +handler = website_handler(URLS) if __name__ == "__main__": diff --git a/src/utils.py b/src/utils.py index 1a82db2567a124c8c61bdd6665ad0cf9ecb5c541..87fc9afeb453a4a8fbd7a789d82fb466c4743bb7 100644 --- a/src/utils.py +++ b/src/utils.py @@ -3,35 +3,65 @@ import boto3 # pylint: disable=import-error import requests # pylint: disable=import-error -TOPIC_ARN = os.getenv("TOPIC_ARN") +class Check: + _topic_arn = os.getenv("TOPIC_ARN") + _retries = int(os.getenv("RETRIES", "2")) + def run(self): + """Run the check function with retries. Alert if failed.""" + for _ in range(self._retries): + success, message = self._check() + print(message) + if success: + break + else: + self.publish(message) + return success -def publish(message): - """Publish an SNS message.""" - client = boto3.client("sns") - client.publish(TopicArn=TOPIC_ARN, Message=message) + def _check(self): + """The actual check.""" + raise NotImplementedError + def publish(self, message): + """Publish an SNS message.""" + if self._topic_arn is None: + print(f"Publish: {message}") + else: + client = boto3.client("sns") + client.publish(TopicArn=self._topic_arn, Message=message) -def check_url(url, method="GET", valid_codes=(200)): - """Checks validaty of a URL. - Allows specifying the HTTP method and a list of valid codes.""" - try: - response = requests.request(method, url, allow_redirects=False) - return response.status_code in valid_codes - except Exception as e: # pylint: disable=broad-except,invalid-name - print(str(e)) - return False +class CheckURL(Check): + _method = "GET" + _valid_codes = (200,) + def __init__(self, url, method=None, codes=None): + """Initialize the check object.""" + self._url = url + if method is not None: + self._method = method + if codes is not None: + self._valid_codes = codes -def check_urls(checks): - """Check a list of URLs.""" - for check in checks: - if check_url( - check["url"], check.get("method", "GET"), check.get("codes", [200]) - ): - print(f"{check['url']} is OK.") - else: - message = f"Failed check for {check['url']}." - print(message) - publish(message) + def _check(self): + """Checks validaty of a URL.""" + try: + response = requests.request( + self._method, self._url, allow_redirects=False + ) + return ( + response.status_code in self._valid_codes, + f"{self._url} is OK.", + ) + except Exception as e: # pylint: disable=broad-except,invalid-name + return False, str(e) + + +def website_handler(urls): # pylint: disable=unused-argument + """Return a Lambda event handler for checking web sites.""" + + def _handler(event, context): # pylint: disable=unused-argument + for url in urls: + CheckURL(url["url"], url.get("method"), url.get("codes")).run() + + return _handler diff --git a/src/vouch.py b/src/vouch.py index 3a82a4f6b8674a2d3cf349b9b2947b330c7aaf30..55ffac0466eb1204d526f7266cad04b4fc227258 100644 --- a/src/vouch.py +++ b/src/vouch.py @@ -1,13 +1,11 @@ -from utils import check_urls +from utils import website_handler +URLS = [ + {"url": "http://vouch.shore.co.il/", "codes": [301, 302]}, + {"url": "https://vouch.shore.co.il/validate", "codes": [401]}, +] -def handler(event, context): # pylint: disable=unused-argument - """Lambda event handler.""" - checks = [ - {"url": "http://vouch.shore.co.il/", "codes": [301, 302]}, - {"url": "https://vouch.shore.co.il/validate", "codes": [401]}, - ] - check_urls(checks) +handler = website_handler(URLS) if __name__ == "__main__": diff --git a/src/www.py b/src/www.py index 9d06c34d25f65c9c952488f9ae3478925acad081..efd55da1fd5b07bc8bd066da61f318c559a9a71e 100644 --- a/src/www.py +++ b/src/www.py @@ -1,15 +1,13 @@ -from utils import check_urls +from utils import website_handler +URLS = [ + {"url": "http://shore.co.il/", "codes": [301, 302]}, + {"url": "http://www.shore.co.il/", "codes": [301, 302]}, + {"url": "https://shore.co.il/", "codes": [301, 302]}, + {"url": "https://www.shore.co.il/blog"}, +] -def handler(event, context): # pylint: disable=unused-argument - """Lambda event handler.""" - checks = [ - {"url": "http://shore.co.il/", "codes": [301, 302]}, - {"url": "http://www.shore.co.il/", "codes": [301, 302]}, - {"url": "https://shore.co.il/", "codes": [301, 302]}, - {"url": "https://www.shore.co.il/blog"}, - ] - check_urls(checks) +handler = website_handler(URLS) if __name__ == "__main__":