Merge pull request #76542 from primeos/etc-hosts-fqdn-fix

nixos/networking: Add the FQDN and hostname to /etc/hosts
This commit is contained in:
Florian Klink 2020-05-25 22:57:24 +02:00 committed by GitHub
commit 921a4ec9c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 124 additions and 18 deletions

View File

@ -415,6 +415,32 @@ systemd.services.nginx.serviceConfig.ReadWritePaths = [ "/var/www" ];
continue to work through Breezy.
</para>
</listitem>
<listitem>
<para>
In addition to the hostname, the fully qualified domain name (FQDN),
which consists of <literal>${cfg.hostName}</literal> and
<literal>${cfg.domain}</literal> is now added to
<literal>/etc/hosts</literal>, to allow local FQDN resolution, as used by the
<literal>hostname --fqdn</literal> command and other applications that
try to determine the FQDN. These new entries take precedence over entries
from the DNS which could cause regressions in some very specific setups.
Additionally the hostname is now resolved to <literal>127.0.0.2</literal>
instead of <literal>127.0.1.1</literal> to be consistent with what
<literal>nss-myhostname</literal> (from systemd) returns.
The old behaviour can e.g. be restored by using
<literal>networking.hosts = lib.mkForce { "127.0.1.1" = [ config.networking.hostName ]; };</literal>.
</para>
</listitem>
<listitem>
<para>
The hostname (<literal>networking.hostName</literal>) must now be a valid
DNS label (see RFC 1035) and as such must not contain the domain part.
This means that the hostname must start with a letter, end with a letter
or digit, and have as interior characters only letters, digits, and
hyphen. The maximum length is 63 characters. Additionally it is
recommended to only use lower-case characters.
</para>
</listitem>
</itemizedlist>
</section>

View File

