core: firewall integration with DynamicUserNFTSet=

New directive `DynamicUserNFTSet=` provides a method for integrating
configuration of dynamic users into firewall rules with NFT sets.

Example:
```
table inet filter {
        set u {
                typeof meta skuid
        }

        chain service_output {
                meta skuid != @u drop
                accept
        }
}
```

```
/etc/systemd/system/dunft.service
[Service]
DynamicUser=yes
DynamicUserNFTSet=inet:filter:u
ExecStart=/bin/sleep 1000

[Install]
WantedBy=multi-user.target
```

```
$ sudo nft list set inet filter u
table inet filter {
        set u {
                typeof meta skuid
                elements = { 64864 }
        }
}
$ ps -n --format user,group,pid,command -p `pgrep sleep`
    USER    GROUP     PID COMMAND
   64864    64864   55158 /bin/sleep 1000
```
This commit is contained in:
Topi Miettinen
2022-06-08 16:12:25 +00:00
committed by Topi Miettinen
parent c0548df0a2
commit 46c3b1ff88
14 changed files with 310 additions and 0 deletions
+24
View File
@@ -2785,6 +2785,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
@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 = [...];
@@ -3332,6 +3334,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<!--property DynamicUser is not documented!-->
<!--property DynamicUserNFTSet is not documented!-->
<!--property RemoveIPC is not documented!-->
<!--property SetCredential is not documented!-->
@@ -3940,6 +3944,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<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"/>
@@ -4679,6 +4685,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
@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 = [...];
@@ -5250,6 +5258,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<!--property DynamicUser is not documented!-->
<!--property DynamicUserNFTSet is not documented!-->
<!--property RemoveIPC is not documented!-->
<!--property SetCredential is not documented!-->
@@ -5852,6 +5862,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<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"/>
@@ -6480,6 +6492,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
@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 = [...];
@@ -6979,6 +6993,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<!--property DynamicUser is not documented!-->
<!--property DynamicUserNFTSet is not documented!-->
<!--property RemoveIPC is not documented!-->
<!--property SetCredential is not documented!-->
@@ -7499,6 +7515,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<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"/>
@@ -8254,6 +8272,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
@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 = [...];
@@ -8739,6 +8759,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<!--property DynamicUser is not documented!-->
<!--property DynamicUserNFTSet is not documented!-->
<!--property RemoveIPC is not documented!-->
<!--property SetCredential is not documented!-->
@@ -9245,6 +9267,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<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"/>
+34
View File
@@ -3163,6 +3163,40 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX
</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'>
+85
View File
@@ -22,6 +22,7 @@
#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"
@@ -1138,6 +1139,37 @@ static int bus_property_get_exec_dir_symlink(
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),
@@ -1232,6 +1264,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
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),
@@ -3503,6 +3536,58 @@ int bus_exec_context_set_transient_property(
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;
+46
View File
@@ -4083,6 +4083,43 @@ static int add_shifted_fd(int *fds, size_t fds_size, size_t *n_fds, int fd, int
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,
@@ -4284,6 +4321,8 @@ static int exec_child(
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) {
@@ -5346,6 +5385,8 @@ void exec_context_done(ExecContext *c) {
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);
@@ -6020,6 +6061,11 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
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);
+6
View File
@@ -18,6 +18,7 @@ typedef struct Manager Manager;
#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"
@@ -313,6 +314,9 @@ struct ExecContext {
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;
@@ -522,3 +526,5 @@ const char* exec_resource_type_to_string(ExecDirectoryType i) _const_;
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);
+1
View File
@@ -32,6 +32,7 @@
{{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)
+17
View File
@@ -6607,3 +6607,20 @@ int config_parse_cgroup_nft_set(
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);
}
+1
View File
@@ -151,6 +151,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_restrict_network_interfaces);
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);
+3
View File
@@ -1877,6 +1877,9 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
/* 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);
+89
View File
@@ -16,6 +16,7 @@
#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"
@@ -434,6 +435,91 @@ static int bus_append_ip_address_access(sd_bus_message *m, int family, const uni
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;
@@ -2051,6 +2137,9 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
return 1;
}
if (STR_IN_SET(field, "DynamicUserNFTSet"))
return bus_append_nft_set(m, field, eq);
return 0;
}
@@ -38,6 +38,7 @@ DevicePolicy=
DirectoryMode=
DisableControllers=
DynamicUser=
DynamicUserNFTSet=
Environment=
EnvironmentFile=
ExecPaths=
@@ -160,6 +160,7 @@ DeviceAllow=
DevicePolicy=
DisableControllers=
DynamicUser=
DynamicUserNFTSet=
Environment=
EnvironmentFile=
ExecCondition=
@@ -44,6 +44,7 @@ DevicePolicy=
DirectoryMode=
DisableControllers=
DynamicUser=
DynamicUserNFTSet=
Environment=
EnvironmentFile=
ExecPaths=
+1
View File
@@ -37,6 +37,7 @@ DeviceAllow=
DevicePolicy=
DisableControllers=
DynamicUser=
DynamicUserNFTSet=
Environment=
EnvironmentFile=
ExecPaths=