]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: add user and group to NFTSet=
authorTopi Miettinen <toiwoton@gmail.com>
Sun, 22 May 2022 12:17:24 +0000 (15:17 +0300)
committerTopi Miettinen <topimiettinen@users.noreply.github.com>
Wed, 27 Sep 2023 18:10:11 +0000 (18:10 +0000)
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
```

man/systemd.resource-control.xml
src/core/dbus-cgroup.c
src/core/unit.c
src/shared/bus-unit-util.c
src/shared/firewall-util-nft.c
src/shared/firewall-util.h

index 038a58e3dc7562b5e515f370b376b7b865a8c521..e9747cefbb17506a78589dde75a1abe7b05a8b36 100644 (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>
index edb5dfa13e58a18d1e0d49fe34ece919ac234237..5dd34d1c20e8faec525943daa23927978e3eb9b2 100644 (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);
index 80ac7870226b89aecc2edb0e3ea20e8b2943dde6..09fe023ae2e5e3b4e239946ce79abcc7510590fe 100644 (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;
 }
 
index 47cfc199d3a1548067b055f917381308c3c74f1a..f7003df9f0823f02571b2babea635e4193aff543 100644 (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);
index a71ea060e34f078d522064c802d38149a1cea467..fe986ed21261a223670bbde6777a858a1d20e6eb 100644 (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);
index e45f51fab4c0d679542c1b7611f878e424462e16..1e25a82a54308dcf47a52fba182dcb7d2ebdec2b 100644 (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;