forked from coredns/coredns
/
dnssec.go
159 lines (139 loc) · 4.32 KB
/
dnssec.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// Package dnssec implements a plugin that signs responses on-the-fly using
// NSEC black lies.
package dnssec
import (
"time"
"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"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
// Dnssec signs the reply on-the-fly.
type Dnssec struct {
Next plugin.Handler
zones []string
keys []*DNSKEY
splitkeys bool
inflight *singleflight.Group
cache *cache.Cache
}
// New returns a new Dnssec.
func New(zones []string, keys []*DNSKEY, splitkeys bool, next plugin.Handler, c *cache.Cache) Dnssec {
return Dnssec{Next: next,
zones: zones,
keys: keys,
splitkeys: splitkeys,
cache: c,
inflight: new(singleflight.Group),
}
}
// Sign signs the message in state. it takes care of negative or nodata responses. It
// 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,
// starting 3 hours ago.
func (d Dnssec) Sign(state request.Request, now time.Time, server string) *dns.Msg {
req := state.Req
incep, expir := incepExpir(now)
mt, _ := response.Typify(req, time.Now().UTC()) // TODO(miek): need opt record here?
if mt == response.Delegation {
return req
}
if mt == response.NameError || mt == response.NoData {
if req.Ns[0].Header().Rrtype != dns.TypeSOA || len(req.Ns) > 1 {
return req
}
ttl := req.Ns[0].Header().Ttl
if sigs, err := d.sign(req.Ns, state.Zone, ttl, incep, expir, server); err == nil {
req.Ns = append(req.Ns, sigs...)
}
if sigs, err := d.nsec(state, mt, ttl, incep, expir, server); err == nil {
req.Ns = append(req.Ns, sigs...)
}
if len(req.Ns) > 1 { // actually added nsec and sigs, reset the rcode
req.Rcode = dns.RcodeSuccess
}
return req
}
for _, r := range rrSets(req.Answer) {
ttl := r[0].Header().Ttl
if sigs, err := d.sign(r, state.Zone, ttl, incep, expir, server); err == nil {
req.Answer = append(req.Answer, sigs...)
}
}
for _, r := range rrSets(req.Ns) {
ttl := r[0].Header().Ttl
if sigs, err := d.sign(r, state.Zone, ttl, incep, expir, server); err == nil {
req.Ns = append(req.Ns, sigs...)
}
}
for _, r := range rrSets(req.Extra) {
ttl := r[0].Header().Ttl
if sigs, err := d.sign(r, state.Zone, ttl, incep, expir, server); err == nil {
req.Extra = append(req.Extra, sigs...)
}
}
return req
}
func (d Dnssec) sign(rrs []dns.RR, signerName string, ttl, incep, expir uint32, server string) ([]dns.RR, error) {
k := hash(rrs)
sgs, ok := d.get(k, server)
if ok {
return sgs, nil
}
sigs, err := d.inflight.Do(k, func() (interface{}, error) {
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
}
}
}
sig := k.newRRSIG(signerName, ttl, incep, expir)
if e := sig.Sign(k.s, rrs); e != nil {
return sigs, e
}
sigs = append(sigs, sig)
}
d.set(k, sigs)
return sigs, nil
})
return sigs.([]dns.RR), err
}
func (d Dnssec) set(key uint64, sigs []dns.RR) { d.cache.Add(key, sigs) }
func (d Dnssec) get(key uint64, server string) ([]dns.RR, bool) {
if s, ok := d.cache.Get(key); ok {
// we sign for 8 days, check if a signature in the cache reached 3/4 of that
is75 := time.Now().UTC().Add(sixDays)
for _, rr := range s.([]dns.RR) {
if !rr.(*dns.RRSIG).ValidityPeriod(is75) {
cacheMisses.WithLabelValues(server).Inc()
return nil, false
}
}
cacheHits.WithLabelValues(server).Inc()
return s.([]dns.RR), true
}
cacheMisses.WithLabelValues(server).Inc()
return nil, false
}
func incepExpir(now time.Time) (uint32, uint32) {
incep := uint32(now.Add(-3 * time.Hour).Unix()) // -(2+1) hours, be sure to catch daylight saving time and such
expir := uint32(now.Add(eightDays).Unix()) // sign for 8 days
return incep, expir
}
const (
eightDays = 8 * 24 * time.Hour
sixDays = 6 * 24 * time.Hour
defaultCap = 10000 // default capacity of the cache.
)