1
0
mirror of https://github.com/systemd/systemd.git synced 2024-12-23 21:35:11 +03:00

core: add user and group to NFTSet=

The benefit of using this setting is that user and group IDs, especially dynamic and random
IDs used by DynamicUser=, can be used in firewall configuration easily.

Example:

```
[Service]
NFTSet=user:inet:filter:serviceuser
```

Corresponding NFT rules:

```
table inet filter {
        set serviceuser {
                typeof meta skuid
        }
        chain service_output {
                meta skuid @serviceuser accept
                drop
        }
}
```

```
$ cat /etc/systemd/system/dunft.service
[Service]
DynamicUser=yes
NFTSet=user:inet:filter:serviceuser
ExecStart=/bin/sleep 1000

[Install]
WantedBy=multi-user.target
$ sudo nft list set inet filter serviceuser
table inet filter {
        set serviceuser {
                typeof meta skuid
                elements = { 64864 }
        }
}
$ ps -n --format user,group,pid,command -p `systemctl show dunft.service -P MainPID`
    USER    GROUP     PID COMMAND
   64864    64864   55158 /bin/sleep 1000
```
This commit is contained in:
Topi Miettinen 2022-05-22 15:17:24 +03:00 committed by Topi Miettinen
parent dc7d69b3c1
commit 3bb48b19bd
6 changed files with 90 additions and 21 deletions

View File

@ -1504,24 +1504,26 @@ DeviceAllow=/dev/loop-control
<varlistentry>
<term><varname>NFTSet=</varname><replaceable>family</replaceable>:<replaceable>table</replaceable>:<replaceable>set</replaceable></term>
<listitem>
<para>This setting provides a method for integrating dynamic cgroup IDs into firewall rules with
<ulink url="https://netfilter.org/projects/nftables/index.html">NFT</ulink> sets. The benefit of
using this setting is to be able to use the IDs as selectors in firewall rules easily and this in
turn allows more fine grained filtering. NFT rules for cgroup matching use numeric cgroup IDs,
which change every time a service is restarted, making them hard to use in systemd environment
otherwise.</para>
<para>This setting provides a method for integrating dynamic cgroup, user and group IDs into
firewall rules with <ulink url="https://netfilter.org/projects/nftables/index.html">NFT</ulink>
sets. The benefit of using this setting is to be able to use the IDs as selectors in firewall rules
easily and this in turn allows more fine grained filtering. NFT rules for cgroup matching use
numeric cgroup IDs, which change every time a service is restarted, making them hard to use in
systemd environment otherwise. Dynamic and random IDs used by <varname>DynamicUser=</varname> can
be also integrated with this setting.</para>
<para>This option expects a whitespace separated list of NFT set definitions. Each definition
consists of a colon-separated tuple of source type (only <literal>cgroup</literal>), 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. The
type of the element used in the NFT filter must match the type implied by the directive
(<literal>cgroup</literal>) as shown in the table below. When a control group is realized, the
corresponding ID will be appended to the NFT sets and it will be be removed when the control group
is removed. <command>systemd</command> only inserts elements to (or removes from) the sets, so the
related NFT rules, tables and sets must be prepared elsewhere in advance. Failures to manage the
sets will be ignored.</para>
consists of a colon-separated tuple of source type (one of <literal>cgroup</literal>,
<literal>user</literal> or <literal>group</literal>), 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. The type of the element used in
the NFT filter must match the type implied by the directive (<literal>cgroup</literal>,
<literal>user</literal> or <literal>group</literal>) as shown in the table below. When a control
group or a unit is realized, the corresponding ID will be appended to the NFT sets and it will be
be removed when the control group or unit is removed. <command>systemd</command> only inserts
elements to (or removes from) the sets, so the related NFT rules, tables and sets must be prepared
elsewhere in advance. Failures to manage the sets will be ignored.</para>
<table>
<title>Defined <varname>source type</varname> values</title>
@ -1543,6 +1545,16 @@ DeviceAllow=/dev/loop-control
<entry>control group ID</entry>
<entry><literal>cgroupsv2</literal></entry>
</row>
<row>
<entry><literal>user</literal></entry>
<entry>user ID</entry>
<entry><literal>meta skuid</literal></entry>
</row>
<row>
<entry><literal>group</literal></entry>
<entry>group ID</entry>
<entry><literal>meta skgid</literal></entry>
</row>
</tbody>
</tgroup>
</table>
@ -1552,17 +1564,24 @@ DeviceAllow=/dev/loop-control
<para>Example:
<programlisting>[Unit]
NFTSet=cgroup:inet:filter:my_service
NFTSet=cgroup:inet:filter:my_service user:inet:filter:serviceuser
</programlisting>
Corresponding NFT rules:
<programlisting>table inet filter {
set my_service {
type cgroupsv2
}
set serviceuser {
typeof meta skuid
}
chain x {
socket cgroupv2 level 2 @my_service accept
drop
}
chain y {
meta skuid @serviceuser accept
drop
}
}</programlisting>
</para>
<xi:include href="version-info.xml" xpointer="v255"/></listitem>

View File

