2019-06-27 15:56:10 +00:00
|
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
|
with lib;
|
|
|
|
|
let
|
2020-05-03 10:30:31 +00:00
|
|
|
|
keysPath = "/var/lib/yggdrasil/keys.json";
|
|
|
|
|
|
2019-06-27 15:56:10 +00:00
|
|
|
|
cfg = config.services.yggdrasil;
|
2022-08-07 18:21:22 +00:00
|
|
|
|
settingsProvided = cfg.settings != { };
|
2020-05-03 10:30:31 +00:00
|
|
|
|
configFileProvided = cfg.configFile != null;
|
2019-06-27 15:56:10 +00:00
|
|
|
|
|
2022-08-07 18:21:22 +00:00
|
|
|
|
format = pkgs.formats.json { };
|
2023-02-20 21:02:59 +00:00
|
|
|
|
in
|
|
|
|
|
{
|
2022-08-07 18:21:22 +00:00
|
|
|
|
imports = [
|
|
|
|
|
(mkRenamedOptionModule
|
|
|
|
|
[ "services" "yggdrasil" "config" ]
|
|
|
|
|
[ "services" "yggdrasil" "settings" ])
|
|
|
|
|
];
|
|
|
|
|
|
2019-06-27 15:56:10 +00:00
|
|
|
|
options = with types; {
|
|
|
|
|
services.yggdrasil = {
|
2024-04-13 12:54:15 +00:00
|
|
|
|
enable = mkEnableOption "the yggdrasil system service";
|
2019-06-27 15:56:10 +00:00
|
|
|
|
|
2022-08-07 18:21:22 +00:00
|
|
|
|
settings = mkOption {
|
|
|
|
|
type = format.type;
|
2023-02-20 21:02:59 +00:00
|
|
|
|
default = { };
|
2019-06-27 15:56:10 +00:00
|
|
|
|
example = {
|
|
|
|
|
Peers = [
|
|
|
|
|
"tcp://aa.bb.cc.dd:eeeee"
|
|
|
|
|
"tcp://[aaaa:bbbb:cccc:dddd::eeee]:fffff"
|
|
|
|
|
];
|
|
|
|
|
Listen = [
|
|
|
|
|
"tcp://0.0.0.0:xxxxx"
|
|
|
|
|
];
|
|
|
|
|
};
|
2024-04-13 12:54:15 +00:00
|
|
|
|
description = ''
|
2019-06-27 15:56:10 +00:00
|
|
|
|
Configuration for yggdrasil, as a Nix attribute set.
|
|
|
|
|
|
|
|
|
|
Warning: this is stored in the WORLD-READABLE Nix store!
|
2020-05-03 10:30:31 +00:00
|
|
|
|
Therefore, it is not appropriate for private keys. If you
|
|
|
|
|
wish to specify the keys, use {option}`configFile`.
|
|
|
|
|
|
|
|
|
|
If the {option}`persistentKeys` is enabled then the
|
|
|
|
|
keys that are generated during activation will override
|
2022-09-24 17:34:59 +00:00
|
|
|
|
those in {option}`settings` or
|
2020-05-03 10:30:31 +00:00
|
|
|
|
{option}`configFile`.
|
|
|
|
|
|
|
|
|
|
If no keys are specified then ephemeral keys are generated
|
|
|
|
|
and the Yggdrasil interface will have a random IPv6 address
|
2023-01-31 08:58:50 +00:00
|
|
|
|
each time the service is started. This is the default.
|
2019-06-27 15:56:10 +00:00
|
|
|
|
|
2022-09-24 17:34:59 +00:00
|
|
|
|
If both {option}`configFile` and {option}`settings`
|
2020-05-03 10:30:31 +00:00
|
|
|
|
are supplied, they will be combined, with values from
|
|
|
|
|
{option}`configFile` taking precedence.
|
2019-06-27 15:56:10 +00:00
|
|
|
|
|
2022-08-13 09:35:46 +00:00
|
|
|
|
You can use the command `nix-shell -p yggdrasil --run "yggdrasil -genconf"`
|
|
|
|
|
to generate default configuration values with documentation.
|
2019-06-27 15:56:10 +00:00
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2020-05-03 10:30:31 +00:00
|
|
|
|
configFile = mkOption {
|
|
|
|
|
type = nullOr path;
|
|
|
|
|
default = null;
|
|
|
|
|
example = "/run/keys/yggdrasil.conf";
|
2024-04-13 12:54:15 +00:00
|
|
|
|
description = ''
|
2023-02-20 22:08:07 +00:00
|
|
|
|
A file which contains JSON or HJSON configuration for yggdrasil. See
|
|
|
|
|
the {option}`settings` option for more information.
|
2023-01-31 01:14:50 +00:00
|
|
|
|
|
|
|
|
|
Note: This file must not be larger than 1 MB because it is passed to
|
|
|
|
|
the yggdrasil process via systemd‘s LoadCredential mechanism. For
|
|
|
|
|
details, see <https://systemd.io/CREDENTIALS/> and `man 5
|
|
|
|
|
systemd.exec`.
|
2020-05-03 10:30:31 +00:00
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2022-07-02 16:20:27 +00:00
|
|
|
|
group = mkOption {
|
|
|
|
|
type = types.nullOr types.str;
|
|
|
|
|
default = null;
|
|
|
|
|
example = "wheel";
|
2024-04-13 12:54:15 +00:00
|
|
|
|
description = "Group to grant access to the Yggdrasil control socket. If `null`, only root can access the socket.";
|
2022-07-02 16:20:27 +00:00
|
|
|
|
};
|
|
|
|
|
|
2019-06-27 15:56:10 +00:00
|
|
|
|
openMulticastPort = mkOption {
|
|
|
|
|
type = bool;
|
|
|
|
|
default = false;
|
2024-04-13 12:54:15 +00:00
|
|
|
|
description = ''
|
2023-01-31 08:58:50 +00:00
|
|
|
|
Whether to open the UDP port used for multicast peer discovery. The
|
|
|
|
|
NixOS firewall blocks link-local communication, so in order to make
|
|
|
|
|
incoming local peering work you will also need to configure
|
|
|
|
|
`MulticastInterfaces` in your Yggdrasil configuration
|
|
|
|
|
({option}`settings` or {option}`configFile`). You will then have to
|
|
|
|
|
add the ports that you configure there to your firewall configuration
|
|
|
|
|
({option}`networking.firewall.allowedTCPPorts` or
|
|
|
|
|
{option}`networking.firewall.interfaces.<name>.allowedTCPPorts`).
|
2019-06-27 15:56:10 +00:00
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
denyDhcpcdInterfaces = mkOption {
|
|
|
|
|
type = listOf str;
|
2023-02-20 21:02:59 +00:00
|
|
|
|
default = [ ];
|
2019-06-27 15:56:10 +00:00
|
|
|
|
example = [ "tap*" ];
|
2024-04-13 12:54:15 +00:00
|
|
|
|
description = ''
|
2019-06-27 15:56:10 +00:00
|
|
|
|
Disable the DHCP client for any interface whose name matches
|
|
|
|
|
any of the shell glob patterns in this list. Use this
|
|
|
|
|
option to prevent the DHCP client from broadcasting requests
|
|
|
|
|
on the yggdrasil network. It is only necessary to do so
|
|
|
|
|
when yggdrasil is running in TAP mode, because TUN
|
|
|
|
|
interfaces do not support broadcasting.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2023-11-27 00:19:27 +00:00
|
|
|
|
package = mkPackageOption pkgs "yggdrasil" { };
|
2020-05-03 10:30:31 +00:00
|
|
|
|
|
2024-04-13 12:54:15 +00:00
|
|
|
|
persistentKeys = mkEnableOption ''
|
2023-10-18 20:59:26 +00:00
|
|
|
|
persistent keys. If enabled then keys will be generated once and Yggdrasil
|
2020-05-03 10:30:31 +00:00
|
|
|
|
will retain the same IPv6 address when the service is
|
2023-10-18 20:59:26 +00:00
|
|
|
|
restarted. Keys are stored at ${keysPath}
|
2024-04-13 12:54:15 +00:00
|
|
|
|
'';
|
2020-05-03 10:30:31 +00:00
|
|
|
|
|
2023-09-26 08:04:33 +00:00
|
|
|
|
extraArgs = mkOption {
|
|
|
|
|
type = listOf str;
|
|
|
|
|
default = [ ];
|
|
|
|
|
example = [ "-loglevel" "info" ];
|
2024-04-13 12:54:15 +00:00
|
|
|
|
description = "Extra command line arguments.";
|
2023-09-26 08:04:33 +00:00
|
|
|
|
};
|
|
|
|
|
|
2019-06-27 15:56:10 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2023-02-20 21:02:59 +00:00
|
|
|
|
config = mkIf cfg.enable (
|
2023-02-20 22:08:07 +00:00
|
|
|
|
let
|
|
|
|
|
binYggdrasil = "${cfg.package}/bin/yggdrasil";
|
|
|
|
|
binHjson = "${pkgs.hjson-go}/bin/hjson-cli";
|
|
|
|
|
in
|
|
|
|
|
{
|
2023-02-20 21:02:59 +00:00
|
|
|
|
assertions = [{
|
|
|
|
|
assertion = config.networking.enableIPv6;
|
|
|
|
|
message = "networking.enableIPv6 must be true for yggdrasil to work";
|
|
|
|
|
}];
|
|
|
|
|
|
2023-10-28 22:50:27 +00:00
|
|
|
|
# This needs to be a separate service. The yggdrasil service fails if
|
|
|
|
|
# this is put into its preStart.
|
|
|
|
|
systemd.services.yggdrasil-persistent-keys = lib.mkIf cfg.persistentKeys {
|
|
|
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
|
before = [ "yggdrasil.service" ];
|
|
|
|
|
serviceConfig.Type = "oneshot";
|
|
|
|
|
serviceConfig.RemainAfterExit = true;
|
|
|
|
|
script = ''
|
|
|
|
|
if [ ! -e ${keysPath} ]
|
|
|
|
|
then
|
|
|
|
|
mkdir --mode=700 -p ${builtins.dirOf keysPath}
|
|
|
|
|
${binYggdrasil} -genconf -json \
|
|
|
|
|
| ${pkgs.jq}/bin/jq \
|
|
|
|
|
'to_entries|map(select(.key|endswith("Key")))|from_entries' \
|
|
|
|
|
> ${keysPath}
|
|
|
|
|
fi
|
|
|
|
|
'';
|
|
|
|
|
};
|
2023-02-20 21:02:59 +00:00
|
|
|
|
|
|
|
|
|
systemd.services.yggdrasil = {
|
|
|
|
|
description = "Yggdrasil Network Service";
|
|
|
|
|
after = [ "network-pre.target" ];
|
|
|
|
|
wants = [ "network.target" ];
|
|
|
|
|
before = [ "network.target" ];
|
|
|
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
|
|
2023-01-31 01:14:50 +00:00
|
|
|
|
# This script first prepares the config file, then it starts Yggdrasil.
|
|
|
|
|
# The preparation could also be done in ExecStartPre/preStart but only
|
|
|
|
|
# systemd versions >= v252 support reading credentials in ExecStartPre. As
|
|
|
|
|
# of February 2023, systemd v252 is not yet in the stable branch of NixOS.
|
|
|
|
|
#
|
|
|
|
|
# This could be changed in the future once systemd version v252 has
|
|
|
|
|
# reached NixOS but it does not have to be. Config file preparation is
|
|
|
|
|
# fast enough, it does not need elevated privileges, and `set -euo
|
|
|
|
|
# pipefail` should make sure that the service is not started if the
|
|
|
|
|
# preparation fails. Therefore, it is not necessary to move the
|
|
|
|
|
# preparation to ExecStartPre.
|
|
|
|
|
script = ''
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
# prepare config file
|
|
|
|
|
${(if settingsProvided || configFileProvided || cfg.persistentKeys then
|
2023-02-20 21:02:59 +00:00
|
|
|
|
"echo "
|
|
|
|
|
|
|
|
|
|
+ (lib.optionalString settingsProvided
|
|
|
|
|
"'${builtins.toJSON cfg.settings}'")
|
2023-01-31 01:14:50 +00:00
|
|
|
|
+ (lib.optionalString configFileProvided
|
2023-02-20 22:08:07 +00:00
|
|
|
|
"$(${binHjson} -c \"$CREDENTIALS_DIRECTORY/yggdrasil.conf\")")
|
2023-02-20 21:02:59 +00:00
|
|
|
|
+ (lib.optionalString cfg.persistentKeys "$(cat ${keysPath})")
|
|
|
|
|
+ " | ${pkgs.jq}/bin/jq -s add | ${binYggdrasil} -normaliseconf -useconf"
|
|
|
|
|
else
|
2023-01-31 01:14:50 +00:00
|
|
|
|
"${binYggdrasil} -genconf") + " > /run/yggdrasil/yggdrasil.conf"}
|
|
|
|
|
|
|
|
|
|
# start yggdrasil
|
2023-09-26 08:04:33 +00:00
|
|
|
|
${binYggdrasil} -useconffile /run/yggdrasil/yggdrasil.conf ${lib.strings.escapeShellArgs cfg.extraArgs}
|
2023-01-31 01:14:50 +00:00
|
|
|
|
'';
|
2023-02-20 21:02:59 +00:00
|
|
|
|
|
|
|
|
|
serviceConfig = {
|
|
|
|
|
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
|
|
|
|
Restart = "always";
|
|
|
|
|
|
|
|
|
|
DynamicUser = true;
|
|
|
|
|
StateDirectory = "yggdrasil";
|
|
|
|
|
RuntimeDirectory = "yggdrasil";
|
|
|
|
|
RuntimeDirectoryMode = "0750";
|
2023-01-31 01:14:50 +00:00
|
|
|
|
BindReadOnlyPaths = lib.optional cfg.persistentKeys keysPath;
|
|
|
|
|
LoadCredential =
|
|
|
|
|
mkIf configFileProvided "yggdrasil.conf:${cfg.configFile}";
|
2023-02-20 21:02:59 +00:00
|
|
|
|
|
|
|
|
|
AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
|
|
|
|
|
CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
|
|
|
|
|
MemoryDenyWriteExecute = true;
|
|
|
|
|
ProtectControlGroups = true;
|
|
|
|
|
ProtectHome = "tmpfs";
|
|
|
|
|
ProtectKernelModules = true;
|
|
|
|
|
ProtectKernelTunables = true;
|
|
|
|
|
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
|
|
|
|
|
RestrictNamespaces = true;
|
|
|
|
|
RestrictRealtime = true;
|
|
|
|
|
SystemCallArchitectures = "native";
|
|
|
|
|
SystemCallFilter = [ "@system-service" "~@privileged @keyring" ];
|
|
|
|
|
} // (if (cfg.group != null) then {
|
|
|
|
|
Group = cfg.group;
|
|
|
|
|
} else { });
|
|
|
|
|
};
|
2019-06-27 15:56:10 +00:00
|
|
|
|
|
2023-02-20 21:02:59 +00:00
|
|
|
|
networking.dhcpcd.denyInterfaces = cfg.denyDhcpcdInterfaces;
|
|
|
|
|
networking.firewall.allowedUDPPorts = mkIf cfg.openMulticastPort [ 9001 ];
|
2019-06-27 15:56:10 +00:00
|
|
|
|
|
2023-02-20 21:02:59 +00:00
|
|
|
|
# Make yggdrasilctl available on the command line.
|
|
|
|
|
environment.systemPackages = [ cfg.package ];
|
|
|
|
|
}
|
|
|
|
|
);
|
2020-05-30 10:05:48 +00:00
|
|
|
|
meta = {
|
2023-01-24 23:33:40 +00:00
|
|
|
|
doc = ./yggdrasil.md;
|
2020-05-30 10:05:48 +00:00
|
|
|
|
maintainers = with lib.maintainers; [ gazally ehmry ];
|
|
|
|
|
};
|
2019-06-27 15:56:10 +00:00
|
|
|
|
}
|