1
0
mirror of https://github.com/systemd/systemd synced 2024-07-09 04:26:06 +00:00

networkd/wireguard: support network.wireguard.* credentials

Closes #26702
This commit is contained in:
Mike Yuan 2023-12-27 21:38:32 +08:00
parent d7d6195953
commit fa724cd52c
No known key found for this signature in database
GPG Key ID: 417471C0A40F58B3
11 changed files with 173 additions and 46 deletions

View File

@ -1891,13 +1891,22 @@
<varlistentry>
<term><varname>PrivateKey=</varname></term>
<listitem>
<para>The Base64 encoded private key for the interface. It can be
generated using the <command>wg genkey</command> command
<para>The Base64 encoded private key for the interface. It can be generated using
the <command>wg genkey</command> command
(see <citerefentry project="wireguard"><refentrytitle>wg</refentrytitle><manvolnum>8</manvolnum></citerefentry>).
This option or <varname>PrivateKeyFile=</varname> is mandatory to use WireGuard.
Note that because this information is secret, you may want to set
the permissions of the .netdev file to be owned by <literal>root:systemd-network</literal>
with a <literal>0640</literal> file mode.</para>
Specially, if the specified key is prefixed with <literal>@</literal>, it is interpreted as
the name of the credential from which the actual key shall be read. <command>systemd-networkd.service</command>
automatically imports credentials matching <literal>network.wireguard.*</literal>. For more details
on credentials, refer to
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
A private key is mandatory to use WireGuard. If not set, the credential
<literal>network.wireguard.private.<replaceable>netdev</replaceable></literal> is used if exists.
I.e. for <filename>50-foobar.netdev</filename>, <literal>network.wireguard.private.50-foobar</literal>
is tried.</para>
<para>Note that because this information is secret, it's strongly recommended to use an (encrypted)
credential. Alternatively, you may want to set the permissions of the .netdev file to be owned
by <literal>root:systemd-network</literal> with a <literal>0640</literal> file mode.</para>
<xi:include href="version-info.xml" xpointer="v237"/>
</listitem>
@ -1976,9 +1985,9 @@
<listitem>
<para>Sets a Base64 encoded public key calculated by <command>wg pubkey</command>
(see <citerefentry project="wireguard"><refentrytitle>wg</refentrytitle><manvolnum>8</manvolnum></citerefentry>)
from a private key, and usually transmitted out of band to the
author of the configuration file. This option is mandatory for this
section.</para>
from a private key, and usually transmitted out of band to the author of the configuration file.
This option honors the <literal>@</literal> prefix in the same way as the <option>PrivateKey=</option>
setting of the <option>[WireGuard]</option> section. This option is mandatory for this section.</para>
<xi:include href="version-info.xml" xpointer="v237"/>
</listitem>
@ -1986,14 +1995,15 @@
<varlistentry>
<term><varname>PresharedKey=</varname></term>
<listitem>
<para>Optional preshared key for the interface. It can be generated
by the <command>wg genpsk</command> command. This option adds an
additional layer of symmetric-key cryptography to be mixed into the
already existing public-key cryptography, for post-quantum
resistance.
Note that because this information is secret, you may want to set
the permissions of the .netdev file to be owned by <literal>root:systemd-network</literal>
with a <literal>0640</literal> file mode.</para>
<para>Optional preshared key for the interface. It can be generated by the <command>wg genpsk</command>
command. This option adds an additional layer of symmetric-key cryptography to be mixed into the
already existing public-key cryptography, for post-quantum resistance.
This option honors the <literal>@</literal> prefix in the same way as the <option>PrivateKey=</option>
setting of the <option>[WireGuard]</option> section.</para>
<para>Note that because this information is secret, it's strongly recommended to use an (encrypted)
credential. Alternatively, you may want to set the permissions of the .netdev file to be owned
by <literal>root:systemd-network</literal> with a <literal>0640</literal> file mode.</para>
<xi:include href="version-info.xml" xpointer="v237"/>
</listitem>
@ -2034,13 +2044,15 @@
<varlistentry>
<term><varname>Endpoint=</varname></term>
<listitem>
<para>Sets an endpoint IP address or hostname, followed by a colon, and then
a port number. IPv6 address must be in the square brackets. For example,
<literal>111.222.333.444:51820</literal> for IPv4 and <literal>[1111:2222::3333]:51820</literal>
for IPv6 address. This endpoint will be updated automatically once to
the most recent source IP address and port of correctly
<para>Sets an endpoint IP address or hostname, followed by a colon, and then a port number.
IPv6 address must be in the square brackets. For example, <literal>111.222.333.444:51820</literal>
for IPv4 and <literal>[1111:2222::3333]:51820</literal> for IPv6 address. This endpoint will be
updated automatically once to the most recent source IP address and port of correctly
authenticated packets from the peer at configuration time.</para>
<para>This option honors the <literal>@</literal> prefix in the same way as the <option>PrivateKey=</option>
setting of the <option>[WireGuard]</option> section.</para>
<xi:include href="version-info.xml" xpointer="v237"/>
</listitem>
</varlistentry>

