From 35f26cc5b6371a83b2c1bdffe78e17fc5b929e23 Mon Sep 17 00:00:00 2001 From: Christian Hofstaedtler Date: Mon, 17 Feb 2014 14:49:37 +0100 Subject: [PATCH] JSON API: refuse modifying out-of-zone records --- pdns/ws-auth.cc | 9 +- regression-tests.api/test_RecursorConfig.py | 10 +++ regression-tests.api/test_Zones.py | 92 +++++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) diff --git a/pdns/ws-auth.cc b/pdns/ws-auth.cc index 2aed7a0748..06678def02 100644 --- a/pdns/ws-auth.cc +++ b/pdns/ws-auth.cc @@ -522,6 +522,10 @@ static void apiServerZoneRRset(HttpRequest* req, HttpResponse* resp) { qtype = stringFromJson(document, "type"); changetype = toUpper(stringFromJson(document, "changetype")); + string dotsuffix = "." + zonename; + if(!iends_with(qname, dotsuffix) && qname != zonename) + throw ApiException("RRset "+qname+" IN "+qtype.getName()+": Name is out of zone"); + if (changetype == "DELETE") { // delete all matching qname/qtype RRs di.backend->replaceRRSet(di.id, qname, qtype, vector()); @@ -546,13 +550,16 @@ static void apiServerZoneRRset(HttpRequest* req, HttpResponse* resp) { if(rr.qtype.getCode() == QType::MX || rr.qtype.getCode() == QType::SRV) rr.content = lexical_cast(rr.priority)+" "+rr.content; + if(rr.qname != qname || rr.qtype != qtype) + throw ApiException("Record "+rr.qname+" IN "+rr.qtype.getName()+" "+rr.content+": Record bundled with wrong RRset"); + try { shared_ptr drc(DNSRecordContent::mastermake(rr.qtype.getCode(), 1, rr.content)); string tmp = drc->serialize(rr.qname); } catch(std::exception& e) { - throw ApiException("Record "+rr.qname+" IN " +rr.qtype.getName()+ " " + rr.content+": "+e.what()); + throw ApiException("Record "+rr.qname+" IN "+rr.qtype.getName()+" "+rr.content+": "+e.what()); } } // Actually store the change. diff --git a/regression-tests.api/test_RecursorConfig.py b/regression-tests.api/test_RecursorConfig.py index 30ec071691..ede214af75 100644 --- a/regression-tests.api/test_RecursorConfig.py +++ b/regression-tests.api/test_RecursorConfig.py @@ -20,3 +20,13 @@ class RecursorConfig(ApiTestCase): self.assertSuccessJson(r) data = r.json() self.assertEquals("127.0.0.1/32", data["value"][0]) + + def test_ConfigAllowFromReplaceError(self): + """Test the error case, should return 422.""" + payload = {'value': ["abcdefgh"]} + r = self.session.put( + self.url("/servers/localhost/config/allow-from"), + data=json.dumps(payload), + headers={'content-type': 'application/json'}) + self.assertEquals(r.status_code, 422) + data = r.json() diff --git a/regression-tests.api/test_Zones.py b/regression-tests.api/test_Zones.py index 7e98a8ffe9..1a89ca3abf 100644 --- a/regression-tests.api/test_Zones.py +++ b/regression-tests.api/test_Zones.py @@ -211,6 +211,98 @@ class AuthZones(ApiTestCase): headers={'content-type': 'application/json'}) self.assertSuccessJson(r) + def test_ZoneRRUpdateQTypeMismatch(self): + payload, zone = self.create_zone() + name = payload['name'] + # replace with qtype mismatch + payload = { + 'changetype': 'replace', + 'name': name, + 'type': 'A', + 'records': [ + { + "name": name, + "type": "NS", + "priority": 0, + "ttl": 3600, + "content": "ns1.bar.com", + "disabled": False + } + ] + } + r = self.session.patch( + self.url("/servers/localhost/zones/" + name + "/rrset"), + data=json.dumps(payload), + headers={'content-type': 'application/json'}) + self.assertEquals(r.status_code, 422) + + def test_ZoneRRUpdateQNameMismatch(self): + payload, zone = self.create_zone() + name = payload['name'] + # replace with qname mismatch + payload = { + 'changetype': 'replace', + 'name': name, + 'type': 'NS', + 'records': [ + { + "name": 'blah.'+name, + "type": "NS", + "priority": 0, + "ttl": 3600, + "content": "ns1.bar.com", + "disabled": False + } + ] + } + r = self.session.patch( + self.url("/servers/localhost/zones/" + name + "/rrset"), + data=json.dumps(payload), + headers={'content-type': 'application/json'}) + self.assertEquals(r.status_code, 422) + + def test_ZoneRRUpdateOutOfZone(self): + payload, zone = self.create_zone() + name = payload['name'] + # replace with qname mismatch + payload = { + 'changetype': 'replace', + 'name': 'not-in-zone', + 'type': 'NS', + 'records': [ + { + "name": name, + "type": "NS", + "priority": 0, + "ttl": 3600, + "content": "ns1.bar.com", + "disabled": False + } + ] + } + r = self.session.patch( + self.url("/servers/localhost/zones/" + name + "/rrset"), + data=json.dumps(payload), + headers={'content-type': 'application/json'}) + self.assertEquals(r.status_code, 422) + self.assertIn('out of zone', r.json()['error']) + + def test_ZoneRRDeleteOutOfZone(self): + payload, zone = self.create_zone() + name = payload['name'] + # replace with qname mismatch + payload = { + 'changetype': 'delete', + 'name': 'not-in-zone', + 'type': 'NS' + } + r = self.session.patch( + self.url("/servers/localhost/zones/" + name + "/rrset"), + data=json.dumps(payload), + headers={'content-type': 'application/json'}) + self.assertEquals(r.status_code, 422) + self.assertIn('out of zone', r.json()['error']) + @unittest.skipIf(not isRecursor(), "Not applicable") class RecursorZones(ApiTestCase): -- 2.47.2