zone private keys loading
This commit is contained in:
parent
ef6e27f54c
commit
b91f1f01c6
@ -6,9 +6,13 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
### Added
|
||||
- Added recovery from journal to named startup
|
||||
- SQLite journal for dynamic update persistence
|
||||
- Private Key generation during startup, for dnssec zones
|
||||
- Read private key from filesystem during start and registers to zone
|
||||
|
||||
### Changed
|
||||
- Removed many of the unwraps in named binary
|
||||
- Reworked all errors to use error-chain
|
||||
- Adjusted interface for Signer to use duration
|
||||
|
||||
### Fixed
|
||||
- TXT record case sensitivity
|
||||
|
10
Cargo.toml
10
Cargo.toml
@ -5,11 +5,11 @@ authors = ["Benjamin Fry <benjaminfry@me.com>"]
|
||||
|
||||
# A short blurb about the package. This is not rendered in any format when
|
||||
# uploaded to crates.io (aka this is not markdown)
|
||||
description = """\
|
||||
TRust-DNS is a safe and secure DNS server and client with DNSec support.\
|
||||
It is working to be a replacement for BIND9. DNSSec on the client side, with\
|
||||
NSEC validation for negative records, is complete. The client and server both\
|
||||
support dynamic DNS with authenticated requests.\
|
||||
description = """
|
||||
TRust-DNS is a safe and secure DNS server and client with DNSec support.
|
||||
Eentually this could be a replacement for BIND9. DNSSec on the client side,
|
||||
with NSEC validation for negative records, is complete. The client and
|
||||
server both support dynamic DNS with authenticated requests.
|
||||
"""
|
||||
|
||||
# These URLs point to more information about the repository
|
||||
|
43
README.md
43
README.md
@ -25,9 +25,9 @@ ground up.
|
||||
## Client
|
||||
|
||||
Using the client is safe. The client is currently hardcoded to a 5 second,
|
||||
timeout. I'll make this configurable if people ask for that, please file a
|
||||
request for any features. Please send feedback! It currently does not cache
|
||||
responses, if this is a feature you'd like earlier rather than later, post a
|
||||
timeout. I'll make this configurable if people ask for that, please file a
|
||||
request for any features. Please send feedback! It currently does not cache
|
||||
responses, if this is a feature you'd like earlier rather than later, post a
|
||||
request. The validation of DNSSec is complete including NSEC. As of now NSEC3
|
||||
is broken, and I may never plan to support it. I have some alternative ideas
|
||||
for private data in the zone.
|
||||
@ -48,25 +48,34 @@ the details in DNS from the caller
|
||||
## Server
|
||||
|
||||
The server code is complete, the daemon supports IPv4 and IPv6, UDP and TCP.
|
||||
There currently is no way to limit TCP and AXFR operations, so it is still not
|
||||
recommended to put into production as TCP can be used to DOS the service.
|
||||
Master file parsing is complete and supported. There is currently no forking
|
||||
option, and the server is not yet threaded. There is still a lot of work to do
|
||||
before a server can be trusted with this externally. Running it behind a firewall
|
||||
on a private network would be safe.
|
||||
There currently is no way to limit TCP and AXFR operations, so it is still not
|
||||
recommended to put into production as TCP can be used to DOS the service.
|
||||
Master file parsing is complete and supported. There is currently no forking
|
||||
option, and the server is not yet threaded. There is still a lot of work to do
|
||||
before a server can be trusted with this externally. Running it behind a firewall
|
||||
on a private network would be safe.
|
||||
|
||||
Zone signing support is a work in progress, there is currently no way to
|
||||
associate keys to zones. Dynamic DNS is also complete, but currently there is
|
||||
no storage or syncing with other servers, so it's not recommended to use this
|
||||
feature yet, and is disabled by default on zones.
|
||||
Zone signing support is complete, to insert a key store a pem encoded rsa file
|
||||
in the same directory as the initial zone file with the `.key` suffix. *Note*:
|
||||
this must be only readable by the current user. If one is not present one will
|
||||
be created and written to the correct location. This also acts as the initial
|
||||
key for dynamic update SIG(0) validation. To get the public key, the `DNSKEY`
|
||||
record for the zone can be queried. This is needed to provide to other
|
||||
upstream servers to create the `DS` key. Dynamic DNS is also complete,
|
||||
if enabled, a journal file will be stored next to the zone file with the
|
||||
`jrnl` suffix. *Note*: if the key is changed or updated, it is currently the
|
||||
operators responsibility to remove the only public key from the zone, this
|
||||
allows for the `DNSKEY` to exist for some unspecified period of time during
|
||||
key rotation. Rotating the key currently is not available online and requires
|
||||
a restart of the server process.
|
||||
|
||||
## DNSSec status
|
||||
|
||||
Currently the root key is hardcoded into the system. This gives validation of
|
||||
DNSKEY and DS records back to the root. NSEC is implemented, but not NSEC3.
|
||||
Because caching is not yet enabled, it has been noticed that some DNS servers
|
||||
appear to rate limit the connections, validating RRSIG records back to the root
|
||||
can require a significant number of additional queries for those records.
|
||||
DNSKEY and DS records back to the root. NSEC is implemented, but not NSEC3.
|
||||
Because caching is not yet enabled, it has been noticed that some DNS servers
|
||||
appear to rate limit the connections, validating RRSIG records back to the root
|
||||
can require a significant number of additional queries for those records.
|
||||
|
||||
Zones will be automatically resigned on any record updates via dynamic DNS.
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use chrono::offset::utc::UTC;
|
||||
use chrono::UTC;
|
||||
use openssl::crypto::pkey::Role;
|
||||
|
||||
use ::authority::{Journal, RRSet, UpdateResult, ZoneType};
|
||||
@ -1003,7 +1003,7 @@ impl Authority {
|
||||
/// Signs any records in the zone that have serial numbers greater than or equal to `serial`
|
||||
fn sign_zone(&mut self) {
|
||||
debug!("signing zone: {}", self.origin);
|
||||
let now = UTC::now().timestamp() as u32;
|
||||
let inception = UTC::now();
|
||||
let zone_ttl = self.get_minimum_ttl();
|
||||
|
||||
for rr_set in self.records.iter_mut().filter_map(|(_, rr_set)| {
|
||||
@ -1017,14 +1017,16 @@ impl Authority {
|
||||
let rrsig_temp = Record::with(rr_set.get_name().clone(), RecordType::RRSIG, zone_ttl);
|
||||
|
||||
for signer in self.secure_keys.iter() {
|
||||
let expiration = inception + signer.get_sig_duration();
|
||||
|
||||
let hash = signer.hash_rrset(rr_set.get_name(),
|
||||
self.class,
|
||||
rr_set.get_name().num_labels(),
|
||||
rr_set.get_record_type(),
|
||||
signer.get_algorithm(),
|
||||
rr_set.get_ttl(),
|
||||
signer.get_expiration(),
|
||||
now,
|
||||
expiration.timestamp() as u32,
|
||||
inception.timestamp() as u32,
|
||||
signer.calculate_key_tag(),
|
||||
signer.get_signer_name(),
|
||||
// TODO: this is a nasty clone... the issue is that the vec
|
||||
@ -1042,9 +1044,9 @@ impl Authority {
|
||||
// original_ttl: u32,
|
||||
rr_set.get_ttl(),
|
||||
// sig_expiration: u32,
|
||||
signer.get_expiration(),
|
||||
expiration.timestamp() as u32,
|
||||
// sig_inception: u32,
|
||||
now,
|
||||
inception.timestamp() as u32,
|
||||
// key_tag: u16,
|
||||
signer.calculate_key_tag(),
|
||||
// signer_name: Name,
|
||||
@ -1127,13 +1129,14 @@ pub mod authority_tests {
|
||||
}
|
||||
|
||||
pub fn create_secure_example() -> Authority {
|
||||
use chrono::Duration;
|
||||
use openssl::crypto::pkey::PKey;
|
||||
use ::rr::dnssec::{Algorithm, Signer};
|
||||
|
||||
let mut authority: Authority = create_example();
|
||||
let mut pkey = PKey::new();
|
||||
pkey.gen(512);
|
||||
let signer = Signer::new(Algorithm::RSASHA256, pkey, authority.get_origin().clone(), u32::max_value(), 0);
|
||||
let signer = Signer::new(Algorithm::RSASHA256, pkey, authority.get_origin().clone(), Duration::weeks(1));
|
||||
|
||||
authority.add_secure_key(signer);
|
||||
authority.secure_zone();
|
||||
|
@ -1273,6 +1273,7 @@ mod test {
|
||||
|
||||
#[cfg(test)]
|
||||
fn create_sig0_ready_client<'a>(catalog: &'a mut Catalog) -> (Client<TestClientConnection<'a>>, Signer, domain::Name) {
|
||||
use chrono::Duration;
|
||||
use ::rr::rdata::DNSKEY;
|
||||
|
||||
let mut authority = create_example();
|
||||
@ -1285,8 +1286,7 @@ mod test {
|
||||
let signer = Signer::new(Algorithm::RSASHA256,
|
||||
pkey,
|
||||
domain::Name::with_labels(vec!["trusted".to_string(), "example".to_string(), "com".to_string()]),
|
||||
0,
|
||||
0);
|
||||
Duration::max_value());
|
||||
|
||||
// insert the KEY for the trusted.example.com
|
||||
let mut auth_key = Record::with(domain::Name::with_labels(vec!["trusted".to_string(), "example".to_string(), "com".to_string()]),
|
||||
|
61
src/named.rs
61
src/named.rs
@ -35,17 +35,22 @@ extern crate rustc_serialize;
|
||||
extern crate docopt;
|
||||
#[macro_use] extern crate log;
|
||||
extern crate mio;
|
||||
extern crate openssl;
|
||||
extern crate chrono;
|
||||
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::collections::BTreeMap;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::net::ToSocketAddrs;
|
||||
|
||||
use chrono::{Duration};
|
||||
use mio::tcp::TcpListener;
|
||||
use mio::udp::UdpSocket;
|
||||
use log::LogLevel;
|
||||
use docopt::Docopt;
|
||||
use openssl::crypto::pkey::PKey;
|
||||
|
||||
use trust_dns::logger;
|
||||
use trust_dns::version;
|
||||
@ -54,6 +59,7 @@ use trust_dns::config::{Config, ZoneConfig};
|
||||
use trust_dns::serialize::txt::Parser;
|
||||
use trust_dns::rr::Name;
|
||||
use trust_dns::server::Server;
|
||||
use trust_dns::rr::dnssec::{Algorithm, Signer};
|
||||
|
||||
// the Docopt usage string.
|
||||
// http://docopt.org
|
||||
@ -86,9 +92,10 @@ fn load_zone(zone_dir: &Path, zone: &ZoneConfig) -> Result<Authority, String> {
|
||||
let zone_name: Name = zone.get_zone().expect("bad zone name");
|
||||
let zone_path: PathBuf = zone_dir.to_owned().join(zone.get_file());
|
||||
let journal_path: PathBuf = zone_path.with_extension(".jrnl");
|
||||
let key_path: PathBuf = zone_path.with_extension(".key");
|
||||
|
||||
|
||||
if zone.is_update_allowed() && journal_path.exists() {
|
||||
// load the zone
|
||||
let mut authority = if zone.is_update_allowed() && journal_path.exists() {
|
||||
info!("recovering zone from journal: {:?}", journal_path);
|
||||
let journal = match Journal::from_file(&journal_path) {
|
||||
Ok(j) => j,
|
||||
@ -103,7 +110,7 @@ fn load_zone(zone_dir: &Path, zone: &ZoneConfig) -> Result<Authority, String> {
|
||||
authority.journal(journal);
|
||||
info!("recovered zone: {}", zone_name);
|
||||
|
||||
Ok(authority)
|
||||
authority
|
||||
} else if zone_path.exists() {
|
||||
info!("loading zone file: {:?}", zone_path);
|
||||
|
||||
@ -134,10 +141,54 @@ fn load_zone(zone_dir: &Path, zone: &ZoneConfig) -> Result<Authority, String> {
|
||||
}
|
||||
|
||||
info!("loaded zone: {}", zone_name);
|
||||
Ok(authority)
|
||||
authority
|
||||
} else {
|
||||
Err(format!("no zone file defined at: {:?}", zone_path))
|
||||
return Err(format!("no zone file defined at: {:?}", zone_path))
|
||||
};
|
||||
|
||||
// load any keys for the Zone, if it is a dynamic update zone, then keys are required
|
||||
if zone.is_dnssec_enabled() {
|
||||
let pkey = if key_path.exists() {
|
||||
info!("reading key: {:?}", key_path);
|
||||
|
||||
// TODO: validate owndership
|
||||
let mut file = match File::open(&key_path) {
|
||||
Ok(f) => f,
|
||||
Err(e) => return Err(format!("error opening private key file: {:?}: {}", key_path, e)),
|
||||
};
|
||||
|
||||
match PKey::private_rsa_key_from_pem(&mut file) {
|
||||
Ok(pkey) => pkey,
|
||||
Err(e) => return Err(format!("error reading private key file: {:?}: {}", key_path, e)),
|
||||
}
|
||||
} else {
|
||||
info!("creating key: {:?}", key_path);
|
||||
|
||||
// TODO: establish proper ownership
|
||||
let mut file = match File::create(&key_path) {
|
||||
Ok(f) => f,
|
||||
Err(e) => return Err(format!("error creating private key file: {:?}: {}", key_path, e))
|
||||
};
|
||||
|
||||
let mut pkey: PKey = PKey::new();
|
||||
pkey.gen(2048);
|
||||
|
||||
if let Err(e) = pkey.write_pem(&mut file) {
|
||||
fs::remove_file(&key_path).ok(); // ignored
|
||||
return Err(format!("error writing private key file: {:?}: {}", key_path, e))
|
||||
}
|
||||
|
||||
pkey
|
||||
};
|
||||
|
||||
// add the key to the zone
|
||||
// TODO: allow the duration of signatutes to be customized
|
||||
let signer = Signer::new(Algorithm::RSASHA256, pkey, authority.get_origin().clone(), Duration::weeks(52));
|
||||
authority.add_secure_key(signer);
|
||||
}
|
||||
|
||||
|
||||
Ok(authority)
|
||||
}
|
||||
|
||||
/// Main method for running the named server.
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
//! signer is a structure for performing many of the signing processes of the DNSSec specification
|
||||
|
||||
use chrono::Duration;
|
||||
use openssl::crypto::pkey::{PKey, Role};
|
||||
|
||||
use ::op::Message;
|
||||
@ -30,25 +31,23 @@ pub struct Signer {
|
||||
algorithm: Algorithm,
|
||||
pkey: PKey,
|
||||
signer_name: Name,
|
||||
expiration: u32,
|
||||
inception: u32,
|
||||
sig_duration: Duration,
|
||||
}
|
||||
|
||||
impl Signer {
|
||||
/// Version of Signer for verifying RRSIGs and SIG0 records.
|
||||
pub fn new_verifier(algorithm: Algorithm, pkey: PKey, signer_name: Name) -> Self {
|
||||
Signer{ algorithm: algorithm, pkey: pkey, signer_name: signer_name, expiration: 0, inception: 0 }
|
||||
Signer{ algorithm: algorithm, pkey: pkey, signer_name: signer_name, sig_duration: Duration::zero() }
|
||||
}
|
||||
|
||||
/// Version of Signer for signing RRSIGs and SIG0 records.
|
||||
pub fn new(algorithm: Algorithm, pkey: PKey, signer_name: Name, expiration: u32, inception: u32) -> Self {
|
||||
Signer{ algorithm: algorithm, pkey: pkey, signer_name: signer_name, expiration: expiration, inception: inception }
|
||||
pub fn new(algorithm: Algorithm, pkey: PKey, signer_name: Name, sig_duration: Duration) -> Self {
|
||||
Signer{ algorithm: algorithm, pkey: pkey, signer_name: signer_name, sig_duration: sig_duration }
|
||||
}
|
||||
|
||||
pub fn get_algorithm(&self) -> Algorithm { self.algorithm }
|
||||
pub fn get_sig_duration(&self) -> Duration { self.sig_duration }
|
||||
pub fn get_signer_name(&self) -> &Name { &self.signer_name }
|
||||
pub fn get_expiration(&self) -> u32 { self.expiration }
|
||||
pub fn get_inception(&self) -> u32 { self.inception }
|
||||
pub fn get_pkey(&self) -> &PKey { &self.pkey }
|
||||
|
||||
pub fn get_public_key(&self) -> Vec<u8> {
|
||||
@ -595,7 +594,7 @@ fn test_sign_and_verify_message_sig0() {
|
||||
|
||||
let mut pkey = PKey::new();
|
||||
pkey.gen(512);
|
||||
let signer = Signer::new(Algorithm::RSASHA256, pkey, Name::root(), u32::max_value(), 0);
|
||||
let signer = Signer::new(Algorithm::RSASHA256, pkey, Name::root(), Duration::max_value());
|
||||
|
||||
let sig = signer.sign_message(&question);
|
||||
println!("sig: {:?}", sig);
|
||||
@ -623,7 +622,7 @@ fn test_hash_rrset() {
|
||||
|
||||
let mut pkey = PKey::new();
|
||||
pkey.gen(512);
|
||||
let signer = Signer::new(Algorithm::RSASHA256, pkey, Name::root(), u32::max_value(), 0);
|
||||
let signer = Signer::new(Algorithm::RSASHA256, pkey, Name::root(), Duration::max_value());
|
||||
|
||||
let origin: Name = Name::parse("example.com.", None).unwrap();
|
||||
let rrsig = Record::new().name(origin.clone()).ttl(86400).rr_type(RecordType::NS).dns_class(DNSClass::IN).rdata(RData::SIG(SIG::new(RecordType::NS, Algorithm::RSASHA256, origin.num_labels(), 86400,
|
||||
@ -653,7 +652,7 @@ fn test_sign_and_verify_rrset() {
|
||||
|
||||
let mut pkey = PKey::new();
|
||||
pkey.gen(512);
|
||||
let signer = Signer::new(Algorithm::RSASHA256, pkey, Name::root(), u32::max_value(), 0);
|
||||
let signer = Signer::new(Algorithm::RSASHA256, pkey, Name::root(), Duration::max_value());
|
||||
|
||||
let origin: Name = Name::parse("example.com.", None).unwrap();
|
||||
let rrsig = Record::new().name(origin.clone()).ttl(86400).rr_type(RecordType::NS).dns_class(DNSClass::IN).rdata(RData::SIG(SIG::new(RecordType::NS, Algorithm::RSASHA256, origin.num_labels(), 86400,
|
||||
@ -672,7 +671,7 @@ fn test_calculate_key_tag() {
|
||||
let mut pkey = PKey::new();
|
||||
pkey.gen(512);
|
||||
println!("pkey: {:?}", pkey.save_pub());
|
||||
let signer = Signer::new(Algorithm::RSASHA256, pkey, Name::root(), u32::max_value(), 0);
|
||||
let signer = Signer::new(Algorithm::RSASHA256, pkey, Name::root(), Duration::max_value());
|
||||
let key_tag = signer.calculate_key_tag();
|
||||
|
||||
println!("key_tag: {}", key_tag);
|
||||
|
@ -13,6 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//! bitmap for expressing the set of supported algorithms in edns.
|
||||
|
||||
use std::convert::From;
|
||||
|
||||
use ::rr::dnssec::Algorithm;
|
||||
|
@ -13,6 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//! Allows for the root trust_anchor to either be added to or replaced for dns_sec validation.
|
||||
|
||||
use std::io::Cursor;
|
||||
use std::default::Default;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user