network: NetLabel integration

New directive `NetLabel=` provides a method for integrating static and dynamic
network configuration into Linux NetLabel subsystem rules, used by Linux
Security Modules (LSMs) for network access control. The label, with suitable
LSM rules, can be used to control connectivity of (for example) a service with
peers in the local network. At least with SELinux, only the ingress can be
controlled but not egress. The benefit of using this setting is that it may be
possible to apply interface independent part of NetLabel configuration at very
early stage of system boot sequence, at the time when the network interfaces
are not available yet, with netlabelctl(8), and the per-interface configuration
with systemd-networkd once the interfaces appear later.  Currently this feature
is only implemented for SELinux.

The option expects a single NetLabel label. The label must conform to lexical
restrictions of LSM labels. When an interface is configured with IP addresses,
the addresses and subnetwork masks will be appended to the NetLabel Fallback
Peer Labeling rules. They will be removed when the interface is
deconfigured. Failures to manage the labels will be ignored.

Example:
```
[DHCPv4]
NetLabel=system_u:object_r:localnet_peer_t:s0
```

With the above rules for interface `eth0`, when the interface is configured with
an IPv4 address of 10.0.0.123/8, `systemd-networkd` performs the equivalent of
`netlabelctl` operation

```
$ sudo netlabelctl unlbl add interface eth0 address:10.0.0.0/8 label:system_u:object_r:localnet_peer_t:s0
```

Result:
```
$ sudo netlabelctl -p unlbl list
...
 interface: eth0
   address: 10.0.0.0/8
    label: "system_u:object_r:localnet_peer_t:s0"
...
```
This commit is contained in:
Topi Miettinen 2022-08-20 20:52:48 +03:00 committed by Yu Watanabe
parent 5b198025de
commit 4b3590c324
14 changed files with 308 additions and 1 deletions

View file

@ -1121,6 +1121,62 @@ Table=1234</programlisting></para>
Defaults to <literal>no</literal>.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>NetLabel=</varname><replaceable>label</replaceable></term>
<listitem>
<para>This setting provides a method for integrating static and dynamic network configuration into
Linux <ulink url="https://docs.kernel.org/netlabel/index.html">NetLabel</ulink> subsystem rules,
used by <ulink url="https://en.wikipedia.org/wiki/Linux_Security_Modules">Linux Security Modules
(LSMs)</ulink> for network access control. The label, with suitable LSM rules, can be used to
control connectivity of (for example) a service with peers in the local network. At least with
SELinux, only the ingress can be controlled but not egress. The benefit of using this setting is
that it may be possible to apply interface independent part of NetLabel configuration at very early
stage of system boot sequence, at the time when the network interfaces are not available yet, with
<citerefentry
project='man-pages'><refentrytitle>netlabelctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
and the per-interface configuration with <command>systemd-networkd</command> once the interfaces
appear later. Currently this feature is only implemented for SELinux.</para>
<para>The option expects a single NetLabel label. The label must conform to lexical restrictions of
LSM labels. When an interface is configured with IP addresses, the addresses and subnetwork masks
will be appended to the <ulink
url="https://github.com/SELinuxProject/selinux-notebook/blob/main/src/network_support.md">NetLabel
Fallback Peer Labeling</ulink> rules. They will be removed when the interface is
deconfigured. Failures to manage the labels will be ignored.</para>
<para>Warning: Once labeling is enabled for network traffic, a lot of LSM access control points in
Linux networking stack go from dormant to active. Care should be taken to avoid getting into a
situation where for example remote connectivity is broken, when the security policy hasn't been
updated to consider LSM per-packet access controls and no rules would allow any network
traffic. Also note that additional configuration with <citerefentry
project='man-pages'><refentrytitle>netlabelctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>
is needed.</para>
<para>Example:
<programlisting>[Address]
NetLabel=system_u:object_r:localnet_peer_t:s0</programlisting>
With the example rules applying for interface <literal>eth0</literal>, when the interface is
configured with an IPv4 address of 10.0.0.123/8, <command>systemd-networkd</command> performs the
equivalent of <command>netlabelctl</command> operation
<programlisting>netlabelctl unlbl add interface eth0 address:10.0.0.0/8 label:system_u:object_r:localnet_peer_t:s0</programlisting>
and the reverse operation when the IPv4 address is deconfigured. The configuration can be used with
LSM rules; in case of SELinux to allow a SELinux domain to receive data from objects of SELinux
<literal>peer</literal> class. For example:
<programlisting>type localnet_peer_t;
allow my_server_t localnet_peer_t:peer recv;</programlisting>
The effect of the above configuration and rules (in absence of other rules as may be the case) is
to only allow <literal>my_server_t</literal> (and nothing else) to receive data from local subnet
10.0.0.0/8 of interface <literal>eth0</literal>.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
@ -2071,6 +2127,15 @@ Table=1234</programlisting></para>
<ulink url="https://tools.ietf.org/html/rfc5227">RFC 5227</ulink>. Defaults to false.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>NetLabel=</varname></term>
<listitem>
<para>This applies the NetLabel for the addresses received with DHCP, like
<varname>NetLabel=</varname> in [Address] section applies it to statically configured
addresses. See <varname>NetLabel=</varname> in [Address] section for more details.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
@ -2197,6 +2262,7 @@ Table=1234</programlisting></para>
<term><varname>UseNTP=</varname></term>
<term><varname>UseHostname=</varname></term>
<term><varname>UseDomains=</varname></term>
<term><varname>NetLabel=</varname></term>
<listitem>
<para>As in the [DHCPv4] section.</para>
</listitem>
@ -2298,6 +2364,15 @@ Table=1234</programlisting></para>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>NetLabel=</varname></term>
<listitem>
<para>This applies the NetLabel for the addresses received with DHCP, like
<varname>NetLabel=</varname> in [Address] section applies it to statically configured
addresses. See <varname>NetLabel=</varname> in [Address] section for more details.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
@ -2555,6 +2630,15 @@ Token=prefixstable:2002:da8:1::</programlisting></para>
specified. Defaults to true.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>NetLabel=</varname></term>
<listitem>
<para>This applies the NetLabel for the addresses received with RA, like
<varname>NetLabel=</varname> in [Address] section applies it to statically configured
addresses. See <varname>NetLabel=</varname> in [Address] section for more details.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>