@ -2235,7 +2235,7 @@ int bus_cgroup_set_property(
while ((r = sd_bus_message_read(message, "(iiss)", &source, &nfproto, &table, &set)) > 0) {
const char *source_name, *nfproto_name;
if (source != NFT_SET_SOURCE_CGROUP)
if (!IN_SET(source, NFT_SET_SOURCE_CGROUP, NFT_SET_SOURCE_USER, NFT_SET_SOURCE_GROUP))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid source %d.", source);
source_name = nft_set_source_to_string(source);

View File

@ -5136,6 +5136,41 @@ PidRef* unit_main_pid(Unit *u) {
return NULL;
}
static void unit_modify_user_nft_set(Unit *u, bool add, NFTSetSource source, uint32_t element) {
int r;
assert(u);
if (!MANAGER_IS_SYSTEM(u->manager))
return;
CGroupContext *c;
c = unit_get_cgroup_context(u);
if (!c)
return;
if (!u->manager->fw_ctx) {
r = fw_ctx_new_full(&u->manager->fw_ctx, /* init_tables= */ false);
if (r < 0)
return;
assert(u->manager->fw_ctx);
}
FOREACH_ARRAY(nft_set, c->nft_set_context.sets, c->nft_set_context.n_sets) {
if (nft_set->source != source)
continue;
r = nft_set_element_modify_any(u->manager->fw_ctx, add, nft_set->nfproto, nft_set->table, nft_set->set, &element, sizeof(element));
if (r < 0)
log_warning_errno(r, "Failed to %s NFT set: family %s, table %s, set %s, ID %u, ignoring: %m",
add? "add" : "delete", nfproto_to_string(nft_set->nfproto), nft_set->table, nft_set->set, element);
else
log_debug("%s NFT set: family %s, table %s, set %s, ID %u",
add? "Added" : "Deleted", nfproto_to_string(nft_set->nfproto), nft_set->table, nft_set->set, element);
}
}
static void unit_unref_uid_internal(
Unit *u,
uid_t *ref_uid,
@ -5162,10 +5197,18 @@ static void unit_unref_uid_internal(
}
static void unit_unref_uid(Unit *u, bool destroy_now) {
assert(u);
unit_modify_user_nft_set(u, /* add = */ false, NFT_SET_SOURCE_USER, u->ref_uid);
unit_unref_uid_internal(u, &u->ref_uid, destroy_now, manager_unref_uid);
}
static void unit_unref_gid(Unit *u, bool destroy_now) {
assert(u);
unit_modify_user_nft_set(u, /* add = */ false, NFT_SET_SOURCE_GROUP, u->ref_gid);
unit_unref_uid_internal(u, (uid_t*) &u->ref_gid, destroy_now, manager_unref_gid);
}
@ -5260,6 +5303,9 @@ int unit_ref_uid_gid(Unit *u, uid_t uid, gid_t gid) {
if (r < 0)
return log_unit_warning_errno(u, r, "Couldn't add UID/GID reference to unit, proceeding without: %m");
unit_modify_user_nft_set(u, /* add = */ true, NFT_SET_SOURCE_USER, uid);
unit_modify_user_nft_set(u, /* add = */ true, NFT_SET_SOURCE_GROUP, gid);
return r;
}

View File

@ -509,7 +509,7 @@ static int bus_append_nft_set(sd_bus_message *m, const char *field, const char *
assert(set);
source = nft_set_source_from_string(source_str);
if (source != NFT_SET_SOURCE_CGROUP)
if (!IN_SET(source, NFT_SET_SOURCE_CGROUP, NFT_SET_SOURCE_USER, NFT_SET_SOURCE_GROUP))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field);
nfproto = nfproto_from_string(nfproto_str);

View File

@ -1203,6 +1203,8 @@ static const char *const nft_set_source_table[] = {
[NFT_SET_SOURCE_PREFIX] = "prefix",
[NFT_SET_SOURCE_IFINDEX] = "ifindex",
[NFT_SET_SOURCE_CGROUP] = "cgroup",
[NFT_SET_SOURCE_USER] = "user",
[NFT_SET_SOURCE_GROUP] = "group",
};
DEFINE_STRING_TABLE_LOOKUP(nft_set_source, int);
@ -1223,7 +1225,7 @@ int nft_set_add(NFTSetContext *s, NFTSetSource source, int nfproto, const char *
_cleanup_free_ char *table_dup = NULL, *set_dup = NULL;
assert(s);
assert(IN_SET(source, NFT_SET_SOURCE_ADDRESS, NFT_SET_SOURCE_PREFIX, NFT_SET_SOURCE_IFINDEX, NFT_SET_SOURCE_CGROUP));
assert(IN_SET(source, NFT_SET_SOURCE_ADDRESS, NFT_SET_SOURCE_PREFIX, NFT_SET_SOURCE_IFINDEX, NFT_SET_SOURCE_CGROUP, NFT_SET_SOURCE_USER, NFT_SET_SOURCE_GROUP));
assert(nfproto_is_valid(nfproto));
assert(table);
assert(set);
@ -1332,7 +1334,7 @@ int config_parse_nft_set(
source = nft_set_source_from_string(source_str);
if (source < 0 ||
(ltype == NFT_SET_PARSE_NETWORK && !IN_SET(source, NFT_SET_SOURCE_ADDRESS, NFT_SET_SOURCE_PREFIX, NFT_SET_SOURCE_IFINDEX)) ||
(ltype == NFT_SET_PARSE_CGROUP && source != NFT_SET_SOURCE_CGROUP)) {
(ltype == NFT_SET_PARSE_CGROUP && !IN_SET(source, NFT_SET_SOURCE_CGROUP, NFT_SET_SOURCE_USER, NFT_SET_SOURCE_GROUP))) {
_cleanup_free_ char *esc = NULL;
esc = cescape(source_str);

View File

@ -37,6 +37,8 @@ typedef enum NFTSetSource {
NFT_SET_SOURCE_PREFIX,
NFT_SET_SOURCE_IFINDEX,
NFT_SET_SOURCE_CGROUP,
NFT_SET_SOURCE_USER,
NFT_SET_SOURCE_GROUP,
_NFT_SET_SOURCE_MAX,
_NFT_SET_SOURCE_INVALID = -EINVAL,
} NFTSetSource;