2017-09-14 09:36:06 +01:00
// Package dnssec implements a plugin that signs responses on-the-fly using
2016-09-25 08:39:20 +01:00
// NSEC black lies.
2016-04-26 17:57:11 +01:00
package dnssec
import (
"time"
2017-09-14 09:36:06 +01:00
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/cache"
"github.com/coredns/coredns/plugin/pkg/response"
"github.com/coredns/coredns/plugin/pkg/singleflight"
2017-02-21 22:51:47 -08:00
"github.com/coredns/coredns/request"
2016-04-26 17:57:11 +01:00
"github.com/miekg/dns"
)
2016-09-23 09:14:12 +01:00
// Dnssec signs the reply on-the-fly.
2016-04-26 17:57:11 +01:00
type Dnssec struct {
2017-09-14 09:36:06 +01:00
Next plugin . Handler
2016-08-19 17:14:17 -07:00
2018-10-20 17:35:59 +02:00
zones [ ] string
keys [ ] * DNSKEY
splitkeys bool
inflight * singleflight . Group
cache * cache . Cache
2016-04-26 17:57:11 +01:00
}
2016-09-23 09:14:12 +01:00
// New returns a new Dnssec.
2018-10-20 17:35:59 +02:00
func New ( zones [ ] string , keys [ ] * DNSKEY , splitkeys bool , next plugin . Handler , c * cache . Cache ) Dnssec {
2016-04-26 17:57:11 +01:00
return Dnssec { Next : next ,
2018-10-20 17:35:59 +02:00
zones : zones ,
keys : keys ,
splitkeys : splitkeys ,
cache : c ,
inflight : new ( singleflight . Group ) ,
2016-04-26 17:57:11 +01:00
}
}
2016-09-07 11:10:16 +01:00
// Sign signs the message in state. it takes care of negative or nodata responses. It
2017-10-20 09:22:02 +01:00
// uses NSEC black lies for authenticated denial of existence. For delegations it
// will insert DS records and sign those.
// Signatures will be cached for a short while. By default we sign for 8 days,
2016-04-26 17:57:11 +01:00
// starting 3 hours ago.
2018-04-27 19:37:31 +01:00
func ( d Dnssec ) Sign ( state request . Request , now time . Time , server string ) * dns . Msg {
2016-04-26 17:57:11 +01:00
req := state . Req
2016-09-07 11:10:16 +01:00
2017-10-20 09:22:02 +01:00
incep , expir := incepExpir ( now )
2017-04-29 15:06:42 +01:00
mt , _ := response . Typify ( req , time . Now ( ) . UTC ( ) ) // TODO(miek): need opt record here?
2016-09-07 11:10:16 +01:00
if mt == response . Delegation {
2023-04-22 22:32:01 +02:00
// We either sign DS or NSEC of DS.
ttl := req . Ns [ 0 ] . Header ( ) . Ttl
ds := [ ] dns . RR { }
for i := range req . Ns {
if req . Ns [ i ] . Header ( ) . Rrtype == dns . TypeDS {
ds = append ( ds , req . Ns [ i ] )
}
}
if len ( ds ) == 0 {
if sigs , err := d . nsec ( state , mt , ttl , incep , expir , server ) ; err == nil {
req . Ns = append ( req . Ns , sigs ... )
}
} else if sigs , err := d . sign ( ds , state . Zone , ttl , incep , expir , server ) ; err == nil {
req . Ns = append ( req . Ns , sigs ... )
}
2016-04-26 17:57:11 +01:00
return req
}
2017-10-08 13:28:35 +02:00
if mt == response . NameError || mt == response . NoData {
2016-04-26 17:57:11 +01:00
if req . Ns [ 0 ] . Header ( ) . Rrtype != dns . TypeSOA || len ( req . Ns ) > 1 {
return req
}
ttl := req . Ns [ 0 ] . Header ( ) . Ttl
2018-04-27 19:37:31 +01:00
if sigs , err := d . sign ( req . Ns , state . Zone , ttl , incep , expir , server ) ; err == nil {
2016-04-26 17:57:11 +01:00
req . Ns = append ( req . Ns , sigs ... )
}
2018-04-27 19:37:31 +01:00
if sigs , err := d . nsec ( state , mt , ttl , incep , expir , server ) ; err == nil {
2016-04-26 17:57:11 +01:00
req . Ns = append ( req . Ns , sigs ... )
}
if len ( req . Ns ) > 1 { // actually added nsec and sigs, reset the rcode
req . Rcode = dns . RcodeSuccess
2023-04-22 22:32:01 +02:00
if state . QType ( ) == dns . TypeNSEC { // If original query was NSEC move Ns to Answer without SOA
req . Answer = req . Ns [ len ( req . Ns ) - 2 : len ( req . Ns ) ]
req . Ns = nil
}
2016-04-26 17:57:11 +01:00
}
return req
}
for _ , r := range rrSets ( req . Answer ) {
ttl := r [ 0 ] . Header ( ) . Ttl
2018-04-27 19:37:31 +01:00
if sigs , err := d . sign ( r , state . Zone , ttl , incep , expir , server ) ; err == nil {
2016-04-26 17:57:11 +01:00
req . Answer = append ( req . Answer , sigs ... )
}
}
for _ , r := range rrSets ( req . Ns ) {
ttl := r [ 0 ] . Header ( ) . Ttl
2018-04-27 19:37:31 +01:00
if sigs , err := d . sign ( r , state . Zone , ttl , incep , expir , server ) ; err == nil {
2016-04-26 17:57:11 +01:00
req . Ns = append ( req . Ns , sigs ... )
}
}
for _ , r := range rrSets ( req . Extra ) {
ttl := r [ 0 ] . Header ( ) . Ttl
2018-04-27 19:37:31 +01:00
if sigs , err := d . sign ( r , state . Zone , ttl , incep , expir , server ) ; err == nil {
2018-08-29 12:26:22 +01:00
req . Extra = append ( req . Extra , sigs ... )
2016-04-26 17:57:11 +01:00
}
}
return req
}
2018-04-27 19:37:31 +01:00
func ( d Dnssec ) sign ( rrs [ ] dns . RR , signerName string , ttl , incep , expir uint32 , server string ) ( [ ] dns . RR , error ) {
2017-06-13 12:39:10 -07:00
k := hash ( rrs )
2018-04-27 19:37:31 +01:00
sgs , ok := d . get ( k , server )
2016-04-26 17:57:11 +01:00
if ok {
return sgs , nil
}
2025-09-10 23:08:27 +03:00
sigs , err := d . inflight . Do ( k , func ( ) ( any , error ) {
2018-10-20 17:35:59 +02:00
var sigs [ ] dns . RR
for _ , k := range d . keys {
if d . splitkeys {
if len ( rrs ) > 0 && rrs [ 0 ] . Header ( ) . Rrtype == dns . TypeDNSKEY {
// We are signing a DNSKEY RRSet. With split keys, we need to use a KSK here.
if ! k . isKSK ( ) {
continue
}
} else {
// For non-DNSKEY RRSets, we want to use a ZSK.
if ! k . isZSK ( ) {
continue
}
}
}
2016-09-23 09:14:12 +01:00
sig := k . newRRSIG ( signerName , ttl , incep , expir )
2018-10-20 17:35:59 +02:00
if e := sig . Sign ( k . s , rrs ) ; e != nil {
return sigs , e
}
sigs = append ( sigs , sig )
2016-04-26 17:57:11 +01:00
}
d . set ( k , sigs )
2018-10-20 17:35:59 +02:00
return sigs , nil
2016-04-26 17:57:11 +01:00
} )
return sigs . ( [ ] dns . RR ) , err
}
2018-08-31 17:26:43 -04:00
func ( d Dnssec ) set ( key uint64 , sigs [ ] dns . RR ) { d . cache . Add ( key , sigs ) }
2016-04-26 17:57:11 +01:00
2018-08-31 17:26:43 -04:00
func ( d Dnssec ) get ( key uint64 , server string ) ( [ ] dns . RR , bool ) {
2016-04-26 17:57:11 +01:00
if s , ok := d . cache . Get ( key ) ; ok {
2018-01-18 10:39:22 +00:00
// we sign for 8 days, check if a signature in the cache reached 3/4 of that
2021-05-14 04:49:16 -04:00
is75 := time . Now ( ) . UTC ( ) . Add ( twoDays )
2018-01-18 10:39:22 +00:00
for _ , rr := range s . ( [ ] dns . RR ) {
if ! rr . ( * dns . RRSIG ) . ValidityPeriod ( is75 ) {
2018-04-27 19:37:31 +01:00
cacheMisses . WithLabelValues ( server ) . Inc ( )
2018-01-18 10:39:22 +00:00
return nil , false
}
}
2018-04-27 19:37:31 +01:00
cacheHits . WithLabelValues ( server ) . Inc ( )
2016-04-26 17:57:11 +01:00
return s . ( [ ] dns . RR ) , true
}
2018-04-27 19:37:31 +01:00
cacheMisses . WithLabelValues ( server ) . Inc ( )
2016-04-26 17:57:11 +01:00
return nil , false
}
func incepExpir ( now time . Time ) ( uint32 , uint32 ) {
2026-01-01 13:50:29 +05:30
incep := uint32 ( now . Add ( - 3 * time . Hour ) . Unix ( ) ) // #nosec G115 -- DNSSEC inception, Year 2106 problem accepted // -(2+1) hours, be sure to catch daylight saving time and such
expir := uint32 ( now . Add ( eightDays ) . Unix ( ) ) // #nosec G115 -- DNSSEC expiration, Year 2106 problem accepted // sign for 8 days
2016-04-26 17:57:11 +01:00
return incep , expir
}
const (
2016-10-17 05:04:36 -07:00
eightDays = 8 * 24 * time . Hour
2021-05-14 04:49:16 -04:00
twoDays = 2 * 24 * time . Hour
2016-10-17 05:04:36 -07:00
defaultCap = 10000 // default capacity of the cache.
2016-04-26 17:57:11 +01:00
)