Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent accidental change of network-key for active authorities #3852

Merged
merged 28 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e798480
Prevent accidental change of network-key for active authorities
alexggh Mar 27, 2024
f32aca3
Make tests happy
alexggh Mar 27, 2024
ec2c1cc
Fix tests
alexggh Mar 27, 2024
ffcabfd
Fixup node key as well
alexggh Mar 27, 2024
f17fa19
Make clippy happy
alexggh Mar 27, 2024
41a37f7
Fix typo
alexggh Mar 27, 2024
6b5d8ff
Update substrate/client/cli/src/commands/generate_node_key.rs
alexggh Mar 27, 2024
4771ad5
Update substrate/client/cli/src/commands/generate_node_key.rs
alexggh Mar 27, 2024
5a581ba
Update polkadot/.rpm/polkadot.spec
alexggh Mar 28, 2024
f7b25ed
Update polkadot/scripts/packaging/deb-maintainer-scripts/postinst
alexggh Mar 28, 2024
f0c0653
Generate key with PreExecStart
alexggh Mar 28, 2024
361255e
Address review feedback
alexggh Mar 28, 2024
f14821b
Trivial updates
alexggh Mar 29, 2024
4b96b9f
Remove PreStartExec
alexggh Mar 29, 2024
c15527b
Merge remote-tracking branch 'origin/master' into alexaggh/node_identity
alexggh Apr 3, 2024
35291ca
Address review feedback
alexggh Apr 4, 2024
c4b9658
Fixup unittest
alexggh Apr 4, 2024
369cc13
Cli update
alexggh Apr 5, 2024
93ac920
Fix unused
alexggh Apr 5, 2024
db5e1d7
Cargo fmt
alexggh Apr 9, 2024
89635bb
Merge remote-tracking branch 'origin/master' into alexaggh/node_identity
alexggh Apr 9, 2024
37534d2
Merge branch 'master' into alexaggh/node_identity
alexggh Apr 10, 2024
6b94cc7
Update substrate/client/cli/src/error.rs
alexggh Apr 11, 2024
9f13c16
Add prdoc
alexggh Apr 11, 2024
64e40e5
Merge branch 'master' into alexaggh/node_identity
alexggh Apr 11, 2024
d05cc30
Merge branch 'master' into alexaggh/node_identity
alexggh Apr 12, 2024
6630a1e
Merge branch 'master' into alexaggh/node_identity
bkchr Apr 12, 2024
a85f870
Merge branch 'master' into alexaggh/node_identity
alexggh Apr 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion polkadot/tests/benchmark_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,13 @@ async fn build_chain(runtime: &str, base_path: &Path) {
let mut cmd = Command::new(cargo_bin("polkadot"))
.stdout(process::Stdio::piped())
.stderr(process::Stdio::piped())
.args(["--chain", runtime, "--force-authoring", "--alice"])
.args([
"--chain",
runtime,
"--force-authoring",
"--alice",
"--unsafe-force-node-key-generation",
])
.arg("-d")
.arg(base_path)
.arg("--no-hardware-benchmarks")
Expand Down
4 changes: 2 additions & 2 deletions substrate/bin/utils/subkey/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@

use clap::Parser;
use sc_cli::{
Error, GenerateCmd, GenerateNodeKeyCmd, InspectKeyCmd, InspectNodeKeyCmd, SignCmd, VanityCmd,
Error, GenerateCmd, GenerateKeyCmdCommon, InspectKeyCmd, InspectNodeKeyCmd, SignCmd, VanityCmd,
VerifyCmd,
};

Expand All @@ -324,7 +324,7 @@ use sc_cli::{
pub enum Subkey {
/// Generate a random node key, write it to a file or stdout and write the
/// corresponding peer-id to stderr
GenerateNodeKey(GenerateNodeKeyCmd),
GenerateNodeKey(GenerateKeyCmdCommon),

/// Generate a random account
Generate(GenerateCmd),
Expand Down
150 changes: 124 additions & 26 deletions substrate/client/cli/src/commands/generate_node_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,19 @@

//! Implementation of the `generate-node-key` subcommand

use crate::Error;
use clap::Parser;
use crate::{build_network_key_dir_or_default, Error, NODE_KEY_ED25519_FILE};
use clap::{Args, Parser};
use libp2p_identity::{ed25519, Keypair};
use sc_service::BasePath;
use std::{
fs,
io::{self, Write},
path::PathBuf,
};

/// The `generate-node-key` command
#[derive(Debug, Parser)]
#[command(
name = "generate-node-key",
about = "Generate a random node key, write it to a file or stdout \
and write the corresponding peer-id to stderr"
)]
pub struct GenerateNodeKeyCmd {
/// Common arguments accross all generate key commands, subkey and node.
#[derive(Debug, Args, Clone)]
pub struct GenerateKeyCmdCommon {
/// Name of file to save secret key to.
/// If not given, the secret key is printed to stdout.
#[arg(long)]
Expand All @@ -45,32 +41,111 @@ pub struct GenerateNodeKeyCmd {
bin: bool,
}

impl GenerateNodeKeyCmd {
/// The `generate-node-key` command
#[derive(Debug, Clone, Parser)]
#[command(
name = "generate-node-key",
about = "Generate a random node key, write it to a file or stdout \
and write the corresponding peer-id to stderr"
)]
pub struct GenerateNodeKeyCmd {
#[clap(flatten)]
pub common: GenerateKeyCmdCommon,
/// Specify the chain specification.
///
/// It can be any of the predefined chains like dev, local, staging, polkadot, kusama.
#[arg(long, value_name = "CHAIN_SPEC")]
pub chain: Option<String>,
/// A directory where the key should be saved. If a key already
/// exists in the directory, it won't be overwritten.
#[arg(long, conflicts_with_all = ["file", "default_base_path"])]
base_path: Option<PathBuf>,

/// Save the key in the default directory. If a key already
/// exists in the directory, it won't be overwritten.
#[arg(long, conflicts_with_all = ["base_path", "file"])]
default_base_path: bool,
alexggh marked this conversation as resolved.
Show resolved Hide resolved
}

impl GenerateKeyCmdCommon {
/// Run the command
pub fn run(&self) -> Result<(), Error> {
let keypair = ed25519::Keypair::generate();
generate_key(&self.file, self.bin, None, &None, false, None)
}
}

impl GenerateNodeKeyCmd {
/// Run the command
pub fn run(&self, chain_spec_id: &str, executable_name: &String) -> Result<(), Error> {
generate_key(
&self.common.file,
self.common.bin,
Some(chain_spec_id),
&self.base_path,
self.default_base_path,
Some(executable_name),
)
}
}

// Utility function for generating a key based on the provided CLI arguments
//
// `file` - Name of file to save secret key to
// `bin`
fn generate_key(
file: &Option<PathBuf>,
bin: bool,
chain_spec_id: Option<&str>,
base_path: &Option<PathBuf>,
default_base_path: bool,
executable_name: Option<&String>,
) -> Result<(), Error> {
let keypair = ed25519::Keypair::generate();

let secret = keypair.secret();
let secret = keypair.secret();

let file_data = if self.bin {
secret.as_ref().to_owned()
} else {
array_bytes::bytes2hex("", secret).into_bytes()
};
let file_data = if bin {
secret.as_ref().to_owned()
} else {
array_bytes::bytes2hex("", secret).into_bytes()
};

match &self.file {
Some(file) => fs::write(file, file_data)?,
None => io::stdout().lock().write_all(&file_data)?,
}
match (file, base_path, default_base_path) {
(Some(file), None, false) => fs::write(file, file_data)?,
(None, Some(_), false) | (None, None, true) => {
let network_path = build_network_key_dir_or_default(
base_path.clone().map(BasePath::new),
chain_spec_id.unwrap_or_default(),
executable_name.ok_or(Error::Input("Executable name not provided".into()))?,
);

eprintln!("{}", Keypair::from(keypair).public().to_peer_id());
fs::create_dir_all(network_path.as_path())?;

Ok(())
let key_path = network_path.join(NODE_KEY_ED25519_FILE);
if key_path.exists() {
eprintln!("Skip generation, a key already exists in {:?}", key_path);
return Err(Error::KeyAlreadyExistsInPath(key_path));
} else {
eprintln!("Generating key in {:?}", key_path);
fs::write(key_path, file_data)?
}
},
(None, None, false) => io::stdout().lock().write_all(&file_data)?,
(_, _, _) => {
// This should not happen, arguments are marked as mutually exclusive.
return Err(Error::Input("Mutually exclusive arguments provided".into()));
},
}

eprintln!("{}", Keypair::from(keypair).public().to_peer_id());

Ok(())
}

#[cfg(test)]
mod tests {
pub mod tests {
use crate::DEFAULT_NETWORK_CONFIG_PATH;

use super::*;
use std::io::Read;
use tempfile::Builder;
Expand All @@ -80,9 +155,32 @@ mod tests {
let mut file = Builder::new().prefix("keyfile").tempfile().unwrap();
let file_path = file.path().display().to_string();
let generate = GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--file", &file_path]);
assert!(generate.run().is_ok());
assert!(generate.run("test", &String::from("test")).is_ok());
let mut buf = String::new();
assert!(file.read_to_string(&mut buf).is_ok());
assert!(array_bytes::hex2bytes(&buf).is_ok());
}

#[test]
fn generate_node_key_base_path() {
let base_dir = Builder::new().prefix("keyfile").tempdir().unwrap();
let key_path = base_dir
.path()
.join("chains/test_id/")
.join(DEFAULT_NETWORK_CONFIG_PATH)
.join(NODE_KEY_ED25519_FILE);
let base_path = base_dir.path().display().to_string();
let generate =
GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--base-path", &base_path]);
assert!(generate.run("test_id", &String::from("test")).is_ok());
let buf = fs::read_to_string(key_path.as_path()).unwrap();
assert!(array_bytes::hex2bytes(&buf).is_ok());

assert!(generate.run("test_id", &String::from("test")).is_err());
let new_buf = fs::read_to_string(key_path).unwrap();
assert_eq!(
array_bytes::hex2bytes(&new_buf).unwrap(),
array_bytes::hex2bytes(&buf).unwrap()
);
}
}
6 changes: 4 additions & 2 deletions substrate/client/cli/src/commands/inspect_node_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,17 @@ impl InspectNodeKeyCmd {

#[cfg(test)]
mod tests {
use super::{super::GenerateNodeKeyCmd, *};
use crate::commands::generate_node_key::GenerateNodeKeyCmd;

use super::*;

#[test]
fn inspect_node_key() {
let path = tempfile::tempdir().unwrap().into_path().join("node-id").into_os_string();
let path = path.to_str().unwrap();
let cmd = GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--file", path]);

assert!(cmd.run().is_ok());
assert!(cmd.run("test", &String::from("test")).is_ok());

let cmd = InspectNodeKeyCmd::parse_from(&["inspect-node-key", "--file", path]);
assert!(cmd.run().is_ok());
Expand Down
5 changes: 4 additions & 1 deletion substrate/client/cli/src/commands/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ impl KeySubcommand {
/// run the key subcommands
pub fn run<C: SubstrateCli>(&self, cli: &C) -> Result<(), Error> {
match self {
KeySubcommand::GenerateNodeKey(cmd) => cmd.run(),
KeySubcommand::GenerateNodeKey(cmd) => {
let chain_spec = cli.load_spec(cmd.chain.as_deref().unwrap_or(""))?;
cmd.run(chain_spec.id(), &C::executable_name())
},
KeySubcommand::Generate(cmd) => cmd.run(),
KeySubcommand::Inspect(cmd) => cmd.run(),
KeySubcommand::Insert(cmd) => cmd.run(cli),
Expand Down
2 changes: 1 addition & 1 deletion substrate/client/cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ mod verify;
pub use self::{
build_spec_cmd::BuildSpecCmd, chain_info_cmd::ChainInfoCmd, check_block_cmd::CheckBlockCmd,
export_blocks_cmd::ExportBlocksCmd, export_state_cmd::ExportStateCmd, generate::GenerateCmd,
generate_node_key::GenerateNodeKeyCmd, import_blocks_cmd::ImportBlocksCmd,
generate_node_key::GenerateKeyCmdCommon, import_blocks_cmd::ImportBlocksCmd,
insert_key::InsertKeyCmd, inspect_key::InspectKeyCmd, inspect_node_key::InspectNodeKeyCmd,
key::KeySubcommand, purge_chain_cmd::PurgeChainCmd, revert_cmd::RevertCmd, run_cmd::RunCmd,
sign::SignCmd, vanity::VanityCmd, verify::VerifyCmd,
Expand Down
42 changes: 36 additions & 6 deletions substrate/client/cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,8 +428,10 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
/// By default this is retrieved from `NodeKeyParams` if it is available. Otherwise its
/// `NodeKeyConfig::default()`.
fn node_key(&self, net_config_dir: &PathBuf) -> Result<NodeKeyConfig> {
let is_dev = self.is_dev()?;
let role = self.role(is_dev)?;
self.node_key_params()
.map(|x| x.node_key(net_config_dir))
.map(|x| x.node_key(net_config_dir, role, is_dev))
.unwrap_or_else(|| Ok(Default::default()))
}

Expand Down Expand Up @@ -463,11 +465,9 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
let is_dev = self.is_dev()?;
let chain_id = self.chain_id(is_dev)?;
let chain_spec = cli.load_spec(&chain_id)?;
let base_path = self
.base_path()?
.unwrap_or_else(|| BasePath::from_project("", "", &C::executable_name()));
let config_dir = base_path.config_dir(chain_spec.id());
let net_config_dir = config_dir.join(DEFAULT_NETWORK_CONFIG_PATH);
let base_path = base_path_or_default(self.base_path()?, &C::executable_name());
let config_dir = build_config_dir(&base_path, chain_spec.id());
let net_config_dir = build_net_config_dir(&config_dir);
let client_id = C::client_id();
let database_cache_size = self.database_cache_size()?.unwrap_or(1024);
let database = self.database()?.unwrap_or(
Expand Down Expand Up @@ -665,3 +665,33 @@ pub fn generate_node_name() -> String {
}
}
}

/// Returns the value of `base_path` or the default_path if it is None
pub(crate) fn base_path_or_default(
base_path: Option<BasePath>,
executable_name: &String,
) -> BasePath {
base_path.unwrap_or_else(|| BasePath::from_project("", "", executable_name))
}

/// Returns the default path for configuration directory based on the chain_spec
pub(crate) fn build_config_dir(base_path: &BasePath, chain_spec_id: &str) -> PathBuf {
base_path.config_dir(chain_spec_id)
}

/// Returns the default path for the network configuration inside the configuration dir
pub(crate) fn build_net_config_dir(config_dir: &PathBuf) -> PathBuf {
config_dir.join(DEFAULT_NETWORK_CONFIG_PATH)
}

/// Returns the default path for the network directory starting from the provided base_path
/// or from the default base_path.
pub(crate) fn build_network_key_dir_or_default(
base_path: Option<BasePath>,
chain_spec_id: &str,
executable_name: &String,
) -> PathBuf {
let config_dir =
build_config_dir(&base_path_or_default(base_path, executable_name), chain_spec_id);
build_net_config_dir(&config_dir)
}
17 changes: 17 additions & 0 deletions substrate/client/cli/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

//! Initialization errors.

use std::path::PathBuf;

use sp_core::crypto;

/// Result type alias for the CLI.
Expand Down Expand Up @@ -78,6 +80,21 @@ pub enum Error {

#[error(transparent)]
GlobalLoggerError(#[from] sc_tracing::logging::Error),

#[error(
"Starting an authorithy without network key in {0}.
\n This is not a safe operation because other authorities in the network may depend on your node having a stable identity.
\n Otherwise these other authorities may not being able to reach you.
\n If it is the first time running your node you could use one of the following methods:
\n 1. [Preferred] Separately generate the key with: <NODE_BINARY> key generate-node-key --base-path <YOUR_BASE_PATH>
\n 2. [Preferred] Separately generate the key with: <NODE_BINARY> key generate-node-key --file <YOUR_PATH_TO_NODE_KEY>
\n 3. [Preferred] Separately generate the key with: <NODE_BINARY> key generate-node-key --default-base-path
\n 4. [Unsafe] Pass --unsafe-force-node-key-generation and make sure you remove it for subsequent node restarts"

alexggh marked this conversation as resolved.
Show resolved Hide resolved
)]
NetworkKeyNotFound(PathBuf),
#[error("A network key already exists in path {0}")]
KeyAlreadyExistsInPath(PathBuf),
}

impl From<&str> for Error {
Expand Down
Loading
Loading