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

Authorization Setup for Write Access on RPC Calls #620

Merged
merged 14 commits into from
Oct 16, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
420 changes: 242 additions & 178 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ members = [
"ipld/graphsync",
"utils/bigint",
"tests/serialization_tests",
"utils/auth",
"utils/bitfield",
"utils/test_utils",
"utils/commcid",
Expand Down
3 changes: 2 additions & 1 deletion forest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ pin-project-lite = "0.1"
message_pool = { package = "message_pool", path = "../blockchain/message_pool" }
wallet = {package = "key_management", path = "../key_management"}
jsonrpc-v2 = { version = "0.5.2", features = ["easy-errors", "macros"] }
uuid = { version = "0.8.1", features = ["v4"] }
uuid = { version = "0.8.1", features = ["v4"] }
auth = { path = "../utils/auth"}
40 changes: 40 additions & 0 deletions forest/src/cli/auth_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2020 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

// Copyright 2020 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

flodesi marked this conversation as resolved.
Show resolved Hide resolved
use super::stringify_rpc_err;
use rpc_client::{auth_new, new_client};
use structopt::StructOpt;

#[derive(Debug, StructOpt)]
pub enum AuthCommands {
/// Create a new Authentication token with given permission
#[structopt(about = "<String> Create Authentication token with given permission")]
CreateToken {
#[structopt(
short,
help = "permission to assign to the token, one of: read, write, sign, admin"
)]
perm: String,
},
}

impl AuthCommands {
pub async fn run(&self) {
// TODO handle cli config
match self {
Self::CreateToken { perm } => {
let perm: String = perm.parse().unwrap();
let mut client = new_client();

let obj = auth_new(&mut client, perm)
.await
.map_err(stringify_rpc_err)
flodesi marked this conversation as resolved.
Show resolved Hide resolved
.unwrap();
println!("{}", serde_json::to_string_pretty(&obj).unwrap());
}
}
}
}
5 changes: 5 additions & 0 deletions forest/src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// Copyright 2020 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

mod auth_cmd;
mod chain_cmd;
mod config;
mod fetch_params_cmd;
mod genesis;
mod genesis_cmd;

pub(super) use self::auth_cmd::AuthCommands;
pub(super) use self::chain_cmd::ChainCommands;
pub use self::config::Config;
pub(super) use self::fetch_params_cmd::FetchCommands;
Expand Down Expand Up @@ -50,6 +52,9 @@ pub enum Subcommand {
#[structopt(name = "chain", about = "Interact with Filecoin blockchain")]
Chain(ChainCommands),

#[structopt(name = "auth", about = "Manage RPC Permissions")]
Auth(AuthCommands),

#[structopt(name = "genesis", about = "Work with blockchain genesis")]
Genesis(GenesisCommands),
}
Expand Down
12 changes: 8 additions & 4 deletions forest/src/daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use super::cli::{block_until_sigint, initialize_genesis, Config};
use async_std::sync::RwLock;
use async_std::task;
use auth::generate_priv_key;
use beacon::DrandBeacon;
use chain::ChainStore;
use chain_sync::ChainSyncer;
Expand All @@ -15,7 +16,7 @@ use message_pool::{MessagePool, MpoolRpcProvider};
use rpc::{start_rpc, RpcState};
use std::sync::Arc;
use utils::write_to_file;
use wallet::PersistentKeyStore;
use wallet::{KeyStore, PersistentKeyStore};

/// Starts daemon process
pub(super) async fn start(config: Config) {
Expand All @@ -38,9 +39,12 @@ pub(super) async fn start(config: Config) {
});

// Initialize keystore
let keystore = Arc::new(RwLock::new(
PersistentKeyStore::new(config.data_dir.to_string()).unwrap(),
));
let mut ks = PersistentKeyStore::new(config.data_dir.to_string()).unwrap();
if ks.get("auth-jwt-private").is_err() {
flodesi marked this conversation as resolved.
Show resolved Hide resolved
ks.put("auth-jwt-private".to_owned(), generate_priv_key())
.unwrap();
}
let keystore = Arc::new(RwLock::new(ks));