View File

@ -155,7 +155,21 @@
<para>Note that the resulting files are created world-readable, it's hence recommended to not include
secrets in these credentials, but supply them via separate credentials directly to
<filename>systemd-networkd.service</filename>.</para>
<filename>systemd-networkd.service</filename>, e.g. <varname>network.wireguard.*</varname>
as described below.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>network.wireguard.*</varname></term>
<listitem>
<para>Configures secrets for WireGuard netdevs. Read by
<citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
For more information, refer to the <option>[WireGuard]</option> section of
<citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>

View File

@ -12,6 +12,7 @@
#include "sd-resolve.h"
#include "alloc-util.h"
#include "creds-util.h"
#include "dns-domain.h"
#include "event-util.h"
#include "fd-util.h"
@ -25,6 +26,7 @@
#include "networkd-util.h"
#include "parse-helpers.h"
#include "parse-util.h"
#include "path-util.h"
#include "random-util.h"
#include "resolve-private.h"
#include "string-util.h"
@ -480,6 +482,8 @@ static int wireguard_decode_key_and_warn(
const char *lvalue) {
_cleanup_(erase_and_freep) void *key = NULL;
_cleanup_(erase_and_freep) char *cred = NULL;
const char *cred_name;
size_t len;
int r;
@ -493,10 +497,22 @@ static int wireguard_decode_key_and_warn(
return 0;
}
if (!streq(lvalue, "PublicKey"))
cred_name = startswith(rvalue, "@");
if (cred_name) {
r = read_credential(cred_name, (void**) &cred, /* ret_size = */ NULL);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to read credential for wireguard key (%s=), ignoring assignment: %m",
lvalue);
return 0;
}
} else if (!streq(lvalue, "PublicKey"))
(void) warn_file_is_world_accessible(filename, NULL, unit, line);
r = unbase64mem_full(rvalue, strlen(rvalue), true, &key, &len);
r = unbase64mem_full(cred ?: rvalue, SIZE_MAX, /* secure = */ true, &key, &len);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
@ -721,23 +737,39 @@ int config_parse_wireguard_endpoint(
void *data,
void *userdata) {
assert(filename);
assert(rvalue);
assert(userdata);
Wireguard *w = WIREGUARD(userdata);
_cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL;
_cleanup_free_ char *host = NULL;
union in_addr_union addr;
const char *p;
_cleanup_free_ char *cred = NULL;
const char *cred_name, *endpoint;
uint16_t port;
int family, r;
int r;
assert(filename);
assert(rvalue);
r = wireguard_peer_new_static(w, filename, section_line, &peer);
if (r < 0)
return log_oom();
r = in_addr_port_ifindex_name_from_string_auto(rvalue, &family, &addr, &port, NULL, NULL);
cred_name = startswith(rvalue, "@");
if (cred_name) {
r = read_credential(cred_name, (void**) &cred, /* ret_size = */ NULL);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to read credential for wireguard endpoint, ignoring assignment: %m");
return 0;
}
endpoint = strstrip(cred);
} else
endpoint = rvalue;
union in_addr_union addr;
int family;
r = in_addr_port_ifindex_name_from_string_auto(endpoint, &family, &addr, &port, NULL, NULL);
if (r >= 0) {
if (family == AF_INET)
peer->endpoint.in = (struct sockaddr_in) {
@ -761,17 +793,23 @@ int config_parse_wireguard_endpoint(
return 0;
}
p = strrchr(rvalue, ':');
_cleanup_free_ char *host = NULL;
const char *p;
p = strrchr(endpoint, ':');
if (!p) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Unable to find port of endpoint, ignoring assignment: %s",
rvalue);
rvalue); /* We log the original assignment instead of resolved credential here,
as the latter might be previously encrypted and we'd expose them in
unprotected logs otherwise. */
return 0;
}
host = strndup(rvalue, p - rvalue);
host = strndup(endpoint, p - endpoint);
if (!host)
return log_oom();
p++;
if (!dns_name_is_valid(host)) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
@ -780,7 +818,6 @@ int config_parse_wireguard_endpoint(
return 0;
}
p++;
r = parse_ip_port(p, &port);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
@ -1078,6 +1115,55 @@ static int wireguard_peer_verify(WireguardPeer *peer) {
return 0;
}
static int wireguard_read_default_key_cred(NetDev *netdev, const char *filename) {
Wireguard *w = WIREGUARD(netdev);
_cleanup_free_ char *config_name = NULL;
int r;
assert(filename);
r = path_extract_filename(filename, &config_name);
if (r < 0)
return log_netdev_error_errno(netdev, r,
"%s: Failed to extract config name, ignoring network device: %m",
filename);
char *p = endswith(config_name, ".netdev");
if (!p)
/* Fuzzer run? Then we just ignore this device. */
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
"%s: Invalid netdev config name, refusing default key lookup.",
filename);
*p = '\0';
_cleanup_(erase_and_freep) char *cred = NULL;
r = read_credential(strjoina("network.wireguard.private.", config_name), (void**) &cred, /* ret_size = */ NULL);
if (r < 0)
return log_netdev_error_errno(netdev, r,
"%s: No private key specified and default key isn't available, "
"ignoring network device: %m",
filename);
_cleanup_(erase_and_freep) void *key = NULL;
size_t len;
r = unbase64mem_full(cred, SIZE_MAX, /* secure = */ true, &key, &len);
if (r < 0)
return log_netdev_error_errno(netdev, r,
"%s: No private key specified and default key cannot be parsed, "
"ignoring network device: %m",
filename);
if (len != WG_KEY_LEN)
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
"%s: No private key specified and default key is invalid. "
"Ignoring network device.",
filename);
memcpy(w->private_key, key, WG_KEY_LEN);
return 0;
}
static int wireguard_verify(NetDev *netdev, const char *filename) {
Wireguard *w = WIREGUARD(netdev);
int r;
@ -1088,10 +1174,11 @@ static int wireguard_verify(NetDev *netdev, const char *filename) {
"Failed to read private key from %s. Ignoring network device.",
w->private_key_file);
if (eqzero(w->private_key))
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
"%s: Missing PrivateKey= or PrivateKeyFile=, "
"Ignoring network device.", filename);
if (eqzero(w->private_key)) {
r = wireguard_read_default_key_cred(netdev, filename);
if (r < 0)
return r;
}
LIST_FOREACH(peers, peer, w->peers) {
if (wireguard_peer_verify(peer) < 0) {

View File

@ -0,0 +1 @@
192.168.27.3:51820

View File

@ -0,0 +1 @@
EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong=

View File

@ -4,6 +4,6 @@ Name=wg97
Kind=wireguard
[WireGuard]
PrivateKey=EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong=
#PrivateKey=EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong=
ListenPort=51821
FwMark=1235

View File

@ -0,0 +1 @@
6Fsg8XN0DE6aPQgAX4r2oazEYJOGqyHUz3QRH/jCB+I=

View File

@ -13,8 +13,8 @@ RouteMetric=456
[WireGuardPeer]
PublicKey=RDf+LSpeEre7YEIKaxg+wbpsNV7du+ktR99uBEtIiCA=
AllowedIPs=fd31:bf08:57cb::/48,192.168.26.3/24
#Endpoint=wireguard.example.com:51820
Endpoint=192.168.27.3:51820
#Endpoint=192.168.27.3:51820
Endpoint=@network.wireguard.peer0.endpoint
PresharedKey=IIWIV17wutHv7t4cR6pOT91z6NSz/T8Arh0yaywhw3M=
PersistentKeepalive=20
RouteTable=1234

View File

@ -1,5 +1,5 @@
[WireGuardPeer]
PublicKey=9uioxkGzjvGjkse3V35I9AhorWfIjBcrf3UPMS0bw2c=
PresharedKey=6Fsg8XN0DE6aPQgAX4r2oazEYJOGqyHUz3QRH/jCB+I=
PresharedKey=@network.wireguard.peer2.psk
AllowedIPs=192.168.124.3

View File

@ -27,6 +27,7 @@ network_unit_dir = '/run/systemd/network'
networkd_conf_dropin_dir = '/run/systemd/networkd.conf.d'
networkd_ci_temp_dir = '/run/networkd-ci'
udev_rules_dir = '/run/udev/rules.d'
credstore_dir = '/run/credstore'
dnsmasq_pid_file = '/run/networkd-ci/test-dnsmasq.pid'
dnsmasq_log_file = '/run/networkd-ci/test-dnsmasq.log'
@ -298,6 +299,11 @@ def copy_network_unit(*units, copy_dropins=True):
if has_link:
udev_reload()
def copy_credential(src, target):
mkdir_p(credstore_dir)
cp(os.path.join(networkd_ci_temp_dir, src),
os.path.join(credstore_dir, target))
def remove_network_unit(*units):
"""
Remove previously copied unit files from the testbed.
@ -1784,6 +1790,10 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
@expectedFailureIfModuleIsNotAvailable('wireguard')
def test_wireguard(self):
copy_credential('25-wireguard-endpoint-peer0-cred.txt', 'network.wireguard.peer0.endpoint')
copy_credential('25-wireguard-preshared-key-peer2-cred.txt', 'network.wireguard.peer2.psk')
copy_credential('25-wireguard-no-peer-private-key-cred.txt', 'network.wireguard.private.25-wireguard-no-peer')
copy_network_unit('25-wireguard.netdev', '25-wireguard.network',
'25-wireguard-23-peers.netdev', '25-wireguard-23-peers.network',
'25-wireguard-preshared-key.txt', '25-wireguard-private-key.txt',

View File

@ -50,6 +50,7 @@ SystemCallErrorNumber=EPERM
SystemCallFilter=@system-service
Type=notify-reload
User=systemd-network
ImportCredential=network.wireguard.*
{{SERVICE_WATCHDOG}}
[Install]