Skip to content

Commit

Permalink
Add tmkms init subcommand (#89)
Browse files Browse the repository at this point in the history
Adds a configuration generator subcommand ala `gaiad init` which
generates a "home directory" for the KMS containing a `tmkms.toml` file
along with a Secret Connection key (`kms-identity.key`).

The configuration file is automatically customized based on what Cargo
features are enabled.
  • Loading branch information
tony-iqlusion authored Jun 23, 2020
1 parent 0baeaab commit d48c961
Show file tree
Hide file tree
Showing 26 changed files with 643 additions and 37 deletions.
3 changes: 0 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ x25519-dalek = "0.6"
yubihsm = { version = "0.34", features = ["secp256k1", "setup", "usb"], optional = true }
zeroize = "1"

[dev-dependencies]
tempfile = "3"

[dev-dependencies.abscissa_core]
version = "0.5"
features = ["testing"]
Expand Down
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,31 @@ cargo install tmkms --features=yubihsm --version=0.4.0

Alternatively, substitute `--features=ledgertm` to enable Ledger support.

## Usage
## Configuration: `tmkms init`

After compiling, start `tmkms` with the following:
The `tmkms init` command can be used to generate a directory containing
the configuration files needed to run the KMS. Run the following:

```
$ tmkms init /path/to/kms/home
```

This will output a `tmkms.toml` file, a `kms-identity.key` (used to authenticate
the KMS to the validator), and create `secrets` and `state` subdirectories.

Please look through `tmkms.toml` after it's generated, as various sections
will require some customization.

The `tmkms init` command also accepts a `-n` or `--networks` argument which can
be used to specify certain well-known Tendermint chains to initialize:

```
$ tmkms init -n cosmoshub,irishub,columbus /path/to/kms/home
```

## Running: `tmkms start`

After creading the configuration, start `tmkms` with the following:

```
$ tmkms start
Expand Down
18 changes: 12 additions & 6 deletions src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
//! Subcommands of the `tmkms` command-line application
pub mod init;
#[cfg(feature = "ledgertm")]
mod ledger;
pub mod ledger;
#[cfg(feature = "softsign")]
mod softsign;
mod start;
mod version;
pub mod softsign;
pub mod start;
pub mod version;
#[cfg(feature = "yubihsm")]
mod yubihsm;
pub mod yubihsm;

#[cfg(feature = "ledgertm")]
pub use self::ledger::LedgerCommand;
Expand All @@ -16,7 +17,8 @@ pub use self::softsign::SoftsignCommand;
#[cfg(feature = "yubihsm")]
pub use self::yubihsm::YubihsmCommand;

pub use self::{start::StartCommand, version::VersionCommand};
pub use self::{init::InitCommand, start::StartCommand, version::VersionCommand};

use crate::config::{KmsConfig, CONFIG_ENV_VAR, CONFIG_FILE_NAME};
use abscissa_core::{Command, Configurable, Help, Options, Runnable};
use std::{env, path::PathBuf};
Expand All @@ -28,6 +30,10 @@ pub enum KmsCommand {
#[options(help = "show help for a command")]
Help(Help<Self>),

/// `init` subcommand
#[options(help = "initialize KMS configuration")]
Init(InitCommand),

/// `start` subcommand
#[options(help = "start the KMS application")]
Start(StartCommand),
Expand Down
138 changes: 138 additions & 0 deletions src/commands/init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//! `init` subcommand
pub mod config_builder;
pub mod networks;

use self::{config_builder::ConfigBuilder, networks::Network};
use crate::{config::CONFIG_FILE_NAME, connection::secret_connection, prelude::*};
use abscissa_core::{Command, Options, Runnable};
use std::{
fs,
os::unix::fs::PermissionsExt,
path::{Path, PathBuf},
process,
};

/// Subdirectories to create within the parent directory
pub const SUBDIRECTORIES: &[&str] = &["schema", "secrets", "state"];

/// Filesystem permissions to set on the secrets directory
pub const SECRETS_DIR_PERMISSIONS: u32 = 0o700;

/// Default name of the Secret Connection key
pub const SECRET_CONNECTION_KEY: &str = "kms-identity.key";

/// Abort the operation, printing a formatted message and exiting the process
/// with a status code of 1 (i.e. error)
macro_rules! abort {
($fmt:expr, $($arg:tt)+) => {
status_err!(format!($fmt, $($arg)+));
process::exit(1);
};
}

/// `init` subcommand
#[derive(Command, Debug, Options)]
pub struct InitCommand {
#[options(
short = "n",
long = "networks",
help = "Tendermint networks to configure (comma separated)"
)]
networks: Option<String>,

#[options(free, help = "path where config files should be generated")]
output_paths: Vec<PathBuf>,
}

impl Runnable for InitCommand {
fn run(&self) {
if self.output_paths.len() != 1 {
eprintln!("Usage: tmkms init [-f] KMS_HOME_PATH");
process::exit(1);
}

// Parse specified networks to initialize
let mut networks = vec![];
match &self.networks {
Some(chain_ids) => {
for chain_id in chain_ids.split(',') {
networks.push(Network::parse(chain_id));
}
}
None => {
networks.push(Network::CosmosHub);
}
}

let kms_home = {
let output_path = &self.output_paths[0];

// Create KMS home directory
if !output_path.exists() {
status_ok!("Creating", "{}", output_path.display());

fs::create_dir_all(&output_path).unwrap_or_else(|e| {
abort!("couldn't create `{}`: {}", output_path.display(), e);
});
}

fs::canonicalize(&output_path).unwrap_or_else(|e| {
abort!("couldn't canonicalize `{}`: {}", output_path.display(), e);
})
};

// Create subdirectories within the KMS home directory
for subdir in SUBDIRECTORIES {
let subdir_path = kms_home.join(subdir);

fs::create_dir_all(&subdir_path).unwrap_or_else(|e| {
abort!("couldn't create `{}`: {}", subdir_path.display(), e);
});
}

// Restrict filesystem permissions to the `secrets` subdirectory
let secrets_dir = kms_home.join("secrets");

set_permissions(&secrets_dir, SECRETS_DIR_PERMISSIONS);

let config_path = kms_home.join(CONFIG_FILE_NAME);
let config_toml = ConfigBuilder::new(&kms_home, &networks).generate();

fs::write(&config_path, config_toml).unwrap_or_else(|e| {
abort!("couldn't write `{}`: {}", config_path.display(), e);
});

status_ok!("Generated", "KMS configuration: {}", config_path.display());

let secret_connection_key = secrets_dir.join(SECRET_CONNECTION_KEY);
secret_connection::generate_key(&secret_connection_key).unwrap_or_else(|e| {
abort!(
"couldn't generate `{}`: {}",
secret_connection_key.display(),
e
);
});

status_ok!(
"Generated",
"Secret Connection key: {}",
secret_connection_key.display()
);

// TODO(tarcieri): generate consensus and account keys when using softsign
}
}

/// Set Unix permissions on the given path.
///
/// On error, prints a message and exits the process with status 1 (error)
fn set_permissions(path: impl AsRef<Path>, mode: u32) {
fs::set_permissions(path.as_ref(), fs::Permissions::from_mode(mode)).unwrap_or_else(|e| {
abort!(
"couldn't set permissions on `{}`: {}",
path.as_ref().display(),
e
);
});
}
Loading

0 comments on commit d48c961

Please sign in to comment.