Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configurable DNS settings #2353

Merged
merged 6 commits into from
Sep 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,17 @@ In addition to the container runtime daemons, these credential settings will als
10.1.1.1 test2.example.com
```

The following allows for custom DNS settings, which are used to generate the `/etc/resolv.conf`.
If either DNS setting is not populated, the system will use the DHCP lease of the primary interface to gather these setings.
See the `resolv.conf` [man page](https://man7.org/linux/man-pages/man5/resolv.conf.5.html) for more detail.
* `settings.dns.name-servers`: An array of IP address strings that represent the desired name server(s).
* `settings.dns.search-list`: An array of domain strings that represent the desired domain search path(s).
```
[settings.dns]
name-servers = ["1.2.3.4", "5.6.7.8"]
search-list = ["foo.bar", "baz.foo"]
```

##### Proxy settings

These settings will configure the proxying behavior of the following services:
Expand Down
4 changes: 4 additions & 0 deletions Release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,7 @@ version = "1.9.2"
]
"(1.9.0, 1.9.1)" = []
"(1.9.1, 1.9.2)" = []
"(1.9.2, 1.10.0)" = [
"migrate_v1.10.0_dns-settings.lz4",
"migrate_v1.10.0_dns-settings-metadata.lz4",
]
6 changes: 6 additions & 0 deletions packages/release/netdog.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{{#if settings.dns.name-servers}}
arnaldo2792 marked this conversation as resolved.
Show resolved Hide resolved
name-servers = [{{join_array ", " settings.dns.name-servers }}]
{{/if}}
{{#if settings.dns.search-list}}
search-list = [{{join_array ", " settings.dns.search-list }}]
{{/if}}
3 changes: 3 additions & 0 deletions packages/release/release.spec
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Source201: proxy-env
Source202: hostname-env
Source203: hosts.template
Source204: modprobe-conf.template
Source205: netdog.template

Source1001: multi-user.target
Source1002: configured.target
Expand Down Expand Up @@ -167,6 +168,7 @@ install -p -m 0644 %{S:201} %{buildroot}%{_cross_templatedir}/proxy-env
install -p -m 0644 %{S:202} %{buildroot}%{_cross_templatedir}/hostname-env
install -p -m 0644 %{S:203} %{buildroot}%{_cross_templatedir}/hosts
install -p -m 0644 %{S:204} %{buildroot}%{_cross_templatedir}/modprobe-conf
install -p -m 0644 %{S:205} %{buildroot}%{_cross_templatedir}/netdog-toml

install -d %{buildroot}%{_cross_udevrulesdir}
install -p -m 0644 %{S:1016} %{buildroot}%{_cross_udevrulesdir}/61-mount-cdrom.rules
Expand Down Expand Up @@ -216,6 +218,7 @@ ln -s preconfigured.target %{buildroot}%{_cross_unitdir}/default.target
%{_cross_unitdir}/systemd-tmpfiles-setup.service.d/00-debug.conf
%dir %{_cross_templatedir}
%{_cross_templatedir}/modprobe-conf
%{_cross_templatedir}/netdog-toml
%{_cross_templatedir}/motd
%{_cross_templatedir}/proxy-env
%{_cross_templatedir}/hostname-env
Expand Down
14 changes: 14 additions & 0 deletions sources/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion sources/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ members = [
"api/prairiedog",

# "api/migration/migrations/vX.Y.Z/..."
# (all migrations currently archived; replace this line with new ones)
"api/migration/migrations/v1.10.0/dns-settings",
"api/migration/migrations/v1.10.0/dns-settings-metadata",

"bottlerocket-release",

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "dns-settings-metadata"
version = "0.1.0"
license = "Apache-2.0 OR MIT"
edition = "2018"
publish = false

[dependencies]
migration-helpers = { path = "../../../migration-helpers", version = "0.1.0"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#![deny(rust_2018_idioms)]

use migration_helpers::common_migrations::{AddMetadataMigration, SettingMetadata};
use migration_helpers::{migrate, Result};
use std::process;

/// We added a new setting and `affected-services` metadata for `settings.dns`
fn run() -> Result<()> {
migrate(AddMetadataMigration(&[SettingMetadata {
metadata: &["affected-services"],
setting: "settings.dns",
}]))
}

// Returning a Result from main makes it print a Debug representation of the error, but with Snafu
// we have nice Display representations of the error, so we wrap "main" (run) and print any error.
// /~https://github.com/shepmaster/snafu/issues/110
fn main() {
if let Err(e) = run() {
eprintln!("{}", e);
process::exit(1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "dns-settings"
version = "0.1.0"
license = "Apache-2.0 OR MIT"
edition = "2018"
publish = false

[dependencies]
migration-helpers = { path = "../../../migration-helpers", version = "0.1.0"}
24 changes: 24 additions & 0 deletions sources/api/migration/migrations/v1.10.0/dns-settings/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#![deny(rust_2018_idioms)]

use migration_helpers::common_migrations::AddPrefixesMigration;
use migration_helpers::{migrate, Result};
use std::process;

/// We added new settings under `settings.dns` for configuring /etc/resolv.conf
fn run() -> Result<()> {
migrate(AddPrefixesMigration(vec![
"settings.dns",
"services.dns",
"configuration-files.netdog-toml",
]))
}

// Returning a Result from main makes it print a Debug representation of the error, but with Snafu
// we have nice Display representations of the error, so we wrap "main" (run) and print any error.
// /~https://github.com/shepmaster/snafu/issues/110
fn main() {
if let Err(e) = run() {
eprintln!("{}", e);
process::exit(1);
}
}
4 changes: 4 additions & 0 deletions sources/api/netdog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ valid example: `netdog.default-interface=eno1:dhcp4,dhcp6?`.
The subcommand `prepare-primary-interface` writes the default sysctls for the primary interface to
file in `/etc/sysctl.d`, and then executes `systemd-sysctl` to apply them.

The subcommand `write-resolv-conf` writes the resolv.conf, favoring DNS API settings and
supplementing any missing settings with DNS settings from the primary interface's DHCP lease. It
is meant to be used as a restart command for DNS API settings.

## Colophon

This text was generated using [cargo-readme](https://crates.io/crates/cargo-readme), and includes the rustdoc from `src/main.rs`.
60 changes: 27 additions & 33 deletions sources/api/netdog/src/cli/install.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
use super::{error, InterfaceFamily, InterfaceType, Result};
use crate::lease::LeaseInfo;
use crate::{CURRENT_IP, PRIMARY_INTERFACE, RESOLV_CONF};
use crate::dns::DnsSettings;
use crate::lease::{lease_path, LeaseInfo};
use crate::{CURRENT_IP, PRIMARY_INTERFACE};
use argh::FromArgs;
use rand::prelude::SliceRandom;
use rand::thread_rng;
use snafu::ResultExt;
use std::fmt::Write;
use snafu::{OptionExt, ResultExt};
use std::fs;
use std::net::IpAddr;
use std::path::PathBuf;
Expand Down Expand Up @@ -52,37 +50,33 @@ pub(crate) fn run(args: InstallArgs) -> Result<()> {
}

match (&args.interface_type, &args.interface_family) {
(InterfaceType::Dhcp, InterfaceFamily::Ipv4) => {
let info =
LeaseInfo::from_lease(&args.data_file).context(error::LeaseParseFailedSnafu {
path: &args.data_file,
(InterfaceType::Dhcp, InterfaceFamily::Ipv4 | InterfaceFamily::Ipv6) => {
// A lease should exist when using DHCP
let primary_lease_path =
lease_path(&primary_interface).context(error::MissingLeaseSnafu {
interface: primary_interface,
Comment on lines +53 to +57
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd still like to use args.data_file in some way, if only to check that it matches primary_lease_path with an early return if it doesn't.

Otherwise we potentially end up rewriting resolv.conf whenever any DHCP lease is renewed, instead of just the one for the primary interface.

Also, depending on the order in which leases are obtained, we might not have a lease for the primary interface yet early in the boot, if other interfaces are also using DHCP.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point about the timing!

Also, there's a check just before the match statement that returns if the install interface isn't the primary interface

})?;
// Randomize name server order, for libc implementations like musl that send
// queries to the first N servers.
let mut dns_servers: Vec<_> = info.dns_servers.iter().collect();
dns_servers.shuffle(&mut thread_rng());
write_resolv_conf(&dns_servers, &info.dns_search)?;
write_current_ip(&info.ip_address.addr())?;
}
_ => eprintln!("Unhandled 'install' command: {:?}", &args),
}
Ok(())
}
if args.data_file != primary_lease_path {
return error::PrimaryLeaseConflictSnafu {
wicked_path: args.data_file,
generated_path: primary_lease_path,
}
.fail();
}

/// Write resolver configuration for libc.
fn write_resolv_conf(dns_servers: &[&IpAddr], dns_search: &Option<Vec<String>>) -> Result<()> {
let mut output = String::new();
// Use DNS API settings if they exist, supplementing any missing settings with settings
// derived from the primary interface's DHCP lease
let lease =
LeaseInfo::from_lease(primary_lease_path).context(error::LeaseParseFailedSnafu)?;
let dns_settings = DnsSettings::from_config_or_lease(Some(&lease))
.context(error::GetDnsSettingsSnafu)?;
dns_settings
.write_resolv_conf()
.context(error::ResolvConfWriteFailedSnafu)?;

if let Some(s) = dns_search {
writeln!(output, "search {}", s.join(" ")).context(error::ResolvConfBuildFailedSnafu)?;
}

for n in dns_servers {
writeln!(output, "nameserver {}", n).context(error::ResolvConfBuildFailedSnafu)?;
write_current_ip(&lease.ip_address.addr())?;
}
}

fs::write(RESOLV_CONF, output)
.context(error::ResolvConfWriteFailedSnafu { path: RESOLV_CONF })?;
Ok(())
}

Expand Down
28 changes: 21 additions & 7 deletions sources/api/netdog/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub(crate) mod node_ip;
pub(crate) mod prepare_primary_interface;
pub(crate) mod remove;
pub(crate) mod set_hostname;
pub(crate) mod write_resolv_conf;

pub(crate) use generate_hostname::GenerateHostnameArgs;
pub(crate) use generate_net_config::GenerateNetConfigArgs;
Expand All @@ -15,6 +16,7 @@ pub(crate) use remove::RemoveArgs;
use serde::{Deserialize, Serialize};
pub(crate) use set_hostname::SetHostnameArgs;
use snafu::ResultExt;
pub(crate) use write_resolv_conf::WriteResolvConfArgs;

#[derive(Debug, PartialEq, Deserialize)]
#[serde(rename_all = "kebab-case")]
Expand Down Expand Up @@ -46,7 +48,7 @@ where

/// Potential errors during netdog execution
mod error {
use crate::{lease, net_config, wicked};
use crate::{dns, lease, net_config, wicked};
use snafu::Snafu;
use std::io;
use std::path::PathBuf;
Expand All @@ -61,6 +63,12 @@ mod error {
#[snafu(display("Failed to read current IP data in '{}': {}", path.display(), source))]
CurrentIpReadFailed { path: PathBuf, source: io::Error },

#[snafu(display("Unable to gather DNS settings: {}", source))]
GetDnsSettings { source: dns::Error },

#[snafu(display("Failed to read/parse DNS settings from DHCP lease: {}", source))]
DnsFromLease { source: dns::Error },

#[snafu(display("'systemd-sysctl' failed: {}", stderr))]
FailedSystemdSysctl { stderr: String },

Expand All @@ -85,8 +93,11 @@ mod error {
source: serde_json::error::Error,
},

#[snafu(display("Failed to read/parse lease data in '{}': {}", path.display(), source))]
LeaseParseFailed { path: PathBuf, source: lease::Error },
#[snafu(display("Failed to read/parse lease data: {}", source))]
LeaseParseFailed { source: lease::Error },

#[snafu(display("No DHCP lease found for interface '{}'", interface))]
MissingLease { interface: String },

#[snafu(display("Unable to read/parse network config from '{}': {}", path.display(), source))]
NetConfigParse {
Expand All @@ -100,11 +111,14 @@ mod error {
#[snafu(display("Failed to read primary interface from '{}': {}", path.display(), source))]
PrimaryInterfaceRead { path: PathBuf, source: io::Error },

#[snafu(display("Failed to build resolver configuration: {}", source))]
ResolvConfBuildFailed { source: std::fmt::Error },
#[snafu(display("Conflicting primary lease location; from wicked: '{}', generated by netdog: '{}'", wicked_path.display(), generated_path.display()))]
PrimaryLeaseConflict {
wicked_path: PathBuf,
generated_path: PathBuf,
},

#[snafu(display("Failed to write resolver configuration to '{}': {}", path.display(), source))]
ResolvConfWriteFailed { path: PathBuf, source: io::Error },
#[snafu(display("Failed to write resolver configuration: {}", source))]
ResolvConfWriteFailed { source: dns::Error },

#[snafu(display("Failed to build sysctl config: {}", source))]
SysctlConfBuild { source: std::fmt::Error },
Expand Down
37 changes: 37 additions & 0 deletions sources/api/netdog/src/cli/write_resolv_conf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use super::{error, Result};
use crate::dns::DnsSettings;
use crate::lease::{lease_path, LeaseInfo};
use crate::PRIMARY_INTERFACE;
use argh::FromArgs;
use snafu::ResultExt;
use std::fs;

#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand, name = "write-resolv-conf")]
/// Writes /etc/resolv.conf, using DNS API settings if they exist
pub(crate) struct WriteResolvConfArgs {}

pub(crate) fn run() -> Result<()> {
// Use DNS API settings if they exist, supplementing any missing settings with settings
// derived from the primary interface's DHCP lease if it exists
let primary_interface = fs::read_to_string(PRIMARY_INTERFACE)
.context(error::PrimaryInterfaceReadSnafu {
path: PRIMARY_INTERFACE,
})?
.trim()
.to_lowercase();

let primary_lease_path = lease_path(&primary_interface);
let dns_settings = if let Some(primary_lease_path) = primary_lease_path {
let lease =
LeaseInfo::from_lease(&primary_lease_path).context(error::LeaseParseFailedSnafu)?;
DnsSettings::from_config_or_lease(Some(&lease)).context(error::GetDnsSettingsSnafu)?
} else {
DnsSettings::from_config_or_lease(None).context(error::GetDnsSettingsSnafu)?
};

dns_settings
.write_resolv_conf()
.context(error::ResolvConfWriteFailedSnafu)?;
Ok(())
}
Loading