add config option for allow_networks

This commit is contained in:
Benjamin Fry 2024-01-07 10:18:38 -08:00
parent 4f4f3172bf
commit f141667a0b
11 changed files with 134 additions and 10 deletions

3
Cargo.lock generated
View File

@ -1189,6 +1189,9 @@ name = "ipnet"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
dependencies = [
"serde",
]
[[package]]
name = "is-terminal"

View File

@ -391,10 +391,11 @@ fn main() {
.iter()
.flat_map(|x| (*x, listen_port).to_socket_addrs().unwrap())
.collect();
let allow_networks = config.get_allow_networks();
// now, run the server, based on the config
#[cfg_attr(not(feature = "dns-over-tls"), allow(unused_mut))]
let mut server = ServerFuture::new(catalog);
let mut server = ServerFuture::with_access(catalog, allow_networks);
// load all the listeners
for udp_socket in &sockaddrs {

View File

@ -22,12 +22,8 @@ use hickory_client::tcp::TcpClientStream;
use hickory_client::udp::UdpClientStream;
use hickory_server::server::Protocol;
// TODO: Needed for when TLS tests are added back
// #[cfg(feature = "dns-over-openssl")]
// use hickory_proto::openssl::TlsClientStreamBuilder;
use hickory_proto::iocompat::AsyncIoTokioAsStd;
use server_harness::{named_test_harness, query_a};
use server_harness::{named_test_harness, query_a, query_a_refused};
#[test]
fn test_example_toml_startup() {
@ -342,3 +338,67 @@ fn test_forward() {
assert!(!response.header().authoritative());
})
}
#[test]
fn test_allow_networks_toml_startup() {
named_test_harness("example_allow_networks.toml", |socket_ports| {
let mut io_loop = Runtime::new().unwrap();
let tcp_port = socket_ports.get_v4(Protocol::Tcp);
let addr: SocketAddr = SocketAddr::new(
Ipv4Addr::new(127, 0, 0, 1).into(),
tcp_port.expect("no tcp_port"),
);
let (stream, sender) = TcpClientStream::<AsyncIoTokioAsStd<TokioTcpStream>>::new(addr);
let client = AsyncClient::new(Box::new(stream), sender, None);
let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect");
hickory_proto::spawn_bg(&io_loop, bg);
// ipv4 should succeed
query_a(&mut io_loop, &mut client);
let tcp_port = socket_ports.get_v6(Protocol::Tcp);
let addr: SocketAddr = SocketAddr::new(
Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1).into(),
tcp_port.expect("no tcp_port"),
);
let (stream, sender) = TcpClientStream::<AsyncIoTokioAsStd<TokioTcpStream>>::new(addr);
let client = AsyncClient::new(Box::new(stream), sender, None);
let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect");
hickory_proto::spawn_bg(&io_loop, bg);
// ipv6 should succeed
query_a(&mut io_loop, &mut client);
})
}
#[test]
fn test_deny_networks_toml_startup() {
named_test_harness("example_deny_networks.toml", |socket_ports| {
let mut io_loop = Runtime::new().unwrap();
let tcp_port = socket_ports.get_v4(Protocol::Tcp);
let addr: SocketAddr = SocketAddr::new(
Ipv4Addr::new(127, 0, 0, 1).into(),
tcp_port.expect("no tcp_port"),
);
let (stream, sender) = TcpClientStream::<AsyncIoTokioAsStd<TokioTcpStream>>::new(addr);
let client = AsyncClient::new(Box::new(stream), sender, None);
let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect");
hickory_proto::spawn_bg(&io_loop, bg);
// ipv4 should be refused
query_a_refused(&mut io_loop, &mut client);
let tcp_port = socket_ports.get_v6(Protocol::Tcp);
let addr: SocketAddr = SocketAddr::new(
Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1).into(),
tcp_port.expect("no tcp_port"),
);
let (stream, sender) = TcpClientStream::<AsyncIoTokioAsStd<TokioTcpStream>>::new(addr);
let client = AsyncClient::new(Box::new(stream), sender, None);
let (mut client, bg) = io_loop.block_on(client).expect("client failed to connect");
hickory_proto::spawn_bg(&io_loop, bg);
// ipv6 should be refused
query_a_refused(&mut io_loop, &mut client);
})
}

View File

