pdns.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import requests
  2. import json
  3. import random
  4. from desecapi import settings
  5. from desecapi.exceptions import PdnsException
  6. headers_nslord = {
  7. 'Accept': 'application/json',
  8. 'User-Agent': 'desecapi',
  9. 'X-API-Key': settings.NSLORD_PDNS_API_TOKEN,
  10. }
  11. headers_nsmaster = {
  12. 'User-Agent': 'desecapi',
  13. 'X-API-Key': settings.NSMASTER_PDNS_API_TOKEN,
  14. }
  15. def _pdns_delete(url):
  16. # We first delete the zone from nslord, the main authoritative source of our DNS data.
  17. # However, we do not want to wait for the zone to expire on the slave ("nsmaster").
  18. # We thus issue a second delete request on nsmaster to delete the zone there immediately.
  19. r1 = requests.delete(settings.NSLORD_PDNS_API + url, headers=headers_nslord)
  20. if r1.status_code < 200 or r1.status_code >= 300:
  21. # Deletion technically does not fail if the zone didn't exist in the first place
  22. if r1.status_code == 422 and 'Could not find domain' in r1.text:
  23. pass
  24. else:
  25. raise PdnsException(r1)
  26. # Delete from nsmaster as well
  27. r2 = requests.delete(settings.NSMASTER_PDNS_API + url, headers=headers_nsmaster)
  28. if r2.status_code < 200 or r2.status_code >= 300:
  29. # Deletion technically does not fail if the zone didn't exist in the first place
  30. if r2.status_code == 422 and 'Could not find domain' in r2.text:
  31. pass
  32. else:
  33. raise PdnsException(r2)
  34. return (r1, r2)
  35. def _pdns_post(url, body):
  36. r = requests.post(settings.NSLORD_PDNS_API + url, data=json.dumps(body), headers=headers_nslord)
  37. if r.status_code < 200 or r.status_code >= 300:
  38. raise PdnsException(r)
  39. return r
  40. def _pdns_patch(url, body):
  41. r = requests.patch(settings.NSLORD_PDNS_API + url, data=json.dumps(body), headers=headers_nslord)
  42. if r.status_code < 200 or r.status_code >= 300:
  43. raise PdnsException(r)
  44. return r
  45. def _pdns_get(url):
  46. r = requests.get(settings.NSLORD_PDNS_API + url, headers=headers_nslord)
  47. if r.status_code < 200 or r.status_code >= 400:
  48. raise PdnsException(r)
  49. return r
  50. def _pdns_put(url):
  51. r = requests.put(settings.NSLORD_PDNS_API + url, headers=headers_nslord)
  52. if r.status_code < 200 or r.status_code >= 500:
  53. raise PdnsException(r)
  54. return r
  55. def create_zone(domain, nameservers):
  56. """
  57. Commands pdns to create a zone with the given name and nameservers.
  58. """
  59. name = domain.name
  60. if not name.endswith('.'):
  61. name += '.'
  62. salt = '%016x' % random.randrange(16**16)
  63. payload = {'name': name, 'kind': 'MASTER', 'dnssec': True,
  64. 'nsec3param': '1 0 127 %s' % salt, 'nameservers': nameservers}
  65. _pdns_post('/zones', payload)
  66. notify_zone(domain)
  67. def delete_zone(domain):
  68. """
  69. Commands pdns to delete a zone with the given name.
  70. """
  71. _pdns_delete('/zones/' + domain.pdns_id)
  72. def get_keys(domain):
  73. """
  74. Retrieves a dict representation of the DNSSEC key information
  75. """
  76. try:
  77. r = _pdns_get('/zones/%s/cryptokeys' % domain.pdns_id)
  78. return [{k: key[k] for k in ('dnskey', 'ds', 'flags', 'keytype')}
  79. for key in r.json()
  80. if key['active'] and key['keytype'] in ['csk', 'ksk']]
  81. except PdnsException as e:
  82. if e.status_code in [400, 404]:
  83. return []
  84. raise e
  85. def get_zone(domain):
  86. """
  87. Retrieves a dict representation of the zone from pdns
  88. """
  89. r = _pdns_get('/zones/' + domain.pdns_id)
  90. return r.json()
  91. def get_rrset_datas(domain):
  92. """
  93. Retrieves a dict representation of the RRsets in a given zone
  94. """
  95. return [{'domain': domain,
  96. 'subname': rrset['name'][:-(len(domain.name) + 2)],
  97. 'type': rrset['type'],
  98. 'records': [record['content'] for record in rrset['records']],
  99. 'ttl': rrset['ttl']}
  100. for rrset in get_zone(domain)['rrsets']]
  101. def set_rrset(rrset, notify=True):
  102. return set_rrsets(rrset.domain, [rrset], notify=notify)
  103. def set_rrsets(domain, rrsets, notify=True):
  104. data = {'rrsets':
  105. [{'name': rrset.name, 'type': rrset.type, 'ttl': rrset.ttl,
  106. 'changetype': 'REPLACE',
  107. 'records': [{'content': record.content, 'disabled': False}
  108. for record in rrset.records.all()]
  109. }
  110. for rrset in rrsets]
  111. }
  112. _pdns_patch('/zones/' + domain.pdns_id, data)
  113. if notify:
  114. notify_zone(domain)
  115. def notify_zone(domain):
  116. """
  117. Commands pdns to notify the zone to the pdns slaves.
  118. """
  119. _pdns_put('/zones/%s/notify' % domain.pdns_id)