nixos/yggdrasil: add service
This commit is contained in:
parent
3cc9bcb338
commit
b8cb8c39d6
4 changed files with 306 additions and 0 deletions
|
@ -730,6 +730,7 @@
|
|||
./services/networking/xinetd.nix
|
||||
./services/networking/xl2tpd.nix
|
||||
./services/networking/xrdp.nix
|
||||
./services/networking/yggdrasil.nix
|
||||
./services/networking/zerobin.nix
|
||||
./services/networking/zeronet.nix
|
||||
./services/networking/zerotierone.nix
|
||||
|
|
181
nixos/modules/services/networking/yggdrasil.nix
Normal file
181
nixos/modules/services/networking/yggdrasil.nix
Normal file
|
@ -0,0 +1,181 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.yggdrasil;
|
||||
configProvided = (cfg.config != {});
|
||||
configAsFile = (if configProvided then
|
||||
toString (pkgs.writeTextFile {
|
||||
name = "yggdrasil-conf";
|
||||
text = builtins.toJSON cfg.config;
|
||||
})
|
||||
else null);
|
||||
configFileProvided = (cfg.configFile != null);
|
||||
generateConfig = (
|
||||
if configProvided && configFileProvided then
|
||||
"${pkgs.jq}/bin/jq -s add /run/yggdrasil/configFile.json ${configAsFile}"
|
||||
else if configProvided then
|
||||
"cat ${configAsFile}"
|
||||
else if configFileProvided then
|
||||
"cat /run/yggdrasil/configFile.json"
|
||||
else
|
||||
"${cfg.package}/bin/yggdrasil -genconf"
|
||||
);
|
||||
|
||||
in {
|
||||
options = with types; {
|
||||
services.yggdrasil = {
|
||||
enable = mkEnableOption "the yggdrasil system service";
|
||||
|
||||
configFile = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
example = "/run/keys/yggdrasil.conf";
|
||||
description = ''
|
||||
A file which contains JSON configuration for yggdrasil.
|
||||
|
||||
You do not have to supply a complete configuration, as
|
||||
yggdrasil will use default values for anything which is
|
||||
omitted. If the encryption and signing keys are omitted,
|
||||
yggdrasil will generate new ones each time the service is
|
||||
started, resulting in a random IPv6 address on the yggdrasil
|
||||
network each time.
|
||||
|
||||
If both this option and <option>config</option> are
|
||||
supplied, they will be combined, with values from
|
||||
<option>config</option> taking precedence.
|
||||
|
||||
You can use the command <code>nix-shell -p yggdrasil --run
|
||||
"yggdrasil -genconf -json"</code> to generate a default
|
||||
JSON configuration.
|
||||
'';
|
||||
};
|
||||
|
||||
config = mkOption {
|
||||
type = attrs;
|
||||
default = {};
|
||||
example = {
|
||||
Peers = [
|
||||
"tcp://aa.bb.cc.dd:eeeee"
|
||||
"tcp://[aaaa:bbbb:cccc:dddd::eeee]:fffff"
|
||||
];
|
||||
Listen = [
|
||||
"tcp://0.0.0.0:xxxxx"
|
||||
];
|
||||
};
|
||||
description = ''
|
||||
Configuration for yggdrasil, as a Nix attribute set.
|
||||
|
||||
Warning: this is stored in the WORLD-READABLE Nix store!
|
||||
Therefore, it is not appropriate for private keys. If you
|
||||
do not specify the keys, yggdrasil will generate a new set
|
||||
each time the service is started, creating a random IPv6
|
||||
address on the yggdrasil network each time.
|
||||
|
||||
If you wish to specify the keys, use
|
||||
<option>configFile</option>. If both
|
||||
<option>configFile</option> and <option>config</option> are
|
||||
supplied, they will be combined, with values from
|
||||
<option>config</option> taking precedence.
|
||||
|
||||
You can use the command <code>nix-shell -p yggdrasil --run
|
||||
"yggdrasil -genconf"</code> to generate default
|
||||
configuration values with documentation.
|
||||
'';
|
||||
};
|
||||
|
||||
openMulticastPort = mkOption {
|
||||
type = bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to open the UDP port used for multicast peer
|
||||
discovery. The NixOS firewall blocks link-local
|
||||
communication, so in order to make local peering work you
|
||||
will also need to set <code>LinkLocalTCPPort</code> in your
|
||||
yggdrasil configuration (<option>config</option> or
|
||||
<option>configFile</option>) to a port number other than 0,
|
||||
and then add that port to
|
||||
<option>networking.firewall.allowedTCPPorts</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
denyDhcpcdInterfaces = mkOption {
|
||||
type = listOf str;
|
||||
default = [];
|
||||
example = [ "tap*" ];
|
||||
description = ''
|
||||
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.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = package;
|
||||
default = pkgs.yggdrasil;
|
||||
defaultText = "pkgs.yggdrasil";
|
||||
description = "Yggdrasil package to use.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{ assertion = config.networking.enableIPv6;
|
||||
message = "networking.enableIPv6 must be true for yggdrasil to work";
|
||||
}
|
||||
];
|
||||
|
||||
environment.etc."yggdrasil.conf" = {
|
||||
enable = true;
|
||||
mode = "symlink";
|
||||
source = "/run/yggdrasil/yggdrasil.conf";
|
||||
};
|
||||
|
||||
systemd.services.yggdrasil = {
|
||||
description = "Yggdrasil Network Service";
|
||||
path = [ cfg.package ] ++ optional (configProvided && configFileProvided) pkgs.jq;
|
||||
bindsTo = [ "network-online.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
preStart = ''
|
||||
${generateConfig} | yggdrasil -normaliseconf -useconf > /run/yggdrasil/yggdrasil.conf
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/yggdrasil -useconffile /etc/yggdrasil.conf";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
Restart = "always";
|
||||
|
||||
RuntimeDirectory = "yggdrasil";
|
||||
RuntimeDirectoryMode = "0700";
|
||||
BindReadOnlyPaths = mkIf configFileProvided
|
||||
[ "${cfg.configFile}:/run/yggdrasil/configFile.json" ];
|
||||
|
||||
DynamicUser = true;
|
||||
AmbientCapabilities = "CAP_NET_ADMIN";
|
||||
CapabilityBoundingSet = "CAP_NET_ADMIN";
|
||||
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 = "~@clock @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @resources";
|
||||
};
|
||||
};
|
||||
|
||||
networking.dhcpcd.denyInterfaces = cfg.denyDhcpcdInterfaces;
|
||||
networking.firewall.allowedUDPPorts = mkIf cfg.openMulticastPort [ 9001 ];
|
||||
|
||||
# Make yggdrasilctl available on the command line.
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
};
|
||||
meta.maintainers = with lib.maintainers; [ gazally ];
|
||||
}
|
|
@ -293,5 +293,6 @@ in
|
|||
xrdp = handleTest ./xrdp.nix {};
|
||||
xss-lock = handleTest ./xss-lock.nix {};
|
||||
yabar = handleTest ./yabar.nix {};
|
||||
yggdrasil = handleTest ./yggdrasil.nix {};
|
||||
zookeeper = handleTest ./zookeeper.nix {};
|
||||
}
|
||||
|
|
123
nixos/tests/yggdrasil.nix
Normal file
123
nixos/tests/yggdrasil.nix
Normal file
|
@ -0,0 +1,123 @@
|
|||
let
|
||||
aliceIp6 = "200:3b91:b2d8:e708:fbf3:f06:fdd5:90d0";
|
||||
aliceKeys = {
|
||||
EncryptionPublicKey = "13e23986fe76bc3966b42453f479bc563348b7ff76633b7efcb76e185ec7652f";
|
||||
EncryptionPrivateKey = "9f86947b15e86f9badac095517a1982e39a2db37ca726357f95987b898d82208";
|
||||
SigningPublicKey = "e2c43349083bc1e998e4ec4535b4c6a8f44ca9a5a8e07336561267253b2be5f4";
|
||||
SigningPrivateKey = "fe3add8da35316c05f6d90d3ca79bd2801e6ccab6d37e5339fef4152589398abe2c43349083bc1e998e4ec4535b4c6a8f44ca9a5a8e07336561267253b2be5f4";
|
||||
};
|
||||
bobIp6 = "201:ebbd:bde9:f138:c302:4afa:1fb6:a19a";
|
||||
bobConfig = {
|
||||
InterfacePeers = {
|
||||
eth1 = [ "tcp://192.168.1.200:12345" ];
|
||||
};
|
||||
MulticastInterfaces = [ "eth1" ];
|
||||
LinkLocalTCPPort = 54321;
|
||||
EncryptionPublicKey = "c99d6830111e12d1b004c52fe9e5a2eef0f6aefca167aca14589a370b7373279";
|
||||
EncryptionPrivateKey = "2e698a53d3fdce5962d2ff37de0fe77742a5c8b56cd8259f5da6aa792f6e8ba3";
|
||||
SigningPublicKey = "de111da0ec781e45bf6c63ecb45a78c24d7d4655abfaeea83b26c36eb5c0fd5b";
|
||||
SigningPrivateKey = "2a6c21550f3fca0331df50668ffab66b6dce8237bcd5728e571e8033b363e247de111da0ec781e45bf6c63ecb45a78c24d7d4655abfaeea83b26c36eb5c0fd5b";
|
||||
};
|
||||
|
||||
in import ./make-test.nix ({ pkgs, ...} : {
|
||||
name = "yggdrasil";
|
||||
meta = with pkgs.stdenv.lib.maintainers; {
|
||||
maintainers = [ gazally ];
|
||||
};
|
||||
|
||||
nodes = rec {
|
||||
# Alice is listening for peerings on a specified port,
|
||||
# but has multicast peering disabled. Alice has part of her
|
||||
# yggdrasil config in Nix and part of it in a file.
|
||||
alice =
|
||||
{ ... }:
|
||||
{
|
||||
networking = {
|
||||
interfaces.eth1.ipv4.addresses = [{
|
||||
address = "192.168.1.200";
|
||||
prefixLength = 24;
|
||||
}];
|
||||
firewall.allowedTCPPorts = [ 80 12345 ];
|
||||
};
|
||||
services.httpd.enable = true;
|
||||
services.httpd.adminAddr = "foo@example.org";
|
||||
|
||||
services.yggdrasil = {
|
||||
enable = true;
|
||||
config = {
|
||||
Listen = ["tcp://0.0.0.0:12345"];
|
||||
MulticastInterfaces = [ ];
|
||||
};
|
||||
configFile = toString (pkgs.writeTextFile {
|
||||
name = "yggdrasil-alice-conf";
|
||||
text = builtins.toJSON aliceKeys;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
# Bob is set up to peer with Alice, and also to do local multicast
|
||||
# peering. Bob's yggdrasil config is in a file.
|
||||
bob =
|
||||
{ ... }:
|
||||
{
|
||||
networking.firewall.allowedTCPPorts = [ 54321 ];
|
||||
services.yggdrasil = {
|
||||
enable = true;
|
||||
openMulticastPort = true;
|
||||
configFile = toString (pkgs.writeTextFile {
|
||||
name = "yggdrasil-bob-conf";
|
||||
text = builtins.toJSON bobConfig;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
# Carol only does local peering. Carol's yggdrasil config is all Nix.
|
||||
carol =
|
||||
{ ... }:
|
||||
{
|
||||
networking.firewall.allowedTCPPorts = [ 43210 ];
|
||||
services.yggdrasil = {
|
||||
enable = true;
|
||||
denyDhcpcdInterfaces = [ "ygg0" ];
|
||||
config = {
|
||||
IfTAPMode = true;
|
||||
IFName = "ygg0";
|
||||
MulticastInterfaces = [ "eth1" ];
|
||||
LinkLocalTCPPort = 43210;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript =
|
||||
''
|
||||
# Give Alice a head start so she is ready when Bob calls.
|
||||
$alice->start;
|
||||
$alice->waitForUnit("yggdrasil.service");
|
||||
|
||||
$bob->start;
|
||||
$carol->start;
|
||||
$bob->waitForUnit("yggdrasil.service");
|
||||
$carol->waitForUnit("yggdrasil.service");
|
||||
|
||||
$carol->waitUntilSucceeds("[ `ip -o -6 addr show dev ygg0 scope global | grep -v tentative | wc -l` -ge 1 ]");
|
||||
my $carolIp6 = (split /[ \/]+/, $carol->succeed("ip -o -6 addr show dev ygg0 scope global"))[3];
|
||||
|
||||
# If Alice can talk to Carol, then Bob's outbound peering and Carol's
|
||||
# local peering have succeeded and everybody is connected.
|
||||
$alice->waitUntilSucceeds("ping -c 1 $carolIp6");
|
||||
$alice->succeed("ping -c 1 ${bobIp6}");
|
||||
|
||||
$bob->succeed("ping -c 1 ${aliceIp6}");
|
||||
$bob->succeed("ping -c 1 $carolIp6");
|
||||
|
||||
$carol->succeed("ping -c 1 ${aliceIp6}");
|
||||
$carol->succeed("ping -c 1 ${bobIp6}");
|
||||
|
||||
$carol->fail("journalctl -u dhcpcd | grep ygg0");
|
||||
|
||||
$alice->waitForUnit("httpd.service");
|
||||
$carol->succeed("curl --fail -g http://[${aliceIp6}]");
|
||||
|
||||
'';
|
||||
})
|
Loading…
Reference in a new issue