pdns.py 4.1 KB

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