mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-11 15:20:29 -08:00
Compare commits
6 Commits
docu-tests
...
systemd-un
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b3bebb9b9 | ||
|
|
9948169127 | ||
|
|
eced56bd70 | ||
|
|
df1e195b5d | ||
|
|
e1e280c4c5 | ||
|
|
06cd178977 |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -2003,9 +2003,11 @@ dependencies = [
|
||||
"rosenpass-util",
|
||||
"rosenpass-wireguard-broker",
|
||||
"rtnetlink",
|
||||
"serde",
|
||||
"stacker",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"toml",
|
||||
"x25519-dalek",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -119,6 +119,9 @@
|
||||
|
||||
|
||||
checks = {
|
||||
systemd-rosenpass = pkgs.testers.runNixOSTest ./tests/systemd/rosenpass.nix;
|
||||
systemd-rp = pkgs.testers.runNixOSTest ./tests/systemd/rp.nix;
|
||||
|
||||
cargo-fmt = pkgs.runCommand "check-cargo-fmt"
|
||||
{ inherit (self.devShells.${system}.default) nativeBuildInputs buildInputs; } ''
|
||||
cargo fmt --manifest-path=${./.}/Cargo.toml --check --all && touch $out
|
||||
|
||||
@@ -20,7 +20,7 @@ in
|
||||
runCommandNoCC "lace-result" { } ''
|
||||
mkdir {bin,$out}
|
||||
tar -cvf $out/rosenpass-${stdenvNoCC.hostPlatform.system}-${version}.tar \
|
||||
-C ${package} bin/rosenpass \
|
||||
-C ${package} bin/rosenpass lib/systemd \
|
||||
-C ${rp} bin/rp
|
||||
cp ${oci-image} \
|
||||
$out/rosenpass-oci-image-${stdenvNoCC.hostPlatform.system}-${version}.tar.gz
|
||||
|
||||
@@ -12,6 +12,8 @@ let
|
||||
extensions = [
|
||||
"lock"
|
||||
"rs"
|
||||
"service"
|
||||
"target"
|
||||
"toml"
|
||||
];
|
||||
# Files to explicitly include
|
||||
@@ -69,6 +71,13 @@ rustPlatform.buildRustPackage {
|
||||
|
||||
hardeningDisable = lib.optional isStatic "fortify";
|
||||
|
||||
postInstall = ''
|
||||
mkdir -p $out/lib/systemd/system
|
||||
install systemd/rosenpass@.service $out/lib/systemd/system
|
||||
install systemd/rp@.service $out/lib/systemd/system
|
||||
install systemd/rosenpass.target $out/lib/systemd/system
|
||||
'';
|
||||
|
||||
meta = {
|
||||
inherit (cargoToml.package) description homepage;
|
||||
license = with lib.licenses; [ mit asl20 ];
|
||||
|
||||
@@ -12,6 +12,8 @@ repository = "https://github.com/rosenpass/rosenpass"
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
base64ct = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
x25519-dalek = { version = "2", features = ["static_secrets"] }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ pub enum Command {
|
||||
public_keys_dir: PathBuf,
|
||||
},
|
||||
Exchange(ExchangeOptions),
|
||||
ExchangeConfig {
|
||||
config_file: PathBuf,
|
||||
},
|
||||
Help,
|
||||
}
|
||||
|
||||
@@ -19,6 +22,7 @@ enum CommandType {
|
||||
GenKey,
|
||||
PubKey,
|
||||
Exchange,
|
||||
ExchangeConfig,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -32,9 +36,10 @@ fn fatal<T>(note: &str, command: Option<CommandType>) -> Result<T, String> {
|
||||
Some(command) => match command {
|
||||
CommandType::GenKey => Err(format!("{}\nUsage: rp genkey PRIVATE_KEYS_DIR", note)),
|
||||
CommandType::PubKey => Err(format!("{}\nUsage: rp pubkey PRIVATE_KEYS_DIR PUBLIC_KEYS_DIR", note)),
|
||||
CommandType::Exchange => Err(format!("{}\nUsage: rp exchange PRIVATE_KEYS_DIR [dev <device>] [listen <ip>:<port>] [peer PUBLIC_KEYS_DIR [endpoint <ip>:<port>] [persistent-keepalive <interval>] [allowed-ips <ip1>/<cidr1>[,<ip2>/<cidr2>]...]]...", note)),
|
||||
CommandType::Exchange => Err(format!("{}\nUsage: rp exchange PRIVATE_KEYS_DIR [dev <device>] [ip <ip1>/<cidr1>] [listen <ip>:<port>] [peer PUBLIC_KEYS_DIR [endpoint <ip>:<port>] [persistent-keepalive <interval>] [allowed-ips <ip1>/<cidr1>[,<ip2>/<cidr2>]...]]...", note)),
|
||||
CommandType::ExchangeConfig => Err(format!("{}\nUsage: rp exchange-config <CONFIG_FILE>", note)),
|
||||
},
|
||||
None => Err(format!("{}\nUsage: rp [verbose] genkey|pubkey|exchange [ARGS]...", note)),
|
||||
None => Err(format!("{}\nUsage: rp [verbose] genkey|pubkey|exchange|exchange-config [ARGS]...", note)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +149,13 @@ impl ExchangeOptions {
|
||||
return fatal("dev option requires parameter", Some(CommandType::Exchange));
|
||||
}
|
||||
}
|
||||
"ip" => {
|
||||
if let Some(ip) = args.next() {
|
||||
options.ip = Some(ip);
|
||||
} else {
|
||||
return fatal("is option requires parameter", Some(CommandType::Exchange));
|
||||
}
|
||||
}
|
||||
"listen" => {
|
||||
if let Some(addr) = args.next() {
|
||||
if let Ok(addr) = addr.parse::<SocketAddr>() {
|
||||
@@ -246,6 +258,21 @@ impl Cli {
|
||||
let options = ExchangeOptions::parse(&mut args)?;
|
||||
cli.command = Some(Command::Exchange(options));
|
||||
}
|
||||
"exchange-config" => {
|
||||
if cli.command.is_some() {
|
||||
return fatal("Too many commands supplied", None);
|
||||
}
|
||||
|
||||
if let Some(config_file) = args.next() {
|
||||
let config_file = PathBuf::from(config_file);
|
||||
cli.command = Some(Command::ExchangeConfig { config_file });
|
||||
} else {
|
||||
return fatal(
|
||||
"Required position argument: CONFIG_FILE",
|
||||
Some(CommandType::ExchangeConfig),
|
||||
);
|
||||
}
|
||||
}
|
||||
"help" => {
|
||||
cli.command = Some(Command::Help);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
use std::{net::SocketAddr, path::PathBuf};
|
||||
use anyhow::Error;
|
||||
use serde::Deserialize;
|
||||
use std::future::Future;
|
||||
use std::ops::DerefMut;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::{net::SocketAddr, path::PathBuf, process::Command};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
use crate::key::WG_B64_LEN;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Deserialize)]
|
||||
pub struct ExchangePeer {
|
||||
pub public_keys_dir: PathBuf,
|
||||
pub endpoint: Option<SocketAddr>,
|
||||
@@ -13,11 +19,12 @@ pub struct ExchangePeer {
|
||||
pub allowed_ips: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Deserialize)]
|
||||
pub struct ExchangeOptions {
|
||||
pub verbose: bool,
|
||||
pub private_keys_dir: PathBuf,
|
||||
pub dev: Option<String>,
|
||||
pub ip: Option<String>,
|
||||
pub listen: Option<SocketAddr>,
|
||||
pub peers: Vec<ExchangePeer>,
|
||||
}
|
||||
@@ -131,6 +138,27 @@ mod netlink {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
struct CleanupHandlers(
|
||||
Arc<::futures::lock::Mutex<Vec<Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>>>>,
|
||||
);
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
impl CleanupHandlers {
|
||||
fn new() -> Self {
|
||||
CleanupHandlers(Arc::new(::futures::lock::Mutex::new(vec![])))
|
||||
}
|
||||
|
||||
async fn enqueue(&self, handler: Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>) {
|
||||
self.0.lock().await.push(Box::pin(handler))
|
||||
}
|
||||
|
||||
async fn run(self) -> Result<Vec<()>, Error> {
|
||||
futures::future::try_join_all(self.0.lock().await.deref_mut()).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||
use std::fs;
|
||||
@@ -151,15 +179,50 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||
let (connection, rtnetlink, _) = rtnetlink::new_connection()?;
|
||||
tokio::spawn(connection);
|
||||
|
||||
let link_name = options.dev.unwrap_or("rosenpass0".to_string());
|
||||
let link_name = options.dev.clone().unwrap_or("rosenpass0".to_string());
|
||||
let link_index = netlink::link_create_and_up(&rtnetlink, link_name.clone()).await?;
|
||||
|
||||
let cleanup_handlers = CleanupHandlers::new();
|
||||
let final_cleanup_handlers = (&cleanup_handlers).clone();
|
||||
|
||||
cleanup_handlers
|
||||
.enqueue(Box::pin(async move {
|
||||
netlink::link_cleanup_standalone(link_index).await
|
||||
}))
|
||||
.await;
|
||||
|
||||
ctrlc_async::set_async_handler(async move {
|
||||
netlink::link_cleanup_standalone(link_index)
|
||||
final_cleanup_handlers
|
||||
.run()
|
||||
.await
|
||||
.expect("Failed to clean up");
|
||||
})?;
|
||||
|
||||
if let Some(ip) = options.ip {
|
||||
let dev = options.dev.clone().unwrap_or("rosenpass0".to_string());
|
||||
Command::new("ip")
|
||||
.arg("address")
|
||||
.arg("add")
|
||||
.arg(ip.clone())
|
||||
.arg("dev")
|
||||
.arg(dev.clone())
|
||||
.status()
|
||||
.expect("failed to configure ip");
|
||||
cleanup_handlers
|
||||
.enqueue(Box::pin(async move {
|
||||
Command::new("ip")
|
||||
.arg("address")
|
||||
.arg("del")
|
||||
.arg(ip)
|
||||
.arg("dev")
|
||||
.arg(dev)
|
||||
.status()
|
||||
.expect("failed to remove ip");
|
||||
Ok(())
|
||||
}))
|
||||
.await;
|
||||
}
|
||||
|
||||
// Deploy the classic wireguard private key
|
||||
let (connection, mut genetlink, _) = genetlink::new_connection()?;
|
||||
tokio::spawn(connection);
|
||||
@@ -254,6 +317,29 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> {
|
||||
broker_peer,
|
||||
peer.endpoint.map(|x| x.to_string()),
|
||||
)?;
|
||||
|
||||
// Configure routes
|
||||
if let Some(allowed_ips) = peer.allowed_ips {
|
||||
Command::new("ip")
|
||||
.arg("route")
|
||||
.arg("replace")
|
||||
.arg(allowed_ips.clone())
|
||||
.arg("dev")
|
||||
.arg(options.dev.clone().unwrap_or("rosenpass0".to_string()))
|
||||
.status()
|
||||
.expect("failed to configure route");
|
||||
cleanup_handlers
|
||||
.enqueue(Box::pin(async move {
|
||||
Command::new("ip")
|
||||
.arg("route")
|
||||
.arg("del")
|
||||
.arg(allowed_ips)
|
||||
.status()
|
||||
.expect("failed to remove ip");
|
||||
Ok(())
|
||||
}))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
let out = srv.event_loop();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::process::exit;
|
||||
use std::{fs, process::exit};
|
||||
|
||||
use cli::{Cli, Command};
|
||||
use exchange::exchange;
|
||||
@@ -36,6 +36,13 @@ async fn main() {
|
||||
options.verbose = cli.verbose;
|
||||
exchange(options).await
|
||||
}
|
||||
Command::ExchangeConfig { config_file } => {
|
||||
let s: String = fs::read_to_string(config_file).expect("cannot read config");
|
||||
let mut options: exchange::ExchangeOptions =
|
||||
toml::from_str::<exchange::ExchangeOptions>(&s).expect("cannot parse config");
|
||||
options.verbose = options.verbose || cli.verbose;
|
||||
exchange(options).await
|
||||
}
|
||||
Command::Help => {
|
||||
println!("Usage: rp [verbose] genkey|pubkey|exchange [ARGS]...");
|
||||
Ok(())
|
||||
|
||||
2
systemd/rosenpass.target
Normal file
2
systemd/rosenpass.target
Normal file
@@ -0,0 +1,2 @@
|
||||
[Unit]
|
||||
Description=Rosenpass target
|
||||
47
systemd/rosenpass@.service
Normal file
47
systemd/rosenpass@.service
Normal file
@@ -0,0 +1,47 @@
|
||||
[Unit]
|
||||
Description=Rosenpass key exchange for %I
|
||||
Documentation=man:rosenpass(1)
|
||||
Documentation=https://rosenpass.eu/docs
|
||||
|
||||
After=network-online.target nss-lookup.target sys-devices-virtual-net-%i.device
|
||||
Wants=network-online.target nss-lookup.target
|
||||
BindsTo=sys-devices-virtual-net-%i.device
|
||||
PartOf=rosenpass.target
|
||||
|
||||
[Service]
|
||||
ExecStart=rosenpass exchange-config /etc/rosenpass/%i.toml
|
||||
LoadCredential=pqsk:/etc/rosenpass/%i/pqsk
|
||||
|
||||
AmbientCapabilities=CAP_NET_ADMIN
|
||||
CapabilityBoundingSet=~CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_AUDIT_WRITE CAP_BLOCK_SUSPEND CAP_BPF CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_IPC_OWNER CAP_IPC_LOCK CAP_KILL CAP_LEASE CAP_LINUX_IMMUTABLE CAP_MAC_ADMIN CAP_MAC_OVERRIDE CAP_MKNOD CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_SYS_ADMIN CAP_SYS_BOOT CAP_SYS_CHROOT CAP_SYSLOG CAP_SYS_MODULE CAP_SYS_NICE CAP_SYS_RESOURCE CAP_SYS_PACCT CAP_SYS_PTRACE CAP_SYS_RAWIO CAP_SYS_TIME CAP_SYS_TTY_CONFIG CAP_WAKE_ALARM
|
||||
DynamicUser=true
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
PrivateDevices=true
|
||||
ProcSubset=pid
|
||||
ProtectClock=true
|
||||
ProtectControlGroups=true
|
||||
ProtectHome=true
|
||||
ProtectHostname=true
|
||||
ProtectKernelLogs=true
|
||||
ProtectKernelModules=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectProc=noaccess
|
||||
RestrictAddressFamilies=AF_NETLINK AF_INET AF_INET6
|
||||
RestrictNamespaces=true
|
||||
RestrictRealtime=true
|
||||
SystemCallArchitectures=native
|
||||
SystemCallFilter=~@clock
|
||||
SystemCallFilter=~@cpu-emulation
|
||||
SystemCallFilter=~@debug
|
||||
SystemCallFilter=~@module
|
||||
SystemCallFilter=~@mount
|
||||
SystemCallFilter=~@obsolete
|
||||
SystemCallFilter=~@privileged
|
||||
SystemCallFilter=~@raw-io
|
||||
SystemCallFilter=~@reboot
|
||||
SystemCallFilter=~@swap
|
||||
UMask=0077
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
48
systemd/rp@.service
Normal file
48
systemd/rp@.service
Normal file
@@ -0,0 +1,48 @@
|
||||
[Unit]
|
||||
Description=Rosenpass key exchange for %I
|
||||
Documentation=man:rosenpass(1)
|
||||
Documentation=https://rosenpass.eu/docs
|
||||
|
||||
After=network-online.target nss-lookup.target
|
||||
Wants=network-online.target nss-lookup.target
|
||||
PartOf=rosenpass.target
|
||||
|
||||
[Service]
|
||||
ExecStart=rp exchange-config /etc/rosenpass/%i.toml
|
||||
LoadCredential=pqpk:/etc/rosenpass/%i/pqpk
|
||||
LoadCredential=pqsk:/etc/rosenpass/%i/pqsk
|
||||
LoadCredential=wgsk:/etc/rosenpass/%i/wgsk
|
||||
|
||||
AmbientCapabilities=CAP_NET_ADMIN
|
||||
CapabilityBoundingSet=~CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_AUDIT_WRITE CAP_BLOCK_SUSPEND CAP_BPF CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_IPC_OWNER CAP_IPC_LOCK CAP_KILL CAP_LEASE CAP_LINUX_IMMUTABLE CAP_MAC_ADMIN CAP_MAC_OVERRIDE CAP_MKNOD CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_SYS_ADMIN CAP_SYS_BOOT CAP_SYS_CHROOT CAP_SYSLOG CAP_SYS_MODULE CAP_SYS_NICE CAP_SYS_RESOURCE CAP_SYS_PACCT CAP_SYS_PTRACE CAP_SYS_RAWIO CAP_SYS_TIME CAP_SYS_TTY_CONFIG CAP_WAKE_ALARM
|
||||
DynamicUser=true
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
PrivateDevices=true
|
||||
ProcSubset=pid
|
||||
ProtectClock=true
|
||||
ProtectControlGroups=true
|
||||
ProtectHome=true
|
||||
ProtectHostname=true
|
||||
ProtectKernelLogs=true
|
||||
ProtectKernelModules=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectProc=noaccess
|
||||
RestrictAddressFamilies=AF_NETLINK AF_INET AF_INET6
|
||||
RestrictNamespaces=true
|
||||
RestrictRealtime=true
|
||||
SystemCallArchitectures=native
|
||||
SystemCallFilter=~@clock
|
||||
SystemCallFilter=~@cpu-emulation
|
||||
SystemCallFilter=~@debug
|
||||
SystemCallFilter=~@module
|
||||
SystemCallFilter=~@mount
|
||||
SystemCallFilter=~@obsolete
|
||||
SystemCallFilter=~@privileged
|
||||
SystemCallFilter=~@raw-io
|
||||
SystemCallFilter=~@reboot
|
||||
SystemCallFilter=~@swap
|
||||
UMask=0077
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
183
tests/systemd/rosenpass.nix
Normal file
183
tests/systemd/rosenpass.nix
Normal file
@@ -0,0 +1,183 @@
|
||||
# This test is largely inspired from:
|
||||
# https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/rosenpass.nix
|
||||
# https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/wireguard/basic.nix
|
||||
{ pkgs, ... }:
|
||||
|
||||
let
|
||||
server = {
|
||||
ip4 = "192.168.0.1";
|
||||
ip6 = "fd00::1";
|
||||
wg = {
|
||||
ip4 = "10.23.42.1";
|
||||
ip6 = "fc00::1";
|
||||
public = "mQufmDFeQQuU/fIaB2hHgluhjjm1ypK4hJr1cW3WqAw=";
|
||||
secret = "4N5Y1dldqrpsbaEiY8O0XBUGUFf8vkvtBtm8AoOX7Eo=";
|
||||
listen = 10000;
|
||||
};
|
||||
};
|
||||
|
||||
client = {
|
||||
ip4 = "192.168.0.2";
|
||||
ip6 = "fd00::2";
|
||||
wg = {
|
||||
ip4 = "10.23.42.2";
|
||||
ip6 = "fc00::2";
|
||||
public = "Mb3GOlT7oS+F3JntVKiaD7SpHxLxNdtEmWz/9FMnRFU=";
|
||||
secret = "uC5dfGMv7Oxf5UDfdPkj6rZiRZT2dRWp5x8IQxrNcUE=";
|
||||
};
|
||||
};
|
||||
|
||||
server_config = {
|
||||
listen = [ "0.0.0.0:9999" ];
|
||||
public_key = "/etc/rosenpass/rp0/pqpk";
|
||||
secret_key = "/run/credentials/rosenpass@rp0.service/pqsk";
|
||||
verbosity = "Verbose";
|
||||
peers = [{
|
||||
device = "rp0";
|
||||
peer = client.wg.public;
|
||||
public_key = "/etc/rosenpass/rp0/peers/client/pqpk";
|
||||
}];
|
||||
};
|
||||
client_config = {
|
||||
listen = [ "0.0.0.0:9999" ]; # TODO: Should not be necessary to set, but wouldn't parse.
|
||||
public_key = "/etc/rosenpass/rp0/pqpk";
|
||||
secret_key = "/run/credentials/rosenpass@rp0.service/pqsk";
|
||||
verbosity = "Verbose";
|
||||
peers = [{
|
||||
device = "rp0";
|
||||
peer = server.wg.public;
|
||||
public_key = "/etc/rosenpass/rp0/peers/server/pqpk";
|
||||
endpoint = "${server.ip4}:9999";
|
||||
}];
|
||||
};
|
||||
|
||||
config = pkgs.runCommand "config" { } ''
|
||||
mkdir -pv $out
|
||||
cp -v ${(pkgs.formats.toml {}).generate "rp0.toml" server_config} $out/server
|
||||
cp -v ${(pkgs.formats.toml {}).generate "rp0.toml" client_config} $out/client
|
||||
'';
|
||||
in
|
||||
{
|
||||
name = "rosenpass unit";
|
||||
|
||||
nodes =
|
||||
let
|
||||
shared = peer: { config, modulesPath, pkgs, ... }: {
|
||||
# Need to work around a problem in recent systemd changes.
|
||||
# It won't be necessary in other distros (for which the systemd file was designed), this is NixOS specific
|
||||
# https://github.com/NixOS/nixpkgs/issues/258371#issuecomment-1925672767
|
||||
# This can potentially be removed in future nixpkgs updates
|
||||
systemd.packages = [
|
||||
(pkgs.runCommand "rosenpass" { } ''
|
||||
mkdir -p $out/lib/systemd/system
|
||||
< ${pkgs.rosenpass}/lib/systemd/system/rosenpass.target > $out/lib/systemd/system/rosenpass.target
|
||||
< ${pkgs.rosenpass}/lib/systemd/system/rosenpass@.service \
|
||||
sed 's@^\(\[Service]\)$@\1\nEnvironment=PATH=${pkgs.wireguard-tools}/bin@' |
|
||||
sed 's@^ExecStartPre=envsubst @ExecStartPre='"${pkgs.envsubst}"'/bin/envsubst @' |
|
||||
sed 's@^ExecStart=rosenpass @ExecStart='"${pkgs.rosenpass}"'/bin/rosenpass @' > $out/lib/systemd/system/rosenpass@.service
|
||||
'')
|
||||
];
|
||||
networking.wireguard = {
|
||||
enable = true;
|
||||
interfaces.rp0 = {
|
||||
ips = [ "${peer.wg.ip4}/32" "${peer.wg.ip6}/128" ];
|
||||
privateKeyFile = "/etc/wireguard/wgsk";
|
||||
};
|
||||
};
|
||||
environment.etc."wireguard/wgsk".text = peer.wg.secret;
|
||||
networking.interfaces.eth1 = {
|
||||
ipv4.addresses = [{
|
||||
address = peer.ip4;
|
||||
prefixLength = 24;
|
||||
}];
|
||||
ipv6.addresses = [{
|
||||
address = peer.ip6;
|
||||
prefixLength = 64;
|
||||
}];
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
server = {
|
||||
imports = [ (shared server) ];
|
||||
networking.firewall.allowedUDPPorts = [ 9999 server.wg.listen ];
|
||||
networking.wireguard.interfaces.rp0 = {
|
||||
listenPort = server.wg.listen;
|
||||
peers = [
|
||||
{
|
||||
allowedIPs = [ client.wg.ip4 client.wg.ip6 ];
|
||||
publicKey = client.wg.public;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
client = {
|
||||
imports = [ (shared client) ];
|
||||
networking.wireguard.interfaces.rp0 = {
|
||||
peers = [
|
||||
{
|
||||
allowedIPs = [ "10.23.42.0/24" "fc00::/64" ];
|
||||
publicKey = server.wg.public;
|
||||
endpoint = "${server.ip4}:${toString server.wg.listen}";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
testScript = { ... }: ''
|
||||
from os import system
|
||||
rosenpass = "${pkgs.rosenpass}/bin/rosenpass"
|
||||
|
||||
start_all()
|
||||
|
||||
for machine in [server, client]:
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
machine.wait_for_unit("network-online.target")
|
||||
|
||||
with subtest("Key, Config, and Service Setup"):
|
||||
for name, machine, remote in [("server", server, client), ("client", client, server)]:
|
||||
# generate all the keys
|
||||
system(f"{rosenpass} gen-keys --public-key {name}-pqpk --secret-key {name}-pqsk")
|
||||
|
||||
# copy private keys to our side
|
||||
machine.copy_from_host(f"{name}-pqsk", "/etc/rosenpass/rp0/pqsk")
|
||||
machine.copy_from_host(f"{name}-pqpk", "/etc/rosenpass/rp0/pqpk")
|
||||
|
||||
# copy public keys to other side
|
||||
remote.copy_from_host(f"{name}-pqpk", f"/etc/rosenpass/rp0/peers/{name}/pqpk")
|
||||
|
||||
machine.copy_from_host(f"${config}/{name}", "/etc/rosenpass/rp0.toml")
|
||||
|
||||
for machine in [server, client]:
|
||||
machine.wait_for_unit("wireguard-rp0.service")
|
||||
|
||||
with subtest("wg network test"):
|
||||
client.succeed("wg show all preshared-keys | grep none", timeout=5);
|
||||
client.succeed("ping -c5 ${server.wg.ip4}")
|
||||
server.succeed("ping -c5 ${client.wg.ip6}")
|
||||
|
||||
with subtest("Set up rosenpass"):
|
||||
for machine in [server, client]:
|
||||
machine.succeed("systemctl start rosenpass@rp0.service")
|
||||
|
||||
for machine in [server, client]:
|
||||
machine.wait_for_unit("rosenpass@rp0.service")
|
||||
|
||||
|
||||
with subtest("compare preshared keys"):
|
||||
client.wait_until_succeeds("wg show all preshared-keys | grep --invert-match none", timeout=5);
|
||||
server.wait_until_succeeds("wg show all preshared-keys | grep --invert-match none", timeout=5);
|
||||
|
||||
def get_psk(m):
|
||||
psk = m.succeed("wg show rp0 preshared-keys | awk '{print $2}'")
|
||||
psk = psk.strip()
|
||||
assert len(psk.split()) == 1, "Only one PSK"
|
||||
return psk
|
||||
|
||||
assert get_psk(client) == get_psk(server), "preshared keys need to match"
|
||||
|
||||
with subtest("rosenpass network test"):
|
||||
client.succeed("ping -c5 ${server.wg.ip4}")
|
||||
server.succeed("ping -c5 ${client.wg.ip6}")
|
||||
'';
|
||||
}
|
||||
139
tests/systemd/rp.nix
Normal file
139
tests/systemd/rp.nix
Normal file
@@ -0,0 +1,139 @@
|
||||
{ pkgs, ... }:
|
||||
|
||||
let
|
||||
server = {
|
||||
ip4 = "192.168.0.1";
|
||||
ip6 = "fd00::1";
|
||||
wg = {
|
||||
ip6 = "fc00::1";
|
||||
listen = 10000;
|
||||
};
|
||||
};
|
||||
|
||||
client = {
|
||||
ip4 = "192.168.0.2";
|
||||
ip6 = "fd00::2";
|
||||
wg = {
|
||||
ip6 = "fc00::2";
|
||||
};
|
||||
};
|
||||
|
||||
server_config = {
|
||||
listen = "${server.ip4}:9999";
|
||||
private_keys_dir = "/run/credentials/rp@test-rp-device0.service";
|
||||
verbose = true;
|
||||
dev = "test-rp-device0";
|
||||
ip = "fc00::1/64";
|
||||
peers = [{
|
||||
public_keys_dir = "/etc/rosenpass/test-rp-device0/peers/client";
|
||||
allowed_ips = "fc00::2";
|
||||
}];
|
||||
};
|
||||
client_config = {
|
||||
private_keys_dir = "/run/credentials/rp@test-rp-device0.service";
|
||||
verbose = true;
|
||||
dev = "test-rp-device0";
|
||||
ip = "fc00::2/128";
|
||||
peers = [{
|
||||
public_keys_dir = "/etc/rosenpass/test-rp-device0/peers/server";
|
||||
endpoint = "${server.ip4}:9999";
|
||||
allowed_ips = "fc00::/64";
|
||||
}];
|
||||
};
|
||||
|
||||
config = pkgs.runCommand "config" { } ''
|
||||
mkdir -pv $out
|
||||
cp -v ${(pkgs.formats.toml {}).generate "test-rp-device0.toml" server_config} $out/server
|
||||
cp -v ${(pkgs.formats.toml {}).generate "test-rp-device0.toml" client_config} $out/client
|
||||
'';
|
||||
in
|
||||
{
|
||||
name = "rp systemd unit";
|
||||
|
||||
nodes =
|
||||
let
|
||||
shared = peer: { config, modulesPath, pkgs, ... }: {
|
||||
# Need to work around a problem in recent systemd changes.
|
||||
# It won't be necessary in other distros (for which the systemd file was designed), this is NixOS specific
|
||||
# https://github.com/NixOS/nixpkgs/issues/258371#issuecomment-1925672767
|
||||
# This can potentially be removed in future nixpkgs updates
|
||||
systemd.packages = [
|
||||
(pkgs.runCommand "rp@.service" { } ''
|
||||
mkdir -p $out/lib/systemd/system
|
||||
< ${pkgs.rosenpass}/lib/systemd/system/rosenpass.target > $out/lib/systemd/system/rosenpass.target
|
||||
< ${pkgs.rosenpass}/lib/systemd/system/rp@.service \
|
||||
sed 's@^\(\[Service]\)$@\1\nEnvironment=PATH=${pkgs.iproute2}/bin:${pkgs.wireguard-tools}/bin@' |
|
||||
sed 's@^ExecStartPre=envsubst @ExecStartPre='"${pkgs.envsubst}"'/bin/envsubst @' |
|
||||
sed 's@^ExecStart=rp @ExecStart='"${pkgs.rosenpass}"'/bin/rp @' > $out/lib/systemd/system/rp@.service
|
||||
'')
|
||||
];
|
||||
environment.systemPackages = [ pkgs.wireguard-tools ];
|
||||
networking.interfaces.eth1 = {
|
||||
ipv4.addresses = [{
|
||||
address = peer.ip4;
|
||||
prefixLength = 24;
|
||||
}];
|
||||
ipv6.addresses = [{
|
||||
address = peer.ip6;
|
||||
prefixLength = 64;
|
||||
}];
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
server = {
|
||||
imports = [ (shared server) ];
|
||||
networking.firewall.allowedUDPPorts = [ 9999 server.wg.listen ];
|
||||
};
|
||||
client = {
|
||||
imports = [ (shared client) ];
|
||||
};
|
||||
};
|
||||
testScript = { ... }: ''
|
||||
from os import system
|
||||
rp = "${pkgs.rosenpass}/bin/rp"
|
||||
|
||||
start_all()
|
||||
|
||||
for machine in [server, client]:
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
machine.wait_for_unit("network-online.target")
|
||||
|
||||
with subtest("Key, Config, and Service Setup"):
|
||||
for name, machine, remote in [("server", server, client), ("client", client, server)]:
|
||||
# create all the keys
|
||||
system(f"{rp} genkey {name}-sk")
|
||||
system(f"{rp} pubkey {name}-sk {name}-pk")
|
||||
|
||||
# copy secret keys to our side
|
||||
for file in ["pqpk", "pqsk", "wgsk"]:
|
||||
machine.copy_from_host(f"{name}-sk/{file}", f"/etc/rosenpass/test-rp-device0/{file}")
|
||||
# copy public keys to other side
|
||||
for file in ["pqpk", "wgpk"]:
|
||||
remote.copy_from_host(f"{name}-pk/{file}", f"/etc/rosenpass/test-rp-device0/peers/{name}/{file}")
|
||||
|
||||
machine.copy_from_host(f"${config}/{name}", "/etc/rosenpass/test-rp-device0.toml")
|
||||
|
||||
for machine in [server, client]:
|
||||
machine.succeed("systemctl start rp@test-rp-device0.service")
|
||||
|
||||
for machine in [server, client]:
|
||||
machine.wait_for_unit("rp@test-rp-device0.service")
|
||||
|
||||
with subtest("compare preshared keys"):
|
||||
client.wait_until_succeeds("wg show all preshared-keys | grep --invert-match none", timeout=5);
|
||||
server.wait_until_succeeds("wg show all preshared-keys | grep --invert-match none", timeout=5);
|
||||
|
||||
def get_psk(m):
|
||||
psk = m.succeed("wg show test-rp-device0 preshared-keys | awk '{print $2}'")
|
||||
psk = psk.strip()
|
||||
assert len(psk.split()) == 1, "Only one PSK"
|
||||
return psk
|
||||
|
||||
assert get_psk(client) == get_psk(server), "preshared keys need to match"
|
||||
|
||||
with subtest("network test"):
|
||||
client.succeed("ping -c5 ${server.wg.ip6}")
|
||||
server.succeed("ping -c5 ${client.wg.ip6}")
|
||||
'';
|
||||
}
|
||||
Reference in New Issue
Block a user