core: firewall integration of cgroups with NFTSet=

New directive `NFTSet=` 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 control group as a selector in firewall rules easily and this in
turn allows more fine grained filtering. Also, 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.

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 be "cgroupsv2". When a control group for a unit is realized, the cgroup 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.

If the firewall rules are reinstalled so that the contents of NFT sets are
destroyed, command systemctl daemon-reload can be used to refill the sets.

Example:

```
table inet filter {
...
        set timesyncd {
                type cgroupsv2
        }

        chain ntp_output {
                socket cgroupv2 != @timesyncd counter drop
                accept
        }
...
}
```

/etc/systemd/system/systemd-timesyncd.service.d/override.conf
```
[Service]
NFTSet=cgroup:inet:filter:timesyncd
```

```
$ sudo nft list set inet filter timesyncd
table inet filter {
        set timesyncd {
                type cgroupsv2
                elements = { "system.slice/systemd-timesyncd.service" }
        }
}
```
This commit is contained in:
Topi Miettinen
2023-09-27 18:10:11 +00:00
committed by Topi Miettinen
parent b28bd48238
commit dc7d69b3c1
17 changed files with 416 additions and 12 deletions
+89
View File
@@ -17,6 +17,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"
@@ -449,6 +450,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);
assert(field);
assert(eq);
if (isempty(eq)) {
r = sd_bus_message_append(m, "(sv)", field, "a(iiss)", 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(iiss)");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_open_container(m, 'a', "(iiss)");
if (r < 0)
return bus_log_create_error(r);
for (const char *p = eq;;) {
_cleanup_free_ char *tuple = NULL, *source_str = NULL, *nfproto_str = NULL, *table = NULL, *set = NULL;
const char *q = NULL;
int source, nfproto;
r = extract_first_word(&p, &tuple, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
if (r == -ENOMEM)
return log_oom();
if (r < 0)
return log_error_errno(r, "Failed to parse %s: %m", field);
if (r == 0)
break;
if (isempty(tuple))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field);
q = tuple;
r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE, &source_str, &nfproto_str, &table, &set, NULL);
if (r == -ENOMEM)
return log_oom();
if (r != 4 || !isempty(q))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field);
assert(source_str);
assert(nfproto_str);
assert(table);
assert(set);
source = nft_set_source_from_string(source_str);
if (source != NFT_SET_SOURCE_CGROUP)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field);
nfproto = nfproto_from_string(nfproto_str);
if (nfproto < 0 || !nft_identifier_valid(table) || !nft_identifier_valid(set))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field);
r = sd_bus_message_append(m, "(iiss)", source, nfproto, table, set);
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;
@@ -914,6 +1000,9 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons
if (streq(field, "MemoryPressureThresholdSec"))
return bus_append_parse_sec_rename(m, field, eq);
if (streq(field, "NFTSet"))
return bus_append_nft_set(m, field, eq);
return 0;
}
+7 -3
View File
@@ -1202,6 +1202,7 @@ static const char *const nft_set_source_table[] = {
[NFT_SET_SOURCE_ADDRESS] = "address",
[NFT_SET_SOURCE_PREFIX] = "prefix",
[NFT_SET_SOURCE_IFINDEX] = "ifindex",
[NFT_SET_SOURCE_CGROUP] = "cgroup",
};
DEFINE_STRING_TABLE_LOOKUP(nft_set_source, int);
@@ -1218,11 +1219,11 @@ void nft_set_context_clear(NFTSetContext *s) {
s->sets = mfree(s->sets);
}
static int nft_set_add(NFTSetContext *s, NFTSetSource source, int nfproto, const char *table, const char *set) {
int nft_set_add(NFTSetContext *s, NFTSetSource source, int nfproto, const char *table, const char *set) {
_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));
assert(IN_SET(source, NFT_SET_SOURCE_ADDRESS, NFT_SET_SOURCE_PREFIX, NFT_SET_SOURCE_IFINDEX, NFT_SET_SOURCE_CGROUP));
assert(nfproto_is_valid(nfproto));
assert(table);
assert(set);
@@ -1285,6 +1286,7 @@ int config_parse_nft_set(
assert(lvalue);
assert(rvalue);
assert(nft_set_context);
assert(IN_SET(ltype, NFT_SET_PARSE_NETWORK, NFT_SET_PARSE_CGROUP));
if (isempty(rvalue)) {
nft_set_context_clear(nft_set_context);
@@ -1328,7 +1330,9 @@ int config_parse_nft_set(
assert(set);
source = nft_set_source_from_string(source_str);
if (source < 0) {
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)) {
_cleanup_free_ char *esc = NULL;
esc = cescape(source_str);
+8
View File
@@ -36,6 +36,7 @@ typedef enum NFTSetSource {
NFT_SET_SOURCE_ADDRESS,
NFT_SET_SOURCE_PREFIX,
NFT_SET_SOURCE_IFINDEX,
NFT_SET_SOURCE_CGROUP,
_NFT_SET_SOURCE_MAX,
_NFT_SET_SOURCE_INVALID = -EINVAL,
} NFTSetSource;
@@ -89,4 +90,11 @@ int nft_set_element_modify_any(
const void *element,
size_t element_size);
int nft_set_add(NFTSetContext *s, NFTSetSource source, int nfproto, const char *table, const char *set);
typedef enum NFTSetParseFlags {
NFT_SET_PARSE_NETWORK,
NFT_SET_PARSE_CGROUP,
} NFTSetParseFlags;
CONFIG_PARSER_PROTOTYPE(config_parse_nft_set);