]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
nspawn: add greater control over how /etc/resolv.conf is handled
authorLennart Poettering <lennart@poettering.net>
Sat, 12 May 2018 19:50:57 +0000 (12:50 -0700)
committerLennart Poettering <lennart@poettering.net>
Tue, 22 May 2018 14:19:26 +0000 (16:19 +0200)
Fixes: #8014 #1781
man/systemd-nspawn.xml
man/systemd.nspawn.xml
src/nspawn/nspawn-gperf.gperf
src/nspawn/nspawn-settings.c
src/nspawn/nspawn-settings.h
src/nspawn/nspawn.c

index 9a0e02187f7abc4a80cbe5b5c977664dcf4b51f1..03e79683bcda9f5f411b3c66b03d34bb314d1772 100644 (file)
         <option>--link-journal=try-guest</option>.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--resolv-conf=</option></term>
+
+        <listitem><para>Configures how <filename>/etc/resolv.conf</filename> inside of the container (i.e. DNS
+        configuration synchronization from host to container) shall be handled. Takes one of <literal>off</literal>,
+        <literal>copy-host</literal>, <literal>copy-static</literal>, <literal>bind-host</literal>,
+        <literal>bind-static</literal>, <literal>delete</literal> or <literal>auto</literal>. If set to
+        <literal>off</literal> the <filename>/etc/resolv.conf</filename> file in the container is left as it is
+        included in the image, and neither modified nor bind mounted over. If set to <literal>copy-host</literal>, the
+        <filename>/etc/resolv.conf</filename> file from the host is copied into the container. Similar, if
+        <literal>bind-host</literal> is used, the file is bind mounted from the host into the container. If set to
+        <literal>copy-static</literal> the static <filename>resolv.conf</filename> file supplied with
+        <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> is
+        copied into the container, and correspondingly <literal>bind-static</literal> bind mounts it there. If set to
+        <literal>delete</literal> the <filename>/etc/resolv.conf</filename> file in the container is deleted if it
+        exists. Finally, if set to <literal>auto</literal> the file is left as it is if private networking is turned on
+        (see <option>--private-network</option>). Otherwise, if <filename>systemd-resolved.service</filename> is
+        connectible its static <filename>resolv.conf</filename> file is used, and if not the host's
+        <filename>/etc/resolv.conf</filename> file is used. In the latter cases the file is copied if the image is
+        writable, and bind mounted otherwise. It's recommended to use <literal>copy</literal> if the container shall be
+        able to make changes to the DNS configuration on its own, deviating from the host's settings. Otherwise
+        <literal>bind</literal> is preferable, as it means direct changes to <filename>/etc/resolv.conf</filename> in
+        the container are not allowed, as it is a read-only bind mount (but note that if the container has enough
+        privileges, it might simply go ahead and unmount the bind mount anyway). Note that both if the file is bind
+        mounted and if it is copied no further propagation of configuration is generally done after the one-time early
+        initialization (this is because the file is usually updated through copying and renaming). Defaults to
+        <literal>auto</literal>.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--read-only</option></term>
 
index 1780bfd79a2277815637ef2a83bed647f3768454..679052ae78bc4020a473d25283f22cb2ae5d3f7c 100644 (file)
         details.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>ResolvConf=</varname></term>
+
+        <listitem><para>Configures how <filename>/etc/resolv.conf</filename> in the container shall be handled. This is
+        equivalent to the <option>--resolv-conf=</option> command line switch, and takes the same argument. See
+        <citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
+        details.</para></listitem>
+      </varlistentry>
+
     </variablelist>
   </refsect1>
 
index f8234e75d489ed24f431bda542820326a026ec22..0f31aa2ec4f43c6f234df36e13cd652a81782ad5 100644 (file)
@@ -53,6 +53,7 @@ Exec.Hostname,                config_parse_hostname,       0,                 of
 Exec.NoNewPrivileges,         config_parse_tristate,       0,                 offsetof(Settings, no_new_privileges)
 Exec.OOMScoreAdjust,          config_parse_oom_score_adjust, 0,               0
 Exec.CPUAffinity,             config_parse_cpu_affinity,   0,                 0
+Exec.ResolvConf,              config_parse_resolv_conf,    0,                 offsetof(Settings, resolv_conf)
 Files.ReadOnly,               config_parse_tristate,       0,                 offsetof(Settings, read_only)
 Files.Volatile,               config_parse_volatile_mode,  0,                 offsetof(Settings, volatile_mode)
 Files.Bind,                   config_parse_bind,           0,                 0