// Initialize database
let mut db = RocksDb::new(config.data_dir + "/db");
Expand Down
4 changes: 4 additions & 0 deletions forest/src/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ pub(super) async fn process(command: Subcommand) {
Subcommand::Chain(cmd) => {
cmd.run().await;
}
Subcommand::Auth(cmd) => {
cmd.run().await;
}

Subcommand::Genesis(cmd) => {
cmd.run().await;
}
Expand Down
10 changes: 7 additions & 3 deletions key_management/src/keystore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ const KEYSTORE_NAME: &str = "/keystore.json";

/// KeyInfo struct, this contains the type of key (stored as a string) and the private key.
/// note how the private key is stored as a byte vector
///
/// TODO need to update keyinfo to not use SignatureType, use string instead to save keys like
/// jwt secret
#[derive(Clone, PartialEq, Debug, Eq, Serialize, Deserialize)]
pub struct KeyInfo {
key_type: SignatureType,
Expand Down Expand Up @@ -148,7 +151,8 @@ pub struct PersistentKeyStore {

impl PersistentKeyStore {
pub fn new(location: String) -> Result<Self, Error> {
let file_op = File::open(&format!("{}{}", location, KEYSTORE_NAME));
let loc = format!("{}{}", location, KEYSTORE_NAME);
let file_op = File::open(&loc);
match file_op {
Ok(file) => {
let reader = BufReader::new(file);
Expand All @@ -160,15 +164,15 @@ impl PersistentKeyStore {
.unwrap_or_default();
Ok(Self {
key_info: data,
location,
location: loc,
})
}
Err(e) => {
if e.kind() == ErrorKind::NotFound {
warn!("keystore.json does not exist, initializing new keystore");
Ok(Self {
key_info: HashMap::new(),
location,
location: loc,
})
} else {
Err(Error::Other(e.to_string()))
Expand Down
4 changes: 3 additions & 1 deletion node/rpc-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ serde_json = "1.0"
jsonrpc-v2 = { version = "0.5.2", features = ["easy-errors", "macros"] }
log = "0.4.8"
crypto = { package = "forest_crypto", path = "../../crypto", features = ["json"] }
wallet = {package = "key_management", path = "../../key_management", features = ["json"] }
wallet = {package = "key_management", path = "../../key_management", features = ["json"] }
lazy_static = "1.4.0"
auth = { path = "../../utils/auth"}
20 changes: 20 additions & 0 deletions node/rpc-client/src/auth_ops.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2020 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

use super::client::Filecoin;
use auth::*;
use jsonrpc_v2::Error as JsonRpcError;
use jsonrpsee::raw::RawClient;
use jsonrpsee::transport::http::HttpTransportClient as HTC;

/// Creates a new JWT Token
pub async fn auth_new(client: &mut RawClient<HTC>, perm: String) -> Result<String, JsonRpcError> {
let ret: String = match perm.as_str() {
"admin" => Filecoin::auth_new(client, ADMIN.clone()).await?,
"sign" => Filecoin::auth_new(client, SIGN.clone()).await?,
"write" => Filecoin::auth_new(client, WRITE.clone()).await?,
"read" => Filecoin::auth_new(client, READ.clone()).await?,
_ => return Err(JsonRpcError::INVALID_PARAMS),
};
Ok(ret)
}
3 changes: 3 additions & 0 deletions node/rpc-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ use message::unsigned_message::json::UnsignedMessageJson;

jsonrpsee::rpc_api! {
pub Filecoin {
/// Auth
#[rpc(method = "Filecoin.AuthNew", positional_params)]
fn auth_new(perm: Vec<String>) -> String;
/// Chain
#[rpc(method = "Filecoin.ChainGetBlock", positional_params)]
fn chain_get_block(cid: CidJson) -> BlockHeaderJson;
Expand Down
2 changes: 2 additions & 0 deletions node/rpc-client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Copyright 2020 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

mod auth_ops;
mod chain_ops;
mod client;

pub use self::auth_ops::*;
pub use self::chain_ops::*;
pub use self::client::*;
4 changes: 4 additions & 0 deletions node/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ num-bigint = { path = "../../utils/bigint", package = "forest_bigint" }
thiserror = "1.0"
state_tree = { path = "../../vm/state_tree" }
forest_libp2p = { path = "../forest_libp2p" }
jsonwebtoken = "7.2.0"
lazy_static = "1.4.0"
auth = { path = "../../utils/auth"}
utils = { path = "../../node/utils" }

[dev-dependencies]
db = { path = "../db" }
Expand Down
40 changes: 40 additions & 0 deletions node/rpc/src/auth_api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2020 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

use crate::RpcState;
use auth::*;
use blockstore::BlockStore;
use jsonrpc_v2::{Data, Error as JsonRpcError, Params};
use wallet::KeyStore;

/// RPC call to create a new JWT Token
pub(crate) async fn auth_new<DB, KS>(
data: Data<RpcState<DB, KS>>,
Params(params): Params<(Vec<String>,)>,
) -> Result<String, JsonRpcError>
where
DB: BlockStore + Send + Sync + 'static,
KS: KeyStore + Send + Sync + 'static,
{
let (perms,) = params;
let ks = data.keystore.read().await;
let ki = ks.get("auth-jwt-private")?;
let token = create_token(perms, ki.private_key())?;
Ok(token)
}

/// RPC call to verify JWT Token and return the token's permissions
pub(crate) async fn auth_verify<DB, KS>(
data: Data<RpcState<DB, KS>>,
Params(params): Params<(String,)>,
) -> Result<Vec<String>, JsonRpcError>
where
DB: BlockStore + Send + Sync + 'static,
KS: KeyStore + Send + Sync + 'static,
{
let ks = data.keystore.read().await;
let (token,) = params;
let ki = ks.get("auth-jwt-private")?;
let perms = verify_token(&token, ki.private_key())?;
Ok(perms)
}
54 changes: 53 additions & 1 deletion node/rpc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,38 @@
// Copyright 2020 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

#[macro_use]
extern crate lazy_static;

mod auth_api;
mod chain_api;
mod mpool_api;
mod sync_api;
mod wallet_api;

use async_std::sync::{RwLock, Sender};
use auth::{has_perms, Error};
use blockstore::BlockStore;
use chain_sync::{BadBlockCache, SyncState};
use forest_libp2p::NetworkMessage;
use jsonrpc_v2::{Data, MapRouter, RequestObject, Server};
use jsonrpc_v2::{Data, Error as JsonRpcError, ErrorLike, MapRouter, RequestObject, Server};
use message_pool::{MessagePool, MpoolRpcProvider};
use std::sync::Arc;
use tide::{Request, Response, StatusCode};
use utils::get_home_dir;
use wallet::KeyStore;
use wallet::PersistentKeyStore;

lazy_static! {
pub static ref WRITE_ACCESS: Vec<String> = vec![
"Filecoin.MpoolPush".to_string(),
"Filecoin.WalletNew".to_string(),
"Filecoin.WalletHas".to_string(),
"Filecoin.WalletList".to_string(),
"Filecoin.WalletDefaultAddress".to_string(),
"Filecoin.WalletList".to_string(),
];
}
flodesi marked this conversation as resolved.
Show resolved Hide resolved

/// This is where you store persistant data, or at least access to stateful data.
pub struct RpcState<DB, KS>
Expand All @@ -33,6 +51,35 @@ where

async fn handle_json_rpc(mut req: Request<Server<MapRouter>>) -> tide::Result {
let call: RequestObject = req.body_json().await?;
// TODO find a cleaner way *if possibe* to parse the RequestObject to get the method name in RPC call
let call_str = format!("{:?}", call);
let start = call_str
.find("method: \"")
.ok_or_else(|| Error::MethodParam)?
+ 9;
let end = call_str
.find("\", params")
.ok_or_else(|| Error::MethodParam)?;
let method_name = &call_str[start..end];
// check for write access
if WRITE_ACCESS.contains(&method_name.to_string()) {
if let Some(header) = req.header("Authorization") {
let header_raw = header.get(0).unwrap().message();
let keystore = PersistentKeyStore::new(get_home_dir() + "/.forest")?;
let ki = keystore
.get("auth-jwt-private")
.map_err(|_| Error::Other("No JWT private key found".to_owned()))?;
let key = ki.private_key();
let perm = has_perms(header_raw, "write", key);
if perm.is_err() {
return Ok(Response::new(StatusCode::Ok).body_json(&perm.unwrap_err())?);
}
} else {
return Ok(Response::new(StatusCode::Ok)
.body_json(&JsonRpcError::from(Error::NoAuthHeader))?);
}
}

let res = req.state().handle(call).await;
Ok(Response::new(StatusCode::Ok).body_json(&res)?)
}
Expand All @@ -42,13 +89,18 @@ where
DB: BlockStore + Send + Sync + 'static,
KS: KeyStore + Send + Sync + 'static,
{
use auth_api::*;
use chain_api::*;
use mpool_api::*;
use sync_api::*;
use wallet_api::*;

let rpc = Server::new()
.with_data(Data::new(state))
// Auth API
.with_method("Filecoin.AuthNew", auth_new::<DB, KS>)
.with_method("Filecoin.AuthVerify", auth_verify::<DB, KS>)
// Chain API
.with_method(
"Filecoin.ChainGetMessage",
chain_api::chain_get_message::<DB, KS>,
Expand Down
15 changes: 15 additions & 0 deletions utils/auth/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "auth"
version = "0.1.0"
authors = ["ChainSafe Systems <info@chainsafe.io>"]
edition = "2018"

[dependencies]
jsonrpc-v2 = { version = "0.5.2", features = ["easy-errors", "macros"] }
jsonwebtoken = "7.2.0"
lazy_static = "1.4.0"
serde = { version = "1.0.101", default-features = false, features = ["derive"] }
thiserror = "1.0"
wallet = {package = "key_management", path = "../../key_management" }
rand = "0.7.3"
crypto = { package = "forest_crypto", path = "../../crypto" }
Loading