Closes #35623.
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b ProtectHostname = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
- readonly s ProtectHostnameEx = '...';
+ readonly (ss) ProtectHostnameEx = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b MemoryKSM = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
<para><varname>ProtectHostnameEx</varname> implement the destination parameter of the
unit file setting <varname>ProtectHostname=</varname> listed in
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
- Unlike boolean <varname>ProtectHostname</varname>, <varname>ProtectHostnameEx</varname>
- is a string type.</para>
+ Unlike boolean <varname>ProtectHostname</varname>, <varname>ProtectHostnameEx</varname> is a pair of
+ strings, the first one is a boolean string or special value <literal>private</literal>, and the second
+ one is an optional private hostname that will be set in a new UTS namespace for the unit.</para>
</refsect2>
</refsect1>
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b ProtectHostname = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
- readonly s ProtectHostnameEx = '...';
+ readonly (ss) ProtectHostnameEx = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b MemoryKSM = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b ProtectHostname = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
- readonly s ProtectHostnameEx = '...';
+ readonly (ss) ProtectHostnameEx = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b MemoryKSM = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b ProtectHostname = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
- readonly s ProtectHostnameEx = '...';
+ readonly (ss) ProtectHostnameEx = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b MemoryKSM = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
<varlistentry>
<term><varname>ProtectHostname=</varname></term>
- <listitem><para>Takes a boolean argument or <literal>private</literal>. If enabled, sets up a new UTS namespace
- for the executed processes. If set to a true value, changing hostname or domainname via
- <function>sethostname()</function> and <function>setdomainname()</function> system calls is prevented. If set to
- <literal>private</literal>, changing hostname or domainname is allowed but only affects the unit's UTS namespace.
- Defaults to off.</para>
+ <listitem><para>Takes a boolean argument or <literal>private</literal>. If enabled, sets up a new UTS
+ namespace for the executed processes. If enabled, a hostname can be optionally specified following a
+ colon (e.g. <literal>yes:foo</literal> or <literal>private:host.example.com</literal>), and the
+ hostname is set in the new UTS namespace for the unit. If set to a true value, changing hostname or
+ domainname via <function>sethostname()</function> and <function>setdomainname()</function> system
+ calls is prevented. If set to <literal>private</literal>, changing hostname or domainname is allowed
+ but only affects the unit's UTS namespace. Defaults to off.</para>
<para>Note that the implementation of this setting might be impossible (for example if UTS namespaces
are not available), and the unit should be written in a way that does not solely rely on this setting
#include "creds-util.h"
#include "dbus-execute.h"
#include "dbus-util.h"
+#include "dns-domain.h"
#include "env-util.h"
#include "errno-list.h"
#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
#include "hexdecoct.h"
+#include "hostname-util.h"
#include "iovec-util.h"
#include "ioprio-util.h"
#include "journal-file.h"
static BUS_DEFINE_PROPERTY_GET_REF(property_get_private_users_ex, "s", PrivateUsers, private_users_to_string);
static BUS_DEFINE_PROPERTY_GET_REF(property_get_protect_control_groups_ex, "s", ProtectControlGroups, protect_control_groups_to_string);
static BUS_DEFINE_PROPERTY_GET_REF(property_get_private_pids, "s", PrivatePIDs, private_pids_to_string);
-static BUS_DEFINE_PROPERTY_GET_REF(property_get_protect_hostname_ex, "s", ProtectHostname, protect_hostname_to_string);
static BUS_DEFINE_PROPERTY_GET_REF(property_get_syslog_level, "i", int, LOG_PRI);
static BUS_DEFINE_PROPERTY_GET_REF(property_get_syslog_facility, "i", int, LOG_FAC);
static BUS_DEFINE_PROPERTY_GET(property_get_cpu_affinity_from_numa, "b", ExecContext, exec_context_get_cpu_affinity_from_numa);
return sd_bus_message_append_basic(reply, 'b', &b);
}
+static int property_get_protect_hostname_ex(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ ExecContext *c = ASSERT_PTR(userdata);
+
+ return sd_bus_message_append(reply, "(ss)", protect_hostname_to_string(c->protect_hostname), c->private_hostname);
+}
+
const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ProtectProc", "s", property_get_protect_proc, offsetof(ExecContext, protect_proc), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ProcSubset", "s", property_get_proc_subset, offsetof(ExecContext, proc_subset), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ProtectHostname", "b", property_get_protect_hostname, offsetof(ExecContext, protect_hostname), SD_BUS_VTABLE_PROPERTY_CONST),
- SD_BUS_PROPERTY("ProtectHostnameEx", "s", property_get_protect_hostname_ex, offsetof(ExecContext, protect_hostname), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("ProtectHostnameEx", "(ss)", property_get_protect_hostname_ex, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MemoryKSM", "b", bus_property_get_tristate, offsetof(ExecContext, memory_ksm), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("NetworkNamespacePath", "s", NULL, offsetof(ExecContext, network_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("IPCNamespacePath", "s", NULL, offsetof(ExecContext, ipc_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST),
}
if (streq(name, "ProtectHostnameEx")) {
- const char *s;
- ProtectHostname t;
+ const char *s, *h = NULL;
- r = sd_bus_message_read(message, "s", &s);
+ r = sd_bus_message_read(message, "(ss)", &s, &h);
if (r < 0)
return r;
- t = protect_hostname_from_string(s);
- if (t < 0)
+ if (!isempty(h) && !hostname_is_valid(h, /* flags = */ 0))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname in %s setting: %s", name, h);
+
+ ProtectHostname t = protect_hostname_from_string(s);
+ if (t < 0 || (t == PROTECT_HOSTNAME_NO && !isempty(h)))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid %s setting: %s", name, s);
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
c->protect_hostname = t;
- (void) unit_write_settingf(u, flags, name, "ProtectHostname=%s",
- protect_hostname_to_string(c->protect_hostname));
+ r = free_and_strdup(&c->private_hostname, empty_to_null(h));
+ if (r < 0)
+ return r;
+
+ (void) unit_write_settingf(u, flags, name, "ProtectHostname=%s%s%s",
+ protect_hostname_to_string(c->protect_hostname),
+ c->private_hostname ? ":" : "",
+ strempty(c->private_hostname));
}
return 1;
#include "exit-status.h"
#include "fd-util.h"
#include "hexdecoct.h"
+#include "hostname-setup.h"
#include "io-util.h"
#include "iovec-util.h"
#include "journal-send.h"
#endif
static int apply_protect_hostname(const ExecContext *c, const ExecParameters *p, int *ret_exit_status) {
+ int r;
+
assert(c);
assert(p);
log_exec_warning(c, p,
"ProtectHostname=%s is configured, but UTS namespace setup is prohibited (container manager?), ignoring namespace setup.",
protect_hostname_to_string(c->protect_hostname));
+
+ } else if (c->private_hostname) {
+ r = sethostname_idempotent(c->private_hostname);
+ if (r < 0) {
+ *ret_exit_status = EXIT_NAMESPACE;
+ return log_exec_error_errno(c, p, r, "Failed to set private hostname '%s': %m", c->private_hostname);
+ }
}
} else
log_exec_warning(c, p,
#if HAVE_SECCOMP
if (c->protect_hostname == PROTECT_HOSTNAME_YES) {
- int r;
-
if (skip_seccomp_unavailable(c, p, "ProtectHostname="))
return 0;
if (r < 0)
return r;
+ r = serialize_item(f, "exec-context-private-hostname", c->private_hostname);
+ if (r < 0)
+ return r;
+
r = serialize_item(f, "exec-context-protect-proc", protect_proc_to_string(c->protect_proc));
if (r < 0)
return r;
c->protect_hostname = protect_hostname_from_string(val);
if (c->protect_hostname < 0)
return -EINVAL;
+ } else if ((val = startswith(l, "exec-context-private-hostname="))) {
+ r = free_and_strdup(&c->private_hostname, val);
+ if (r < 0)
+ return r;
} else if ((val = startswith(l, "exec-context-protect-proc="))) {
c->protect_proc = protect_proc_from_string(val);
if (c->protect_proc < 0)
c->root_image_policy = image_policy_free(c->root_image_policy);
c->mount_image_policy = image_policy_free(c->mount_image_policy);
c->extension_image_policy = image_policy_free(c->extension_image_policy);
+
+ c->private_hostname = mfree(c->private_hostname);
}
int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix) {
"%sRestrictRealtime: %s\n"
"%sRestrictSUIDSGID: %s\n"
"%sKeyringMode: %s\n"
- "%sProtectHostname: %s\n"
+ "%sProtectHostname: %s%s%s\n"
"%sProtectProc: %s\n"
"%sProcSubset: %s\n",
prefix, c->umask,
prefix, yes_no(c->restrict_realtime),
prefix, yes_no(c->restrict_suid_sgid),
prefix, exec_keyring_mode_to_string(c->keyring_mode),
- prefix, protect_hostname_to_string(c->protect_hostname),
+ prefix, protect_hostname_to_string(c->protect_hostname), c->private_hostname ? ":" : "", strempty(c->private_hostname),
prefix, protect_proc_to_string(c->protect_proc),
prefix, proc_subset_to_string(c->proc_subset));
ProtectHome protect_home;
PrivatePIDs private_pids;
ProtectHostname protect_hostname;
+ char *private_hostname;
bool dynamic_user;
bool remove_ipc;
{% else %}
{{type}}.SmackProcessLabel, config_parse_warn_compat, DISABLED_CONFIGURATION, 0
{% endif %}
-{{type}}.ProtectHostname, config_parse_protect_hostname, 0, offsetof({{type}}, exec_context.protect_hostname)
+{{type}}.ProtectHostname, config_parse_protect_hostname, 0, offsetof({{type}}, exec_context)
{{type}}.MemoryKSM, config_parse_tristate, 0, offsetof({{type}}, exec_context.memory_ksm)
{%- endmacro -%}
#include "fs-util.h"
#include "fstab-util.h"
#include "hexdecoct.h"
+#include "hostname-util.h"
#include "iovec-util.h"
#include "ioprio-util.h"
#include "ip-protocol-list.h"
DEFINE_CONFIG_PARSE_ENUM(config_parse_job_mode, job_mode, JobMode);
DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess);
DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_home, protect_home, ProtectHome);
-DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_hostname, protect_hostname, ProtectHostname);
DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_system, protect_system, ProtectSystem);
DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_preserve_mode, exec_preserve_mode, ExecPreserveMode);
DEFINE_CONFIG_PARSE_ENUM(config_parse_service_type, service_type, ServiceType);
return config_parse_nft_set(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &c->nft_set_context, u);
}
+
+int config_parse_protect_hostname(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = ASSERT_PTR(data);
+ Unit *u = ASSERT_PTR(userdata);
+ _cleanup_free_ char *h = NULL, *p = NULL;
+ int r;
+
+ if (isempty(rvalue)) {
+ c->protect_hostname = PROTECT_HOSTNAME_NO;
+ c->private_hostname = mfree(c->private_hostname);
+ return 1;
+ }
+
+ const char *colon = strchr(rvalue, ':');
+ if (colon) {
+ r = unit_full_printf_full(u, colon + 1, HOST_NAME_MAX, &h);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to resolve unit specifiers in '%s', ignoring: %m", colon + 1);
+ return 0;
+ }
+
+ if (!hostname_is_valid(h, /* flags = */ 0))
+ return log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid hostname is specified to %s=, ignoring: %s", lvalue, h);
+
+ p = strndup(rvalue, colon - rvalue);
+ if (!p)
+ return log_oom();
+ }
+
+ ProtectHostname t = protect_hostname_from_string(p ?: rvalue);
+ if (t < 0 || (t == PROTECT_HOSTNAME_NO && h))
+ return log_syntax_parse_error(unit, filename, line, 0, lvalue, rvalue);
+
+ c->protect_hostname = t;
+ free_and_replace(c->private_hostname, h);
+ return 1;
+}
"SyslogIdentifier",
"ProtectSystem",
"ProtectHome",
- "ProtectHostnameEx",
"PrivateTmpEx",
"PrivateUsersEx",
"ProtectControlGroupsEx",
return 1;
}
+ if (streq(field, "ProtectHostnameEx")) {
+ const char *colon = strchr(eq, ':');
+ if (colon) {
+ if (isempty(colon + 1))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse argument: %s=%s", field, eq);
+
+ _cleanup_free_ char *p = strndup(eq, colon - eq);
+ if (!p)
+ return -ENOMEM;
+
+ r = sd_bus_message_append(m, "(sv)", field, "(ss)", p, colon + 1);
+ } else
+ r = sd_bus_message_append(m, "(sv)", field, "(ss)", eq, NULL);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ return 1;
+ }
return 0;
}
# can only set hostname.
(! systemd-run --wait -p ProtectHostname=yes hostname foo)
+ # ProtectHostname=yes can optionally take a hostname.
+ systemd-run --wait -p ProtectHostnameEx=yes:hoge \
+ -P bash -xec '
+ test "$(hostname)" = "hoge"
+ (! hostname foo)
+ test "$(hostname)" = "hoge"
+ '
+
+ # Verify host hostname is unchanged.
+ test "$(hostname)" = "$LEGACY_HOSTNAME"
+ test "$(hostnamectl hostname)" = "$HOSTNAME_FROM_SYSTEMD"
+
+ # ProtectHostname= supportes specifiers.
+ mkdir -p /run/systemd/system/
+ cat >/run/systemd/system/test-protect-hostname-yes@.service <<EOF
+[Service]
+Type=oneshot
+ExecStart=bash -xec 'test "\$\$(hostname)" = "%i"; (! hostname foo); test "\$\$(hostname)" = "%i"'
+ProtectHostname=yes:%i
+EOF
+ systemctl daemon-reload
+ systemctl start --wait test-protect-hostname-yes@hoge.example.com.service
+
+ # Verify host hostname is unchanged.
+ test "$(hostname)" = "$LEGACY_HOSTNAME"
+ test "$(hostnamectl hostname)" = "$HOSTNAME_FROM_SYSTEMD"
+
systemd-run --wait -p ProtectHostname=yes -p PrivateMounts=yes \
findmnt --mountpoint /proc/sys/kernel/hostname
}
test "$(hostname)" = "$LEGACY_HOSTNAME"
test "$(hostnamectl hostname)" = "$HOSTNAME_FROM_SYSTEMD"
+ # ProtectHostname=private can optionally take a hostname.
+ systemd-run --wait -p ProtectHostnameEx=private:hoge \
+ -P bash -xec '
+ test "$(hostname)" = "hoge"
+ hostname foo
+ test "$(hostname)" = "foo"
+ '
+
+ # Verify host hostname is unchanged.
+ test "$(hostname)" = "$LEGACY_HOSTNAME"
+ test "$(hostnamectl hostname)" = "$HOSTNAME_FROM_SYSTEMD"
+
+ # ProtectHostname= supportes specifiers.
+ mkdir -p /run/systemd/system/
+ cat >/run/systemd/system/test-protect-hostname-private@.service <<EOF
+[Service]
+Type=oneshot
+ExecStart=bash -xec 'test "\$\$(hostname)" = "%i"; hostname foo; test "\$\$(hostname)" = "foo"'
+ProtectHostname=private:%i
+EOF
+ systemctl daemon-reload
+ systemctl start --wait test-protect-hostname-private@hoge.example.com.service
+
+ # Verify host hostname is unchanged.
+ test "$(hostname)" = "$LEGACY_HOSTNAME"
+ test "$(hostnamectl hostname)" = "$HOSTNAME_FROM_SYSTEMD"
+
# Verify /proc/sys/kernel/hostname is not bind mounted from host read-only.
(! systemd-run --wait -p ProtectHostnameEx=private -p PrivateMounts=yes \
findmnt --mountpoint /proc/sys/kernel/hostname)
}
+testcase_invalid() {
+ # ProtectHostname=no cannot take hostname.
+ (! systemd-run --wait -p ProtectHostnameEx=no:hoge true)
+
+ # Invalid hostname.
+ (! systemd-run --wait -p ProtectHostnameEx=yes: true)
+ (! systemd-run --wait -p ProtectHostnameEx=yes:.foo true)
+ (! systemd-run --wait -p ProtectHostnameEx=yes:foo.-example.com true)
+ (! systemd-run --wait -p ProtectHostnameEx=yes:foo..example.com true)
+ (! systemd-run --wait -p ProtectHostnameEx=private: true)
+ (! systemd-run --wait -p ProtectHostnameEx=private:.foo true)
+ (! systemd-run --wait -p ProtectHostnameEx=private:foo.-example.com true)
+ (! systemd-run --wait -p ProtectHostnameEx=private:foo..example.com true)
+}
+
run_testcases