chores.py 3.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. import time
  2. from socket import gethostbyname
  3. from django.conf import settings
  4. from django.core.mail import get_connection, mail_admins
  5. from django.core.management import BaseCommand
  6. from django.utils import timezone
  7. import dns.message, dns.rdatatype, dns.query
  8. from desecapi import models, serializers
  9. from desecapi.pdns_change_tracker import PDNSChangeTracker
  10. class Command(BaseCommand):
  11. @staticmethod
  12. def delete_expired_captchas():
  13. models.Captcha.objects.filter(created__lt=timezone.now() - settings.CAPTCHA_VALIDITY_PERIOD).delete()
  14. @staticmethod
  15. def delete_never_activated_users():
  16. # delete inactive users whose activation link expired and who never logged in
  17. # (this will not delete users who have used their account and were later disabled)
  18. models.User.objects.filter(is_active=False, last_login__exact=None,
  19. created__lt=timezone.now() - settings.VALIDITY_PERIOD_VERIFICATION_SIGNATURE).delete()
  20. @staticmethod
  21. def update_healthcheck_timestamp():
  22. name = 'internal-timestamp.desec.test'
  23. try:
  24. domain = models.Domain.objects.get(name=name)
  25. except models.Domain.DoesNotExist:
  26. # Fail silently. If external alerting is configured, it will catch the problem; otherwise, we don't need it.
  27. print(f'{name} zone is not configured; skipping TXT record update')
  28. return
  29. instances = domain.rrset_set.filter(subname='', type='TXT').all()
  30. timestamp = int(time.time())
  31. content = f'"{timestamp}"'
  32. data = [{
  33. 'subname': '',
  34. 'type': 'TXT',
  35. 'ttl': '3600',
  36. 'records': [content]
  37. }]
  38. context = {'domain': domain}
  39. serializer = serializers.RRsetSerializer(instances, data=data, many=True, partial=True, context=context)
  40. serializer.is_valid(raise_exception=True)
  41. with PDNSChangeTracker():
  42. serializer.save()
  43. print(f'TXT {name} updated to {content}')
  44. @staticmethod
  45. def alerting_healthcheck():
  46. name = 'external-timestamp.desec.test'
  47. try:
  48. models.Domain.objects.get(name=name)
  49. except models.Domain.DoesNotExist:
  50. print(f'{name} zone is not configured; skipping alerting health check')
  51. return
  52. timestamps = []
  53. qname = dns.name.from_text(name)
  54. query = dns.message.make_query(qname, dns.rdatatype.TXT)
  55. server = gethostbyname('ns1.desec.io')
  56. response = None
  57. try:
  58. response = dns.query.tcp(query, server, timeout=5)
  59. for content in response.find_rrset(dns.message.ANSWER, qname, dns.rdataclass.IN, dns.rdatatype.TXT):
  60. timestamps.append(str(content)[1:-1])
  61. except Exception:
  62. pass
  63. now = time.time()
  64. if any(now - 600 <= int(timestamp) <= now for timestamp in timestamps):
  65. print(f'TXT {name} up to date.')
  66. return
  67. timestamps = ', '.join(timestamps)
  68. print(f'TXT {name} out of date! Timestamps: {timestamps}')
  69. subject = 'ALERT Alerting system down?'
  70. message = f'TXT query for {name} on {server} gave the following response:\n'
  71. message += f'{str(response)}\n\n'
  72. message += f'Extracted timestamps in TXT RRset:\n{timestamps}'
  73. mail_admins(subject, message, connection=get_connection('django.core.mail.backends.smtp.EmailBackend'))
  74. def handle(self, *args, **kwargs):
  75. try:
  76. self.alerting_healthcheck()
  77. self.update_healthcheck_timestamp()
  78. self.delete_expired_captchas()
  79. self.delete_never_activated_users()
  80. except Exception as e:
  81. subject = 'chores Exception!'
  82. message = f'{type(e)}\n\n{str(e)}'
  83. print(f'Chores exception: {type(e)}, {str(e)}')
  84. mail_admins(subject, message, connection=get_connection('django.core.mail.backends.smtp.EmailBackend'))