]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/network/netdev/wireguard.c
network/route: introduce ref/unref functions for Route object
[thirdparty/systemd.git] / src / network / netdev / wireguard.c
index 0e992f9ad1b183e6d3afb0ecd8f455ddc80e2963..95c9c8c330093d5e20f666b49d9184c9f937b5b8 100644 (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"
@@ -225,17 +227,12 @@ cancel:
 static int wireguard_set_interface(NetDev *netdev) {
         _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL;
         WireguardIPmask *mask_start = NULL;
-        WireguardPeer *peer_start;
         bool sent_once = false;
         uint32_t serial;
-        Wireguard *w;
+        Wireguard *w = WIREGUARD(netdev);
         int r;
 
-        assert(netdev);
-        w = WIREGUARD(netdev);
-        assert(w);
-
-        for (peer_start = w->peers; peer_start || !sent_once; ) {
+        for (WireguardPeer *peer_start = w->peers; peer_start || !sent_once; ) {
                 uint16_t i = 0;
 
                 message = sd_netlink_message_unref(message);
@@ -428,11 +425,7 @@ static int peer_resolve_endpoint(WireguardPeer *peer) {
 }
 
 static void wireguard_resolve_endpoints(NetDev *netdev) {
-        Wireguard *w;
-
-        assert(netdev);
-        w = WIREGUARD(netdev);
-        assert(w);
+        Wireguard *w = WIREGUARD(netdev);
 
         LIST_FOREACH(peers, peer, w->peers)
                 if (peer_resolve_endpoint(peer) == -ENOBUFS)
@@ -441,7 +434,6 @@ static void wireguard_resolve_endpoints(NetDev *netdev) {
 }
 
 static int netdev_wireguard_post_create(NetDev *netdev, Link *link) {
-        assert(netdev);
         assert(WIREGUARD(netdev));
 
         (void) wireguard_set_interface(netdev);
@@ -490,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;
 
@@ -503,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) {
@@ -537,11 +543,7 @@ int config_parse_wireguard_private_key(
                 void *data,
                 void *userdata) {
 
-        Wireguard *w;
-
-        assert(data);
-        w = WIREGUARD(data);
-        assert(w);
+        Wireguard *w = WIREGUARD(data);
 
         return wireguard_decode_key_and_warn(rvalue, w->private_key, unit, filename, line, lvalue);
 }
@@ -558,12 +560,8 @@ int config_parse_wireguard_private_key_file(
                 void *data,
                 void *userdata) {
 
+        Wireguard *w = WIREGUARD(data);
         _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);
@@ -592,14 +590,10 @@ int config_parse_wireguard_peer_key(
                 void *data,
                 void *userdata) {
 
+        Wireguard *w = WIREGUARD(data);
         _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();
@@ -626,15 +620,11 @@ int config_parse_wireguard_preshared_key_file(
                 void *data,
                 void *userdata) {
 
+        Wireguard *w = WIREGUARD(data);
         _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();
@@ -669,19 +659,15 @@ int config_parse_wireguard_allowed_ips(
                 void *data,
                 void *userdata) {
 
+        assert(rvalue);
+
+        Wireguard *w = WIREGUARD(data);
         _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();
@@ -751,26 +737,39 @@ int config_parse_wireguard_endpoint(
                 void *data,
                 void *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;
-        Wireguard *w;
-        int family, r;
+        int r;
 
         assert(filename);
         assert(rvalue);
-        assert(userdata);
-
-        w = WIREGUARD(userdata);
-        assert(w);
 
         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) {
@@ -794,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,
@@ -813,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,
@@ -846,17 +850,13 @@ int config_parse_wireguard_keepalive(
                 void *data,
                 void *userdata) {
 
+        assert(rvalue);
+
+        Wireguard *w = WIREGUARD(data);
         _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();
@@ -927,18 +927,14 @@ int config_parse_wireguard_peer_route_table(
                 void *data,
                 void *userdata) {
 
+        Wireguard *w = WIREGUARD(userdata);
         _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL;
-        NetDev *netdev = ASSERT_PTR(userdata);
-        Wireguard *w;
         int r;
 
         assert(filename);
         assert(lvalue);
         assert(rvalue);
-        assert(netdev->manager);
-
-        w = WIREGUARD(netdev);
-        assert(w);
+        assert(NETDEV(w)->manager);
 
         r = wireguard_peer_new_static(w, filename, section_line, &peer);
         if (r < 0)
@@ -957,7 +953,7 @@ int config_parse_wireguard_peer_route_table(
                 return 0;
         }
 
-        r = manager_get_route_table_from_string(netdev->manager, rvalue, &peer->route_table);
+        r = manager_get_route_table_from_string(NETDEV(w)->manager, rvalue, &peer->route_table);
         if (r < 0) {
                 log_syntax(unit, LOG_WARNING, filename, line, r,
                            "Failed to parse %s=, ignoring assignment: %s",
@@ -1051,21 +1047,13 @@ int config_parse_wireguard_peer_route_priority(
 }
 
 static void wireguard_init(NetDev *netdev) {
-        Wireguard *w;
-
-        assert(netdev);
-        w = WIREGUARD(netdev);
-        assert(w);
+        Wireguard *w = WIREGUARD(netdev);
 
         w->flags = WGDEVICE_F_REPLACE_PEERS;
 }
 
 static void wireguard_done(NetDev *netdev) {
-        Wireguard *w;
-
-        assert(netdev);
-        w = WIREGUARD(netdev);
-        assert(w);
+        Wireguard *w = WIREGUARD(netdev);
 
         explicit_bzero_safe(w->private_key, WG_KEY_LEN);
         free(w->private_key_file);
@@ -1086,8 +1074,12 @@ static int wireguard_read_key_file(const char *filename, uint8_t dest[static WG_
         assert(dest);
 
         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,
+                        AT_FDCWD, filename, UINT64_MAX, WG_KEY_LEN,
+                        READ_FULL_FILE_SECURE |
+                        READ_FULL_FILE_UNBASE64 |
+                        READ_FULL_FILE_WARN_WORLD_READABLE |
+                        READ_FULL_FILE_CONNECT_SOCKET |
+                        READ_FULL_FILE_FAIL_WHEN_LARGER,
                         NULL, &key, &key_len);
         if (r < 0)
                 return r;
@@ -1123,13 +1115,58 @@ static int wireguard_peer_verify(WireguardPeer *peer) {
         return 0;
 }
 
-static int wireguard_verify(NetDev *netdev, const char *filename) {
-        Wireguard *w;
+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(netdev);
-        w = WIREGUARD(netdev);
-        assert(w);
+        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;
 
         r = wireguard_read_key_file(w->private_key_file, w->private_key);
         if (r < 0)
@@ -1137,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) {
@@ -1152,26 +1190,39 @@ static int wireguard_verify(NetDev *netdev, const char *filename) {
                         continue;
 
                 LIST_FOREACH(ipmasks, ipmask, peer->ipmasks) {
-                        _cleanup_(route_freep) Route *route = NULL;
+                        _cleanup_(route_unrefp) Route *route = NULL;
 
                         r = route_new(&route);
                         if (r < 0)
                                 return log_oom();
 
+                        /* For route_section_verify() below. */
+                        r = config_section_new(peer->section->filename, peer->section->line, &route->section);
+                        if (r < 0)
+                                return log_oom();
+
+                        route->source = NETWORK_CONFIG_SOURCE_STATIC;
                         route->family = ipmask->family;
                         route->dst = ipmask->ip;
                         route->dst_prefixlen = ipmask->cidr;
-                        route->scope = RT_SCOPE_UNIVERSE;
                         route->protocol = RTPROT_STATIC;
+                        route->protocol_set = true;
                         route->table = peer->route_table_set ? peer->route_table : w->route_table;
+                        route->table_set = true;
                         route->priority = peer->route_priority_set ? peer->route_priority : w->route_priority;
-                        if (route->priority == 0 && route->family == AF_INET6)
-                                route->priority = IP6_RT_PRIO_USER;
-                        route->source = NETWORK_CONFIG_SOURCE_STATIC;
+                        route->priority_set = true;
 
-                        r = set_ensure_consume(&w->routes, &route_hash_ops, TAKE_PTR(route));
+                        if (route_section_verify(route) < 0)
+                                continue;
+
+                        r = set_ensure_put(&w->routes, &route_hash_ops, route);
                         if (r < 0)
                                 return log_oom();
+                        if (r == 0)
+                                continue;
+
+                        route->wireguard = w;
+                        TAKE_PTR(route);
                 }
         }