Skip to content

Commit

Permalink
Merge pull request #45 from ngi-nix/algae-rosenpass
Browse files Browse the repository at this point in the history
rosenpass: Add package, module and tests
  • Loading branch information
andres-nav authored Sep 15, 2023
2 parents 0d1d22b + db76f58 commit e393afa
Show file tree
Hide file tree
Showing 17 changed files with 19,558 additions and 1 deletion.
2 changes: 2 additions & 0 deletions all-packages.nix
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
withRedis = true;
withTest = true;
};
rosenpass = callPackage ./pkgs/rosenpass {};
rosenpass-tools = callPackage ./pkgs/rosenpass-tools {};
};

nixpkgs-candidates = {
Expand Down
3 changes: 2 additions & 1 deletion modules/all-modules.nix
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# LiberaForms is intentionally disabled.
# Refer to <https://github.com/ngi-nix/ngipkgs/issues/40>.
#liberaforms = import ./liberaforms.nix;
pretalx = import ./pretalx.nix;
flarum = import ./flarum.nix;
pretalx = import ./pretalx.nix;
rosenpass = import ./rosenpass.nix;
}
178 changes: 178 additions & 0 deletions modules/rosenpass.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
{
config,
lib,
options,
pkgs,
...
}:
with builtins;
with lib; let
cfg = config.services.rosenpass;
opt = options.services.rosenpass;
in {
options.services.rosenpass = with types; {
enable = mkEnableOption "Whether to enable the Rosenpass service to provide post-quantum secure key exchange for WireGuard.";

package = mkPackageOption pkgs "rosenpass" {};

user = mkOption {
type = str;
default = "rosenpass";
description = "User to run Rosenpass as.";
};

group = mkOption {
type = str;
default = "rosenpass";
description = "Primary group of the user running Rosenpass.";
};

publicKeyFile = mkOption {
type = path;
description = "Path to a file containing the public key of the local Rosenpass peer. Generate this by running `rosenpass gen-keys`.";
};

secretKeyFile = mkOption {
type = path;
description = "Path to a file containing the secret key of the local Rosenpass peer. Generate this by running `rosenpass gen-keys`.";
};

defaultDevice = mkOption {
type = nullOr str;
description = "Name of the network interface to use for all peers by default.";
example = "wg0";
};

listen = mkOption {
type = listOf str;
description = "List of local endpoints to listen for connections.";
default = [];
example = literalExpression "[ \"0.0.0.0:10000\" ]";
};

verbosity = mkOption {
type = enum ["Verbose" "Quiet"];
default = "Quiet";
description = "Verbosity of output produced by the service.";
};

peers = let
peer = submodule {
options = {
publicKeyFile = mkOption {
type = path;
description = "Path to a file containing the public key of the remote Rosenpass peer.";
};

endpoint = mkOption {
type = nullOr str;
default = null;
description = "Endpoint of the remote Rosenpass peer.";
};

device = mkOption {
type = str;
default = cfg.defaultDevice;
defaultText = literalExpression "config.${opt.defaultDevice}";
description = "Name of the local WireGuard interface to use for this peer.";
};

wireguard = mkOption {
type = submodule {
options = {
publicKey = mkOption {
type = str;
description = "WireGuard public key corresponding to the remote Rosenpass peer.";
};
};
};
description = "WireGuard configuration for this peer.";
};
};
};
in
mkOption {
type = listOf peer;
description = "List of peers to exchange keys with.";
default = [];
};

extraConfig = mkOption {
type = attrs;
description = ''
Extra configuration to be merged with the generated Rosenpass configuration file.
'';
default = {};
};
};

config = mkIf cfg.enable {
warnings = let
netdevsList = attrValues config.systemd.network.netdevs;
publicKeyInNetdevs = peer: any (netdev: any (publicKeyInWireguardPeers peer) netdev.wireguardPeers) netdevsList;
publicKeyInWireguardPeers = peer: x: x.wireguardPeerConfig ? PublicKey && x.wireguardPeerConfig.PublicKey == peer.wireguard.publicKey;

# NOTE: In the message below, we tried to refer to
# options.systemd.network.netdevs."<name>".wireguardPeers.*.PublicKey
# directly, but don't know how to traverse "<name>" and * in this path.
warningMsg = peer: "It appears that you have configured a Rosenpass peer with the Wireguard public key '${peer.wireguard.publicKey}' but there is no corresponding Wireguard peer configuration in any of `${options.systemd.network.netdevs}.\"<name>\".wireguardPeers.*.PublicKey`. While this may work as expected, such a scenario is unusual. Please double-check your configuration.";
in
concatMap (peer: optional (!publicKeyInNetdevs peer) (warningMsg peer)) cfg.peers;

environment.systemPackages = [cfg.package pkgs.wireguard-tools];

users.users."${cfg.user}" = {
isSystemUser = true;
createHome = false;
group = cfg.group;
};

users.groups."${cfg.group}" = {};

# NOTE: It would be possible to use systemd credentials for pqsk.
# <https://systemd.io/CREDENTIALS/>
systemd.services.rosenpass = let
generatePeerConfig = {
publicKeyFile,
endpoint,
device,
wireguard,
}:
{
inherit device;
public_key = publicKeyFile;
peer = wireguard.publicKey;
extra_params = [];
}
// (optionalAttrs (endpoint != null) {inherit endpoint;});

generateConfig = {
publicKeyFile,
secretKeyFile,
listen,
verbosity,
peers,
...
}: {
inherit listen verbosity;
public_key = publicKeyFile;
secret_key = secretKeyFile;
peers = map generatePeerConfig peers;
};
toml = pkgs.formats.toml {};
configFile = toml.generate "config.toml" (recursiveUpdate (generateConfig cfg) cfg.extraConfig);
in {
wantedBy = ["multi-user.target"];
after = ["network-online.target"];
path = [pkgs.wireguard-tools];

script = "${cfg.package}/bin/rosenpass exchange-config ${configFile}";

serviceConfig = {
User = cfg.user;
Group = cfg.group;
AmbientCapabilities = ["CAP_NET_ADMIN"];
};
};
};
}
35 changes: 35 additions & 0 deletions pkgs/rosenpass-tools/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
lib,
makeWrapper,
stdenv,
coreutils,
findutils,
gawk,
rosenpass,
wireguard-tools,
}:
stdenv.mkDerivation {
pname = "rosenpass-tools";
inherit (rosenpass) version src;

nativeBuildInputs = [makeWrapper];

postInstall = let
rpDependencies = [
coreutils
findutils
gawk
rosenpass
wireguard-tools
];
in ''
install -D $src/rp $out/bin/rp
install -D $src/doc/rp.1 $out/share/man/man1/rp.1
wrapProgram $out/bin/rp --prefix PATH : ${lib.makeBinPath rpDependencies}
'';

meta = {
inherit (rosenpass.meta) homepage license maintainers;
description = rosenpass.meta.description + " This package contains `rp`, which is a script that wraps the `rosenpass` binary.";
};
}
69 changes: 69 additions & 0 deletions pkgs/rosenpass/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
lib,
fetchFromGitHub,
nixosTests,
rustPlatform,
targetPlatform,
cmake,
libsodium,
pkg-config,
}:
rustPlatform.buildRustPackage rec {
pname = "rosenpass";
version = "0.2.0";
src = fetchFromGitHub {
owner = pname;
repo = pname;
rev = "v${version}";
hash = "sha256-r7/3C5DzXP+9w4rp9XwbP+/NK1axIP6s3Iiio1xRMbk=";
};

cargoHash = "sha256-g2w3lZXQ3Kg3ydKdFs8P2lOPfIkfTbAF0MhxsJoX/E4=";

nativeBuildInputs = [
cmake # for oqs build in the oqs-sys crate
pkg-config # let libsodium-sys-stable find libsodium
rustPlatform.bindgenHook # for C-bindings in the crypto libs
];

buildInputs = [libsodium];

# liboqs requires quite a lot of stack memory, thus we adjust
# Increase the default stack size picked for new threads (which is used
# by `cargo test`) to be _big enough_.
# Only set this value for the check phase (not as an environment variable for the derivation),
# because it is only required in this phase.
preCheck = "export RUST_MIN_STACK=${builtins.toString (8 * 1024 * 1024)}"; # 8 MiB

# nix defaults to building for aarch64 _without_ the armv8-a
# crypto extensions, but liboqs depends on these
preBuild =
lib.optionalString targetPlatform.isAarch
''NIX_CFLAGS_COMPILE="$NIX_CFLAGS_COMPILE -march=armv8-a+crypto"'';

preInstall = "install -D doc/rosenpass.1 $out/share/man/man1/rosenpass.1";

meta = with lib; {
description = "Build post-quantum-secure VPNs with WireGuard!";
homepage = "https://rosenpass.eu/";
license = with licenses; [
mit
/*
or
*/
asl20
];
platforms = platforms.all;
maintainers = with maintainers;
[
andresnav
imincik
lorenzleutgeb
]
++ (with (import ../../maintainers/maintainers-list.nix); [augustebaum kubaneko]);
};

passthru.tests = {
inherit (nixosTests) rosenpass;
};
}
9 changes: 9 additions & 0 deletions tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,13 @@
];
};
};

rosenpass = import ./rosenpass {
configurations.common = {...}: {
imports = [
modules.rosenpass
modules.sops-nix
];
};
};
}
Loading

0 comments on commit e393afa

Please sign in to comment.