chore(doc): Docs for rosenpass::{config, cli} (#560)
Some checks are pending
Nix / Build i686-linux.default (push) Blocked by required conditions
Nix / Build i686-linux.rosenpass (push) Waiting to run
Nix / Build i686-linux.rosenpass-oci-image (push) Blocked by required conditions
Nix / Run Nix checks on i686-linux (push) Waiting to run
Nix / Build x86_64-darwin.default (push) Blocked by required conditions
Nix / Build x86_64-darwin.release-package (push) Blocked by required conditions
Nix / Build x86_64-darwin.rosenpass (push) Waiting to run
Nix / Build x86_64-darwin.rp (push) Waiting to run
Nix / Build x86_64-darwin.rosenpass-oci-image (push) Blocked by required conditions
Nix / Run Nix checks on x86_64-darwin (push) Waiting to run
Nix / Build x86_64-linux.default (push) Blocked by required conditions
Nix / Build x86_64-linux.proof-proverif (push) Blocked by required conditions
Nix / Build x86_64-linux.proverif-patched (push) Waiting to run
Nix / Build x86_64-linux.release-package (push) Blocked by required conditions
Nix / Build x86_64-linux.rosenpass (push) Waiting to run
Nix / Build aarch64-linux.rosenpass (push) Waiting to run
Nix / Build aarch64-linux.rp (push) Waiting to run
Nix / Build x86_64-linux.rosenpass-oci-image (push) Blocked by required conditions
Nix / Build aarch64-linux.rosenpass-oci-image (push) Blocked by required conditions
Nix / Build x86_64-linux.rosenpass-static (push) Waiting to run
Nix / Build x86_64-linux.rp-static (push) Waiting to run
Nix / Build x86_64-linux.rosenpass-static-oci-image (push) Blocked by required conditions
Nix / Build x86_64-linux.whitepaper (push) Waiting to run
Nix / Run Nix checks on x86_64-linux (push) Waiting to run
Nix / Upload whitepaper x86_64-linux (push) Waiting to run
QC / prettier (push) Waiting to run
QC / Shellcheck (push) Waiting to run
QC / Rust Format (push) Waiting to run
QC / cargo-bench (push) Waiting to run
QC / mandoc (push) Waiting to run
QC / cargo-audit (push) Waiting to run
QC / cargo-clippy (push) Waiting to run
QC / cargo-doc (push) Waiting to run
QC / cargo-test (macos-13) (push) Waiting to run
QC / cargo-test (ubuntu-latest) (push) Waiting to run
QC / cargo-test-nix-devshell-x86_64-linux (push) Waiting to run
QC / cargo-fuzz (push) Waiting to run
QC / codecov (push) Waiting to run
Regressions / multi-peer (push) Waiting to run
Regressions / boot-race (push) Waiting to run

This commit is contained in:
Paul Spooren
2024-12-18 23:11:45 +01:00
committed by GitHub
8 changed files with 347 additions and 73 deletions

View File

@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::app_server::AppServer; use crate::app_server::AppServer;
#[derive(Debug, Serialize, Deserialize, Default, Clone)] #[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
pub struct ApiConfig { pub struct ApiConfig {
/// Where in the file-system to create the unix socket the rosenpass API will be listening for /// Where in the file-system to create the unix socket the rosenpass API will be listening for
/// connections on /// connections on

View File

@@ -1,3 +1,8 @@
//! Contains the code used to parse command line parameters for rosenpass.
//!
//! [CliArgs::run] is called by the rosenpass main function and contains the
//! bulk of our boostrapping code while the main function just sets up the basic environment
use anyhow::{bail, ensure, Context}; use anyhow::{bail, ensure, Context};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use rosenpass_cipher_traits::Kem; use rosenpass_cipher_traits::Kem;
@@ -31,15 +36,25 @@ use {
std::thread, std::thread,
}; };
/// enum representing a choice of interface to a WireGuard broker /// How to reach a WireGuard PSK Broker
#[derive(Debug)] #[derive(Debug)]
pub enum BrokerInterface { pub enum BrokerInterface {
/// The PSK Broker is listening on a unix socket at the given path
Socket(PathBuf), Socket(PathBuf),
/// The PSK Broker broker is already connected to this process; a
/// unix socket stream can be reached at the given file descriptor.
///
/// This is generally used with file descriptor passing.
FileDescriptor(i32), FileDescriptor(i32),
/// Create a socketpair(3p), spawn the PSK broker process from within rosenpass,
/// and hand one end of the socketpair to the broker process via file
/// descriptor passing to the subprocess
SocketPair, SocketPair,
} }
/// struct holding all CLI arguments for `clap` crate to parse /// Command line arguments to the Rosenpass binary.
///
/// Used for parsing with [clap].
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author, version, about, long_about, arg_required_else_help = true)] #[command(author, version, about, long_about, arg_required_else_help = true)]
pub struct CliArgs { pub struct CliArgs {
@@ -80,6 +95,7 @@ pub struct CliArgs {
#[arg(short, long, group = "psk-broker-specs")] #[arg(short, long, group = "psk-broker-specs")]
psk_broker_spawn: bool, psk_broker_spawn: bool,
/// The subcommand to be invoked
#[command(subcommand)] #[command(subcommand)]
pub command: Option<CliCommand>, pub command: Option<CliCommand>,
@@ -98,6 +114,10 @@ pub struct CliArgs {
} }
impl CliArgs { impl CliArgs {
/// Apply the command line parameters to the Rosenpass configuration struct
///
/// Generally the flow of control here is that all the command line parameters
/// are merged into the configuration file to avoid much code duplication.
pub fn apply_to_config(&self, _cfg: &mut config::Rosenpass) -> anyhow::Result<()> { pub fn apply_to_config(&self, _cfg: &mut config::Rosenpass) -> anyhow::Result<()> {
#[cfg(feature = "experiment_api")] #[cfg(feature = "experiment_api")]
self.api.apply_to_config(_cfg)?; self.api.apply_to_config(_cfg)?;
@@ -123,9 +143,11 @@ impl CliArgs {
None None
} }
/// Return the WireGuard PSK broker interface configured.
///
/// Returns `None` if the `experiment_api` feature is disabled.
#[cfg(feature = "experiment_api")] #[cfg(feature = "experiment_api")]
/// returns the broker interface set by CLI args
/// returns `None` if the `experiment_api` feature isn't enabled
pub fn get_broker_interface(&self) -> Option<BrokerInterface> { pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
if let Some(path_ref) = self.psk_broker_path.as_ref() { if let Some(path_ref) = self.psk_broker_path.as_ref() {
Some(BrokerInterface::Socket(path_ref.to_path_buf())) Some(BrokerInterface::Socket(path_ref.to_path_buf()))
@@ -138,9 +160,10 @@ impl CliArgs {
} }
} }
/// Return the WireGuard PSK broker interface configured.
///
/// Returns `None` if the `experiment_api` feature is disabled.
#[cfg(not(feature = "experiment_api"))] #[cfg(not(feature = "experiment_api"))]
/// returns the broker interface set by CLI args
/// returns `None` if the `experiment_api` feature isn't enabled
pub fn get_broker_interface(&self) -> Option<BrokerInterface> { pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
None None
} }
@@ -244,15 +267,17 @@ pub enum CliCommand {
} }
impl CliArgs { impl CliArgs {
/// Runs the command specified via CLI /// Run Rosenpass with the given command line parameters
/// ///
/// ## TODO /// This contains the bulk of our startup logic with
/// - This method consumes the [`CliCommand`] value. It might be wise to use a reference... /// the main function just setting up the basic environment
/// and then calling this function.
pub fn run( pub fn run(
self, self,
broker_interface: Option<BrokerInterface>, broker_interface: Option<BrokerInterface>,
test_helpers: Option<AppServerTest>, test_helpers: Option<AppServerTest>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
// TODO: This method consumes the [`CliCommand`] value. It might be wise to use a reference...
use CliCommand::*; use CliCommand::*;
match &self.command { match &self.command {
Some(GenConfig { config_file, force }) => { Some(GenConfig { config_file, force }) => {
@@ -403,6 +428,7 @@ impl CliArgs {
Ok(()) Ok(())
} }
/// Used by [Self::run] to start the Rosenpass key exchange server
fn event_loop( fn event_loop(
config: config::Rosenpass, config: config::Rosenpass,
broker_interface: Option<BrokerInterface>, broker_interface: Option<BrokerInterface>,
@@ -470,6 +496,19 @@ impl CliArgs {
srv.event_loop() srv.event_loop()
} }
/// Create the WireGuard PSK broker to be used by
/// [crate::app_server::AppServer].
///
/// If the `experiment_api`
/// feature flag is set, then this communicates with a PSK broker
/// running in a different process as configured via
/// the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
/// fields.
///
/// If the `experiment_api`
/// feature flag is not set, then this returns a [NativeUnixBroker],
/// sending pre-shared keys directly to WireGuard from within this
/// process.
#[cfg(feature = "experiment_api")] #[cfg(feature = "experiment_api")]
fn create_broker( fn create_broker(
broker_interface: Option<BrokerInterface>, broker_interface: Option<BrokerInterface>,
@@ -485,6 +524,19 @@ impl CliArgs {
} }
} }
/// Create the WireGuard PSK broker to be used by
/// [crate::app_server::AppServer].
///
/// If the `experiment_api`
/// feature flag is set, then this communicates with a PSK broker
/// running in a different process as configured via
/// the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
/// fields.
///
/// If the `experiment_api`
/// feature flag is not set, then this returns a [NativeUnixBroker],
/// sending pre-shared keys directly to WireGuard from within this
/// process.
#[cfg(not(feature = "experiment_api"))] #[cfg(not(feature = "experiment_api"))]
fn create_broker( fn create_broker(
_broker_interface: Option<BrokerInterface>, _broker_interface: Option<BrokerInterface>,
@@ -492,6 +544,10 @@ impl CliArgs {
Ok(Box::new(NativeUnixBroker::new())) Ok(Box::new(NativeUnixBroker::new()))
} }
/// Used by [Self::create_broker] if the `experiment_api` is configured
/// to set up the connection with the PSK broker process as configured
/// via the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
/// fields.
#[cfg(feature = "experiment_api")] #[cfg(feature = "experiment_api")]
fn get_broker_socket(broker_interface: BrokerInterface) -> Result<UnixStream, anyhow::Error> { fn get_broker_socket(broker_interface: BrokerInterface) -> Result<UnixStream, anyhow::Error> {
// Connect to the psk broker unix socket if one was specified // Connect to the psk broker unix socket if one was specified
@@ -549,7 +605,7 @@ impl CliArgs {
} }
/// generate secret and public keys, store in files according to the paths passed as arguments /// generate secret and public keys, store in files according to the paths passed as arguments
fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> { pub fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
let mut ssk = crate::protocol::SSk::random(); let mut ssk = crate::protocol::SSk::random();
let mut spk = crate::protocol::SPk::random(); let mut spk = crate::protocol::SPk::random();
StaticKem::keygen(ssk.secret_mut(), spk.deref_mut())?; StaticKem::keygen(ssk.secret_mut(), spk.deref_mut())?;

View File

@@ -4,8 +4,9 @@
//! [`Rosenpass`] which holds such a configuration. //! [`Rosenpass`] which holds such a configuration.
//! //!
//! ## TODO //! ## TODO
//! - support `~` in <https://github.com/rosenpass/rosenpass/issues/237> //! - TODO: support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
//! - provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247> //! - TODO: provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
use crate::protocol::{SPk, SSk}; use crate::protocol::{SPk, SSk};
use rosenpass_util::file::LoadValue; use rosenpass_util::file::LoadValue;
use std::{ use std::{
@@ -31,7 +32,10 @@ fn empty_api_config() -> crate::api::config::ApiConfig {
} }
} }
#[derive(Debug, Serialize, Deserialize)] /// Configuration for the Rosenpass key exchange
///
/// i.e. configuration for the `rosenpass exchange` and `rosenpass exchange-config` commands
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Rosenpass { pub struct Rosenpass {
// TODO: Raise error if secret key or public key alone is set during deserialization // TODO: Raise error if secret key or public key alone is set during deserialization
// SEE: https://github.com/serde-rs/serde/issues/2793 // SEE: https://github.com/serde-rs/serde/issues/2793
@@ -46,7 +50,10 @@ pub struct Rosenpass {
/// list of [`SocketAddr`] to listen on /// list of [`SocketAddr`] to listen on
/// ///
/// Examples: /// Examples:
/// - `0.0.0.0:123` ///
/// - `0.0.0.0:123` Listen on any interface using IPv4, port 123
/// - `[::1]:1234` Listen on IPv6 localhost, port 1234
/// - `[::]:4476` Listen on any IPv4 or IPv6 interface, port 4476
pub listen: Vec<SocketAddr>, pub listen: Vec<SocketAddr>,
/// log verbosity /// log verbosity
@@ -68,6 +75,7 @@ pub struct Rosenpass {
pub config_file_path: PathBuf, pub config_file_path: PathBuf,
} }
/// Public key and secret key locations.
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)] #[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
pub struct Keypair { pub struct Keypair {
/// path to the public key file /// path to the public key file
@@ -78,6 +86,7 @@ pub struct Keypair {
} }
impl Keypair { impl Keypair {
/// Construct a keypair from its fields
pub fn new<Pk: AsRef<Path>, Sk: AsRef<Path>>(public_key: Pk, secret_key: Sk) -> Self { pub fn new<Pk: AsRef<Path>, Sk: AsRef<Path>>(public_key: Pk, secret_key: Sk) -> Self {
let public_key = public_key.as_ref().to_path_buf(); let public_key = public_key.as_ref().to_path_buf();
let secret_key = secret_key.as_ref().to_path_buf(); let secret_key = secret_key.as_ref().to_path_buf();
@@ -88,62 +97,72 @@ impl Keypair {
} }
} }
/// ## TODO /// Level of verbosity for [crate::app_server::AppServer]
/// - replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246> ///
/// The value of the field [crate::app_server::AppServer::verbosity]. See the field documentation
/// for details.
///
/// - TODO: replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)]
pub enum Verbosity { pub enum Verbosity {
Quiet, Quiet,
Verbose, Verbose,
} }
/// ## TODO /// Configuration data for a single Rosenpass peer
/// - examples
/// - documentation
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RosenpassPeer { pub struct RosenpassPeer {
/// path to the public key of the peer /// path to the public key of the peer
pub public_key: PathBuf, pub public_key: PathBuf,
/// ## TODO /// The hostname and port to connect to
/// - documentation ///
/// Can be a
///
/// - hostname and port, e.g. `localhost:8876` or `rosenpass.eu:1427`
/// - IPv4 address and port, e.g. `1.2.3.4:7764`
/// - IPv6 address and port, e.g. `[fe80::24]:7890`
pub endpoint: Option<String>, pub endpoint: Option<String>,
/// path to the pre-shared key with the peer /// path to the pre-shared key shared with the peer
/// ///
/// NOTE: this item can be skipped in the config if you do not use a pre-shared key with the peer /// NOTE: this item can be skipped in the config if you do not use a pre-shared key with the peer
pub pre_shared_key: Option<PathBuf>, pub pre_shared_key: Option<PathBuf>,
/// ## TODO /// If this field is set to a path, the Rosenpass will write the exchanged symmetric keys
/// - documentation /// to the given file and write a notification to standard out to let the calling application
/// know that a new key was exchanged
#[serde(default)] #[serde(default)]
pub key_out: Option<PathBuf>, pub key_out: Option<PathBuf>,
/// ## TODO /// Information for supplying exchanged keys directly to WireGuard
/// - documentation
/// - make this field only available on binary builds, not on library builds <https://github.com/rosenpass/rosenpass/issues/249>
#[serde(flatten)] #[serde(flatten)]
pub wg: Option<WireGuard>, pub wg: Option<WireGuard>,
} }
/// ## TODO /// Information for supplying exchanged keys directly to WireGuard
/// - documentation
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct WireGuard { pub struct WireGuard {
/// ## TODO /// Name of the WireGuard interface to supply with pre-shared keys generated by the Rosenpass
/// - documentation /// key exchange
pub device: String, pub device: String,
/// ## TODO /// WireGuard public key of the peer to supply with pre-shared keys
/// - documentation
pub peer: String, pub peer: String,
/// ## TODO /// Extra parameters passed to the `wg` command
/// - documentation
#[serde(default)] #[serde(default)]
pub extra_params: Vec<String>, pub extra_params: Vec<String>,
} }
impl Default for Rosenpass { impl Default for Rosenpass {
/// Generate an empty configuration
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
#[doc = "```"]
fn default() -> Self { fn default() -> Self {
Self::empty() Self::empty()
} }
@@ -156,8 +175,15 @@ impl Rosenpass {
/// checked whether they even exist. /// checked whether they even exist.
/// ///
/// ## TODO /// ## TODO
///
/// - consider using a different algorithm to determine home directory the below one may /// - consider using a different algorithm to determine home directory the below one may
/// behave unexpectedly on Windows /// behave unexpectedly on Windows
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
#[doc = "```"]
pub fn load<P: AsRef<Path>>(p: P) -> anyhow::Result<Self> { pub fn load<P: AsRef<Path>>(p: P) -> anyhow::Result<Self> {
// read file and deserialize // read file and deserialize
let mut config: Self = toml::from_str(&fs::read_to_string(&p)?)?; let mut config: Self = toml::from_str(&fs::read_to_string(&p)?)?;
@@ -185,7 +211,13 @@ impl Rosenpass {
Ok(config) Ok(config)
} }
/// Write a config to a file /// Encode a configuration object as toml and write it to a file
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
#[doc = "```"]
pub fn store<P: AsRef<Path>>(&self, p: P) -> anyhow::Result<()> { pub fn store<P: AsRef<Path>>(&self, p: P) -> anyhow::Result<()> {
let serialized_config = let serialized_config =
toml::to_string_pretty(&self).expect("unable to serialize the default config"); toml::to_string_pretty(&self).expect("unable to serialize the default config");
@@ -194,6 +226,12 @@ impl Rosenpass {
} }
/// Commit the configuration to where it came from, overwriting the original file /// Commit the configuration to where it came from, overwriting the original file
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
#[doc = "```"]
pub fn commit(&self) -> anyhow::Result<()> { pub fn commit(&self) -> anyhow::Result<()> {
let mut f = fopen_w(&self.config_file_path, Visibility::Public)?; let mut f = fopen_w(&self.config_file_path, Visibility::Public)?;
f.write_all(toml::to_string_pretty(&self)?.as_bytes())?; f.write_all(toml::to_string_pretty(&self)?.as_bytes())?;
@@ -201,13 +239,21 @@ impl Rosenpass {
self.store(&self.config_file_path) self.store(&self.config_file_path)
} }
/// Apply the configuration in this object to the given [crate::app_server::AppServer]
pub fn apply_to_app_server(&self, _srv: &mut AppServer) -> anyhow::Result<()> { pub fn apply_to_app_server(&self, _srv: &mut AppServer) -> anyhow::Result<()> {
#[cfg(feature = "experiment_api")] #[cfg(feature = "experiment_api")]
self.api.apply_to_app_server(_srv)?; self.api.apply_to_app_server(_srv)?;
Ok(()) Ok(())
} }
/// Validate a configuration /// Check that the configuration is sound, ensuring
/// for instance that the referenced files exist
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_validate.rs")]
#[doc = "```"]
pub fn validate(&self) -> anyhow::Result<()> { pub fn validate(&self) -> anyhow::Result<()> {
if let Some(ref keypair) = self.keypair { if let Some(ref keypair) = self.keypair {
// check the public key file exists // check the public key file exists
@@ -284,6 +330,21 @@ impl Rosenpass {
Ok(()) Ok(())
} }
/// Check that the configuration is useful given the feature set Rosenpass was compiled with
/// and the configuration values.
///
/// This was introduced when we introduced a unix-socket API feature allowing the server
/// keypair to be supplied via the API; in this process we also made [Self::keypair] optional.
/// With respect to this particular feature, this function ensures that [Self::keypair] is set
/// when Rosenpass is compiles without the `experiment_api` flag. When `experiment_api` is
/// used, the function ensures that [Self::keypair] is only `None`, if the Rosenpass API is
/// enabled in the configuration.
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_validate.rs")]
#[doc = "```"]
pub fn check_usefullness(&self) -> anyhow::Result<()> { pub fn check_usefullness(&self) -> anyhow::Result<()> {
#[cfg(not(feature = "experiment_api"))] #[cfg(not(feature = "experiment_api"))]
ensure!(self.keypair.is_some(), "Server keypair missing."); ensure!(self.keypair.is_some(), "Server keypair missing.");
@@ -299,15 +360,38 @@ impl Rosenpass {
Ok(()) Ok(())
} }
/// Produce an empty confuguration
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
#[doc = "```"]
pub fn empty() -> Self { pub fn empty() -> Self {
Self::new(None) Self::new(None)
} }
/// Produce configuration from the keypair
///
/// Shorthand for calling [Self::new] with Some([Keypair]::new(sk, pk)).
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
#[doc = "```"]
pub fn from_sk_pk<Sk: AsRef<Path>, Pk: AsRef<Path>>(sk: Sk, pk: Pk) -> Self { pub fn from_sk_pk<Sk: AsRef<Path>, Pk: AsRef<Path>>(sk: Sk, pk: Pk) -> Self {
Self::new(Some(Keypair::new(pk, sk))) Self::new(Some(Keypair::new(pk, sk)))
} }
/// Creates a new configuration /// Initialize a minimal configuration with the [Self::keypair] field supplied
/// as a parameter
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
#[doc = "```"]
pub fn new(keypair: Option<Keypair>) -> Self { pub fn new(keypair: Option<Keypair>) -> Self {
Self { Self {
keypair, keypair,
@@ -321,6 +405,14 @@ impl Rosenpass {
} }
/// Add IPv4 __and__ IPv6 IF_ANY address to the listen interfaces /// Add IPv4 __and__ IPv6 IF_ANY address to the listen interfaces
///
/// I.e. listen on any interface.
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_add_if_any.rs")]
#[doc = "```"]
pub fn add_if_any(&mut self, port: u16) { pub fn add_if_any(&mut self, port: u16) {
let ipv4_any = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), port)); let ipv4_any = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), port));
let ipv6_any = SocketAddr::V6(SocketAddrV6::new( let ipv6_any = SocketAddr::V6(SocketAddrV6::new(
@@ -333,8 +425,20 @@ impl Rosenpass {
self.listen.push(ipv6_any); self.listen.push(ipv6_any);
} }
/// from chaotic args /// Parser for the old, IP style grammar.
/// Quest: the grammar is undecideable, what do we do here? ///
/// See out manual page rosenpass-exchange(1) on details about the grammar.
///
/// This function parses the grammar and turns it into an instance of the configuration
/// struct.
///
/// TODO: the grammar is undecidable, what do we do here?
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_parse_args_simple.rs")]
#[doc = "```"]
pub fn parse_args(args: Vec<String>) -> anyhow::Result<Self> { pub fn parse_args(args: Vec<String>) -> anyhow::Result<Self> {
let mut config = Self::new(Some(Keypair::new("", ""))); let mut config = Self::new(Some(Keypair::new("", "")));
@@ -525,11 +629,13 @@ impl Rosenpass {
} }
impl Default for Verbosity { impl Default for Verbosity {
/// Self::Quiet
fn default() -> Self { fn default() -> Self {
Self::Quiet Self::Quiet
} }
} }
/// Example configuration generated by the command `rosenpass gen-config <TOML-FILE>`.
pub static EXAMPLE_CONFIG: &str = r###"public_key = "/path/to/rp-public-key" pub static EXAMPLE_CONFIG: &str = r###"public_key = "/path/to/rp-public-key"
secret_key = "/path/to/rp-secret-key" secret_key = "/path/to/rp-secret-key"
listen = [] listen = []
@@ -553,7 +659,7 @@ key_out = "/path/to/rp-key-out.txt" # path to store the key
mod test { mod test {
use super::*; use super::*;
use std::{borrow::Borrow, net::IpAddr}; use std::borrow::Borrow;
fn toml_des<S: Borrow<str>>(s: S) -> Result<toml::Table, toml::de::Error> { fn toml_des<S: Borrow<str>>(s: S) -> Result<toml::Table, toml::de::Error> {
toml::from_str(s.borrow()) toml::from_str(s.borrow())
@@ -664,37 +770,6 @@ mod test {
Ok(()) Ok(())
} }
#[test]
fn test_simple_cli_parse() {
let args = split_str(
"public-key /my/public-key secret-key /my/secret-key verbose \
listen 0.0.0.0:9999 peer public-key /peer/public-key endpoint \
peer.test:9999 outfile /peer/rp-out",
);
let config = Rosenpass::parse_args(args).unwrap();
assert_eq!(
config.keypair,
Some(Keypair::new("/my/public-key", "/my/secret-key"))
);
assert_eq!(config.verbosity, Verbosity::Verbose);
assert_eq!(
&config.listen,
&vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9999)]
);
assert_eq!(
config.peers,
vec![RosenpassPeer {
public_key: PathBuf::from("/peer/public-key"),
endpoint: Some("peer.test:9999".into()),
pre_shared_key: None,
key_out: Some(PathBuf::from("/peer/rp-out")),
..Default::default()
}]
)
}
#[test] #[test]
fn test_cli_parse_multiple_peers() { fn test_cli_parse_multiple_peers() {
let args = split_str( let args = split_str(

View File

@@ -0,0 +1,10 @@
use rosenpass::config::Rosenpass;
#[test]
fn config_Rosenpass_add_if_any_example() {
let mut v = Rosenpass::empty();
v.add_if_any(4000);
assert!(v.listen.iter().any(|a| format!("{a:?}") == "0.0.0.0:4000"));
assert!(v.listen.iter().any(|a| format!("{a:?}") == "[::]:4000"));
}

View File

@@ -0,0 +1,18 @@
use rosenpass::config::{Keypair, Rosenpass};
#[test]
fn example_config_rosenpass_new() {
let (sk, pk) = ("./example.sk", "./example.pk");
assert_eq!(Rosenpass::empty(), Rosenpass::new(None));
assert_eq!(Rosenpass::empty(), Rosenpass::default());
assert_eq!(
Rosenpass::from_sk_pk(sk, pk),
Rosenpass::new(Some(Keypair::new(pk, sk)))
);
let mut v = Rosenpass::empty();
v.keypair = Some(Keypair::new(pk, sk));
assert_eq!(Rosenpass::from_sk_pk(sk, pk), v);
}

View File

@@ -0,0 +1,36 @@
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
path::PathBuf,
};
use rosenpass::config::{Keypair, Rosenpass, RosenpassPeer, Verbosity};
#[test]
fn parse_simple() {
let argv = "public-key /my/public-key secret-key /my/secret-key verbose \
listen 0.0.0.0:9999 peer public-key /peer/public-key endpoint \
peer.test:9999 outfile /peer/rp-out";
let argv = argv.split(' ').map(|s| s.to_string()).collect();
let config = Rosenpass::parse_args(argv).unwrap();
assert_eq!(
config.keypair,
Some(Keypair::new("/my/public-key", "/my/secret-key"))
);
assert_eq!(config.verbosity, Verbosity::Verbose);
assert_eq!(
&config.listen,
&vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9999)]
);
assert_eq!(
config.peers,
vec![RosenpassPeer {
public_key: PathBuf::from("/peer/public-key"),
endpoint: Some("peer.test:9999".into()),
pre_shared_key: None,
key_out: Some(PathBuf::from("/peer/rp-out")),
..Default::default()
}]
);
}

View File

@@ -0,0 +1,42 @@
use std::path::PathBuf;
use rosenpass::config::{Rosenpass, Verbosity};
#[test]
fn example_config_rosenpass_store() -> anyhow::Result<()> {
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
let tmpdir = tempfile::tempdir()?;
let sk = tmpdir.path().join("example.sk");
let pk = tmpdir.path().join("example.pk");
let cfg = tmpdir.path().join("config.toml");
let mut c = Rosenpass::from_sk_pk(&sk, &pk);
// Can not commit config, path not known
assert!(c.commit().is_err());
// We can store it to an explicit path though
c.store(&cfg)?;
// Storing does not set commitment path
assert!(c.commit().is_err());
// We can reload the config now and the configurations
// are equal if we adjust the commitment path
let mut c2 = Rosenpass::load(&cfg)?;
c.config_file_path = PathBuf::from(&cfg);
assert_eq!(c, c2);
// And this loaded config can now be committed
c2.verbosity = Verbosity::Verbose;
c2.commit()?;
// And the changes actually made it to disk
let c3 = Rosenpass::load(cfg)?;
assert_eq!(c2, c3);
assert_ne!(c, c3);
Ok(())
}

View File

@@ -0,0 +1,37 @@
use std::fs;
use rosenpass::{cli::generate_and_save_keypair, config::Rosenpass};
#[test]
fn example_config_rosenpass_validate() -> anyhow::Result<()> {
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
let tmpdir = tempfile::tempdir()?;
// Empty validates OK
assert!(Rosenpass::empty().validate().is_ok());
// Missing secret key does not pass usefulness
assert!(Rosenpass::empty().check_usefullness().is_err());
let sk = tmpdir.path().join("example.sk");
let pk = tmpdir.path().join("example.pk");
let cfg = Rosenpass::from_sk_pk(&sk, &pk);
// Missing secret key does not validate
assert!(cfg.validate().is_err());
// But passes usefulness (the configuration is useful but invalid)
assert!(cfg.check_usefullness().is_ok());
// Providing empty key files does not help
fs::write(&sk, b"")?;
fs::write(&pk, b"")?;
assert!(cfg.validate().is_err());
// But after providing proper key files, the configuration validates
generate_and_save_keypair(sk, pk)?;
assert!(cfg.validate().is_ok());
Ok(())
}