View file

@ -115,6 +115,8 @@ sources = files(
'networkd-ndisc.h',
'networkd-neighbor.c',
'networkd-neighbor.h',
'networkd-netlabel.c',
'networkd-netlabel.h',
'networkd-network-bus.c',
'networkd-network-bus.h',
'networkd-network.c',

View file

@ -12,6 +12,7 @@
#include "networkd-dhcp-server.h"
#include "networkd-ipv4acd.h"
#include "networkd-manager.h"
#include "networkd-netlabel.h"
#include "networkd-network.h"
#include "networkd-queue.h"
#include "networkd-route-util.h"
@ -137,6 +138,7 @@ Address *address_free(Address *address) {
config_section_free(address->section);
free(address->label);
free(address->netlabel);
return mfree(address);
}
@ -392,6 +394,7 @@ int address_dup(const Address *src, Address **ret) {
dest->link = NULL;
dest->label = NULL;
dest->acd = NULL;
dest->netlabel = NULL;
if (src->family == AF_INET) {
r = free_and_strdup(&dest->label, src->label);
@ -399,6 +402,10 @@ int address_dup(const Address *src, Address **ret) {
return r;
}
r = free_and_strdup(&dest->netlabel, src->netlabel);
if (r < 0)
return r;
*ret = TAKE_PTR(dest);
return 0;
}
@ -485,6 +492,8 @@ static int address_update(Address *address) {
if (r < 0)
return log_link_warning_errno(link, r, "Could not enable IP masquerading: %m");
address_add_netlabel(address);
if (address_is_ready(address) && address->callback) {
r = address->callback(address);
if (r < 0)
@ -511,6 +520,8 @@ static int address_drop(Address *address) {
if (r < 0)
log_link_warning_errno(link, r, "Failed to disable IP masquerading, ignoring: %m");
address_del_netlabel(address);
if (address->state == 0)
address_free(address);
@ -1941,6 +1952,47 @@ int config_parse_duplicate_address_detection(
return 0;
}
int config_parse_address_netlabel(
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) {
Network *network = userdata;
_cleanup_(address_free_or_set_invalidp) Address *n = NULL;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
assert(network);
r = address_new_static(network, filename, section_line, &n);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to allocate new address, ignoring assignment: %m");
return 0;
}
r = config_parse_string(unit, filename, line, section, section_line,
lvalue, CONFIG_PARSE_STRING_SAFE, rvalue, &n->netlabel, network);
if (r < 0)
return r;
TAKE_PTR(n);
return 0;
}
static int address_section_verify(Address *address) {
if (section_is_invalid(address->section))
return -EINVAL;

View file

@ -38,7 +38,7 @@ struct Address {
unsigned char scope;
uint32_t flags;
uint32_t route_metric; /* route metric for prefix route */
char *label;
char *label, *netlabel;
int set_broadcast;
struct in_addr broadcast;
@ -130,3 +130,4 @@ CONFIG_PARSER_PROTOTYPE(config_parse_address_flags);
CONFIG_PARSER_PROTOTYPE(config_parse_address_scope);
CONFIG_PARSER_PROTOTYPE(config_parse_address_route_metric);
CONFIG_PARSER_PROTOTYPE(config_parse_duplicate_address_detection);
CONFIG_PARSER_PROTOTYPE(config_parse_address_netlabel);

View file

@ -412,6 +412,10 @@ static int dhcp_pd_request_address(
log_dhcp_pd_address(link, address);
r = free_and_strdup_warn(&address->netlabel, link->network->dhcp_pd_netlabel);
if (r < 0)
return r;
if (address_get(link, address, &existing) < 0)
link->dhcp_pd_configured = false;
else

View file

@ -877,6 +877,10 @@ static int dhcp4_request_address(Link *link, bool announce) {
if (r < 0)
return r;
r = free_and_strdup_warn(&addr->netlabel, link->network->dhcp_netlabel);
if (r < 0)
return r;
if (address_get(link, addr, &existing) < 0) /* The address is new. */
link->dhcp4_configured = false;
else

View file

@ -224,6 +224,10 @@ static int dhcp6_request_address(
if (verify_dhcp6_address(link, addr) < 0)
return 0;
r = free_and_strdup_warn(&addr->netlabel, link->network->dhcp6_netlabel);
if (r < 0)
return r;
if (address_get(link, addr, &existing) < 0)
link->dhcp6_configured = false;
else

View file

@ -278,6 +278,10 @@ static int ndisc_request_address(Address *in, Link *link, sd_ndisc_router *rt) {
address->source = NETWORK_CONFIG_SOURCE_NDISC;
address->provider.in6 = router;
r = free_and_strdup_warn(&address->netlabel, link->network->ndisc_netlabel);
if (r < 0)
return r;
if (address_get(link, address, &existing) < 0)
link->ndisc_configured = false;
else

View file

@ -0,0 +1,128 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "escape.h"
#include "netlink-util.h"
#include "networkd-address.h"
#include "networkd-link.h"
#include "networkd-manager.h"
#include "networkd-netlabel.h"
#include "networkd-network.h"
static int netlabel_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
int r;
assert_se(rtnl);
assert_se(m);
assert_se(link);
r = sd_netlink_message_get_errno(m);
if (r < 0) {
log_link_message_warning_errno(link, m, r, "NetLabel operation failed, ignoring");
return 1;
}
log_link_debug(link, "NetLabel operation successful");
return 1;
}
static int netlabel_command(uint16_t command, const char *label, const Address *address) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
int r;
assert(command != NLBL_UNLABEL_C_UNSPEC && command < __NLBL_UNLABEL_C_MAX);
assert(address);
assert(address->link);
assert(address->link->ifname);
assert(address->link->manager);
assert(address->link->manager->genl);
assert(IN_SET(address->family, AF_INET, AF_INET6));
r = sd_genl_message_new(address->link->manager->genl, NETLBL_NLTYPE_UNLABELED_NAME, command, &m);
if (r < 0)
return r;
r = sd_netlink_message_append_string(m, NLBL_UNLABEL_A_IFACE, address->link->ifname);
if (r < 0)
return r;
if (command == NLBL_UNLABEL_C_STATICADD) {
assert(label);
r = sd_netlink_message_append_string(m, NLBL_UNLABEL_A_SECCTX, label);
if (r < 0)
return r;
}
union in_addr_union netmask, masked_addr;
r = in_addr_prefixlen_to_netmask(address->family, &netmask, address->prefixlen);
if (r < 0)
return r;
/*
* When adding rules, kernel adds the address to its hash table _applying also the netmask_, but on
* removal, an exact match is required _without netmask applied_, so apply the mask on both
* operations.
*/
masked_addr = address->in_addr;
r = in_addr_mask(address->family, &masked_addr, address->prefixlen);
if (r < 0)
return r;
if (address->family == AF_INET) {
r = sd_netlink_message_append_in_addr(m, NLBL_UNLABEL_A_IPV4ADDR, &masked_addr.in);
if (r < 0)
return r;
r = sd_netlink_message_append_in_addr(m, NLBL_UNLABEL_A_IPV4MASK, &netmask.in);
} else if (address->family == AF_INET6) {
r = sd_netlink_message_append_in6_addr(m, NLBL_UNLABEL_A_IPV6ADDR, &masked_addr.in6);
if (r < 0)
return r;
r = sd_netlink_message_append_in6_addr(m, NLBL_UNLABEL_A_IPV6MASK, &netmask.in6);
}
if (r < 0)
return r;
r = netlink_call_async(address->link->manager->genl, NULL, m, netlabel_handler, link_netlink_destroy_callback,
address->link);
if (r < 0)
return r;
link_ref(address->link);
return 0;
}
void address_add_netlabel(const Address *address) {
int r;
assert(address);
if (!address->netlabel)
return;
r = netlabel_command(NLBL_UNLABEL_C_STATICADD, address->netlabel, address);
if (r < 0)
log_link_warning_errno(address->link, r, "Adding NetLabel %s for IP address %s failed, ignoring", address->netlabel,
IN_ADDR_PREFIX_TO_STRING(address->family, &address->in_addr, address->prefixlen));
else
log_link_debug(address->link, "Adding NetLabel %s for IP address %s", address->netlabel,
IN_ADDR_PREFIX_TO_STRING(address->family, &address->in_addr, address->prefixlen));
}
void address_del_netlabel(const Address *address) {
int r;
assert(address);
if (!address->netlabel)
return;
r = netlabel_command(NLBL_UNLABEL_C_STATICREMOVE, address->netlabel, address);
if (r < 0)
log_link_warning_errno(address->link, r, "Deleting NetLabel %s for IP address %s failed, ignoring", address->netlabel,
IN_ADDR_PREFIX_TO_STRING(address->family, &address->in_addr, address->prefixlen));
else
log_link_debug(address->link, "Deleting NetLabel %s for IP address %s", address->netlabel,
IN_ADDR_PREFIX_TO_STRING(address->family, &address->in_addr, address->prefixlen));
}

View file

@ -0,0 +1,5 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
void address_add_netlabel(const Address *address);
void address_del_netlabel(const Address *address);

View file

@ -25,6 +25,7 @@ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"")
#include "networkd-ipv6ll.h"
#include "networkd-lldp-tx.h"
#include "networkd-ndisc.h"
#include "networkd-netlabel.h"
#include "networkd-network.h"
#include "networkd-neighbor.h"
#include "networkd-nexthop.h"
@ -158,6 +159,7 @@ Address.AutoJoin, config_parse_address_flags,
Address.DuplicateAddressDetection, config_parse_duplicate_address_detection, 0, 0
Address.Scope, config_parse_address_scope, 0, 0
Address.RouteMetric, config_parse_address_route_metric, 0, 0
Address.NetLabel, config_parse_address_netlabel, 0, 0
IPv6AddressLabel.Prefix, config_parse_address_label_prefix, 0, 0
IPv6AddressLabel.Label, config_parse_address_label, 0, 0
Neighbor.Address, config_parse_neighbor_address, 0, 0
@ -246,6 +248,7 @@ DHCPv4.SendVendorOption, config_parse_dhcp_send_option,
DHCPv4.RouteMTUBytes, config_parse_mtu, AF_INET, offsetof(Network, dhcp_route_mtu)
DHCPv4.FallbackLeaseLifetimeSec, config_parse_dhcp_fallback_lease_lifetime, 0, 0
DHCPv4.Use6RD, config_parse_bool, 0, offsetof(Network, dhcp_use_6rd)
DHCPv4.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp_netlabel)
DHCPv6.UseAddress, config_parse_bool, 0, offsetof(Network, dhcp6_use_address)
DHCPv6.UseDelegatedPrefix, config_parse_bool, 0, offsetof(Network, dhcp6_use_pd_prefix)
DHCPv6.UseDNS, config_parse_dhcp_use_dns, AF_INET6, 0
@ -264,6 +267,7 @@ DHCPv6.IAID, config_parse_iaid,
DHCPv6.DUIDType, config_parse_duid_type, 0, offsetof(Network, dhcp6_duid)
DHCPv6.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, dhcp6_duid)
DHCPv6.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp6_use_rapid_commit)
DHCPv6.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp6_netlabel)
IPv6AcceptRA.UseGateway, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_gateway)
IPv6AcceptRA.UseRoutePrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_route_prefix)
IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_autonomous_prefix)
@ -281,6 +285,7 @@ IPv6AcceptRA.PrefixDenyList, config_parse_in_addr_prefixes,
IPv6AcceptRA.RouteAllowList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_allow_listed_route_prefix)
IPv6AcceptRA.RouteDenyList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_deny_listed_route_prefix)
IPv6AcceptRA.Token, config_parse_address_generation_type, 0, offsetof(Network, ndisc_tokens)
IPv6AcceptRA.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, ndisc_netlabel)
DHCPServer.ServerAddress, config_parse_dhcp_server_address, 0, 0
DHCPServer.UplinkInterface, config_parse_uplink, 0, 0
DHCPServer.RelayTarget, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_server_relay_target)
@ -347,6 +352,7 @@ DHCPPrefixDelegation.Assign, config_parse_bool,
DHCPPrefixDelegation.ManageTemporaryAddress, config_parse_bool, 0, offsetof(Network, dhcp_pd_manage_temporary_address)
DHCPPrefixDelegation.Token, config_parse_address_generation_type, 0, offsetof(Network, dhcp_pd_tokens)
DHCPPrefixDelegation.RouteMetric, config_parse_uint32, 0, offsetof(Network, dhcp_pd_route_metric)
DHCPPrefixDelegation.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp_pd_netlabel)
IPv6SendRA.RouterLifetimeSec, config_parse_router_lifetime, 0, offsetof(Network, router_lifetime_usec)
IPv6SendRA.Managed, config_parse_bool, 0, offsetof(Network, router_managed)
IPv6SendRA.OtherInformation, config_parse_bool, 0, offsetof(Network, router_other_information)

