PowerDNS / pdns

PowerDNS Authoritative, PowerDNS Recursor, dnsdist
https://www.powerdns.com/
GNU General Public License v2.0
3.69k stars 907 forks source link

CASE records (LUA records on servers without DNSSEC signing key) #12597

Open lukaslihotzki opened 1 year ago

lukaslihotzki commented 1 year ago

Short description

This feature proposes the CASE record type, so LUA records can choose dynamically between predefined RR-sets instead of computing the RR-set completely dynamically. For example:

; closestContinent returns a string
www LUA A "closestContinent('EU','NA')"
www CASE 'EU' A 1.1.1.1 ; EU server 1
www CASE 'EU' A 2.2.2.2 ; EU server 2
www CASE 'NA' A 3.3.3.3 ; NA server 1
www CASE 'NA' A 4.4.4.4 ; NA server 2

Usecase

Currently, "the signing key must be available on the server creating answers based on LUA records". However, a lot of use cases for LUA records (like GeoIP, load balancing, and fallback) could be implemented without sharing the signing key to all servers that evaluate LUA records, by pre-signing all possible cases.

Security considerations

If all cases are publicly obtainable (like in GeoIP and load balancing), anyone can obtain valid RRSIGs for all cases and choose any of them when responding to intercepted requests. Therefore, offering RRSIGs for all cases to nameservers does not increase the attack surface. When there are cases that aren't publicly obtainable normally (like fallback), this option is still less risky than sharing the signing key.

Description

CASE records contain the fields case id (string), record type, and record data. If CASE records are present for the same name and record type as LUA records, the Lua code must return a case id instead of a RR set. The response RR set then contains the records whose case id match the case id returned by the LUA record. CASE records are stored in backends and sent over AXFR like LUA records. Offline signing (like for signed AXFR) is done per case, and the resulting RRSIG records are also put into their corresponding CASE records, for example:

www LUA A "closestContinent('EU','NA')"
www CASE 'EU' A 1.1.1.1
www CASE 'EU' A 2.2.2.2
www CASE 'EU' RRSIG A 5 3 86400 20030322173103 ( … )
www CASE 'NA' A 3.3.3.3
www CASE 'NA' A 4.4.4.4
www CASE 'NA' RRSIG A 5 3 86400 20030322173103 ( … )
Habbie commented 1 year ago

I have thought about something like this (where the signer signs all possible outputs) many times, but never wrote it down. What I did not think of before was to actively communicate in the (transferred) zone file which RRSIG goes with which case - after all, a serving auth can "just" figure out what goes where, or just serve all RRSIGs. So - good thinking on that!

This will not get priority from us to work on, but I do like the twist you put on this (compared to what was only in my head). I'm marking this auth-helpneeded for now, meaning that we don't hate the idea but likely won't work on it any time soon - but we will take PRs seriously.

lukaslihotzki commented 1 year ago

Thanks for the positive response.

These are my thoughts about NODATA (no error, 0 Answer RRs) responses: If the present bit in the NSEC record is set, the server cannot produce a valid NODATA response, and therefore must use a non-empty CASE. If it is unset, the server can respond with NODATA, while still being able to respond with a non-empty CASE, because the NSEC record is not used for non-empty responses. I think both options should be expressible with CASE records.

My proposal how to do this is: Introduce the default case, containing the RR set described in the zone file that is not wrapped in CASE records. The default case could be referenced by nil in the LUA record, or as a fallback for any undefined case id. The present bitmap calculation does ignore any CASE records, so the present bit is set if the default case is non-empty. This should be straight-forward to implement, but the non-empty default case (needed to disallow NODATA) would loose its meaningful case id. For example, one GeoIP region would be unnamed then. CASE records always trigger the creation of a NSEC record, so NXDOMAIN responses cannot be chosen dynamically, to keep it simple.

For the binary format, I suggest to limit the case id to a single <character-string> (max 255 characters), so the binary format would be the case id <character-string>, then 16-bit RR type, then the RR data.

If we wanted to use integers as case id, the semantics of this proposal could also be implemented when the cases were encoded as alternative DNS classes (there's a private use area of 0xFF00 - 0xFFFE), assuming the IN class should be the target class (which would be good enough probably). I just realized this possibility, I still prefer CASE records with string case id (meaningful case id, practically unlimited number of cases, support of all classes).

rgacogne commented 1 year ago

If it is unset, the server can respond with NODATA, while still being able to respond with a non-empty CASE, because the NSEC record is not used for non-empty responses.

rfc8198-enabled resolvers will no longer query the authoritative server once they have seen that NSEC-record, though.

peterthomassen commented 1 year ago

rfc8198-enabled resolvers will no longer query the authoritative server once they have seen that NSEC-record, though.

NSEC3 narrow mode could help, but contradicts the initial requirement that things be pre-signed and private keys not be shared with secondaries. So I guess this is indeed not possible.

However, one could inject an auxiliary NSEC3 record for CASE qnames before pre-signing, e.g. by placing a NULL record at the same qname when the CASE default record is absent. This should take care of DoE without side effects.