1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! `init` subcommand

pub mod config_builder;
pub mod networks;

use self::{config_builder::ConfigBuilder, networks::Network};
use crate::{config::CONFIG_FILE_NAME, key_utils, prelude::*};
use abscissa_core::Command;
use clap::Parser;
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, Parser)]
pub struct InitCommand {
    /// Tendermint networks to configure (comma separated)
    #[clap(short = 'n', long = "networks")]
    networks: Option<String>,

    /// 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);
        key_utils::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
        );
    });
}