@ -16,7 +16,10 @@ use std::{
use hickory_client::{client::*, proto::xfer::DnsResponse};
#[cfg(feature = "dnssec")]
use hickory_proto::rr::dnssec::*;
use hickory_proto::rr::{rdata::A, *};
use hickory_proto::{
op::ResponseCode,
rr::{rdata::A, *},
};
use hickory_server::server::Protocol;
use regex::Regex;
use tokio::runtime::Runtime;
@ -257,6 +260,15 @@ pub fn query_a<C: ClientHandle>(io_loop: &mut Runtime, client: &mut C) {
}
}
// This only validates that a query to the server works, it shouldn't be used for more than this.
// i.e. more complex checks live with the clients and authorities to validate deeper functionality
#[allow(dead_code)]
pub fn query_a_refused<C: ClientHandle>(io_loop: &mut Runtime, client: &mut C) {
let name = Name::from_str("www.example.com").unwrap();
let response = query_message(io_loop, client, name, RecordType::A);
assert_eq!(response.response_code(), ResponseCode::Refused);
}
// This only validates that a query to the server works, it shouldn't be used for more than this.
// i.e. more complex checks live with the clients and authorities to validate deeper functionality
#[allow(dead_code)]

View File

@ -122,7 +122,7 @@ h2 = { workspace = true, features = ["stream"], optional = true }
h3 = { workspace = true, optional = true }
h3-quinn = { workspace = true, optional = true }
http = { workspace = true, optional = true }
ipnet.workspace = true
ipnet = { workspace = true, features = ["serde"] }
openssl = { workspace = true, features = ["v102", "v110"], optional = true }
prefix-trie.workspace = true
rusqlite = { workspace = true, features = ["bundled", "time"], optional = true }

View File

@ -32,6 +32,12 @@ impl Access {
}
}
pub(crate) fn insert_all(&mut self, networks: &[IpNet]) {
for net in networks {
self.insert(*net);
}
}
/// Evaluate the IP address against the allowed networks
///
/// # Return
@ -67,7 +73,7 @@ mod tests {
#[test]
fn test_none() {
let mut access = Access::default();
let access = Access::default();
assert!(access.allow("192.168.1.1".parse().unwrap()).is_ok());
assert!(access.allow("fd00::1".parse().unwrap()).is_ok());
}

View File

@ -19,6 +19,7 @@ use std::str::FromStr;
use std::time::Duration;
use cfg_if::cfg_if;
use ipnet::IpNet;
use serde::{self, Deserialize};
use crate::proto::error::ProtoResult;
@ -68,6 +69,9 @@ pub struct Config {
/// Certificate to associate to TLS connections (currently the same is used for HTTPS and TLS)
#[cfg(feature = "dnssec")]
tls_cert: Option<dnssec::TlsCertConfig>,
/// Networks allowed to access the server
#[serde(default)]
allow_networks: Vec<IpNet>,
}
impl Config {
@ -160,6 +164,11 @@ impl Config {
}
}
}
/// get the networks allowed to connect to this server
pub fn get_allow_networks(&self) -> &[IpNet] {
&self.allow_networks
}
}
/// Configuration for a zone

View File

@ -13,6 +13,7 @@ use std::{
use futures_util::{FutureExt, StreamExt};
use hickory_proto::{op::MessageType, rr::Record};
use ipnet::IpNet;
#[cfg(feature = "dns-over-rustls")]
use rustls::{Certificate, PrivateKey, ServerConfig};
use tokio::{net, task::JoinSet};
@ -49,11 +50,19 @@ pub struct ServerFuture<T: RequestHandler> {
impl<T: RequestHandler> ServerFuture<T> {
/// Creates a new ServerFuture with the specified Handler.
pub fn new(handler: T) -> Self {
Self::with_access(handler, &[])
}
/// Creates a new ServerFuture with the specified Handler and Access
pub fn with_access(handler: T, allowed_networks: &[IpNet]) -> Self {
let mut access = Access::default();
access.insert_all(allowed_networks);
Self {
handler: Arc::new(handler),
join_set: JoinSet::new(),
shutdown_token: CancellationToken::new(),
access: Arc::new(Access::default()),
access: Arc::new(access),
}
}

View File

@ -40,6 +40,10 @@
## directory: path on the host filesystem to where zone files are stored.
# directory = "/var/named"
## Allowed networks, a list of CIDRs in IPv4 or IPv6 formats,
## any request that does not originate from the specified networks will be denied
# allow_networks = ["127/8", "::1/128"]
## Default zones, these should be present on all nameservers, except in rare
## configuration cases
[[zones]]

View File

@ -0,0 +1,10 @@
listen_addrs_ipv4 = ["0.0.0.0"]
listen_addrs_ipv6 = ["::0"]
## Allowed networks, all of the 127 network space is allowed (local), and only the localhost in ipv6 is allowed.
allow_networks = ["127.0.0.0/8", "::1/128"]
[[zones]]
zone = "example.com"
zone_type = "Primary"
file = "example.com.zone"

View File

@ -0,0 +1,10 @@
listen_addrs_ipv4 = ["0.0.0.0"]
listen_addrs_ipv6 = ["::0"]
## Allowed networks, only the 0 address is allowed, which means everything is denied
allow_networks = ["0.0.0.0/8", "::/128"]
[[zones]]
zone = "example.com"
zone_type = "Primary"
file = "example.com.zone"