View file

@ -712,6 +712,7 @@ static Network *network_free(Network *network) {
set_free(network->dhcp_request_options);
ordered_hashmap_free(network->dhcp_client_send_options);
ordered_hashmap_free(network->dhcp_client_send_vendor_options);
free(network->dhcp_netlabel);
/* DHCPv6 client */
free(network->dhcp6_mudurl);
@ -720,10 +721,12 @@ static Network *network_free(Network *network) {
set_free(network->dhcp6_request_options);
ordered_hashmap_free(network->dhcp6_client_send_options);
ordered_hashmap_free(network->dhcp6_client_send_vendor_options);
free(network->dhcp6_netlabel);
/* DHCP PD */
free(network->dhcp_pd_uplink_name);
set_free(network->dhcp_pd_tokens);
free(network->dhcp_pd_netlabel);
/* Router advertisement */
ordered_set_free(network->router_search_domains);
@ -738,6 +741,7 @@ static Network *network_free(Network *network) {
set_free(network->ndisc_deny_listed_route_prefix);
set_free(network->ndisc_allow_listed_route_prefix);
set_free(network->ndisc_tokens);
free(network->ndisc_netlabel);
/* LLDP */
free(network->lldp_mudurl);

View file

@ -155,6 +155,7 @@ struct Network {
Set *dhcp_request_options;
OrderedHashmap *dhcp_client_send_options;
OrderedHashmap *dhcp_client_send_vendor_options;
char *dhcp_netlabel;
/* DHCPv6 Client support */
bool dhcp6_use_address;
@ -180,6 +181,7 @@ struct Network {
OrderedHashmap *dhcp6_client_send_options;
OrderedHashmap *dhcp6_client_send_vendor_options;
Set *dhcp6_request_options;
char *dhcp6_netlabel;
/* DHCP Server Support */
bool dhcp_server;
@ -237,6 +239,7 @@ struct Network {
Set *dhcp_pd_tokens;
int dhcp_pd_uplink_index;
char *dhcp_pd_uplink_name;
char *dhcp_pd_netlabel;
/* Bridge Support */
int use_bpdu;
@ -321,6 +324,7 @@ struct Network {
Set *ndisc_deny_listed_route_prefix;
Set *ndisc_allow_listed_route_prefix;
Set *ndisc_tokens;
char *ndisc_netlabel;
/* LLDP support */
LLDPMode lldp_mode; /* LLDP reception */

View file

@ -132,6 +132,7 @@ MUDURL=
RouteMTUBytes=
FallbackLeaseLifetimeSec=
Use6RD=
NetLabel=
[DHCPv6]
UseAddress=
UseDelegatedPrefix=
@ -153,6 +154,7 @@ RouteMetric=
IAID=
DUIDType=
DUIDRawData=
NetLabel=
[DHCPv6PrefixDelegation]
SubnetId=
Announce=
@ -160,6 +162,7 @@ Assign=
ManageTemporaryAddress=
Token=
RouteMetric=
NetLabel=
[DHCPPrefixDelegation]
UplinkInterface=
SubnetId=
@ -168,6 +171,7 @@ Assign=
ManageTemporaryAddress=
Token=
RouteMetric=
NetLabel=
[Route]
Destination=
Protocol=
@ -346,6 +350,7 @@ EmitDomains=
Managed=
OtherInformation=
UplinkInterface=
NetLabel=
[IPv6PrefixDelegation]
RouterPreference=
DNSLifetimeSec=