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 12 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
1 change: 1 addition & 0 deletions polkadot/scripts/packaging/polkadot.service
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Documentation=https://github.com/paritytech/polkadot

[Service]
EnvironmentFile=-/etc/default/polkadot
ExecStartPre=/usr/bin/polkadot key generate-node-key --default-base-path
ExecStart=/usr/bin/polkadot $POLKADOT_CLI_ARGS
User=polkadot
Group=polkadot
Expand Down
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
37 changes: 34 additions & 3 deletions substrate/bin/utils/subkey/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,8 @@

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

#[derive(Debug, Parser)]
Expand Down Expand Up @@ -344,11 +344,42 @@ pub enum Subkey {
/// Verify a signature for a message, provided on STDIN, with a given (public or secret) key.
Verify(VerifyCmd),
}
struct SubkeyCli;
// Implemented because GenerateNodeKeyCmd requires an implementation of SubstrateCli
impl SubstrateCli for SubkeyCli {
fn impl_name() -> String {
"subkey".into()
}

fn impl_version() -> String {
"1.0".into()
}

fn description() -> String {
"Utility for generating and restoring with Substrate keys".into()
}

fn author() -> String {
"Parity Team <[email protected]>".into()
}

fn support_url() -> String {
"https://github.com/paritytech/polkadot-sdk/issues/new".into()
}

fn copyright_start_year() -> i32 {
2024
}

fn load_spec(&self, _id: &str) -> std::result::Result<Box<dyn sc_cli::ChainSpec>, String> {
Err("Unsupported".into())
}
}

/// Run the subkey command, given the appropriate runtime.
pub fn run() -> Result<(), Error> {
match Subkey::parse() {
Subkey::GenerateNodeKey(cmd) => cmd.run(),
Subkey::GenerateNodeKey(cmd) => cmd.run(&SubkeyCli),
Subkey::Generate(cmd) => cmd.run(),
Subkey::Inspect(cmd) => cmd.run(),
Subkey::InspectNodeKey(cmd) => cmd.run(),
Expand Down
121 changes: 113 additions & 8 deletions substrate/client/cli/src/commands/generate_node_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@

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

use crate::Error;
use crate::{build_network_key_dir_or_default, Error, SubstrateCli, NODE_KEY_ED25519_FILE};
use clap::Parser;
use libp2p_identity::{ed25519, Keypair};
use sc_service::BasePath;
use std::{
fs,
io::{self, Write},
Expand All @@ -36,18 +37,33 @@ use std::{
pub struct GenerateNodeKeyCmd {
/// Name of file to save secret key to.
/// If not given, the secret key is printed to stdout.
#[arg(long)]
#[arg(long, conflicts_with_all = ["base_path", "default_base_path", "chain"])]
file: Option<PathBuf>,

/// The output is in raw binary format.
/// If not given, the output is written as an hex encoded string.
#[arg(long)]
bin: bool,

/// 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,
}

impl GenerateNodeKeyCmd {
/// Run the command
pub fn run(&self) -> Result<(), Error> {
pub fn run<C: SubstrateCli>(&self, cli: &C) -> Result<(), Error> {
let keypair = ed25519::Keypair::generate();

let secret = keypair.secret();
Expand All @@ -58,9 +74,33 @@ impl GenerateNodeKeyCmd {
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 (&self.file, &self.base_path, self.default_base_path) {
(Some(file), None, false) => fs::write(file, file_data)?,
(None, Some(_), false) | (None, None, true) => {
let chain_spec = cli.load_spec(self.chain.as_deref().unwrap_or(""))?;

let network_path = build_network_key_dir_or_default(
self.base_path.clone().map(BasePath::new),
&chain_spec,
cli,
);

fs::create_dir_all(network_path.as_path())?;

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 Ok(());
} 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());
Expand All @@ -70,19 +110,84 @@ impl GenerateNodeKeyCmd {
}

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

use super::*;
use sc_service::{ChainSpec, ChainType, GenericChainSpec, NoExtension};
use std::io::Read;
use tempfile::Builder;

struct Cli;

impl SubstrateCli for Cli {
fn impl_name() -> String {
"test".into()
}

fn impl_version() -> String {
"2.0".into()
}

fn description() -> String {
"test".into()
}

fn support_url() -> String {
"test.test".into()
}

fn copyright_start_year() -> i32 {
2021
}

fn author() -> String {
"test".into()
}

fn load_spec(&self, _: &str) -> std::result::Result<Box<dyn ChainSpec>, String> {
Ok(Box::new(
GenericChainSpec::<()>::builder(Default::default(), NoExtension::None)
.with_name("test")
.with_id("test_id")
.with_chain_type(ChainType::Development)
.with_genesis_config_patch(Default::default())
.build(),
))
}
}

#[test]
fn generate_node_key() {
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(&Cli).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(&Cli).is_ok());
let buf = fs::read_to_string(key_path.as_path()).unwrap();
assert!(array_bytes::hex2bytes(&buf).is_ok());

assert!(generate.run(&Cli).is_ok());
let new_buf = fs::read_to_string(key_path).unwrap();
assert_eq!(
array_bytes::hex2bytes(&new_buf).unwrap(),
array_bytes::hex2bytes(&buf).unwrap()
);
}
}
44 changes: 43 additions & 1 deletion substrate/client/cli/src/commands/inspect_node_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,57 @@ impl InspectNodeKeyCmd {

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

use super::{super::GenerateNodeKeyCmd, *};
use sc_service::{ChainSpec, ChainType, GenericChainSpec, NoExtension};

struct Cli;

impl SubstrateCli for Cli {
fn impl_name() -> String {
"test".into()
}

fn impl_version() -> String {
"2.0".into()
}

fn description() -> String {
"test".into()
}

fn support_url() -> String {
"test.test".into()
}

fn copyright_start_year() -> i32 {
2021
}

fn author() -> String {
"test".into()
}

fn load_spec(&self, _: &str) -> std::result::Result<Box<dyn ChainSpec>, String> {
Ok(Box::new(
GenericChainSpec::<()>::builder(Default::default(), NoExtension::None)
.with_name("test")
.with_id("test_id")
.with_chain_type(ChainType::Development)
.with_genesis_config_patch(Default::default())
.build(),
))
}
}

#[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(&Cli).is_ok());

let cmd = InspectNodeKeyCmd::parse_from(&["inspect-node-key", "--file", path]);
assert!(cmd.run().is_ok());
Expand Down
2 changes: 1 addition & 1 deletion substrate/client/cli/src/commands/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ 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) => cmd.run(cli),
KeySubcommand::Generate(cmd) => cmd.run(),
KeySubcommand::Inspect(cmd) => cmd.run(),
KeySubcommand::Insert(cmd) => cmd.run(cli),
Expand Down
41 changes: 35 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()?, cli);
let config_dir = build_config_dir(&base_path, &chain_spec);
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,32 @@ 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<C: SubstrateCli>(
base_path: Option<BasePath>,
_cli: &C,
) -> BasePath {
base_path.unwrap_or_else(|| BasePath::from_project("", "", &C::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: &Box<dyn ChainSpec>) -> 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<C: SubstrateCli>(
base_path: Option<BasePath>,
chain_spec: &Box<dyn ChainSpec>,
cli: &C,
) -> PathBuf {
let config_dir = build_config_dir(&base_path_or_default(base_path, cli), chain_spec);
build_net_config_dir(&config_dir)
}
Loading
Loading