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

Conversation

zmrow
Copy link
Contributor

@zmrow zmrow commented Aug 16, 2022

Issue number:
Related to #2204

Description of changes:
It's probably easiest to review this commit by commit, rather than as a whole. All the pieces are pretty small.

    netdog: Add dns module

    This commit adds a new `dns` module to centralize the logic of gathering
    DNS information from an API-generated configuration file, and merging
    any missing settings from a given DHCP lease.  This module also handles
    writing the `/etc/resolv.conf` using the DNS information
    netdog: Update `install` subcommand to use `dns` module

    This change updates the `install` subcommand to make use of the
    newly-added `dns` module.  The most important change here is that the
    code will use DNS settings from config if they exist, supplementing any
    missing settings with settings from DHCP lease.  The code also takes
    into account DHCP6 lease renewals when rewriting the `resolv.conf`.
    When multiple lease files exist, the code uses the DHCP4 lease to gather
    DHCP lease data
    netdog: Add new `write-resolv-conf` subcommand

    This commit adds a new `write-resolv-conf` subcommand, which is meant to
    be used as a restart command for DNS API settings.  It will write the
    `resolv.conf` using DNS settings if they exist, supplementing any
    missing settings with settings from the DHCP lease of the primary
    interface
    settings: Add DNS configurability via `settings.dns`

    This commit adds the ability to configure DNS name servers and search
    suffixes via a new settings prefix `settings.dns`.  These DNS settings
    populate a configuration file which gets used by `netdog` to write the
    `resolv.conf`.  `netdog write-resolv-conf` is triggered via restart
    command.  If DNS settings exist, they are used to write the
    `resolv.conf`, otherwise the DHCP lease for the primary interface is
    used
    migrations: Add migrations for `settings.dns`

    This adds the migrations needed for `settings.dns`, one for the setting,
    its service and configuration file, and one for the setting metadata
    README: Add DNS settings documentation

Testing done:

  • Boot an aws-k8s-1.22 instance and confirm resolv.conf is still written correctly with no DNS settings
  • Boot a metal-dev image with and without user data and ensure resolv.conf is written properly with the correct settings
  • Manually change DNS settings via apiclient and observe resolv.conf written with desired settings, using DHCP if only one of the settings is set.
$ cat /etc/resolv.conf 
search us-west-2.compute.internal
nameserver 192.168.0.2

$ apiclient set -j '{"dns": {"name-servers": ["1.2.3.4", "2.3.4.5"]}}'

$ cat /etc/resolv.conf                                                                                       
search us-west-2.compute.internal
nameserver 1.2.3.4
nameserver 2.3.4.5

$ apiclient set -j '{"dns": {"search-list": ["foo.bar", "baz.foo"]}}'

$ cat /etc/resolv.conf 
search foo.bar baz.foo
nameserver 1.2.3.4
nameserver 2.3.4.5
  • Test migrations work properly

Terms of contribution:

By submitting this pull request, I agree that this contribution is dual-licensed under the terms of both the Apache License, version 2.0, and the MIT license.

@zmrow zmrow requested review from bcressey, etungsten and cbgbt August 16, 2022 20:18
@zmrow zmrow marked this pull request as ready for review August 16, 2022 22:38
@zmrow
Copy link
Contributor Author

zmrow commented Aug 18, 2022

^ Rebases on develop and fix the merge conflict in Release.toml

@zmrow
Copy link
Contributor Author

zmrow commented Aug 18, 2022

^ Fixed a clippy lint I originally missed

sources/api/netdog/src/dns.rs Outdated Show resolved Hide resolved
sources/api/netdog/src/dns.rs Show resolved Hide resolved
sources/api/netdog/src/dns.rs Show resolved Hide resolved
sources/api/netdog/src/dns.rs Outdated Show resolved Hide resolved
sources/api/netdog/src/dns.rs Outdated Show resolved Hide resolved
packages/release/netdog.template Show resolved Hide resolved
Release.toml Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
@zmrow
Copy link
Contributor Author

zmrow commented Aug 19, 2022

^ Addresses @arnaldo2792 's comments

