Systemd/src/network/netdev/wireguard.c

947 lines
30 KiB
C
Raw Normal View History

/* SPDX-License-Identifier: LGPL-2.1-or-later */
/***
Copyright © 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
***/
#include <sys/ioctl.h>
#include <net/if.h>
#include "sd-resolve.h"
#include "alloc-util.h"
#include "event-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "hexdecoct.h"
#include "memory-util.h"
#include "netlink-util.h"
#include "networkd-manager.h"
2018-12-15 12:57:20 +01:00
#include "networkd-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "resolve-private.h"
2018-12-15 12:57:20 +01:00
#include "string-util.h"
#include "strv.h"
#include "wireguard.h"
static void resolve_endpoints(NetDev *netdev);
static void wireguard_peer_free(WireguardPeer *peer) {
WireguardIPmask *mask;
if (!peer)
return;
if (peer->wireguard) {
LIST_REMOVE(peers, peer->wireguard->peers, peer);
set_remove(peer->wireguard->peers_with_unresolved_endpoint, peer);
set_remove(peer->wireguard->peers_with_failed_endpoint, peer);
if (peer->section)
hashmap_remove(peer->wireguard->peers_by_section, peer->section);
}
network_config_section_free(peer->section);
while ((mask = peer->ipmasks)) {
LIST_REMOVE(ipmasks, peer->ipmasks, mask);
free(mask);
}
free(peer->endpoint_host);
free(peer->endpoint_port);
free(peer->preshared_key_file);
explicit_bzero_safe(peer->preshared_key, WG_KEY_LEN);
free(peer);
}
DEFINE_NETWORK_SECTION_FUNCTIONS(WireguardPeer, wireguard_peer_free);
static int wireguard_peer_new_static(Wireguard *w, const char *filename, unsigned section_line, WireguardPeer **ret) {
_cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
_cleanup_(wireguard_peer_freep) WireguardPeer *peer = NULL;
int r;
assert(w);
assert(ret);
assert(filename);
assert(section_line > 0);
r = network_config_section_new(filename, section_line, &n);
if (r < 0)
return r;
peer = hashmap_get(w->peers_by_section, n);
if (peer) {
*ret = TAKE_PTR(peer);
return 0;
}
peer = new(WireguardPeer, 1);
if (!peer)
return -ENOMEM;
*peer = (WireguardPeer) {
.flags = WGPEER_F_REPLACE_ALLOWEDIPS,
.wireguard = w,
.section = TAKE_PTR(n),
};
LIST_PREPEND(peers, w->peers, peer);
r = hashmap_ensure_allocated(&w->peers_by_section, &network_config_hash_ops);
if (r < 0)
return r;
r = hashmap_put(w->peers_by_section, peer->section, peer);
if (r < 0)
return r;
*ret = TAKE_PTR(peer);
return 0;
}
static int wireguard_set_ipmask_one(NetDev *netdev, sd_netlink_message *message, const WireguardIPmask *mask, uint16_t index) {
int r;
assert(message);
assert(mask);
assert(index > 0);
/* This returns 1 on success, 0 on recoverable error, and negative errno on failure. */
r = sd_netlink_message_open_array(message, index);
if (r < 0)
return 0;
r = sd_netlink_message_append_u16(message, WGALLOWEDIP_A_FAMILY, mask->family);
if (r < 0)
goto cancel;
r = netlink_message_append_in_addr_union(message, WGALLOWEDIP_A_IPADDR, mask->family, &mask->ip);
if (r < 0)
goto cancel;
r = sd_netlink_message_append_u8(message, WGALLOWEDIP_A_CIDR_MASK, mask->cidr);
if (r < 0)
goto cancel;
r = sd_netlink_message_close_container(message);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not add wireguard allowed ip: %m");
return 1;
cancel:
r = sd_netlink_message_cancel_array(message);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not cancel wireguard allowed ip message attribute: %m");
return 0;
}
static int wireguard_set_peer_one(NetDev *netdev, sd_netlink_message *message, const WireguardPeer *peer, uint16_t index, WireguardIPmask **mask_start) {
WireguardIPmask *mask, *start;
uint16_t j = 0;
int r;
assert(message);
assert(peer);
assert(index > 0);
assert(mask_start);
/* This returns 1 on success, 0 on recoverable error, and negative errno on failure. */
start = *mask_start ?: peer->ipmasks;
r = sd_netlink_message_open_array(message, index);
if (r < 0)
return 0;
r = sd_netlink_message_append_data(message, WGPEER_A_PUBLIC_KEY, &peer->public_key, sizeof(peer->public_key));
if (r < 0)
goto cancel;
if (!*mask_start) {
r = sd_netlink_message_append_data(message, WGPEER_A_PRESHARED_KEY, &peer->preshared_key, WG_KEY_LEN);
if (r < 0)
goto cancel;
r = sd_netlink_message_append_u32(message, WGPEER_A_FLAGS, peer->flags);
if (r < 0)
goto cancel;
r = sd_netlink_message_append_u16(message, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, peer->persistent_keepalive_interval);
if (r < 0)
goto cancel;
if (IN_SET(peer->endpoint.sa.sa_family, AF_INET, AF_INET6)) {
r = netlink_message_append_sockaddr_union(message, WGPEER_A_ENDPOINT, &peer->endpoint);
if (r < 0)
goto cancel;
}
}
r = sd_netlink_message_open_container(message, WGPEER_A_ALLOWEDIPS);
if (r < 0)
goto cancel;
LIST_FOREACH(ipmasks, mask, start) {
r = wireguard_set_ipmask_one(netdev, message, mask, ++j);
if (r < 0)
return r;
if (r == 0)
break;
}
r = sd_netlink_message_close_container(message);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not add wireguard allowed ip: %m");
r = sd_netlink_message_close_container(message);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not add wireguard peer: %m");
*mask_start = mask; /* Start next cycle from this mask. */
return !mask;
cancel:
r = sd_netlink_message_cancel_array(message);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not cancel wireguard peers: %m");
return 0;
}
static int wireguard_set_interface(NetDev *netdev) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL;
WireguardIPmask *mask_start = NULL;
WireguardPeer *peer, *peer_start;
bool sent_once = false;
uint32_t serial;
Wireguard *w;
int r;
assert(netdev);
w = WIREGUARD(netdev);
assert(w);
for (peer_start = w->peers; peer_start || !sent_once; ) {
uint16_t i = 0;
message = sd_netlink_message_unref(message);
r = sd_genl_message_new(netdev->manager->genl, SD_GENL_WIREGUARD, WG_CMD_SET_DEVICE, &message);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Failed to allocate generic netlink message: %m");
r = sd_netlink_message_append_string(message, WGDEVICE_A_IFNAME, netdev->ifname);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append wireguard interface name: %m");
if (peer_start == w->peers) {
r = sd_netlink_message_append_data(message, WGDEVICE_A_PRIVATE_KEY, &w->private_key, WG_KEY_LEN);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append wireguard private key: %m");
r = sd_netlink_message_append_u16(message, WGDEVICE_A_LISTEN_PORT, w->port);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append wireguard port: %m");
r = sd_netlink_message_append_u32(message, WGDEVICE_A_FWMARK, w->fwmark);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append wireguard fwmark: %m");
r = sd_netlink_message_append_u32(message, WGDEVICE_A_FLAGS, w->flags);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append wireguard flags: %m");
}
r = sd_netlink_message_open_container(message, WGDEVICE_A_PEERS);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append wireguard peer attributes: %m");
LIST_FOREACH(peers, peer, peer_start) {
r = wireguard_set_peer_one(netdev, message, peer, ++i, &mask_start);
if (r < 0)
return r;
if (r == 0)
break;
}
peer_start = peer; /* Start next cycle from this peer. */
r = sd_netlink_message_close_container(message);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not close wireguard container: %m");
r = sd_netlink_send(netdev->manager->genl, message, &serial);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not set wireguard device: %m");
sent_once = true;
}
return 0;
}
static void wireguard_peer_destroy_callback(WireguardPeer *peer) {
NetDev *netdev;
assert(peer);
assert(peer->wireguard);
netdev = NETDEV(peer->wireguard);
if (section_is_invalid(peer->section))
wireguard_peer_free(peer);
netdev_unref(netdev);
}
static int on_resolve_retry(sd_event_source *s, usec_t usec, void *userdata) {
NetDev *netdev = userdata;
Wireguard *w;
assert(netdev);
w = WIREGUARD(netdev);
assert(w);
if (!netdev_is_managed(netdev))
return 0;
assert(set_isempty(w->peers_with_unresolved_endpoint));
SWAP_TWO(w->peers_with_unresolved_endpoint, w->peers_with_failed_endpoint);
resolve_endpoints(netdev);
return 0;
}
/*
* Given the number of retries this function will return will an exponential
* increasing time in milliseconds to wait starting at 200ms and capped at 25 seconds.
*/
static int exponential_backoff_milliseconds(unsigned n_retries) {
return (2 << MIN(n_retries, 7U)) * 100 * USEC_PER_MSEC;
}
static int wireguard_resolve_handler(sd_resolve_query *q,
int ret,
const struct addrinfo *ai,
WireguardPeer *peer) {
NetDev *netdev;
Wireguard *w;
int r;
assert(peer);
assert(peer->wireguard);
w = peer->wireguard;
netdev = NETDEV(w);
if (!netdev_is_managed(netdev))
return 0;
if (ret != 0) {
log_netdev_error(netdev, "Failed to resolve host '%s:%s': %s", peer->endpoint_host, peer->endpoint_port, gai_strerror(ret));
r = set_ensure_put(&w->peers_with_failed_endpoint, NULL, peer);
if (r < 0) {
log_netdev_error(netdev, "Failed to save a peer, dropping the peer: %m");
peer->section->invalid = true;
goto resolve_next;
}
} else if ((ai->ai_family == AF_INET && ai->ai_addrlen == sizeof(struct sockaddr_in)) ||
(ai->ai_family == AF_INET6 && ai->ai_addrlen == sizeof(struct sockaddr_in6)))
memcpy(&peer->endpoint, ai->ai_addr, ai->ai_addrlen);
else
log_netdev_error(netdev, "Neither IPv4 nor IPv6 address found for peer endpoint %s:%s, ignoring the address.",
peer->endpoint_host, peer->endpoint_port);
resolve_next:
if (!set_isempty(w->peers_with_unresolved_endpoint)) {
resolve_endpoints(netdev);
return 0;
}
(void) wireguard_set_interface(netdev);
if (!set_isempty(w->peers_with_failed_endpoint)) {
usec_t usec;
w->n_retries++;
usec = usec_add(now(CLOCK_MONOTONIC), exponential_backoff_milliseconds(w->n_retries));
r = event_reset_time(netdev->manager->event, &w->resolve_retry_event_source,
CLOCK_MONOTONIC, usec, 0, on_resolve_retry, netdev,
0, "wireguard-resolve-retry", true);
if (r < 0) {
log_netdev_warning_errno(netdev, r, "Could not arm resolve retry handler: %m");
return 0;
}
}
return 0;
}
static void resolve_endpoints(NetDev *netdev) {
static const struct addrinfo hints = {
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_DGRAM,
.ai_protocol = IPPROTO_UDP
};
WireguardPeer *peer;
Wireguard *w;
int r;
assert(netdev);
w = WIREGUARD(netdev);
assert(w);
SET_FOREACH(peer, w->peers_with_unresolved_endpoint) {
r = resolve_getaddrinfo(netdev->manager->resolve,
NULL,
peer->endpoint_host,
peer->endpoint_port,
&hints,
wireguard_resolve_handler,
wireguard_peer_destroy_callback,
peer);
if (r == -ENOBUFS)
break;
if (r < 0) {
log_netdev_error_errno(netdev, r, "Failed to create resolver: %m");
continue;
}
/* Avoid freeing netdev. It will be unrefed by the destroy callback. */
netdev_ref(netdev);
(void) set_remove(w->peers_with_unresolved_endpoint, peer);
}
}
static int netdev_wireguard_post_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
assert(netdev);
assert(WIREGUARD(netdev));
(void) wireguard_set_interface(netdev);
resolve_endpoints(netdev);
return 0;
}
int config_parse_wireguard_listen_port(
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) {
uint16_t *s = data;
int r;
assert(rvalue);
assert(data);
if (isempty(rvalue) || streq(rvalue, "auto")) {
*s = 0;
return 0;
}
r = parse_ip_port(rvalue, s);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Invalid port specification, ignoring assignment: %s", rvalue);
return 0;
}
return 0;
}
static int wireguard_decode_key_and_warn(
const char *rvalue,
uint8_t ret[static WG_KEY_LEN],
const char *unit,
const char *filename,
unsigned line,
const char *lvalue) {
_cleanup_(erase_and_freep) void *key = NULL;
size_t len;
int r;
assert(rvalue);
assert(ret);
assert(filename);
assert(lvalue);
if (isempty(rvalue)) {
memzero(ret, WG_KEY_LEN);
return 0;
}
if (!streq(lvalue, "PublicKey"))
(void) warn_file_is_world_accessible(filename, NULL, unit, line);
r = unbase64mem_full(rvalue, strlen(rvalue), true, &key, &len);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to decode wireguard key provided by %s=, ignoring assignment: %m", lvalue);
return 0;
}
if (len != WG_KEY_LEN) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Wireguard key provided by %s= has invalid length (%zu bytes), ignoring assignment.",
lvalue, len);
return 0;
}
memcpy(ret, key, WG_KEY_LEN);
return 0;
}
int config_parse_wireguard_private_key(
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) {
Wireguard *w;
assert(data);
w = WIREGUARD(data);
assert(w);
return wireguard_decode_key_and_warn(rvalue, w->private_key, unit, filename, line, lvalue);
}
int config_parse_wireguard_private_key_file(
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) {
_cleanup_free_ char *path = NULL;
Wireguard *w;
assert(data);
w = WIREGUARD(data);
assert(w);
if (isempty(rvalue)) {
w->private_key_file = mfree(w->private_key_file);
return 0;
}
path = strdup(rvalue);
if (!path)
return log_oom();
if (path_simplify_and_warn(path, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue) < 0)
return 0;
return free_and_replace(w->private_key_file, path);
}
int config_parse_wireguard_peer_key(
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) {
_cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL;
Wireguard *w;
int r;
assert(data);
w = WIREGUARD(data);
assert(w);
r = wireguard_peer_new_static(w, filename, section_line, &peer);
if (r < 0)
return log_oom();
r = wireguard_decode_key_and_warn(rvalue,
streq(lvalue, "PublicKey") ? peer->public_key : peer->preshared_key,
unit, filename, line, lvalue);
if (r < 0)
return r;
TAKE_PTR(peer);
return 0;
}
int config_parse_wireguard_preshared_key_file(
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) {
_cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL;
_cleanup_free_ char *path = NULL;
Wireguard *w;
int r;
assert(data);
w = WIREGUARD(data);
assert(w);
r = wireguard_peer_new_static(w, filename, section_line, &peer);
if (r < 0)
return log_oom();
if (isempty(rvalue)) {
peer->preshared_key_file = mfree(peer->preshared_key_file);
TAKE_PTR(peer);
return 0;
}
path = strdup(rvalue);
if (!path)
return log_oom();
if (path_simplify_and_warn(path, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue) < 0)
return 0;
free_and_replace(peer->preshared_key_file, path);
TAKE_PTR(peer);
return 0;
}
int config_parse_wireguard_allowed_ips(
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) {
_cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL;
union in_addr_union addr;
unsigned char prefixlen;
int r, family;
Wireguard *w;
WireguardIPmask *ipmask;
assert(rvalue);
assert(data);
w = WIREGUARD(data);
assert(w);
r = wireguard_peer_new_static(w, filename, section_line, &peer);
if (r < 0)
return log_oom();
for (const char *p = rvalue;;) {
_cleanup_free_ char *word = NULL;
r = extract_first_word(&p, &word, "," WHITESPACE, 0);
if (r == 0)
break;
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to split allowed ips \"%s\" option: %m", rvalue);
break;
}
r = in_addr_prefix_from_string_auto(word, &family, &addr, &prefixlen);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Network address is invalid, ignoring assignment: %s", word);
continue;
}
ipmask = new(WireguardIPmask, 1);
if (!ipmask)
return log_oom();
*ipmask = (WireguardIPmask) {
.family = family,
.ip.in6 = addr.in6,
.cidr = prefixlen,
};
LIST_PREPEND(ipmasks, peer->ipmasks, ipmask);
}
TAKE_PTR(peer);
return 0;
}
int config_parse_wireguard_endpoint(
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) {
_cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL;
const char *begin, *end;
Wireguard *w;
size_t len;
int r;
assert(data);
assert(rvalue);
w = WIREGUARD(data);
assert(w);
if (rvalue[0] == '[') {
begin = &rvalue[1];
end = strchr(rvalue, ']');
if (!end) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Unable to find matching brace of endpoint, ignoring assignment: %s",
rvalue);
return 0;
}
len = end - begin;
++end;
if (*end != ':' || !*(end + 1)) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Unable to find port of endpoint, ignoring assignment: %s",
rvalue);
return 0;
}
++end;
} else {
begin = rvalue;
end = strrchr(rvalue, ':');
if (!end || !*(end + 1)) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Unable to find port of endpoint, ignoring assignment: %s",
rvalue);
return 0;
}
len = end - begin;
++end;
}
r = wireguard_peer_new_static(w, filename, section_line, &peer);
if (r < 0)
return log_oom();
r = free_and_strndup(&peer->endpoint_host, begin, len);
if (r < 0)
return log_oom();
r = free_and_strdup(&peer->endpoint_port, end);
if (r < 0)
return log_oom();
r = set_ensure_put(&w->peers_with_unresolved_endpoint, NULL, peer);
if (r < 0)
return log_oom();
TAKE_PTR(peer); /* The peer may already have been in the hash map, that is fine too. */
return 0;
}
int config_parse_wireguard_keepalive(
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) {
_cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL;
uint16_t keepalive = 0;
Wireguard *w;
int r;
assert(rvalue);
assert(data);
w = WIREGUARD(data);
assert(w);
r = wireguard_peer_new_static(w, filename, section_line, &peer);
if (r < 0)
return log_oom();
if (streq(rvalue, "off"))
keepalive = 0;
else {
r = safe_atou16(rvalue, &keepalive);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to parse \"%s\" as keepalive interval (range 065535), ignoring assignment: %m",
rvalue);
return 0;
}
}
peer->persistent_keepalive_interval = keepalive;
TAKE_PTR(peer);
return 0;
}
static void wireguard_init(NetDev *netdev) {
Wireguard *w;
assert(netdev);
w = WIREGUARD(netdev);
assert(w);
w->flags = WGDEVICE_F_REPLACE_PEERS;
}
static void wireguard_done(NetDev *netdev) {
Wireguard *w;
assert(netdev);
w = WIREGUARD(netdev);
assert(w);
sd_event_source_unref(w->resolve_retry_event_source);
explicit_bzero_safe(w->private_key, WG_KEY_LEN);
free(w->private_key_file);
hashmap_free_with_destructor(w->peers_by_section, wireguard_peer_free);
set_free(w->peers_with_unresolved_endpoint);
set_free(w->peers_with_failed_endpoint);
}
static int wireguard_read_key_file(const char *filename, uint8_t dest[static WG_KEY_LEN]) {
_cleanup_(erase_and_freep) char *key = NULL;
size_t key_len;
int r;
if (!filename)
return 0;
assert(dest);
(void) warn_file_is_world_accessible(filename, NULL, NULL, 0);
r = read_full_file_full(
AT_FDCWD, filename, UINT64_MAX, SIZE_MAX,
READ_FULL_FILE_SECURE | READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_WARN_WORLD_READABLE | READ_FULL_FILE_CONNECT_SOCKET,
fileio: beef up READ_FULL_FILE_CONNECT_SOCKET to allow setting sender socket name This beefs up the READ_FULL_FILE_CONNECT_SOCKET logic of read_full_file_full() a bit: when used a sender socket name may be specified. If specified as NULL behaviour is as before: the client socket name is picked by the kernel. But if specified as non-NULL the client can pick a socket name to use when connecting. This is useful to communicate a minimal amount of metainformation from client to server, outside of the transport payload. Specifically, these beefs up the service credential logic to pass an abstract AF_UNIX socket name as client socket name when connecting via READ_FULL_FILE_CONNECT_SOCKET, that includes the requesting unit name and the eventual credential name. This allows servers implementing the trivial credential socket logic to distinguish clients: via a simple getpeername() it can be determined which unit is requesting a credential, and which credential specifically. Example: with this patch in place, in a unit file "waldo.service" a configuration line like the following: LoadCredential=foo:/run/quux/creds.sock will result in a connection to the AF_UNIX socket /run/quux/creds.sock, originating from an abstract namespace AF_UNIX socket: @$RANDOM/unit/waldo.service/foo (The $RANDOM is replaced by some randomized string. This is included in the socket name order to avoid namespace squatting issues: the abstract socket namespace is open to unprivileged users after all, and care needs to be taken not to use guessable names) The services listening on the /run/quux/creds.sock socket may thus easily retrieve the name of the unit the credential is requested for plus the credential name, via a simpler getpeername(), discarding the random preifx and the /unit/ string. This logic uses "/" as separator between the fields, since both unit names and credential names appear in the file system, and thus are designed to use "/" as outer separators. Given that it's a good safe choice to use as separators here, too avoid any conflicts. This is a minimal patch only: the new logic is used only for the unit file credential logic. For other places where we use READ_FULL_FILE_CONNECT_SOCKET it is probably a good idea to use this scheme too, but this should be done carefully in later patches, since the socket names become API that way, and we should determine the right amount of info to pass over.
2020-11-02 12:07:51 +01:00
NULL, &key, &key_len);
if (r < 0)
return r;
if (key_len != WG_KEY_LEN)
return -EINVAL;
memcpy(dest, key, WG_KEY_LEN);
return 0;
}
static int wireguard_peer_verify(WireguardPeer *peer) {
NetDev *netdev = NETDEV(peer->wireguard);
int r;
if (section_is_invalid(peer->section))
return -EINVAL;
if (eqzero(peer->public_key))
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
"%s: WireGuardPeer section without PublicKey= configured. "
"Ignoring [WireGuardPeer] section from line %u.",
peer->section->filename, peer->section->line);
r = wireguard_read_key_file(peer->preshared_key_file, peer->preshared_key);
if (r < 0)
return log_netdev_error_errno(netdev, r,
"%s: Failed to read preshared key from '%s'. "
"Ignoring [WireGuardPeer] section from line %u.",
peer->section->filename, peer->preshared_key_file,
peer->section->line);
return 0;
}
static int wireguard_verify(NetDev *netdev, const char *filename) {
WireguardPeer *peer, *peer_next;
Wireguard *w;
int r;
assert(netdev);
w = WIREGUARD(netdev);
assert(w);
r = wireguard_read_key_file(w->private_key_file, w->private_key);
if (r < 0)
return log_netdev_error_errno(netdev, r,
"Failed to read private key from %s. Dropping network device %s.",
w->private_key_file, netdev->ifname);
if (eqzero(w->private_key))
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
"%s: Missing PrivateKey= or PrivateKeyFile=, "
"Dropping network device %s.",
filename, netdev->ifname);
LIST_FOREACH_SAFE(peers, peer, peer_next, w->peers)
if (wireguard_peer_verify(peer) < 0)
wireguard_peer_free(peer);
return 0;
}
const NetDevVTable wireguard_vtable = {
.object_size = sizeof(Wireguard),
.sections = NETDEV_COMMON_SECTIONS "WireGuard\0WireGuardPeer\0",
.post_create = netdev_wireguard_post_create,
.init = wireguard_init,
.done = wireguard_done,
.create_type = NETDEV_CREATE_INDEPENDENT,
.config_verify = wireguard_verify,
.generate_mac = true,
};