core: firewall integration with ControlGroupNFTSet=

New directive `ControlGroupNFTSet=` provides a method for integrating services
into firewall rules with NFT 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]
ControlGroupNFTSet=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
2022-06-08 16:12:25 +00:00
committed by Topi Miettinen
parent ab51fd9dbd
commit c0548df0a2
15 changed files with 304 additions and 0 deletions
+52
View File
@@ -19,6 +19,7 @@
#include "devnum-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "firewall-util.h"
#include "in-addr-prefix-util.h"
#include "inotify-util.h"
#include "io-util.h"
@@ -279,6 +280,8 @@ void cgroup_context_done(CGroupContext *c) {
cpu_set_reset(&c->startup_cpuset_cpus);
cpu_set_reset(&c->cpuset_mems);
cpu_set_reset(&c->startup_cpuset_mems);
c->nft_set_context = nft_set_context_free_many(c->nft_set_context, &c->n_nft_set_contexts);
}
static int unit_get_kernel_memory_limit(Unit *u, const char *file, uint64_t *ret) {
@@ -617,6 +620,11 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) {
SET_FOREACH(iface, c->restrict_network_interfaces)
fprintf(f, "%sRestrictNetworkInterfaces: %s\n", prefix, iface);
}
for (size_t i = 0; i < c->n_nft_set_contexts; i++)
fprintf(f, "%sControlGroupNFTSet: %s:%s:%s\n", prefix,
nfproto_to_string(c->nft_set_context[i].nfproto),
c->nft_set_context[i].table, c->nft_set_context[i].set);
}
void cgroup_context_dump_socket_bind_item(const CGroupSocketBindItem *item, FILE *f) {
@@ -1226,6 +1234,46 @@ static void cgroup_apply_firewall(Unit *u) {
(void) bpf_firewall_install(u);
}
static void cgroup_apply_nft_set(Unit *u) {
int r;
CGroupContext *c;
assert(u);
assert_se(c = unit_get_cgroup_context(u));
for (size_t i = 0; i < c->n_nft_set_contexts; i++) {
NFTSetContext *s = &c->nft_set_context[i];
r = nft_set_element_add_uint64(s, u->cgroup_id);
if (r < 0)
log_warning_errno(r, "Adding NFT family %s table %s set %s cgroup %" PRIu64 " failed, ignoring: %m",
nfproto_to_string(s->nfproto),
s->table,
s->set,
u->cgroup_id);
}
}
static void cgroup_delete_nft_set(Unit *u) {
int r;
CGroupContext *c;
assert(u);
assert_se(c = unit_get_cgroup_context(u));
for (size_t i = 0; i < c->n_nft_set_contexts; i++) {
NFTSetContext *s = &c->nft_set_context[i];
r = nft_set_element_del_uint64(s, u->cgroup_id);
if (r < 0)
log_warning_errno(r, "Deleting NFT family %s table %s set %s cgroup %" PRIu64 " failed, ignoring: %m",
nfproto_to_string(s->nfproto),
s->table,
s->set,
u->cgroup_id);
}
}
static void cgroup_apply_socket_bind(Unit *u) {
assert(u);
@@ -1658,6 +1706,8 @@ static void cgroup_context_apply(
if (apply_mask & CGROUP_MASK_BPF_RESTRICT_NETWORK_INTERFACES)
cgroup_apply_restrict_network_interfaces(u);
cgroup_apply_nft_set(u);
}
static bool unit_get_needs_bpf_firewall(Unit *u) {
@@ -2807,6 +2857,8 @@ void unit_prune_cgroup(Unit *u) {
(void) lsm_bpf_cleanup(u); /* Remove cgroup from the global LSM BPF map */
#endif
cgroup_delete_nft_set(u);
is_root_slice = unit_has_name(u, SPECIAL_ROOT_SLICE);
r = cg_trim_everywhere(u->manager->cgroup_supported, u->cgroup_path, !is_root_slice);
+4
View File
@@ -6,6 +6,7 @@
#include "bpf-lsm.h"
#include "cgroup-util.h"
#include "cpu-set-util.h"
#include "firewall-util.h"
#include "list.h"
#include "time-util.h"
@@ -194,6 +195,9 @@ struct CGroupContext {
ManagedOOMMode moom_mem_pressure;
uint32_t moom_mem_pressure_limit; /* Normalized to 2^32-1 == 100% */
ManagedOOMPreference moom_preference;
NFTSetContext *nft_set_context;
size_t n_nft_set_contexts;
};
/* Used when querying IP accounting data */
+85
View File
@@ -15,6 +15,7 @@
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "firewall-util.h"
#include "in-addr-prefix-util.h"
#include "ip-protocol-list.h"
#include "limits-util.h"
@@ -443,6 +444,36 @@ static int property_get_restrict_network_interfaces(
return sd_bus_message_close_container(reply);
}
static int property_get_cgroup_nft_set(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
int r;
CGroupContext *c = userdata;
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_nft_set_contexts; i++) {
NFTSetContext *s = &c->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_cgroup_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Delegate", "b", bus_property_get_bool, offsetof(CGroupContext, delegate), 0),
@@ -500,6 +531,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = {
SD_BUS_PROPERTY("SocketBindAllow", "a(iiqq)", property_get_socket_bind, offsetof(CGroupContext, socket_bind_allow), 0),
SD_BUS_PROPERTY("SocketBindDeny", "a(iiqq)", property_get_socket_bind, offsetof(CGroupContext, socket_bind_deny), 0),
SD_BUS_PROPERTY("RestrictNetworkInterfaces", "(bas)", property_get_restrict_network_interfaces, 0, 0),
SD_BUS_PROPERTY("ControlGroupNFTSet", "a(iss)", property_get_cgroup_nft_set, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_VTABLE_END
};
@@ -2062,5 +2094,58 @@ int bus_cgroup_set_property(
if (streq(name, "DisableControllers") || (u->transient && u->load_state == UNIT_STUB))
return bus_cgroup_set_transient_property(u, c, name, message, flags, error);
if (streq(name, "ControlGroupNFTSet")) {
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->nft_set_context, &c->n_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->nft_set_context = nft_set_context_free_many(c->nft_set_context, &c->n_nft_set_contexts);
unit_write_settingf(u, flags, name, "%s=", name);
}
return 1;
}
return 0;
}
+1
View File
@@ -241,6 +241,7 @@
{{type}}.SocketBindAllow, config_parse_cgroup_socket_bind, 0, offsetof({{type}}, cgroup_context.socket_bind_allow)
{{type}}.SocketBindDeny, config_parse_cgroup_socket_bind, 0, offsetof({{type}}, cgroup_context.socket_bind_deny)
{{type}}.RestrictNetworkInterfaces, config_parse_restrict_network_interfaces, 0, offsetof({{type}}, cgroup_context)
{{type}}.ControlGroupNFTSet, config_parse_cgroup_nft_set, 0, offsetof({{type}}, cgroup_context)
{%- endmacro -%}
%{
+87
View File
@@ -35,8 +35,10 @@
#include "env-util.h"
#include "errno-list.h"
#include "escape.h"
#include "execute.h"
#include "fd-util.h"
#include "fileio.h"
#include "firewall-util.h"
#include "fs-util.h"
#include "hexdecoct.h"
#include "io-util.h"
@@ -6520,3 +6522,88 @@ int config_parse_tty_size(
return config_parse_unsigned(unit, filename, line, section, section_line, lvalue, ltype, rvalue, data, userdata);
}
static int config_parse_nft_set(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
NFTSetContext **c,
size_t *n,
Unit *u) {
_cleanup_free_ char *family_str = NULL, *table = NULL, *set = NULL, *table_resolved = NULL, *set_resolved = NULL;
int nfproto, r;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(u);
if (isempty(rvalue)) {
/* Empty assignment resets the list */
*c = nft_set_context_free_many(*c, n);
return 0;
}
for (const char *p = rvalue;;) {
r = extract_many_words(&p, ":", EXTRACT_CUNESCAPE, &family_str, &table, &set, NULL);
if (r == -ENOMEM)
return log_oom();
if (r == 0)
break;
if (r != 3) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse NFT set, ignoring: %s", p);
return 0;
}
nfproto = nfproto_from_string(family_str);
if (nfproto < 0) {
log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown NFT protocol family, ignoring: %s", family_str);
return 0;
}
r = unit_path_printf(u, table, &table_resolved);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in '%s', ignoring: %m", table);
return 0;
}
if (nft_identifier_bad(table_resolved))
return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid table name %s, ignoring", table);
r = unit_path_printf(u, set, &set_resolved);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in '%s', ignoring: %m", set);
return 0;
}
if (nft_identifier_bad(set_resolved))
return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid set name %s, ignoring", set);
r = nft_set_context_add(c, n, nfproto, table_resolved, set_resolved);
if (r < 0)
return log_oom();
}
return 0;
}
int config_parse_cgroup_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) {
CGroupContext *c = data;
Unit *u = userdata;
return config_parse_nft_set(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &c->nft_set_context, &c->n_nft_set_contexts, u);
}
+1
View File
@@ -150,6 +150,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_socket_bind);
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);
/* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);