@zmrow
Copy link
Contributor Author

zmrow commented Aug 19, 2022

^ Rebases on develop and fixes the sources/Cargo.toml merge conflict

@zmrow
Copy link
Contributor Author

zmrow commented Aug 19, 2022

^ Changes the type of DnsSettings::name-servers to Option since a user may not supply it... (It was an oversight, search-list was already Option)

sources/models/shared-defaults/defaults.toml Outdated Show resolved Hide resolved
sources/api/netdog/src/dns.rs Outdated Show resolved Hide resolved
@zmrow
Copy link
Contributor Author

zmrow commented Aug 23, 2022

^ Addresses @etungsten 's coments

@zmrow
Copy link
Contributor Author

zmrow commented Aug 23, 2022

^ Fixes the migration filename (thanks @etungsten!)

Comment on lines 52 to 58
if config_exists {
let config_str =
fs::read_to_string(path).context(error::DnsConfReadFailedSnafu { path })?;
let dns_config =
toml::from_str(&config_str).context(error::DnsConfParseSnafu { path })?;

Ok(Some(dns_config))
Copy link
Contributor

Choose a reason for hiding this comment

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

In the from_lease_info() path, if we're given a lease info file with no relevant fields, we return a DnsSettings struct with both optional fields set to None.

Would it make sense to do that here as well, and always return a Result<Self> instead?

Comment on lines 59 to 75
// Use DNS API settings if they exist, otherwise use DHCP lease
let dns_settings = if let Some(settings) =
DnsSettings::from_config().context(error::DnsFromConfSnafu)?
{
settings
} else {
DnsSettings::from_lease_info(&info)
};
dns_settings
.write_resolv_conf()
.context(error::ResolvConfWriteFailedSnafu)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

This relates to from_config() returning a valid DnsSettings struct - potentially we want to divide this up rather than making it all API settings or all lease settings:

  • if we have name-servers in the config, use those, otherwise use the lease
  • if we have a search-list in the config, use it, otherwise use the lease

Comment on lines 14 to 27
pub(crate) fn run() -> Result<()> {
// Use DNS API settings if they exist, otherwise use the primary interface's lease
let dns_settings =
if let Some(settings) = DnsSettings::from_config().context(error::DnsFromConfSnafu)? {
settings
} else {
let lease_path = primary_interface_lease()?;
DnsSettings::from_lease_file(lease_path).context(error::DnsFromLeaseSnafu)?
};
dns_settings
.write_resolv_conf()
.context(error::ResolvConfWriteFailedSnafu)?;
Ok(())
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can the merging of the DNS config settings and the lease settings be a helper function? Especially if becomes slightly more complicated than using one source or the other.

We're doing almost but not quite exactly the same thing as in the DHCP helper entry point, which seems like it could lead to bugs.

Comment on lines 40 to 48
// If both ipv4 and ipv6 leases exist, use the ipv4 lease for DNS settings
let ipv4_exists = Path::exists(&ipv4);
let ipv6_exists = Path::exists(&ipv6);
match (ipv4_exists, ipv6_exists) {
(true, true) => Ok(ipv4),
(true, false) => Ok(ipv4),
(false, true) => Ok(ipv6),
(false, false) => error::MissingLeaseSnafu { path: LEASE_FOLDER }.fail(),
}
Copy link
Contributor

Choose a reason for hiding this comment

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

We entirely ignore the DHCP6 lease coming from the "install" path - it seems like we'd want the behavior to be consistent, otherwise we'll lose the IPv6 version of the settings the next time the lease is renewed and resolv.conf is rewritten.

@zmrow
Copy link
Contributor Author

zmrow commented Aug 26, 2022

^ Addresses @bcressey 's comments. The highlights:

  • DnsSettings public interface is much smaller, preferring creation from config and merging in settings from DHCP lease as necessary. This reduces the paths possible, addressing the possibility for bug-ridden multiple paths to the DNS settings.
  • DNS settings from DHCP lease are used if the DNS API settings aren't set
  • The install DHCP helper now correctly uses the proper DHCP path when gathering DNS settings. This means we won't inadvertently lose settings on DHCP lease renewal, and ensures the write-resolv-conf and install paths are doing the same thing.

@zmrow
Copy link
Contributor Author

zmrow commented Aug 30, 2022

^ Above push avoids returning an error if the DHCP lease doesn't exist. The lease won't exist in the case a user is not using DHCP (static addressing).

It also makes the DHCP lease optional when constructing a DnsSettings.

@zmrow
Copy link
Contributor Author

zmrow commented Aug 30, 2022

^ Rebases on develop and fixes the merge conflict with Release.toml

Comment on lines +53 to +57
(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,
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

Comment on lines 40 to 41
if self.nameservers.is_none() {
// Randomize name server order, for libc implementations like musl that send
// queries to the first N servers.
let mut dns_servers: Vec<IpAddr> = lease.dns_servers.clone().into_iter().collect();
dns_servers.shuffle(&mut thread_rng());

self.nameservers = Some(BTreeSet::from_iter(dns_servers.into_iter()));
}
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 prefer to shuffle the nameservers after the merge, just before writing resolv.conf - to guarantee that they're always shuffled. Here we don't shuffle if the nameservers are set via API.

Comment on lines +176 to +170
#[test]
fn empty_config() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe add a test for missing_config too, where the file doesn't exist.

@@ -47,6 +52,7 @@ static PRIMARY_INTERFACE: &str = "/var/lib/netdog/primary_interface";
static DEFAULT_NET_CONFIG_FILE: &str = "/var/lib/bottlerocket/net.toml";
static PRIMARY_SYSCTL_CONF: &str = "/etc/sysctl.d/90-primary_interface.conf";
static SYSTEMD_SYSCTL: &str = "/usr/lib/systemd/systemd-sysctl";
static LEASE_FOLDER: &str = "/var/run/wicked";
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: 🪟 -> 🐧

Suggested change
static LEASE_FOLDER: &str = "/var/run/wicked";
static LEASE_DIR: &str = "/var/run/wicked";

zmrow added 6 commits August 31, 2022 22:31
This commit adds a new `dns` module to centralize the logic of gathering
DNS information from an API-generated configuration file, and merging
any missing settings from a given DHCP lease.  This module also handles
writing the `/etc/resolv.conf` using the DNS information.
This change updates the `install` subcommand to make use of the
newly-added `dns` module.  The most important change here is that the
code will use DNS settings from config if they exist, supplementing any
missing settings with settings from DHCP lease.  The code also takes
into account DHCP6 lease renewals when rewriting the `resolv.conf`.
When multiple lease files exist, the code uses the DHCP4 lease to gather
DHCP lease data.
This commit adds a new `write-resolv-conf` subcommand, which is meant to
be used as a restart command for DNS API settings.  It will write the
`resolv.conf` using DNS settings if they exist, supplementing any
missing settings with settings from the DHCP lease of the primary
interface if it exists.
This commit adds the ability to configure DNS name servers and search
suffixes via a new settings prefix `settings.dns`.  These DNS settings
populate a configuration file which gets used by `netdog` to write the
`resolv.conf`.  `netdog write-resolv-conf` is triggered via restart
command.  If DNS settings exist, they are used to write the
`resolv.conf`, otherwise the DHCP lease for the primary interface is
used.
This adds the migrations needed for `settings.dns`, one for the setting,
its service and configuration file, and one for the setting metadata.
@zmrow
Copy link
Contributor Author

zmrow commented Sep 1, 2022

^ Addresses @bcressey 's comments.

I also simplified a few tests - I was calling DnsSettings::from_config_impl() multiple times when it was unnecessary...

@zmrow zmrow merged commit a3e92e9 into bottlerocket-os:develop Sep 1, 2022
@zmrow zmrow deleted the dns-settings branch September 1, 2022 17:59
@rickymulder
Copy link

Is there plans to merge this into 1.9.x? Attempting to spin up an EKS 1.21 cluster with bottlerocket and my host-network pods can't do cluster dns resolution without calling out the kube-dns service ip.

assert_ne!(resolv_conf == format1, resolv_conf == format2)
}

#[test]
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice tests!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants