pdns.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  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. if data is not None and len(data) > settings.PDNS_MAX_BODY_SIZE:
  39. raise PdnsException(detail='Payload too large', status=413)
  40. r = requests.request(method, settings.NSLORD_PDNS_API + path, data=data, headers=headers_nslord)
  41. if r.status_code not in acceptable_range:
  42. raise PdnsException(r)
  43. return r
  44. def _pdns_post(path, body):
  45. return _pdns_request('post', path, body)
  46. def _pdns_patch(path, body):
  47. return _pdns_request('patch', path, body)
  48. def _pdns_get(path):
  49. return _pdns_request('get', path, acceptable_range=range(200, 400))
  50. def _pdns_put(path):
  51. return _pdns_request('put', path, acceptable_range=range(200, 500))
  52. def create_zone(domain, nameservers):
  53. """
  54. Commands pdns to create a zone with the given name and nameservers.
  55. """
  56. name = domain.name
  57. if not name.endswith('.'):
  58. name += '.'
  59. salt = '%016x' % random.randrange(16**16)
  60. payload = {'name': name, 'kind': 'MASTER', 'dnssec': True,
  61. 'nsec3param': '1 0 127 %s' % salt, 'nameservers': nameservers}
  62. _pdns_post('/zones', payload)
  63. notify_zone(domain)
  64. def delete_zone(domain):
  65. """
  66. Commands pdns to delete a zone with the given name.
  67. """
  68. return _pdns_delete_zone(domain)
  69. def get_keys(domain):
  70. """
  71. Retrieves a dict representation of the DNSSEC key information
  72. """
  73. r = _pdns_get('/zones/%s/cryptokeys' % domain.pdns_id)
  74. return [{k: key[k] for k in ('dnskey', 'ds', 'flags', 'keytype')}
  75. for key in r.json()
  76. if key['active'] and key['keytype'] in ['csk', 'ksk']]
  77. def get_zone(domain):
  78. """
  79. Retrieves a dict representation of the zone from pdns
  80. """
  81. r = _pdns_get('/zones/' + domain.pdns_id)
  82. return r.json()
  83. def get_rrset_datas(domain):
  84. """
  85. Retrieves a dict representation of the RRsets in a given zone
  86. """
  87. return [{'domain': domain,
  88. 'subname': rrset['name'][:-(len(domain.name) + 2)],
  89. 'type': rrset['type'],
  90. 'records': [record['content'] for record in rrset['records']],
  91. 'ttl': rrset['ttl']}
  92. for rrset in get_zone(domain)['rrsets']]
  93. def set_rrsets(domain, rrsets, notify=True):
  94. data = {'rrsets':
  95. [{'name': rrset.name, 'type': rrset.type, 'ttl': rrset.ttl,
  96. 'changetype': 'REPLACE',
  97. 'records': [{'content': record.content, 'disabled': False}
  98. for record in rrset.records.all()]
  99. }
  100. for rrset in rrsets]
  101. }
  102. _pdns_patch('/zones/' + domain.pdns_id, data)
  103. if notify:
  104. notify_zone(domain)
  105. def notify_zone(domain):
  106. """
  107. Commands pdns to notify the zone to the pdns slaves.
  108. """
  109. _pdns_put('/zones/%s/notify' % domain.pdns_id)