testing proper SOA records

This commit is contained in:
AJ ONeal 2017-10-18 18:24:41 -06:00
parent a089bfc8e5
commit 6ac53c1b05
3 changed files with 221 additions and 115 deletions

View File

@ -33,46 +33,56 @@ Test that A queries for ANAME-enabled records (but no address) recurse (regardle
Generally speaking test the cases of 0, 1, and 2 records of any given type (null case, single case, multi case) Generally speaking test the cases of 0, 1, and 2 records of any given type (null case, single case, multi case)
```
# Serve:
node bin/digd.js +norecurse -p 65053 --input sample/db.json
```
``` ```
# Sample Data: # Sample Data:
# no A records for delegated.daplie.me # no A records for out-delegated.example.com
# two external NS records for delegted.daplie.me # two external NS records for delegted.example.com
# zone daplie.me exists # zone example.com exists
# Test: # Test:
# should return NS records in AUTHORITY section, nothing else # should return NS records in AUTHORITY section, nothing else
node bin/dig.js @localhost -p 65053 A delegated.daplie.me node bin/dig.js @localhost -p 65053 A out-delegated.example.com
node bin/dig.js @localhost -p 65053 ANY out-delegated.example.com
# should return SOA records in AUTHORITY section, nothing else
node bin/dig.js @localhost -p 65053 A in-delegated.example.com
node bin/dig.js @localhost -p 65053 ANY in-delegated.example.com
# should return NS records in ANSWER section, nothing else # should return NS records in ANSWER section, nothing else
node bin/dig.js @localhost -p 65053 NS delegated.daplie.me node bin/dig.js @localhost -p 65053 NS out-delegated.example.com
node bin/dig.js @localhost -p 65053 NS in-delegated.example.com
# Sample Data: # Sample Data:
# two A records for daplie.me # two A records for example.com
# no NS records # no NS records
# Test: # Test:
# should return A records in ANSWER section, nothing else # should return A records in ANSWER section, nothing else
node bin/dig.js @localhost -p 65053 A daplie.me node bin/dig.js @localhost -p 65053 A example.com
# should return SOA records in AUTHORITY section, nothing else # should return SOA records in AUTHORITY section, nothing else
node bin/dig.js @localhost -p 65053 A doesntexist.daplie.me node bin/dig.js @localhost -p 65053 A doesntexist.example.com
node bin/dig.js @localhost -p 65053 NS doesntexist.daplie.me node bin/dig.js @localhost -p 65053 NS doesntexist.example.com
# Sample Data: # Sample Data:
# two A records for a.daplie.me # two A records for a.example.com
# has **internal** NS records # has **internal** NS records
# Test: # Test:
# should return A record in ANSWER section, nothing else # should return A record in ANSWER section, nothing else
node bin/dig.js @localhost -p 65053 A a.daplie.me node bin/dig.js @localhost -p 65053 A a.example.com
# should return SOA record in AUTHORITY section, nothing else # should return SOA record in AUTHORITY section, nothing else
node bin/dig.js @localhost -p 65053 A doesntexist.a.daplie.me node bin/dig.js @localhost -p 65053 A doesntexist.a.example.com
# should return NS records in ANSWER section, nothing else # should return NS records in ANSWER section, nothing else
node bin/dig.js @localhost -p 65053 NS a.daplie.me node bin/dig.js @localhost -p 65053 NS a.example.com
``` ```

View File

@ -210,10 +210,14 @@ function domainToSoa(db, domain) {
}; };
} }
function getSoa(db, domain, results, cb) { function getSoa(db, domain, results, cb, answerSoa) {
console.log('[DEV] getSoa entered'); console.log('[DEV] getSoa entered');
results.authority.push(domainToSoa(db, domain)); if (!answerSoa) {
results.authority.push(domainToSoa(db, domain));
} else {
results.answer.push(domainToSoa(db, domain));
}
cb(null, results); cb(null, results);
return; return;
@ -270,11 +274,110 @@ module.exports.query = function (input, query, cb) {
, question: [ query.question[0] ], answer: [], authority: [], additional: [] , question: [ query.question[0] ], answer: [], authority: [], additional: []
}; };
return getRecords(db, qname, function (err, myRecords) { function getNsAndSoa(getNsAlso, answerSoa) {
// If the query is www.foo.delegated.example.com
// and we have been delegated delegated.example.com
// and delegated.example.com exists
// but foo.delegated.example.com does not exist
// what's the best strategy for returning the record?
//
// What does PowerDNS do in these situations?
// https://doc.powerdns.com/md/authoritative/backend-generic-mysql/
// How to optimize:
// Assume that if a record is being requested, it probably exists
// (someone has probably published it somewhere)
// If the record doesn't exist, then see if any of the domains are managed
// [ 'www.john.smithfam.net', 'john.smithfam.net', 'smithfam.net', 'net' ]
// Then if one of those exists, return the SOA record with NXDOMAIN
var qarr = qname.split('.');
var qnames = [];
while (qarr.length) {
qnames.push(qarr.join('.').toLowerCase());
qarr.shift(); // first
}
var myDomains = db.domains.filter(function (d) {
return -1 !== qnames.indexOf(d.id.toLowerCase());
});
// this should result in a REFUSED status
if (!myDomains.length) {
// REFUSED will have no records, so we could still recursion, if enabled
results.header.rcode = REFUSED;
cb(null, results);
return;
}
myDomains.sort(function (d1, d2) {
if (d1.id.length > d2.id.length) {
return -1;
}
if (d1.id.length < d2.id.length) {
return 1;
}
return 0;
});
//console.log('sorted domains', myDomains);
if (!getNsAlso) {
return getSoa(db, myDomains[0], results, cb, answerSoa);
}
return getNs(db, myDomains.slice(0), results, function (err, results) {
//console.log('[DEV] getNs complete');
if (err) { cb(err, results); return; }
// has NS records (or SOA record if NS records match the server itself)
if (results.authority.length) {
console.log(results); cb(null, results); return;
}
// myDomains was sorted such that the longest was first
return getSoa(db, myDomains[0], results, cb);
});
}
if ('SOA' === query.question[0].typeName) {
return getNsAndSoa(false, true);
}
//console.log('[DEV] QUERY NAME', qname);
return getRecords(db, qname, function (err, someRecords) {
var myRecords;
var nsRecords = [];
if (err) { cb(err); return; } if (err) { cb(err); return; }
// filter out NS (delegation) records, unless that is what is intended
someRecords = someRecords.filter(function (r) {
// If it's not an NS record, it's a potential result
if ('NS' !== r.type && 'NS' !== r.typeName) {
return true;
}
// If the query was for NS, it's a potential result
if ('NS' === query.question[0].typeName) {
return true;
}
// If it's a vanity NS, it's not a valid NS for lookup
if (-1 !== db.primaryNameservers.indexOf(r.data.toLowerCase())) {
return false;
}
nsRecords.push(r);
return false;
});
// TODO should NS be returned as ANSWER or AUTHORITY in ANY?
myRecords = someRecords;
if (255 !== query.question[0].type && 'ANY' !== query.question[0].typeName) { if (255 !== query.question[0].type && 'ANY' !== query.question[0].typeName) {
myRecords = myRecords.filter(function (r) { myRecords = myRecords.filter(function (r) {
return ((r.type && r.type === query.question[0].type) return ((r.type && r.type === query.question[0].type)
|| (r.type && r.type === query.question[0].typeName) || (r.type && r.type === query.question[0].typeName)
|| (r.typeName && r.typeName === query.question[0].typeName) || (r.typeName && r.typeName === query.question[0].typeName)
@ -287,72 +390,22 @@ module.exports.query = function (input, query, cb) {
results.answer.push(dbToResourceRecord(r)); results.answer.push(dbToResourceRecord(r));
}); });
results.header.rcode = NOERROR; results.header.rcode = NOERROR;
console.log('[DEV] results', results); //console.log('[DEV] ANSWER results', results);
cb(null, results);
return;
}
else if (nsRecords.length) {
nsRecords.forEach(function (r) {
results.authority.push(dbToResourceRecord(r));
});
results.header.rcode = NOERROR;
//console.log('[DEV] AUTHORITY results', results);
cb(null, results); cb(null, results);
return; return;
} }
if (!myRecords.length) { if (!myRecords.length) {
// If the query is www.foo.delegated.example.com getNsAndSoa(true);
// and we have been delegated delegated.example.com
// and delegated.example.com exists
// but foo.delegated.example.com does not exist
// what's the best strategy for returning the record?
//
// What does PowerDNS do in these situations?
// https://doc.powerdns.com/md/authoritative/backend-generic-mysql/
// How to optimize:
// Assume that if a record is being requested, it probably exists
// (someone has probably published it somewhere)
// If the record doesn't exist, then see if any of the domains are managed
// [ 'www.john.smithfam.net', 'john.smithfam.net', 'smithfam.net', 'net' ]
// Then if one of those exists, return the SOA record with NXDOMAIN
var qarr = qname.split('.');
var qnames = [];
while (qarr.length) {
qnames.push(qarr.join('.').toLowerCase());
qarr.shift(); // first
}
var myDomains = db.domains.filter(function (d) {
return -1 !== qnames.indexOf(d.id.toLowerCase());
});
// this should result in a REFUSED status
if (!myDomains.length) {
// REFUSED will have no records, so we could still recursion, if enabled
results.header.rcode = REFUSED;
cb(null, results);
return;
}
myDomains.sort(function (d1, d2) {
if (d1.id.length > d2.id.length) {
return -1;
}
if (d1.id.length < d2.id.length) {
return 1;
}
return 0;
});
console.log('sorted domains', myDomains);
return getNs(db, myDomains.slice(0), results, function (err, results) {
console.log('[DEV] getNs complete');
if (err) { cb(err, results); return; }
// has NS records (or SOA record if NS records match the server itself)
if (results.authority.length) {
console.log(results); cb(null, results); return;
}
// myDomains was sorted such that the longest was first
getSoa(db, myDomains[0], results, cb);
});
} }
}); });
}; };

View File

@ -1,69 +1,112 @@
'use strict'; 'use strict';
module.exports = { module.exports = {
"primaryNameservers": [ 'localhost' ] // 'ns1.redirect-www.org' "primaryNameservers": [ 'localhost' ] // 'ns1.vanity-dns.org'
, "domains": [ , "domains": [
{ "id": "daplie.me", "revokedAt": 0 } { "id": "example.com", "revokedAt": 0 }
, { "id": "oneal.daplie.me", "revokedAt": 0 } , { "id": "smith.example.com", "revokedAt": 0 }
, { "id": "aj.oneal.daplie.me", "revokedAt": 0, "vanityNs": [ 'ns1.daplie.me', 'ns2.daplie.me' ] } , { "id": "in-delegated.example.com", "revokedAt": 0 }
, { "id": "john.smith.example.com", "revokedAt": 0, "vanityNs": [ 'ns1.dns-server.net', 'ns2.dns-server.net' ] }
// test and probably remove
//, { "id": "out-delegated.example.com", "revokedAt": 0 }
] ]
, "records": [ , "records": [
// zone daplie.me should be able to have some records on its own // zone example.com should be able to have some records on its own
{ "zone": "daplie.me", "name": "daplie.me", "tld": "me", "sld": "daplie", "sub": "" { "zone": "example.com", "name": "example.com", "tld": "com", "sld": "example", "sub": ""
, "type": "A", "address": "23.228.168.108", "aname": "tardigrade.devices.daplie.me" } , "type": "A", "address": "1.2.3.4", "aname": "fido.devices.example.com" }
, { "zone": "example.com", "name": "example.com", "tld": "com", "sld": "example", "sub": ""
, "type": "MX", "priority": 10, "exchange": "mxa.example.org" }
, { "zone": "example.com", "name": "example.com", "tld": "com", "sld": "example", "sub": ""
, "type": "MX", "priority": 10, "exchange": "mxb.example.org" }
, { "zone": "example.com", "name": "example.com", "tld": "com", "sld": "example", "sub": ""
, "type": "SRV", "priority": 10, "weight": 20, "port": 65065, "target": "spot.devices.example.com" }
, { "zone": "example.com", "name": "example.com", "tld": "com", "sld": "example", "sub": ""
, "type": "TXT", "data": [ "foo bar baz" ] }
, { "zone": "example.com", "name": "example.com", "tld": "com", "sld": "example", "sub": ""
, "type": "TXT", "data": [ "foo", "bar", "baz" ] }
, { "zone": "daplie.me", "name": "www.daplie.me", "tld": "me", "sld": "daplie", "sub": "www" // A, CNAME, ANAME, MX, SRV, TXT
, "type": "A", "address": "23.228.168.108", "aname": "tardigrade.devices.daplie.me" } , { "zone": "example.com", "name": "a.example.com", "tld": "com", "sld": "example", "sub": "a"
, "type": "A", "address": "4.3.2.1" }
, { "zone": "example.com", "name": "aaaa.example.com", "tld": "com", "sld": "example", "sub": "aaaa"
, "type": "A", "address": "::1" }
, { "zone": "example.com", "name": "aname.example.com", "tld": "com", "sld": "example", "sub": "aname"
, "type": "A", "aname": "amazon.com" }
, { "zone": "example.com", "name": "devname.example.com", "tld": "com", "sld": "example", "sub": "devname"
, "type": "A", "address": "1.2.3.4", "aname": "fido.devices.example.com" }
, { "zone": "example.com", "name": "cname.example.com", "tld": "com", "sld": "example", "sub": "cname"
, "type": "CNAME", "data": "example.com" } // TODO should return additional
, { "zone": "example.com", "name": "mx.example.com", "tld": "com", "sld": "example", "sub": "mx"
, "type": "MX", "priority": 10, "exchange": "mxa.example.org" }
, { "zone": "example.com", "name": "mx.example.com", "tld": "com", "sld": "example", "sub": "mx"
, "type": "MX", "priority": 10, "exchange": "mxb.example.org" }
, { "zone": "example.com", "name": "srv.example.com", "tld": "com", "sld": "example", "sub": "srv"
, "type": "SRV", "priority": 10, "weight": 20, "port": 65065, "target": "spot.devices.example.com" }
, { "zone": "example.com", "name": "txt.example.com", "tld": "com", "sld": "example", "sub": "txt"
, "type": "TXT", "data": [ "foo bar baz" ] }
, { "zone": "example.com", "name": "mtxt.example.com", "tld": "com", "sld": "example", "sub": "mtxt"
, "type": "TXT", "data": [ "foo", "bar", "baz" ] }
, { "zone": "daplie.me", "name": "aname.daplie.me", "tld": "me", "sld": "daplie", "sub": "aname" // www., email., etc just for fun
, "type": "A", "aname": "google.com" } , { "zone": "example.com", "name": "www.example.com", "tld": "com", "sld": "example", "sub": "www"
, "type": "A", "address": "1.2.3.4", "aname": "fido.devices.example.com" }
, { "zone": "daplie.me", "name": "email.daplie.me", "tld": "me", "sld": "daplie", "sub": "email" , { "zone": "example.com", "name": "email.example.com", "tld": "com", "sld": "example", "sub": "email"
, "type": "CNAME", "data": "mailgun.org" } , "type": "CNAME", "data": "mailgun.org" }
, { "zone": "daplie.me", "name": "tardigrade.devices.daplie.me", "tld": "me", "sld": "daplie", "sub": "tardigrade.devices"
// Out-delegated Domains
, { "zone": "example.com", "type": "NS", "name": "out-delegated.example.com"
, "tld": "com", "sld": "example", "sub": "out-delegated", "data": "ns1.vanity-dns.org" }
, { "zone": "example.com", "type": "NS", "name": "out-delegated.example.com"
, "tld": "com", "sld": "example", "sub": "out-delegated", "data": "ns2.vanity-dns.org" }
// In-delegated Domains
, { "zone": "example.com", "type": "NS", "name": "in-delegated.example.com"
, "tld": "com", "sld": "example", "sub": "in-delegated", "data": "localhost" }
, { "zone": "example.com", "name": "fido.devices.example.com", "tld": "com", "sld": "example", "sub": "fido.devices"
, "device": "abcdef123" , "device": "abcdef123"
, "type": "ANAME", "address": "23.228.168.108" } , "type": "ANAME", "address": "1.2.3.4" }
// zone daplie.me can delegate oneal.daplie.me to the same nameserver // zone example.com can delegate smith.example.com to the same nameserver
// (it's probably programmatically and politically simplest to always delegate from a parent zone) // (it's probably programmatically and politically simplest to always delegate from a parent zone)
// Thought Experiment: could we delegate the root to a child? i.e. daplie.me -> www.daplie.me // Thought Experiment: could we delegate the root to a child? i.e. example.com -> www.example.com
// to let someone exclusively "own" the root domain, but none of the children? // to let someone exclusively "own" the root domain, but none of the children?
, { "zone": "daplie.me", "type": "NS", "name": "oneal.daplie.me" , { "zone": "example.com", "type": "NS", "name": "smith.example.com"
, "tld": "me", "sld": "daplie", "sub": "oneal", "data": "ns1.redirect-www.org" } , "tld": "com", "sld": "example", "sub": "smith", "data": "ns1.vanity-dns.org" }
, { "zone": "daplie.me", "name": "oneal.daplie.me", "tld": "me", "sld": "daplie", "sub": "oneal" , { "zone": "example.com", "name": "smith.example.com", "tld": "com", "sld": "example", "sub": "smith"
, "type": "NS", "data": "ns2.redirect-www.org" } , "type": "NS", "data": "ns2.vanity-dns.org" }
// //
// now the zone "oneal.daplie.me" can be independently owned (and delegated) // now the zone "smith.example.com" can be independently owned (and delegated)
// ... but what about email for aj@daplie.me with aj@daplie.me? // ... but what about email for john@example.com with john@example.com?
, { "zone": "oneal.daplie.me", "name": "oneal.daplie.me", "tld": "daplie.me", "sld": "oneal", "sub": "" , { "zone": "smith.example.com", "name": "smith.example.com", "tld": "example.com", "sld": "smith", "sub": ""
, "type": "A", "address": "45.56.59.142", "aname": "leo.devices.oneal.daplie.me" } , "type": "A", "address": "45.56.59.142", "aname": "rex.devices.smith.example.com" }
, { "zone": "oneal.daplie.me", "name": "www.oneal.daplie.me", "tld": "daplie.me", "sld": "oneal", "sub": "www" , { "zone": "smith.example.com", "name": "www.smith.example.com", "tld": "example.com", "sld": "smith", "sub": "www"
, "type": "CNAME", "data": "oneal.daplie.me" } , "type": "CNAME", "data": "smith.example.com" }
, { "zone": "oneal.daplie.me", "name": "aj.oneal.daplie.me", "tld": "daplie.me", "sld": "oneal", "sub": "aj" , { "zone": "smith.example.com", "name": "john.smith.example.com", "tld": "example.com", "sld": "smith", "sub": "john"
, "type": "NS", "data": "ns1.redirect-www.org" } , "type": "NS", "data": "ns1.vanity-dns.org" }
, { "zone": "oneal.daplie.me", "name": "aj.oneal.daplie.me", "tld": "daplie.me", "sld": "oneal", "sub": "aj" , { "zone": "smith.example.com", "name": "john.smith.example.com", "tld": "example.com", "sld": "smith", "sub": "john"
, "type": "NS", "data": "ns2.redirect-www.org" } , "type": "NS", "data": "ns2.vanity-dns.org" }
// there can be a wildcard, to which a delegation is the exception // there can be a wildcard, to which a delegation is the exception
, { "zone": "oneal.daplie.me", "name": "*.oneal.daplie.me", "tld": "daplie.me", "sld": "oneal", "sub": "*" , { "zone": "smith.example.com", "name": "*.smith.example.com", "tld": "example.com", "sld": "smith", "sub": "*"
, "type": "A", "address": "45.56.59.142", "aname": "leo.devices.oneal.daplie.me" } , "type": "A", "address": "45.56.59.142", "aname": "rex.devices.smith.example.com" }
// there can be an exception to the delegation // there can be an exception to the delegation
, { "zone": "oneal.daplie.me", "name": "exception.aj.oneal.daplie.me", "tld": "daplie.me", "sld": "oneal", "sub": "exception.aj" , { "zone": "smith.example.com", "name": "exception.john.smith.example.com", "tld": "example.com", "sld": "smith", "sub": "exception.john"
, "type": "A", "address": "45.56.59.142", "aname": "leo.devices.oneal.daplie.me" } , "type": "A", "address": "45.56.59.142", "aname": "rex.devices.smith.example.com" }
// //
// aj.oneal.daplie.me // john.smith.example.com
// //
, { "zone": "aj.oneal.daplie.me", "name": "aj.oneal.daplie.me", "tld": "oneal.daplie.me", "sld": "aj", "sub": "" , { "zone": "john.smith.example.com", "name": "john.smith.example.com", "tld": "smith.example.com", "sld": "john", "sub": ""
, "type": "A", "address": "45.56.59.142", "aname": "leo.devices.oneal.daplie.me" } , "type": "A", "address": "45.56.59.142", "aname": "rex.devices.smith.example.com" }
] ]
} }
; ;