Explorar el Código

Merge pull request #37 from desec-io/20170409_acme_dns

feat(api): DNS challenge support for Let's Encrypt
Peter Thomassen hace 8 años
padre
commit
8510f5db79

+ 20 - 0
api/desecapi/migrations/0013_acme_challenge.py

@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.3 on 2016-12-27 07:59
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('desecapi', '0012_move_dyn_flag'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='domain',
+            name='acme_challenge',
+            field=models.CharField(max_length=255, blank=True),
+        ),
+    ]

+ 10 - 3
api/desecapi/models.py

@@ -99,6 +99,7 @@ class Domain(models.Model):
     arecord = models.CharField(max_length=255, blank=True)
     aaaarecord = models.CharField(max_length=1024, blank=True)
     owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='domains')
+    acme_challenge = models.CharField(max_length=255, blank=True)
     _dirtyName = False
     _dirtyRecords = False
 
@@ -127,6 +128,12 @@ class Domain(models.Model):
 
         return val
 
+    def setter_acme_challenge(self, val):
+        if val != self.acme_challenge:
+            self._dirtyRecords = True
+
+        return val
+
     def clean(self):
         if self._dirtyName:
             raise ValidationError('You must not change the domain name')
@@ -142,7 +149,7 @@ class Domain(models.Model):
             pdns.create_zone(self.name)
 
         # update zone to latest information
-        pdns.set_dyn_records(self.name, self.arecord, self.aaaarecord)
+        pdns.set_dyn_records(self.name, self.arecord, self.aaaarecord, self.acme_challenge)
 
     def pdns_sync(self, new_domain):
         """
@@ -156,11 +163,11 @@ class Domain(models.Model):
         # if this zone is new, create it and set dirty flag if necessary
         if new_domain:
             pdns.create_zone(self.name)
-            self._dirtyRecords = bool(self.arecord) or bool(self.aaaarecord)
+            self._dirtyRecords = bool(self.arecord) or bool(self.aaaarecord) or bool(self.acme_challenge)
 
         # make changes if necessary
         if self._dirtyRecords:
-            pdns.set_dyn_records(self.name, self.arecord, self.aaaarecord)
+            pdns.set_dyn_records(self.name, self.arecord, self.aaaarecord, self.acme_challenge)
 
         self._dirtyRecords = False
 

+ 2 - 1
api/desecapi/pdns.py

@@ -127,7 +127,7 @@ def zone_exists(name):
         raise Exception(reply.text)
 
 
-def set_dyn_records(name, a, aaaa):
+def set_dyn_records(name, a, aaaa, acme_challenge=''):
     """
     Commands pdns to set the A and AAAA record for the zone with the given name to the given record values.
     Only supports one A, one AAAA record.
@@ -139,6 +139,7 @@ def set_dyn_records(name, a, aaaa):
         "rrsets": [
             _delete_or_replace_rrset(name, 'a', a),
             _delete_or_replace_rrset(name, 'aaaa', aaaa),
+            _delete_or_replace_rrset('_acme-challenge.%s' % name, 'txt', '"%s"' % acme_challenge),
         ]
     })
 

+ 1 - 1
api/desecapi/serializers.py

@@ -9,7 +9,7 @@ class DomainSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = Domain
-        fields = ('name', 'owner', 'arecord', 'aaaarecord', 'created', 'updated')
+        fields = ('name', 'owner', 'arecord', 'aaaarecord', 'created', 'updated', 'acme_challenge')
         read_only_fields = ('created', 'updated',)
 
 

+ 27 - 1
api/desecapi/tests/testdomains.py

@@ -170,6 +170,16 @@ class AuthenticatedDomainTests(APITestCase):
         self.assertEqual(response.status_code, status.HTTP_200_OK)
         self.assertEqual(response.data['aaaarecord'], 'fe80::a11:10ff:fee0:ff77')
 
+    def testCanUpdateAcmeChallenge(self):
+        url = reverse('domain-detail', args=(self.ownedDomains[1].pk,))
+        response = self.client.get(url)
+        response.data['acme_challenge'] = 'test_challenge'
+        response = self.client.put(url, response.data)
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertEqual(response.data['acme_challenge'], 'test_challenge')
+
     def testPostingCausesPdnsAPICall(self):
         httpretty.enable()
         httpretty.register_uri(httpretty.POST, settings.NSLORD_PDNS_API + '/zones')
@@ -189,13 +199,29 @@ class AuthenticatedDomainTests(APITestCase):
         httpretty.register_uri(httpretty.PATCH, settings.NSLORD_PDNS_API + '/zones/' + name + '.')
 
         url = reverse('domain-list')
-        data = {'name': name, 'arecord': '1.3.3.7', 'aaaarecord': 'dead::beef'}
+        data = {'name': name, 'arecord': '1.3.3.7', 'aaaarecord': 'dead::beef', 'acme_challenge': 'letsencrypt_ftw'}
         response = self.client.post(url, data)
 
         self.assertEqual(httpretty.last_request().method, 'PATCH')
         self.assertTrue(data['name'] in httpretty.last_request().parsed_body)
         self.assertTrue('1.3.3.7' in httpretty.last_request().parsed_body)
         self.assertTrue('dead::beef' in httpretty.last_request().parsed_body)
+        self.assertTrue('letsencrypt_ftw' in httpretty.last_request().parsed_body)
+
+    def testPostDomainCausesPdnsAPIPatch(self):
+        name = utils.generateDomainname()
+
+        httpretty.enable()
+        httpretty.register_uri(httpretty.POST, settings.NSLORD_PDNS_API + '/zones')
+        httpretty.register_uri(httpretty.PATCH, settings.NSLORD_PDNS_API + '/zones/' + name + '.')
+
+        url = reverse('domain-list')
+        data = {'name': name, 'acme_challenge': 'letsencrypt_ftw'}
+        self.client.post(url, data)
+
+        self.assertEqual(httpretty.last_request().method, 'PATCH')
+        self.assertTrue(data['name'] in httpretty.last_request().parsed_body)
+        self.assertTrue('letsencrypt_ftw' in httpretty.last_request().parsed_body)
 
     def testUpdateingCausesPdnsAPICall(self):
         url = reverse('domain-detail', args=(self.ownedDomains[1].pk,))