@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b DynamicUser = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(iss) DynamicUserNFTSet = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b RemoveIPC = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(say) SetCredential = [...];
<!--property DynamicUser is not documented!-->
+ <!--property DynamicUserNFTSet is not documented!-->
+
<!--property RemoveIPC is not documented!-->
<!--property SetCredential is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="DynamicUser"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="DynamicUserNFTSet"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="RemoveIPC"/>
<variablelist class="dbus-property" generated="True" extra-ref="SetCredential"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b DynamicUser = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(iss) DynamicUserNFTSet = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b RemoveIPC = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(say) SetCredential = [...];
<!--property DynamicUser is not documented!-->
+ <!--property DynamicUserNFTSet is not documented!-->
+
<!--property RemoveIPC is not documented!-->
<!--property SetCredential is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="DynamicUser"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="DynamicUserNFTSet"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="RemoveIPC"/>
<variablelist class="dbus-property" generated="True" extra-ref="SetCredential"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b DynamicUser = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(iss) DynamicUserNFTSet = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b RemoveIPC = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(say) SetCredential = [...];
<!--property DynamicUser is not documented!-->
+ <!--property DynamicUserNFTSet is not documented!-->
+
<!--property RemoveIPC is not documented!-->
<!--property SetCredential is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="DynamicUser"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="DynamicUserNFTSet"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="RemoveIPC"/>
<variablelist class="dbus-property" generated="True" extra-ref="SetCredential"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b DynamicUser = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(iss) DynamicUserNFTSet = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b RemoveIPC = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(say) SetCredential = [...];
<!--property DynamicUser is not documented!-->
+ <!--property DynamicUserNFTSet is not documented!-->
+
<!--property RemoveIPC is not documented!-->
<!--property SetCredential is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="DynamicUser"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="DynamicUserNFTSet"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="RemoveIPC"/>
<variablelist class="dbus-property" generated="True" extra-ref="SetCredential"/>
</variablelist>
</refsect1>
+ <refsect1>
+ <title>Firewall Integration</title>
+ <variablelist class='unit-directives'>
+
+ <varlistentry>
+ <term><varname>DynamicUserNFTSet=</varname><replaceable>family</replaceable>:<replaceable>table</replaceable>:<replaceable>set</replaceable></term>
+ <listitem><para>This setting provides a method for integrating <varname>DynamicUser=</varname>
+ configuration into firewall rules with NFT sets. This option expects a whitespace separated list of
+ NFT set definitions. Each definition consists of a colon-separated tuple of NFT address family (one
+ of <literal>arp</literal>, <literal>bridge</literal>, <literal>inet</literal>, <literal>ip</literal>,
+ <literal>ip6</literal>, or <literal>netdev</literal>), table name and set name. The names of tables
+ and sets must conform to lexical restrictions of NFT table names. When the unit starts, the user ID
+ will be appended to the NFT sets and it will be removed when the unit is stopped. Failures to manage
+ the sets will be ignored.</para>
+
+ <para>Example:
+ <programlisting>[Service]
+DynamicUserNFTSet=inet:filter:u</programlisting>
+ Corresponding NFT rules:
+ <programlisting>table inet filter {
+ set u {
+ typeof meta skuid
+ }
+ chain service_output {
+ meta skuid != @u drop
+ accept
+ }
+}</programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
<refsect1>
<title>System V Compatibility</title>
<variablelist class='unit-directives'>
#include "execute.h"
#include "fd-util.h"
#include "fileio.h"
+#include "firewall-util.h"
#include "hexdecoct.h"
#include "io-util.h"
#include "ioprio-util.h"
return sd_bus_message_close_container(reply);
}
+static int property_get_dynamic_user_nft_set(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ ExecContext *c = userdata;
+ int r;
+
+ assert(bus);
+ assert(reply);
+ assert(c);
+
+ r = sd_bus_message_open_container(reply, 'a', "(iss)");
+ if (r < 0)
+ return r;
+
+ for (size_t i = 0; i < c->n_dynamic_user_nft_set_contexts; i++) {
+ NFTSetContext *s = &c->dynamic_user_nft_set_context[i];
+
+ r = sd_bus_message_append(reply, "(iss)", s->nfproto, s->table, s->set);
+ if (r < 0)
+ return r;
+ }
+
+ return sd_bus_message_close_container(reply);
+}
+
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("User", "s", NULL, offsetof(ExecContext, user), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Group", "s", NULL, offsetof(ExecContext, group), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DynamicUser", "b", bus_property_get_bool, offsetof(ExecContext, dynamic_user), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("DynamicUserNFTSet", "a(iss)", property_get_dynamic_user_nft_set, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RemoveIPC", "b", bus_property_get_bool, offsetof(ExecContext, remove_ipc), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("SetCredential", "a(say)", property_get_set_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("SetCredentialEncrypted", "a(say)", property_get_set_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
return 1;
+ } else if (streq(name, "DynamicUserNFTSet")) {
+ int nfproto;
+ const char *table, *set;
+ bool empty = true;
+
+ r = sd_bus_message_enter_container(message, 'a', "(iss)");
+ if (r < 0)
+ return r;
+
+ while ((r = sd_bus_message_read(message, "(iss)", &nfproto, &table, &set)) > 0) {
+ const char *nfproto_name;
+
+ nfproto_name = nfproto_to_string(nfproto);
+ if (!nfproto_name)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid protocol %d.", nfproto);
+
+ if (nft_identifier_bad(table))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid NFT table name %s.", table);
+
+ if (nft_identifier_bad(set))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid NFT set name %s.", set);
+
+ if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+ r = nft_set_context_add(&c->dynamic_user_nft_set_context, &c->n_dynamic_user_nft_set_contexts, nfproto, table, set);
+ if (r < 0)
+ return r;
+
+ unit_write_settingf(
+ u, flags|UNIT_ESCAPE_SPECIFIERS, name,
+ "%s=%s:%s:%s",
+ name,
+ nfproto_name,
+ table,
+ set);
+ }
+
+ empty = false;
+ }
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_exit_container(message);
+ if (r < 0)
+ return r;
+
+ if (empty) {
+ c->dynamic_user_nft_set_context = nft_set_context_free_many(c->dynamic_user_nft_set_context, &c->n_dynamic_user_nft_set_contexts);
+ unit_write_settingf(u, flags, name, "%s=", name);
+ }
+
+ return 1;
+
} else if ((suffix = startswith(name, "Limit"))) {
const char *soft = NULL;
int ri;
return 1;
}
+static void exec_op_dynamic_user_nft_set(bool add, const ExecContext *c, uid_t uid) {
+ int r;
+
+ assert(c);
+
+ for (size_t i = 0; i < c->n_dynamic_user_nft_set_contexts; i++) {
+ NFTSetContext *s = &c->dynamic_user_nft_set_context[i];
+ if (add)
+ r = nft_set_element_add_uint32(s, uid);
+ else
+ r = nft_set_element_del_uint32(s, uid);
+ if (r < 0)
+ log_warning_errno(r, "%s NFT family %s table %s set %s UID " UID_FMT " failed, ignoring: %m",
+ add? "Adding" : "Deleting", nfproto_to_string(s->nfproto), s->table, s->set, uid);
+ }
+}
+
+static void exec_add_dynamic_user_nft_set(const ExecContext *c, uid_t uid) {
+ exec_op_dynamic_user_nft_set(true, c, uid);
+}
+
+void exec_delete_dynamic_user_nft_set(const ExecContext *c, DynamicUser *d) {
+ int r;
+ uid_t uid;
+
+ if (!d)
+ return;
+
+ r = dynamic_user_current(d, &uid);
+ if (r < 0) {
+ log_warning_errno(r, "Can't get current dynamic user, ignoring: %m");
+ return;
+ }
+
+ exec_op_dynamic_user_nft_set(false, c, uid);
+}
+
static int exec_child(
Unit *unit,
const ExecCommand *command,
if (dcreds->user)
username = dcreds->user->name;
+ exec_add_dynamic_user_nft_set(context, uid);
+
} else {
r = get_fixed_user(context, &username, &uid, &gid, &home, &shell);
if (r < 0) {
c->user = mfree(c->user);
c->group = mfree(c->group);
+ c->dynamic_user_nft_set_context = nft_set_context_free_many(c->dynamic_user_nft_set_context, &c->n_dynamic_user_nft_set_contexts);
+
c->supplementary_groups = strv_free(c->supplementary_groups);
c->pam_name = mfree(c->pam_name);
fprintf(f, "%sGroup: %s\n", prefix, c->group);
fprintf(f, "%sDynamicUser: %s\n", prefix, yes_no(c->dynamic_user));
+ for (size_t i = 0; i < c->n_dynamic_user_nft_set_contexts; i++)
+ fprintf(f, "%sDynamicUserNFTSet: %s:%s:%s\n", prefix,
+ nfproto_to_string(c->dynamic_user_nft_set_context[i].nfproto),
+ c->dynamic_user_nft_set_context[i].table,
+ c->dynamic_user_nft_set_context[i].set);
strv_dump(f, prefix, "SupplementaryGroups", c->supplementary_groups);
#include "cpu-set-util.h"
#include "exec-util.h"
#include "fdset.h"
+#include "firewall-util.h"
#include "list.h"
#include "missing_resource.h"
#include "namespace.h"
bool mount_apivfs;
bool dynamic_user;
+ size_t n_dynamic_user_nft_set_contexts;
+ NFTSetContext *dynamic_user_nft_set_context;
+
bool remove_ipc;
bool memory_deny_write_execute;
ExecDirectoryType exec_resource_type_from_string(const char *s) _pure_;
bool exec_needs_mount_namespace(const ExecContext *context, const ExecParameters *params, const ExecRuntime *runtime);
+
+void exec_delete_dynamic_user_nft_set(const ExecContext *c, DynamicUser *d);
{{type}}.PassEnvironment, config_parse_pass_environ, 0, offsetof({{type}}, exec_context.pass_environment)
{{type}}.UnsetEnvironment, config_parse_unset_environ, 0, offsetof({{type}}, exec_context.unset_environment)
{{type}}.DynamicUser, config_parse_bool, true, offsetof({{type}}, exec_context.dynamic_user)
+{{type}}.DynamicUserNFTSet, config_parse_dynamic_user_nft_set, 0, offsetof({{type}}, exec_context)
{{type}}.RemoveIPC, config_parse_bool, 0, offsetof({{type}}, exec_context.remove_ipc)
{{type}}.StandardInput, config_parse_exec_input, 0, offsetof({{type}}, exec_context)
{{type}}.StandardOutput, config_parse_exec_output, 0, offsetof({{type}}, exec_context)
return config_parse_nft_set(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &c->nft_set_context, &c->n_nft_set_contexts, u);
}
+
+int config_parse_dynamic_user_nft_set(
+ 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 = data;
+ Unit *u = userdata;
+
+ return config_parse_nft_set(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &c->dynamic_user_nft_set_context, &c->n_dynamic_user_nft_set_contexts, u);
+}
CONFIG_PARSER_PROTOTYPE(config_parse_watchdog_sec);
CONFIG_PARSER_PROTOTYPE(config_parse_tty_size);
CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_nft_set);
+CONFIG_PARSER_PROTOTYPE(config_parse_dynamic_user_nft_set);
/* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
/* Get rid of the IPC bits of the user */
unit_unref_uid_gid(UNIT(s), true);
+ /* Delete DynamicUserNFTSet= */
+ exec_delete_dynamic_user_nft_set(&s->exec_context, s->dynamic_creds.user);
+
/* Release the user, and destroy it if we are the only remaining owner */
dynamic_creds_destroy(&s->dynamic_creds);
#include "exec-util.h"
#include "exit-status.h"
#include "fileio.h"
+#include "firewall-util.h"
#include "hexdecoct.h"
#include "hostname-util.h"
#include "in-addr-util.h"
return sd_bus_message_close_container(m);
}
+static int bus_append_nft_set(sd_bus_message *m, const char *field, const char *eq) {
+ int r;
+
+ assert(m);
+
+ if (isempty(eq)) {
+ r = sd_bus_message_append(m, "(sv)", field, "a(iss)", 0);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ return 1;
+ }
+
+ r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, 'v', "a(iss)");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, 'a', "(iss)");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+ int family;
+
+ r = extract_first_word(&eq, &word, ":", 0);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse %s: %m", field);
+ if (isempty(word)) {
+ log_error("Failed to parse %s", field);
+ return 0;
+ }
+
+ family = nfproto_from_string(word);
+ if (family < 0)
+ return log_error_errno(family, "Failed to parse %s: %m", field);
+
+ r = extract_first_word(&eq, &word, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse %s: %m", field);
+ if (isempty(word) || isempty(eq)) {
+ log_error("Failed to parse %s", field);
+ return 0;
+ }
+
+ _cleanup_free_ char *unescaped = NULL;
+ ssize_t l;
+
+ l = cunescape(eq, 0, &unescaped);
+ if (l < 0)
+ return log_error_errno(l, "Failed to unescape %s= value: %s", field, eq);
+
+ r = sd_bus_message_append(m, "(iss)", family, word, eq);
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ return 1;
+}
+
static int bus_append_cgroup_property(sd_bus_message *m, const char *field, const char *eq) {
int r;
return 1;
}
+ if (STR_IN_SET(field, "DynamicUserNFTSet"))
+ return bus_append_nft_set(m, field, eq);
+
return 0;
}
DirectoryMode=
DisableControllers=
DynamicUser=
+DynamicUserNFTSet=
Environment=
EnvironmentFile=
ExecPaths=
DevicePolicy=
DisableControllers=
DynamicUser=
+DynamicUserNFTSet=
Environment=
EnvironmentFile=
ExecCondition=
DirectoryMode=
DisableControllers=
DynamicUser=
+DynamicUserNFTSet=
Environment=
EnvironmentFile=
ExecPaths=
DevicePolicy=
DisableControllers=
DynamicUser=
+DynamicUserNFTSet=
Environment=
EnvironmentFile=
ExecPaths=