index 0acf718456b66f2c05ac2149b2bd1b60e3a4bdb6..367f18c4200c8d6d7e2d72aafcf057b997ffee3f 100644 (file)
@@ -16,6 +16,7 @@
 #include "process-util.h"
 #include "rlimit-util.h"
 #include "socket-util.h"
+#include "string-table.h"
 #include "string-util.h"
 #include "strv.h"
 #include "user-util.h"
@@ -35,6 +36,7 @@ int settings_load(FILE *f, const char *path, Settings **ret) {
         s->start_mode = _START_MODE_INVALID;
         s->personality = PERSONALITY_INVALID;
         s->userns_mode = _USER_NAMESPACE_MODE_INVALID;
+        s->resolv_conf = _RESOLV_CONF_MODE_INVALID;
         s->uid_shift = UID_INVALID;
         s->uid_range = UID_INVALID;
         s->no_new_privileges = -1;
@@ -724,3 +726,17 @@ int config_parse_cpu_affinity(
 
         return 0;
 }
+
+DEFINE_CONFIG_PARSE_ENUM(config_parse_resolv_conf, resolv_conf_mode, ResolvConfMode, "Failed to parse resolv.conf mode");
+
+static const char *const resolv_conf_mode_table[_RESOLV_CONF_MODE_MAX] = {
+        [RESOLV_CONF_OFF] = "off",
+        [RESOLV_CONF_COPY_HOST] = "copy-host",
+        [RESOLV_CONF_COPY_STATIC] = "copy-static",
+        [RESOLV_CONF_BIND_HOST] = "bind-host",
+        [RESOLV_CONF_BIND_STATIC] = "bind-static",
+        [RESOLV_CONF_DELETE] = "delete",
+        [RESOLV_CONF_AUTO] = "auto",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(resolv_conf_mode, ResolvConfMode, RESOLV_CONF_AUTO);
index 8dc310d5693ed15a319c5f882a57f4c5c222e7d7..8b4b897fa6debb3c0d4614f92d9c2b6ab549e3ba 100644 (file)
@@ -33,6 +33,18 @@ typedef enum UserNamespaceMode {
         _USER_NAMESPACE_MODE_INVALID = -1,
 } UserNamespaceMode;
 
+typedef enum ResolvConfMode {
+        RESOLV_CONF_OFF,
+        RESOLV_CONF_COPY_HOST,
+        RESOLV_CONF_COPY_STATIC,
+        RESOLV_CONF_BIND_HOST,
+        RESOLV_CONF_BIND_STATIC,
+        RESOLV_CONF_DELETE,
+        RESOLV_CONF_AUTO,
+        _RESOLV_CONF_MODE_MAX,
+        _RESOLV_CONF_MODE_INVALID = -1
+} ResolvConfMode;
+
 typedef enum SettingsMask {
         SETTING_START_MODE        = UINT64_C(1) << 0,
         SETTING_ENVIRONMENT       = UINT64_C(1) << 1,
@@ -55,9 +67,10 @@ typedef enum SettingsMask {
         SETTING_NO_NEW_PRIVILEGES = UINT64_C(1) << 18,
         SETTING_OOM_SCORE_ADJUST  = UINT64_C(1) << 19,
         SETTING_CPU_AFFINITY      = UINT64_C(1) << 20,
-        SETTING_RLIMIT_FIRST      = UINT64_C(1) << 21, /* we define one bit per resource limit here */
-        SETTING_RLIMIT_LAST       = UINT64_C(1) << (21 + _RLIMIT_MAX - 1),
-        _SETTINGS_MASK_ALL        = (UINT64_C(1) << (21 + _RLIMIT_MAX)) - 1,
+        SETTING_RESOLV_CONF       = UINT64_C(1) << 21,
+        SETTING_RLIMIT_FIRST      = UINT64_C(1) << 22, /* we define one bit per resource limit here */
+        SETTING_RLIMIT_LAST       = UINT64_C(1) << (22 + _RLIMIT_MAX - 1),
+        _SETTINGS_MASK_ALL        = (UINT64_C(1) << (22 + _RLIMIT_MAX)) - 1,
         _FORCE_ENUM_WIDTH         = UINT64_MAX
 } SettingsMask;
 
@@ -96,6 +109,7 @@ typedef struct Settings {
         bool oom_score_adjust_set;
         cpu_set_t *cpuset;
         unsigned cpuset_ncpus;
+        ResolvConfMode resolv_conf;
 
         /* [Image] */
         int read_only;
@@ -143,3 +157,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_syscall_filter);
 CONFIG_PARSER_PROTOTYPE(config_parse_hostname);
 CONFIG_PARSER_PROTOTYPE(config_parse_oom_score_adjust);
 CONFIG_PARSER_PROTOTYPE(config_parse_cpu_affinity);
+CONFIG_PARSER_PROTOTYPE(config_parse_resolv_conf);
+
+const char *resolv_conf_mode_to_string(ResolvConfMode a) _const_;
+ResolvConfMode resolv_conf_mode_from_string(const char *s) _pure_;
index 9cfbb1171e6c7a7573639049c58f63182e0f6cf7..3dbca99ef8bfd23cbabb439cb45eaacb185e1169 100644 (file)
@@ -211,6 +211,7 @@ static int arg_oom_score_adjust = 0;
 static bool arg_oom_score_adjust_set = false;
 static cpu_set_t *arg_cpuset = NULL;
 static unsigned arg_cpuset_ncpus = 0;
+static ResolvConfMode arg_resolv_conf = RESOLV_CONF_AUTO;
 
 static void help(void) {
 
@@ -287,6 +288,7 @@ static void help(void) {
                "     --link-journal=MODE    Link up guest journal, one of no, auto, guest, \n"
                "                            host, try-guest, try-host\n"
                "  -j                        Equivalent to --link-journal=try-guest\n"
+               "     --resolv-conf=MODE     Select mode of /etc/resolv.conf initialization\n"
                "     --read-only            Mount the root directory read-only\n"
                "     --bind=PATH[:PATH[:OPTIONS]]\n"
                "                            Bind mount a file or directory from the host into\n"
@@ -463,6 +465,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_NO_NEW_PRIVILEGES,
                 ARG_OOM_SCORE_ADJUST,
                 ARG_CPU_AFFINITY,
+                ARG_RESOLV_CONF,
         };
 
         static const struct option options[] = {
@@ -521,6 +524,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "rlimit",                 required_argument, NULL, ARG_RLIMIT                 },
                 { "oom-score-adjust",       required_argument, NULL, ARG_OOM_SCORE_ADJUST       },
                 { "cpu-affinity",           required_argument, NULL, ARG_CPU_AFFINITY           },
+                { "resolv-conf",            required_argument, NULL, ARG_RESOLV_CONF            },
                 {}
         };
 
@@ -1222,6 +1226,21 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
                 }
 
+                case ARG_RESOLV_CONF:
+                        if (streq(optarg, "help")) {
+                                DUMP_STRING_TABLE(resolv_conf_mode, ResolvConfMode, _RESOLV_CONF_MODE_MAX);
+                                return 0;
+                        }
+
+                        arg_resolv_conf = resolv_conf_mode_from_string(optarg);
+                        if (arg_resolv_conf < 0) {
+                                log_error("Failed to parse /etc/resolv.conf mode: %s", optarg);
+                                return -EINVAL;
+                        }
+
+                        arg_settings_mask |= SETTING_RESOLV_CONF;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -1507,6 +1526,19 @@ static int setup_timezone(const char *dest) {
         return 0;
 }
 
+static int have_resolv_conf(const char *path) {
+        assert(path);
+
+        if (access(path, F_OK) < 0) {
+                if (errno == ENOENT)
+                        return 0;
+
+                return log_debug_errno(errno, "Failed to determine whether '%s' is available: %m", path);
+        }
+
+        return 1;
+}
+
 static int resolved_listening(void) {
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         _cleanup_free_ char *dns_stub_listener_mode = NULL;
@@ -1536,13 +1568,31 @@ static int resolved_listening(void) {
 }
 
 static int setup_resolv_conf(const char *dest) {
-        _cleanup_free_ char *resolved = NULL, *etc = NULL;
-        const char *where;
-        int r, found;
+        _cleanup_free_ char *etc = NULL;
+        const char *where, *what;
+        ResolvConfMode m;
+        int r;
 
         assert(dest);
 
-        if (arg_private_network)
+        if (arg_resolv_conf == RESOLV_CONF_AUTO) {
+                if (arg_private_network)
+                        m = RESOLV_CONF_OFF;
+                else if (have_resolv_conf(STATIC_RESOLV_CONF) > 0 && resolved_listening() > 0)
+                        /* resolved is enabled on the host. In this, case bind mount its static resolv.conf file into the
+                         * container, so that the container can use the host's resolver. Given that network namespacing is
+                         * disabled it's only natural of the container also uses the host's resolver. It also has the big
+                         * advantage that the container will be able to follow the host's DNS server configuration changes
+                         * transparently. */
+                        m = RESOLV_CONF_BIND_STATIC;
+                else if (have_resolv_conf("/etc/resolv.conf") > 0)
+                        m = arg_read_only && arg_volatile_mode != VOLATILE_YES ? RESOLV_CONF_BIND_HOST : RESOLV_CONF_COPY_HOST;
+                else
+                        m = arg_read_only && arg_volatile_mode != VOLATILE_YES ? RESOLV_CONF_OFF : RESOLV_CONF_DELETE;
+        } else
+                m = arg_resolv_conf;
+
+        if (m == RESOLV_CONF_OFF)
                 return 0;
 
         r = chase_symlinks("/etc", dest, CHASE_PREFIX_ROOT, &etc);
@@ -1552,38 +1602,46 @@ static int setup_resolv_conf(const char *dest) {
         }
 
         where = strjoina(etc, "/resolv.conf");
-        found = chase_symlinks(where, dest, CHASE_NONEXISTENT, &resolved);
-        if (found < 0) {
-                log_warning_errno(found, "Failed to resolve /etc/resolv.conf path in container, ignoring: %m");
+
+        if (m == RESOLV_CONF_DELETE) {
+                if (unlink(where) < 0)
+                        log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno, "Failed to remove '%s', ignoring: %m", where);
+
                 return 0;
         }
 
-        if (access(STATIC_RESOLV_CONF, F_OK) >= 0 &&
-            resolved_listening() > 0) {
+        if (IN_SET(m, RESOLV_CONF_BIND_STATIC, RESOLV_CONF_COPY_STATIC))
+                what = STATIC_RESOLV_CONF;
+        else
+                what = "/etc/resolv.conf";
 
-                /* resolved is enabled on the host. In this, case bind mount its static resolv.conf file into the
-                 * container, so that the container can use the host's resolver. Given that network namespacing is
-                 * disabled it's only natural of the container also uses the host's resolver. It also has the big
-                 * advantage that the container will be able to follow the host's DNS server configuration changes
-                 * transparently. */
+        if (IN_SET(m, RESOLV_CONF_BIND_HOST, RESOLV_CONF_BIND_STATIC)) {
+                _cleanup_free_ char *resolved = NULL;
+                int found;
+
+                found = chase_symlinks(where, dest, CHASE_NONEXISTENT, &resolved);
+                if (found < 0) {
+                        log_warning_errno(found, "Failed to resolve /etc/resolv.conf path in container, ignoring: %m");
+                        return 0;
+                }
 
                 if (found == 0) /* missing? */
                         (void) touch(resolved);
 
-                r = mount_verbose(LOG_DEBUG, STATIC_RESOLV_CONF, resolved, NULL, MS_BIND, NULL);
+                r = mount_verbose(LOG_WARNING, what, resolved, NULL, MS_BIND, NULL);
                 if (r >= 0)
                         return mount_verbose(LOG_ERR, NULL, resolved, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NODEV, NULL);
         }
 
         /* If that didn't work, let's copy the file */
-        r = copy_file("/etc/resolv.conf", where, O_TRUNC|O_NOFOLLOW, 0644, 0, COPY_REFLINK);
+        r = copy_file(what, where, O_TRUNC|O_NOFOLLOW, 0644, 0, COPY_REFLINK);
         if (r < 0) {
                 /* If the file already exists as symlink, let's suppress the warning, under the assumption that
                  * resolved or something similar runs inside and the symlink points there.
                  *
                  * If the disk image is read-only, there's also no point in complaining.
                  */
-                log_full_errno(IN_SET(r, -ELOOP, -EROFS, -EACCES, -EPERM) ? LOG_DEBUG : LOG_WARNING, r,
+                log_full_errno(!IN_SET(RESOLV_CONF_COPY_HOST, RESOLV_CONF_COPY_STATIC) && IN_SET(r, -ELOOP, -EROFS, -EACCES, -EPERM) ? LOG_DEBUG : LOG_WARNING, r,
                                "Failed to copy /etc/resolv.conf to %s, ignoring: %m", where);
                 return 0;
         }
@@ -3385,6 +3443,10 @@ static int merge_settings(Settings *settings, const char *path) {
                 }
         }
 
+        if ((arg_settings_mask & SETTING_RESOLV_CONF) == 0 &&
+            settings->resolv_conf != _RESOLV_CONF_MODE_INVALID)
+                arg_resolv_conf = settings->resolv_conf;
+
         return 0;
 }