From: Topi Miettinen Date: Sun, 22 May 2022 12:17:24 +0000 (+0300) Subject: core: add user and group to NFTSet= X-Git-Tag: v255-rc1~409 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3bb48b19bd0beeaca11712ae4c75d3d8964936af;p=thirdparty%2Fsystemd.git 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 ``` --- diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml index 038a58e3dc7..e9747cefbb1 100644 --- a/man/systemd.resource-control.xml +++ b/man/systemd.resource-control.xml @@ -1504,24 +1504,26 @@ DeviceAllow=/dev/loop-control NFTSet=family:table:set - This setting provides a method for integrating dynamic cgroup IDs into firewall rules with - NFT 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. + This setting provides a method for integrating dynamic cgroup, user and group IDs into + firewall rules with NFT + 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 DynamicUser= can + be also integrated with this setting. This option expects a whitespace separated list of NFT set definitions. Each definition - consists of a colon-separated tuple of source type (only cgroup), NFT address - family (one of arp, bridge, inet, - ip, ip6, or netdev), 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 - (cgroup) 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. systemd 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. + consists of a colon-separated tuple of source type (one of cgroup, + user or group), NFT address family (one of + arp, bridge, inet, ip, + ip6, or netdev), 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 (cgroup, + user or group) 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. systemd 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. Defined <varname>source type</varname> values @@ -1543,6 +1545,16 @@ DeviceAllow=/dev/loop-control control group IDcgroupsv2 + + user + user ID + meta skuid + + + group + group ID + meta skgid +
@@ -1552,17 +1564,24 @@ DeviceAllow=/dev/loop-control Example: [Unit] -NFTSet=cgroup:inet:filter:my_service +NFTSet=cgroup:inet:filter:my_service user:inet:filter:serviceuser Corresponding NFT rules: 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 + } }
diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c index edb5dfa13e5..5dd34d1c20e 100644 --- a/src/core/dbus-cgroup.c +++ b/src/core/dbus-cgroup.c @@ -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); diff --git a/src/core/unit.c b/src/core/unit.c index 80ac7870226..09fe023ae2e 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -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; } diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 47cfc199d3a..f7003df9f08 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -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); diff --git a/src/shared/firewall-util-nft.c b/src/shared/firewall-util-nft.c index a71ea060e34..fe986ed2126 100644 --- a/src/shared/firewall-util-nft.c +++ b/src/shared/firewall-util-nft.c @@ -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); diff --git a/src/shared/firewall-util.h b/src/shared/firewall-util.h index e45f51fab4c..1e25a82a543 100644 --- a/src/shared/firewall-util.h +++ b/src/shared/firewall-util.h @@ -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;