hickory_server/authority/authority.rs
1// Copyright 2015-2021 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! All authority related types
9
10use cfg_if::cfg_if;
11use std::fmt;
12
13use crate::{
14 authority::{LookupError, LookupObject, MessageRequest, UpdateResult, ZoneType},
15 proto::rr::{LowerName, RecordSet, RecordType, RrsetRecords},
16 server::RequestInfo,
17};
18#[cfg(feature = "__dnssec")]
19use crate::{
20 dnssec::NxProofKind,
21 proto::{
22 ProtoError,
23 dnssec::{DnsSecResult, Nsec3HashAlgorithm, SigSigner, crypto::Digest, rdata::key::KEY},
24 rr::Name,
25 },
26};
27
28/// LookupOptions that specify different options from the client to include or exclude various records in the response.
29///
30/// For example, `dnssec_ok` (DO) will include `RRSIG` in the response.
31#[derive(Clone, Copy, Debug, Default)]
32pub struct LookupOptions {
33 dnssec_ok: bool,
34}
35
36/// Lookup Options for the request to the authority
37impl LookupOptions {
38 /// Return a new LookupOptions
39 #[cfg(feature = "__dnssec")]
40 pub fn for_dnssec(dnssec_ok: bool) -> Self {
41 Self { dnssec_ok }
42 }
43
44 /// Specify that this lookup should return DNSSEC related records as well, e.g. RRSIG
45 #[allow(clippy::needless_update)]
46 pub fn set_dnssec_ok(self, val: bool) -> Self {
47 Self {
48 dnssec_ok: val,
49 ..self
50 }
51 }
52
53 /// If true this lookup should return DNSSEC related records as well, e.g. RRSIG
54 pub fn dnssec_ok(&self) -> bool {
55 self.dnssec_ok
56 }
57
58 /// Returns the rrset's records with or without RRSIGs, depending on the DO flag.
59 pub fn rrset_with_rrigs<'r>(&self, record_set: &'r RecordSet) -> RrsetRecords<'r> {
60 cfg_if! {
61 if #[cfg(feature = "__dnssec")] {
62 record_set.records(self.dnssec_ok())
63 } else {
64 record_set.records_without_rrsigs()
65 }
66 }
67 }
68}
69
70/// Authority implementations can be used with a `Catalog`
71#[async_trait::async_trait]
72pub trait Authority: Send + Sync {
73 /// Result of a lookup
74 type Lookup: Send + Sync + Sized + 'static;
75
76 /// What type is this zone
77 fn zone_type(&self) -> ZoneType;
78
79 /// Return true if AXFR is allowed
80 fn is_axfr_allowed(&self) -> bool;
81
82 /// Whether the authority can perform DNSSEC validation
83 fn can_validate_dnssec(&self) -> bool {
84 false
85 }
86
87 /// Perform a dynamic update of a zone
88 async fn update(&self, update: &MessageRequest) -> UpdateResult<bool>;
89
90 /// Get the origin of this zone, i.e. example.com is the origin for www.example.com
91 fn origin(&self) -> &LowerName;
92
93 /// Looks up all Resource Records matching the given `Name` and `RecordType`.
94 ///
95 /// # Arguments
96 ///
97 /// * `name` - The name to look up.
98 /// * `rtype` - The `RecordType` to look up. `RecordType::ANY` will return all records matching
99 /// `name`. `RecordType::AXFR` will return all record types except `RecordType::SOA`
100 /// due to the requirements that on zone transfers the `RecordType::SOA` must both
101 /// precede and follow all other records.
102 /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
103 /// algorithms, etc.)
104 ///
105 /// # Return value
106 ///
107 /// A LookupControlFlow containing the lookup that should be returned to the client.
108 async fn lookup(
109 &self,
110 name: &LowerName,
111 rtype: RecordType,
112 lookup_options: LookupOptions,
113 ) -> LookupControlFlow<Self::Lookup>;
114
115 /// Consulting lookup for all Resource Records matching the given `Name` and `RecordType`.
116 /// This will be called in a chained authority configuration after an authority in the chain
117 /// has returned a lookup with a LookupControlFlow::Continue action. Every other authority in
118 /// the chain will be called via this consult method, until one either returns a
119 /// LookupControlFlow::Break action, or all authorities have been consulted. The authority that
120 /// generated the primary lookup (the one returned via 'lookup') will not be consulted.
121 ///
122 /// # Arguments
123 ///
124 /// * `name` - The name to look up.
125 /// * `rtype` - The `RecordType` to look up. `RecordType::ANY` will return all records matching
126 /// `name`. `RecordType::AXFR` will return all record types except `RecordType::SOA`
127 /// due to the requirements that on zone transfers the `RecordType::SOA` must both
128 /// precede and follow all other records.
129 /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
130 /// algorithms, etc.)
131 /// * `last_result` - The lookup returned by a previous authority in a chained configuration.
132 /// If a subsequent authority does not modify this lookup, it will be returned
133 /// to the client after consulting all authorities in the chain.
134 ///
135 /// # Return value
136 ///
137 /// A LookupControlFlow containing the lookup that should be returned to the client. This can
138 /// be the same last_result that was passed in, or a new lookup, depending on the logic of the
139 /// authority in question.
140 async fn consult(
141 &self,
142 _name: &LowerName,
143 _rtype: RecordType,
144 _lookup_options: LookupOptions,
145 last_result: LookupControlFlow<Box<dyn LookupObject>>,
146 ) -> LookupControlFlow<Box<dyn LookupObject>> {
147 last_result
148 }
149
150 /// Using the specified query, perform a lookup against this zone.
151 ///
152 /// # Arguments
153 ///
154 /// * `request` - the query to perform the lookup with.
155 /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
156 /// algorithms, etc.)
157 ///
158 /// # Return value
159 ///
160 /// A LookupControlFlow containing the lookup that should be returned to the client.
161 async fn search(
162 &self,
163 request: RequestInfo<'_>,
164 lookup_options: LookupOptions,
165 ) -> LookupControlFlow<Self::Lookup>;
166
167 /// Get the NS, NameServer, record for the zone
168 async fn ns(&self, lookup_options: LookupOptions) -> LookupControlFlow<Self::Lookup> {
169 self.lookup(self.origin(), RecordType::NS, lookup_options)
170 .await
171 }
172
173 /// Return the NSEC records based on the given name
174 ///
175 /// # Arguments
176 ///
177 /// * `name` - given this name (i.e. the lookup name), return the NSEC record that is less than
178 /// this
179 /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
180 /// algorithms, etc.)
181 async fn get_nsec_records(
182 &self,
183 name: &LowerName,
184 lookup_options: LookupOptions,
185 ) -> LookupControlFlow<Self::Lookup>;
186
187 /// Return the NSEC3 records based on the information available for a query.
188 #[cfg(feature = "__dnssec")]
189 async fn get_nsec3_records(
190 &self,
191 info: Nsec3QueryInfo<'_>,
192 lookup_options: LookupOptions,
193 ) -> LookupControlFlow<Self::Lookup>;
194
195 /// Returns the SOA of the authority.
196 ///
197 /// *Note*: This will only return the SOA, if this is fulfilling a request, a standard lookup
198 /// should be used, see `soa_secure()`, which will optionally return RRSIGs.
199 async fn soa(&self) -> LookupControlFlow<Self::Lookup> {
200 // SOA should be origin|SOA
201 self.lookup(self.origin(), RecordType::SOA, LookupOptions::default())
202 .await
203 }
204
205 /// Returns the SOA record for the zone
206 async fn soa_secure(&self, lookup_options: LookupOptions) -> LookupControlFlow<Self::Lookup> {
207 self.lookup(self.origin(), RecordType::SOA, lookup_options)
208 .await
209 }
210
211 /// Returns the kind of non-existence proof used for this zone.
212 #[cfg(feature = "__dnssec")]
213 fn nx_proof_kind(&self) -> Option<&NxProofKind>;
214}
215
216/// Extension to Authority to allow for DNSSEC features
217#[cfg(feature = "__dnssec")]
218#[async_trait::async_trait]
219pub trait DnssecAuthority: Authority {
220 /// Add a (Sig0) key that is authorized to perform updates against this authority
221 async fn add_update_auth_key(&self, name: Name, key: KEY) -> DnsSecResult<()>;
222
223 /// Add Signer
224 async fn add_zone_signing_key(&self, signer: SigSigner) -> DnsSecResult<()>;
225
226 /// Sign the zone for DNSSEC
227 async fn secure_zone(&self) -> DnsSecResult<()>;
228}
229
230/// Result of a Lookup in the Catalog and Authority
231///
232/// * **All authorities should default to using LookupControlFlow::Continue to wrap their responses.**
233/// These responses may be passed to other authorities for analysis or requery purposes.
234/// * Authorities may use LookupControlFlow::Break to indicate the response must be returned
235/// immediately to the client, without consulting any other authorities. For example, if the
236/// the user configures a blocklist authority, it would not be appropriate to pass the query to
237/// any additional authorities to try to resolve, as that might be used to leak information to a
238/// hostile party, and so a blocklist (or similar) authority should wrap responses for any
239/// blocklist hits in LookupControlFlow::Break.
240/// * Authorities may use LookupControlFlow::Skip to indicate the authority did not attempt to
241/// process a particular query. This might be used, for example, in a block list authority for
242/// any queries that **did not** match the blocklist, to allow the recursor or forwarder to
243/// resolve the query. Skip must not be used to represent an empty lookup; (use
244/// Continue(EmptyLookup) or Break(EmptyLookup) for that.)
245pub enum LookupControlFlow<T, E = LookupError> {
246 /// A lookup response that may be passed to one or more additional authorities before
247 /// being returned to the client.
248 Continue(Result<T, E>),
249 /// A lookup response that must be immediately returned to the client without consulting
250 /// any other authorities.
251 Break(Result<T, E>),
252 /// The authority did not answer the query and the next authority in the chain should
253 /// be consulted.
254 Skip,
255}
256
257impl<T, E> fmt::Display for LookupControlFlow<T, E> {
258 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259 match self {
260 Self::Continue(cont) => match cont {
261 Ok(_) => write!(f, "LookupControlFlow::Continue(Ok)"),
262 Err(_) => write!(f, "LookupControlFlow::Continue(Err)"),
263 },
264 Self::Break(b) => match b {
265 Ok(_) => write!(f, "LookupControlFlow::Break(Ok)"),
266 Err(_) => write!(f, "LookupControlFlow::Break(Err)"),
267 },
268 Self::Skip => write!(f, "LookupControlFlow::Skip"),
269 }
270 }
271}
272
273/// The following are a minimal set of methods typically used with Result or Option, and that
274/// were used in the server code or test suite prior to when the LookupControlFlow type was created
275/// (authority lookup functions previously returned a Result over a Lookup or LookupError type.)
276impl<T, E> LookupControlFlow<T, E> {
277 /// Return true if self is LookupControlFlow::Continue
278 pub fn is_continue(&self) -> bool {
279 matches!(self, Self::Continue(_))
280 }
281
282 /// Return true if self is LookupControlFlow::Break
283 pub fn is_break(&self) -> bool {
284 matches!(self, Self::Break(_))
285 }
286
287 /// Maps inner Ok(T) and Err(E) to Some(Result<T,E>) and Skip to None
288 pub fn map_result(self) -> Option<Result<T, E>> {
289 match self {
290 Self::Continue(Ok(lookup)) | Self::Break(Ok(lookup)) => Some(Ok(lookup)),
291 Self::Continue(Err(e)) | Self::Break(Err(e)) => Some(Err(e)),
292 Self::Skip => None,
293 }
294 }
295}
296
297impl<T: LookupObject + 'static, E: std::fmt::Display> LookupControlFlow<T, E> {
298 /// Return inner Ok variant or panic with a custom error message.
299 pub fn expect(self, msg: &str) -> T {
300 match self {
301 Self::Continue(Ok(ok)) | Self::Break(Ok(ok)) => ok,
302 _ => {
303 panic!("lookupcontrolflow::expect() called on unexpected variant {self}: {msg}");
304 }
305 }
306 }
307
308 /// Return inner Err variant or panic with a custom error message.
309 pub fn expect_err(self, msg: &str) -> E {
310 match self {
311 Self::Continue(Err(e)) | Self::Break(Err(e)) => e,
312 _ => {
313 panic!(
314 "lookupcontrolflow::expect_err() called on unexpected variant {self}: {msg}"
315 );
316 }
317 }
318 }
319
320 /// Return inner Ok variant or panic
321 pub fn unwrap(self) -> T {
322 match self {
323 Self::Continue(Ok(ok)) | Self::Break(Ok(ok)) => ok,
324 Self::Continue(Err(e)) | Self::Break(Err(e)) => {
325 panic!("lookupcontrolflow::unwrap() called on unexpected variant _(Err(_)): {e}");
326 }
327 _ => {
328 panic!("lookupcontrolflow::unwrap() called on unexpected variant: {self}");
329 }
330 }
331 }
332
333 /// Return inner Err variant or panic
334 pub fn unwrap_err(self) -> E {
335 match self {
336 Self::Continue(Err(e)) | Self::Break(Err(e)) => e,
337 _ => {
338 panic!("lookupcontrolflow::unwrap_err() called on unexpected variant: {self}");
339 }
340 }
341 }
342
343 /// Return inner Ok Variant or default value
344 pub fn unwrap_or_default(self) -> T
345 where
346 T: Default,
347 {
348 match self {
349 Self::Continue(Ok(ok)) | Self::Break(Ok(ok)) => ok,
350 _ => T::default(),
351 }
352 }
353
354 /// Maps inner Ok(T) to Ok(U), passing inner Err and Skip values unchanged.
355 pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> LookupControlFlow<U, E> {
356 match self {
357 Self::Continue(cont) => match cont {
358 Ok(t) => LookupControlFlow::Continue(Ok(op(t))),
359 Err(e) => LookupControlFlow::Continue(Err(e)),
360 },
361 Self::Break(b) => match b {
362 Ok(t) => LookupControlFlow::Break(Ok(op(t))),
363 Err(e) => LookupControlFlow::Break(Err(e)),
364 },
365 Self::Skip => LookupControlFlow::<U, E>::Skip,
366 }
367 }
368
369 /// Maps inner Ok(T) to Ok(Box<dyn LookupObject>), passing inner Err and Skip values unchanged.
370 pub fn map_dyn(self) -> LookupControlFlow<Box<dyn LookupObject>, E> {
371 match self {
372 Self::Continue(cont) => match cont {
373 Ok(lookup) => {
374 LookupControlFlow::Continue(Ok(Box::new(lookup) as Box<dyn LookupObject>))
375 }
376 Err(e) => LookupControlFlow::Continue(Err(e)),
377 },
378 Self::Break(b) => match b {
379 Ok(lookup) => {
380 LookupControlFlow::Break(Ok(Box::new(lookup) as Box<dyn LookupObject>))
381 }
382 Err(e) => LookupControlFlow::Break(Err(e)),
383 },
384
385 Self::Skip => LookupControlFlow::<Box<dyn LookupObject>, E>::Skip,
386 }
387 }
388
389 /// Maps inner Err(T) to Err(U), passing Ok and Skip values unchanged.
390 pub fn map_err<U, F: FnOnce(E) -> U>(self, op: F) -> LookupControlFlow<T, U> {
391 match self {
392 Self::Continue(cont) => match cont {
393 Ok(lookup) => LookupControlFlow::Continue(Ok(lookup)),
394 Err(e) => LookupControlFlow::Continue(Err(op(e))),
395 },
396 Self::Break(b) => match b {
397 Ok(lookup) => LookupControlFlow::Break(Ok(lookup)),
398 Err(e) => LookupControlFlow::Break(Err(op(e))),
399 },
400 Self::Skip => LookupControlFlow::Skip,
401 }
402 }
403}
404
405/// Information required to compute the NSEC3 records that should be sent for a query.
406#[cfg(feature = "__dnssec")]
407pub struct Nsec3QueryInfo<'q> {
408 /// The queried name.
409 pub qname: &'q LowerName,
410 /// The queried record type.
411 pub qtype: RecordType,
412 /// Whether there was a wildcard match for `qname` regardless of `qtype`.
413 pub has_wildcard_match: bool,
414 /// The algorithm used to hash the names.
415 pub algorithm: Nsec3HashAlgorithm,
416 /// The salt used for hashing.
417 pub salt: &'q [u8],
418 /// The number of hashing iterations.
419 pub iterations: u16,
420}
421
422#[cfg(feature = "__dnssec")]
423impl Nsec3QueryInfo<'_> {
424 /// Computes the hash of a given name.
425 pub(crate) fn hash_name(&self, name: &Name) -> Result<Digest, ProtoError> {
426 self.algorithm.hash(self.salt, name, self.iterations)
427 }
428
429 /// Computes the hashed owner name from a given name. That is, the hash of the given name,
430 /// followed by the zone name.
431 pub(crate) fn get_hashed_owner_name(
432 &self,
433 name: &LowerName,
434 zone: &Name,
435 ) -> Result<LowerName, ProtoError> {
436 let hash = self.hash_name(name)?;
437 let label = data_encoding::BASE32_DNSSEC.encode(hash.as_ref());
438 Ok(LowerName::new(&zone.prepend_label(label)?))
439 }
440}