@ -8,9 +8,6 @@ let
cfg = config.networking;
localhostMapped4 = cfg.hosts ? "127.0.0.1" && elem "localhost" cfg.hosts."127.0.0.1";
localhostMapped6 = cfg.hosts ? "::1" && elem "localhost" cfg.hosts."::1";
localhostMultiple = any (elem "localhost") (attrValues (removeAttrs cfg.hosts [ "127.0.0.1" "::1" ]));
in
@ -147,12 +144,6 @@ in
config = {
assertions = [{
assertion = localhostMapped4;
message = ''`networking.hosts` doesn't map "127.0.0.1" to "localhost"'';
} {
assertion = !cfg.enableIPv6 || localhostMapped6;
message = ''`networking.hosts` doesn't map "::1" to "localhost"'';
} {
assertion = !localhostMultiple;
message = ''
`networking.hosts` maps "localhost" to something other than "127.0.0.1"
@ -161,22 +152,34 @@ in
'';
}];
networking.hosts = {
"127.0.0.1" = [ "localhost" ];
} // optionalAttrs (cfg.hostName != "") {
"127.0.1.1" = [ cfg.hostName ];
# These entries are required for "hostname -f" and to resolve both the
# hostname and FQDN correctly:
networking.hosts = let
hostnames = # Note: The FQDN (canonical hostname) has to come first:
optional (cfg.hostName != "" && cfg.domain != null) "${cfg.hostName}.${cfg.domain}"
++ optional (cfg.hostName != "") cfg.hostName; # Then the hostname (without the domain)
in {
"127.0.0.2" = hostnames;
} // optionalAttrs cfg.enableIPv6 {
"::1" = [ "localhost" ];
"::1" = hostnames;
};
networking.hostFiles = let
# Note: localhostHosts has to appear first in /etc/hosts so that 127.0.0.1
# resolves back to "localhost" (as some applications assume) instead of
# the FQDN! By default "networking.hosts" also contains entries for the
# FQDN so that e.g. "hostname -f" works correctly.
localhostHosts = pkgs.writeText "localhost-hosts" ''
127.0.0.1 localhost
${optionalString cfg.enableIPv6 "::1 localhost"}
'';
stringHosts =
let
oneToString = set: ip: ip + " " + concatStringsSep " " set.${ip} + "\n";
allToString = set: concatMapStrings (oneToString set) (attrNames set);
in pkgs.writeText "string-hosts" (allToString (filterAttrs (_: v: v != []) cfg.hosts));
extraHosts = pkgs.writeText "extra-hosts" cfg.extraHosts;
in mkBefore [ stringHosts extraHosts ];
in mkBefore [ localhostHosts stringHosts extraHosts ];
environment.etc =
{ # /etc/services: TCP/UDP port assignments.

View File

@ -376,10 +376,20 @@ in
networking.hostName = mkOption {
default = "nixos";
type = types.str;
# Only allow hostnames without the domain name part (i.e. no FQDNs, see
# e.g. "man 5 hostname") and require valid DNS labels (recommended
# syntax). Note: We also allow underscores for compatibility/legacy
# reasons (as undocumented feature):
type = types.strMatching
"^[[:alpha:]]([[:alnum:]_-]{0,61}[[:alnum:]])?$";
description = ''
The name of the machine. Leave it empty if you want to obtain
it from a DHCP server (if using DHCP).
The name of the machine. Leave it empty if you want to obtain it from a
DHCP server (if using DHCP). The hostname must be a valid DNS label (see
RFC 1035 section 2.3.1: "Preferred name syntax") and as such must not
contain the domain part. This means that the hostname must start with a
letter, end with a letter or digit, and have as interior characters only
letters, digits, and hyphen. The maximum length is 63 characters.
Additionally it is recommended to only use lower-case characters.
'';
};

View File

@ -134,6 +134,7 @@ in
hitch = handleTest ./hitch {};
hocker-fetchdocker = handleTest ./hocker-fetchdocker {};
home-assistant = handleTest ./home-assistant.nix {};
hostname = handleTest ./hostname.nix {};
hound = handleTest ./hound.nix {};
hydra = handleTest ./hydra {};
hydra-db-migration = handleTest ./hydra/db-migration.nix {};

66
nixos/tests/hostname.nix Normal file
View File

@ -0,0 +1,66 @@
{ system ? builtins.currentSystem,
config ? {},
pkgs ? import ../.. { inherit system config; }
}:
with import ../lib/testing-python.nix { inherit system pkgs; };
with pkgs.lib;
let
makeHostNameTest = hostName: domain:
let
fqdn = hostName + (optionalString (domain != null) ".${domain}");
in
makeTest {
name = "hostname-${fqdn}";
meta = with pkgs.stdenv.lib.maintainers; {
maintainers = [ primeos blitz ];
};
machine = { lib, ... }: {
networking.hostName = hostName;
networking.domain = domain;
environment.systemPackages = with pkgs; [
inetutils
];
};
testScript = ''
start_all()
machine = ${hostName}
machine.wait_for_unit("network-online.target")
# The FQDN, domain name, and hostname detection should work as expected:
assert "${fqdn}" == machine.succeed("hostname --fqdn").strip()
assert "${optionalString (domain != null) domain}" == machine.succeed("dnsdomainname").strip()
assert (
"${hostName}"
== machine.succeed(
'hostnamectl status | grep "Static hostname" | cut -d: -f2'
).strip()
)
# 127.0.0.1 and ::1 should resolve back to "localhost":
assert (
"localhost" == machine.succeed("getent hosts 127.0.0.1 | awk '{print $2}'").strip()
)
assert "localhost" == machine.succeed("getent hosts ::1 | awk '{print $2}'").strip()
# 127.0.0.2 should resolve back to the FQDN and hostname:
fqdn_and_host_name = "${optionalString (domain != null) "${hostName}.${domain} "}${hostName}"
assert (
fqdn_and_host_name
== machine.succeed("getent hosts 127.0.0.2 | awk '{print $2,$3}'").strip()
)
'';
};
in
{
noExplicitDomain = makeHostNameTest "ahost" null;
explicitDomain = makeHostNameTest "ahost" "adomain";
}