mirror of
https://github.com/systemd/systemd.git
synced 2024-12-22 17:35:35 +03:00
nspawn: add greater control over how /etc/resolv.conf is handled
Fixes: #8014 #1781
This commit is contained in:
parent
8904ab86b0
commit
09d423e921
@ -858,6 +858,35 @@
|
||||
<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>
|
||||
|
||||
|
@ -340,6 +340,15 @@
|
||||
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>
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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_;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user