This triggers whenever a record is changed through the API.
// the DNSSEC related (getDomainMetadata has broader uses too)
virtual bool getAllDomainMetadata(const string& name, std::map<std::string, std::vector<std::string> >& meta) { return false; };
virtual bool getDomainMetadata(const string& name, const std::string& kind, std::vector<std::string>& meta) { return false; }
+ virtual bool getDomainMetadataOne(const string& name, const std::string& kind, std::string& value)
+ {
+ std::vector<std::string> meta;
+ if (getDomainMetadata(name, kind, meta)) {
+ if(!meta.empty()) {
+ value = *meta.begin();
+ return true;
+ }
+ }
+ return false;
+ }
+
virtual bool setDomainMetadata(const string& name, const std::string& kind, const std::vector<std::string>& meta) {return false;}
+ virtual bool setDomainMetadataOne(const string& name, const std::string& kind, const std::string& value)
+ {
+ const std::vector<std::string> meta(1, value);
+ return setDomainMetadata(name, kind, meta);
+ }
+
virtual void getAllDomains(vector<DomainInfo> *domains, bool include_disabled=false) { }
uint32_t calculateEditSOA(SOAData sd, const string& kind);
uint32_t localtime_format_YYYYMMDDSS(time_t t, uint32_t seq);
bool editSOA(DNSSECKeeper& dk, const string& qname, DNSPacket* dp);
+bool editSOARecord(DNSResourceRecord& rr, const string& kind);
#endif
if(rr.qtype.getCode() == QType::SOA && pdns_iequals(rr.qname,qname)) {
string kind;
dk.getFromMeta(qname, "SOA-EDIT", kind);
- if(kind.empty())
- return false;
- SOAData sd;
- fillSOAData(rr.content, sd);
- sd.serial = calculateEditSOA(sd, kind);
- rr.content = serializeSOAData(sd);
- return true;
+ return editSOARecord(rr, kind);
}
}
return false;
}
+bool editSOARecord(DNSResourceRecord& rr, const string& kind) {
+ if(kind.empty())
+ return false;
+
+ SOAData sd;
+ fillSOAData(rr.content, sd);
+ sd.serial = calculateEditSOA(sd, kind);
+ rr.content = serializeSOAData(sd);
+ return true;
+}
uint32_t calculateEditSOA(SOAData sd, const string& kind) {
if(pdns_iequals(kind,"INCEPTION")) {
#include "rapidjson/writer.h"
#include "ws-api.hh"
#include "version.hh"
+#include "dnsseckeeper.hh"
#include <iomanip>
#ifdef HAVE_CONFIG_H
doc.AddMember("name", di.zone.c_str(), doc.GetAllocator());
doc.AddMember("type", "Zone", doc.GetAllocator());
doc.AddMember("kind", di.getKindString(), doc.GetAllocator());
+ string soa_edit_api;
+ di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api);
+ doc.AddMember("soa_edit_api", soa_edit_api.c_str(), doc.GetAllocator());
Value masters;
masters.SetArray();
BOOST_FOREACH(const string& master, di.masters) {
di.backend->setKind(zonename, DomainInfo::stringToKind(stringFromJson(document, "kind")));
di.backend->setMaster(zonename, master);
+
+ if (document["soa_edit_api"].IsString()) {
+ di.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", document["soa_edit_api"].GetString());
+ }
}
static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
di.backend->startTransaction(zonename);
try {
+ string soa_edit_api_kind;
+ di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind);
+ bool soa_edit_done = false;
+
for(SizeType rrsetIdx = 0; rrsetIdx < rrsets.Size(); ++rrsetIdx) {
const Value& rrset = rrsets[rrsetIdx];
string qname, changetype;
new_ptrs.push_back(ptr);
}
+ if (rr.qtype.getCode() == QType::SOA && pdns_iequals(rr.qname, zonename)) {
+ soa_edit_done = editSOARecord(rr, soa_edit_api_kind);
+ }
+
new_records.push_back(rr);
}
}
else
throw ApiException("Changetype not understood");
}
+
+ // edit SOA (if needed)
+ if (!soa_edit_api_kind.empty() && !soa_edit_done) {
+ SOAData sd;
+ if (!B.getSOA(zonename, sd))
+ throw ApiException("No SOA found for domain '"+zonename+"'");
+
+ DNSResourceRecord rr;
+ rr.qname = zonename;
+ rr.content = serializeSOAData(sd);
+ rr.qtype = "SOA";
+ rr.domain_id = di.id;
+ rr.auth = 1;
+ rr.ttl = sd.ttl;
+ rr.priority = 0;
+ editSOARecord(rr, soa_edit_api_kind);
+
+ if (!di.backend->replaceRRSet(di.id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
+ throw ApiException("Hosting backend does not support editing records.");
+ }
+ }
+
} catch(...) {
di.backend->abortTransaction();
throw;
tf.seek(0, os.SEEK_SET) # rewind
subprocess.check_call(["sqlite3", SQLITE_DB], stdin=tf)
- pdnscmd = ("../pdns/pdns_server --daemon=no --local-port=5300 --socket-dir=./ --no-shuffle --launch=gsqlite3 --gsqlite3-dnssec --send-root-referral --allow-2136-from=127.0.0.0/8 --experimental-rfc2136=yes --cache-ttl=0 --no-config --gsqlite3-database="+SQLITE_DB+" --experimental-json-interface=yes --webserver=yes --webserver-port="+WEBPORT+" --webserver-address=127.0.0.1 --query-logging --webserver-password="+WEBPASSWORD).split()
+ pdnscmd = ("../pdns/pdns_server --daemon=no --local-port=5300 --socket-dir=./ --no-shuffle --launch=gsqlite3 --gsqlite3-dnssec --send-root-referral --allow-2136-from=127.0.0.0/8 --experimental-rfc2136=yes --cache-ttl=0 --no-config --gsqlite3-dnssec=on --gsqlite3-database="+SQLITE_DB+" --experimental-json-interface=yes --webserver=yes --webserver-port="+WEBPORT+" --webserver-address=127.0.0.1 --query-logging --webserver-password="+WEBPASSWORD).split()
else:
conf_dir = 'rec-conf.d'
import json
+import time
import requests
import unittest
from test_helper import ApiTestCase, unique_zone_name, isAuth, isRecursor
def test_CreateZone(self):
payload, data = self.create_zone()
- for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial'):
+ for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'soa_edit_api'):
+ self.assertIn(k, data)
+ if k in payload:
+ self.assertEquals(data[k], payload[k])
+ self.assertEquals(data['comments'], [])
+
+ def test_CreateZoneWithSoaEditApi(self):
+ payload, data = self.create_zone(soa_edit_api='EPOCH')
+ for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'soa_edit_api'):
self.assertIn(k, data)
if k in payload:
self.assertEquals(data[k], payload[k])
def test_UpdateZone(self):
payload, zone = self.create_zone()
name = payload['name']
- # update, set as Master
+ # update, set as Master and enable SOA-EDIT-API
payload = {
'kind': 'Master',
- 'masters': ['192.0.2.1','192.0.2.2']
+ 'masters': ['192.0.2.1','192.0.2.2'],
+ 'soa_edit_api': 'EPOCH'
}
r = self.session.put(
self.url("/servers/localhost/zones/" + name),
for k in payload.keys():
self.assertIn(k, data)
self.assertEquals(data[k], payload[k])
- # update, back to Native
+ # update, back to Native and empty(off)
payload = {
- 'kind': 'Native'
+ 'kind': 'Native',
+ 'soa_edit_api': ''
}
r = self.session.put(
self.url("/servers/localhost/zones/" + name),
self.assertEquals(recs, [])
def test_ZoneDisableReenable(self):
- payload, zone = self.create_zone()
+ # This also tests that SOA-EDIT-API works.
+ payload, zone = self.create_zone(soa_edit_api='EPOCH')
name = payload['name']
# disable zone by disabling SOA
rrset = {
data=json.dumps(payload),
headers={'content-type': 'application/json'})
self.assertSuccessJson(r)
- # make sure it's still in zone list
+ # check SOA serial has been edited
+ print r.json()
+ soa_serial1 = [rec for rec in r.json()['records'] if rec['type'] == 'SOA'][0]['content'].split()[2]
+ self.assertNotEquals(soa_serial1, '1')
+ # make sure domain is still in zone list (disabled SOA!)
r = self.session.get(self.url("/servers/localhost/zones"))
domains = r.json()
self.assertEquals(len([domain for domain in domains if domain['name'] == name]), 1)
+ # sleep 1sec to ensure the EPOCH value changes for the next request
+ time.sleep(1)
# verify that modifying it still works
rrset['records'][0]['disabled'] = False
payload = {'rrsets': [rrset]}
data=json.dumps(payload),
headers={'content-type': 'application/json'})
self.assertSuccessJson(r)
+ # check SOA serial has been edited again
+ print r.json()
+ soa_serial2 = [rec for rec in r.json()['records'] if rec['type'] == 'SOA'][0]['content'].split()[2]
+ self.assertNotEquals(soa_serial2, '1')
+ self.assertNotEquals(soa_serial2, soa_serial1)
def test_ZoneRRUpdateQTypeMismatch(self):
payload, zone = self.create_zone()