From: Remi Gacogne Date: Wed, 16 Dec 2020 16:40:15 +0000 (+0100) Subject: rec: Add a unit test for the 'DS entry expired but CNAME is here' issue X-Git-Tag: rec-4.5.0-alpha1~40^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b26212090b16c0a4e7f583e10586e5e85e2b66ae;p=thirdparty%2Fpdns.git rec: Add a unit test for the 'DS entry expired but CNAME is here' issue --- diff --git a/pdns/recursordist/test-syncres_cc.cc b/pdns/recursordist/test-syncres_cc.cc index 26adc9762d..a3845fe154 100644 --- a/pdns/recursordist/test-syncres_cc.cc +++ b/pdns/recursordist/test-syncres_cc.cc @@ -445,7 +445,7 @@ LWResult::Result genericDSAndDNSKEYHandler(LWResult* res, const DNSName& domain, /* sign the SOA */ addRRSIG(keys, res->d_records, auth, 300, false, boost::none, boost::none, now); /* add a NSEC denying the DS */ - std::set types = {nsec3 ? QType::NSEC : QType::NSEC3}; + std::set types = {QType::RRSIG}; if (proveCut) { types.insert(QType::NS); } diff --git a/pdns/recursordist/test-syncres_cc7.cc b/pdns/recursordist/test-syncres_cc7.cc index fb3d39073a..235ff0581c 100644 --- a/pdns/recursordist/test-syncres_cc7.cc +++ b/pdns/recursordist/test-syncres_cc7.cc @@ -1456,4 +1456,140 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nxdomain) BOOST_CHECK_EQUAL(queriesCount, 4U); } +BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cut_with_cname_at_apex) +{ + std::unique_ptr sr; + initSR(sr, true); + + setDNSSECValidation(sr, DNSSECMode::ValidateAll); + + primeHints(); + const DNSName target("powerdns.com."); + const DNSName targetCName("power-dns.com."); + const ComboAddress targetCNameAddr("192.0.2.42"); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys); + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target, targetCName, targetCNameAddr, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, LWResult* res, bool* chained) { + queriesCount++; + + if (type == QType::DS) { + if (domain == DNSName("www.powerdns.com.") || domain == DNSName("www2.powerdns.com.")) { + return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false); + } + else { + return genericDSAndDNSKEYHandler(res, domain, domain, type, keys); + } + } + else if (type == QType::DNSKEY) { + if (domain == g_rootdnsname || domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + return LWResult::Result::Success; + } + else { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + return LWResult::Result::Success; + } + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("com."), 300, res->d_records, keys); + addRRSIG(keys, res->d_records, DNSName("."), 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return LWResult::Result::Success; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + setLWResult(res, 0, false, false, true); + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + } + else { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + if (domain.isPartOf(DNSName("powerdns.com."))) { + addNSECRecordToLW(domain, DNSName("z.powerdns.com."), {QType::NS}, 600, res->d_records); + } + else if (domain == targetCName) { + addNSECRecordToLW(domain, DNSName("z.power-dns.com."), {QType::NS}, 600, res->d_records); + } + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + + return LWResult::Result::Success; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + setLWResult(res, 0, true, false, true); + + if (type == QType::NS) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + else { + if (domain == DNSName("powerdns.com.")) { + addRecordToLW(res, domain, QType::CNAME, targetCName.toString()); + } + else if (domain == DNSName("www.powerdns.com.") || domain == DNSName("www2.powerdns.com.")) { + addRecordToLW(res, domain, QType::A, "192.0.2.43"); + } + else if (domain == targetCName) { + addRecordToLW(res, domain, QType::A, targetCNameAddr.toString()); + } + } + + return LWResult::Result::Success; + } + } + + return LWResult::Result::Timeout; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 2U); + BOOST_CHECK_EQUAL(queriesCount, 10U); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 2U); + BOOST_CHECK_EQUAL(queriesCount, 10U); + + /* this time we ask for www.powerdns.com, let's make sure the CNAME does not get in the way */ + ret.clear(); + res = sr->beginResolve(DNSName("www.powerdns.com."), QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 1U); + BOOST_CHECK_EQUAL(queriesCount, 11U); + + /* now we remove the denial of powerdns.com DS from the cache and ask www2 */ + BOOST_REQUIRE_EQUAL(g_negCache->wipe(target, false), 1); + ret.clear(); + res = sr->beginResolve(DNSName("www2.powerdns.com."), QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 1U); + BOOST_CHECK_EQUAL(queriesCount, 12U); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/pdns/syncres.cc b/pdns/syncres.cc index 0dbcb19bcc..611a07fea6 100644 --- a/pdns/syncres.cc +++ b/pdns/syncres.cc @@ -138,6 +138,7 @@ int SyncRes::beginResolve(const DNSName &qname, const QType &qtype, uint16_t qcl s_queries++; d_wasVariable=false; d_wasOutOfBand=false; + d_cutStates.clear(); if (doSpecialNamesResolve(qname, qtype, qclass, ret)) { d_queryValidationState = vState::Insecure; // this could fool our stats into thinking a validation took place @@ -949,7 +950,7 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName &qname, const QType &qty return res; } - /* if we have not found a cached DS (or denial of), now is the time to look for a CNAME */ + /* if we have not found a cached DS (or denial of), now is the time to look for a CNAME */ if (qtype == QType::DS && doCNAMECacheCheck(qname, qtype, ret, depth, res, state, wasAuthZone, wasForwardRecurse)) { // will reroute us if needed d_wasOutOfBand = wasAuthZone; // Here we have an issue. If we were prevented from going out to the network (cache-only was set, possibly because we