Skip to content

Commit

Permalink
Merge pull request #1539 from golemfactory/prekucki/id-auto-provisioning
Browse files Browse the repository at this point in the history
Prekucki/id auto provisioning
  • Loading branch information
tworec authored Aug 10, 2021
2 parents 9294a73 + a3d3584 commit 54c2683
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 57 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions core/identity/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ structopt = "0.3"
thiserror = "1.0"
tokio = { version = "0.2", features = ["fs", "blocking"] }
uuid = { version = "0.8", features = ["v4"] }
rustc-hex = "2.1.0"

[dev-dependencies]
ya-service-api-derive = "0.1"
Expand Down
37 changes: 37 additions & 0 deletions core/identity/src/autoconf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use std::env;

use ethsign::*;
use rustc_hex::FromHex;

use ya_core_model::NodeId;

use crate::id_key::IdentityKey;
use anyhow::Context;

// autoconfiguration
const ENV_AUTOCONF_PK: &str = "YAGNA_AUTOCONF_ID_SECRET";
const ENV_AUTOCONF_APP_KEY: &str = "YAGNA_AUTOCONF_APPKEY";

pub fn preconfigured_identity(password: Protected) -> anyhow::Result<Option<IdentityKey>> {
let secret_hex: Vec<u8> = match env::var(ENV_AUTOCONF_PK) {
Ok(v) => v
.from_hex()
.with_context(|| format!("Failed to parse identity from {}", ENV_AUTOCONF_PK))?,
Err(_) => return Ok(None),
};
let secret = SecretKey::from_raw(&secret_hex)?;
Ok(Some(IdentityKey::from_secret(None, secret, password)))
}

pub fn preconfigured_node_id() -> anyhow::Result<Option<NodeId>> {
let secret_hex: Vec<u8> = match env::var(ENV_AUTOCONF_PK) {
Ok(v) => v.from_hex()?,
Err(_) => return Ok(None),
};
let secret = SecretKey::from_raw(&secret_hex)?;
Ok(Some(NodeId::from(secret.public().address().as_ref())))
}

pub fn preconfigured_appkey() -> anyhow::Result<Option<String>> {
Ok(env::var(ENV_AUTOCONF_APP_KEY).ok())
}
25 changes: 23 additions & 2 deletions core/identity/src/dao/identity.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
pub use crate::db::models::Identity;
use crate::db::schema as s;
use diesel::prelude::*;

use ya_persistence::executor::{
do_with_transaction, readonly_transaction, AsDao, ConnType, PoolType,
};

pub use crate::db::models::Identity;
use crate::db::schema as s;

type Result<T> = std::result::Result<T, super::Error>;

pub struct IdentityDao<'c> {
Expand Down Expand Up @@ -62,6 +64,25 @@ impl<'c> IdentityDao<'c> {
.await
}

pub async fn init_preconfigured(&self, preconfigured_identity: Identity) -> Result<Identity> {
use crate::db::schema::identity::dsl as id_dsl;
self.with_transaction(move |conn| {
if let Some(id) = id_dsl::identity
.filter(id_dsl::identity_id.eq(preconfigured_identity.identity_id))
.get_result::<Identity>(conn)
.optional()?
{
Ok(id)
} else {
diesel::insert_into(s::identity::table)
.values(&preconfigured_identity)
.execute(conn)?;
Ok(preconfigured_identity)
}
})
.await
}

pub async fn init_default_key<KeyGenerator: Send + 'static + FnOnce() -> Result<Identity>>(
&self,
generator: KeyGenerator,
Expand Down
51 changes: 45 additions & 6 deletions core/identity/src/id_key.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
#![allow(unused)]

use crate::dao::identity::Identity;
use crate::dao::Error;
use std::convert::TryFrom;

use anyhow::Context;
use ethsign::keyfile::Bytes;
use ethsign::{KeyFile, Protected, SecretKey};
use rand::Rng;
use std::convert::TryFrom;
use ya_client_model::NodeId;

use crate::dao::identity::Identity;
use crate::dao::Error;

pub struct IdentityKey {
id: NodeId,
alias: Option<String>,
Expand Down Expand Up @@ -79,6 +81,17 @@ impl IdentityKey {
}
Ok(())
}

pub fn from_secret(alias: Option<String>, secret: SecretKey, password: Protected) -> Self {
let key_file = key_file_from_secret(&secret, password);
let id = NodeId::from(secret.public().address().as_ref());
IdentityKey {
id,
alias,
key_file,
secret: Some(secret),
}
}
}

impl TryFrom<Identity> for IdentityKey {
Expand All @@ -101,6 +114,10 @@ impl TryFrom<Identity> for IdentityKey {
const KEY_ITERATIONS: u32 = 10240;
const KEYSTORE_VERSION: u64 = 3;

pub fn default_password() -> Protected {
Protected::new(Vec::default())
}

pub fn generate_new(alias: Option<String>, password: Protected) -> IdentityKey {
let (key_file, secret) = generate_new_secret(password);
let id = NodeId::from(secret.public().address().as_ref());
Expand All @@ -115,17 +132,39 @@ pub fn generate_new(alias: Option<String>, password: Protected) -> IdentityKey {
fn generate_new_secret(password: Protected) -> (KeyFile, SecretKey) {
let random_bytes: [u8; 32] = rand::thread_rng().gen();
let secret = SecretKey::from_raw(random_bytes.as_ref()).unwrap();
let key_file = KeyFile {
let key_file = key_file_from_secret(&secret, password);
(key_file, secret)
}

fn key_file_from_secret(secret: &SecretKey, password: Protected) -> KeyFile {
KeyFile {
id: format!("{}", uuid::Uuid::new_v4()),
version: KEYSTORE_VERSION,
crypto: secret.to_crypto(&password, KEY_ITERATIONS).unwrap(),
address: Some(Bytes(secret.public().address().to_vec())),
};
(key_file, secret)
}
}

pub fn generate_new_keyfile(password: Protected) -> anyhow::Result<String> {
let (key_file, _) = generate_new_secret(password);

Ok(serde_json::to_string(&key_file).context("serialize keyfile")?)
}

#[cfg(test)]
mod test {
use rustc_hex::FromHex;

use super::*;

#[test]
fn test_import_raw_key() -> anyhow::Result<()> {
let pk = "c19a9a827c9efb910e3e4efb955b57d072775c5ebb93dbdd4d6856d97e555eca";
let pk_bytes: Vec<u8> = pk.from_hex()?;
println!("{}", pk.len());
let secret: SecretKey = SecretKey::from_raw(&pk_bytes)?;
let key_file = key_file_from_secret(&secret, Protected::new(""));
println!("{}", serde_json::to_string_pretty(&key_file)?);
Ok(())
}
}
1 change: 1 addition & 0 deletions core/identity/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ extern crate diesel;
pub mod cli;
pub mod service;

mod autoconf;
pub mod dao;
mod db;
mod id_key;
4 changes: 2 additions & 2 deletions core/identity/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
use futures::lock::Mutex;
use std::sync::Arc;

use crate::cli::Command;

use ya_persistence::executor::DbExecutor;
use ya_service_api_interfaces::{Provider, Service};

use crate::cli::Command;

mod appkey;
mod identity;

Expand Down
67 changes: 48 additions & 19 deletions core/identity/src/service/appkey.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
use uuid::Uuid;
use std::cell::{Ref, RefCell};
use std::collections::HashMap;
use std::rc::Rc;

use actix_rt::Arbiter;
use chrono::Utc;
use futures::prelude::*;
use uuid::Uuid;
use ya_service_bus::{typed as bus, RpcEndpoint};

use ya_core_model::appkey as model;
use ya_core_model::identity as idm;
use ya_persistence::executor::DbExecutor;
use ya_service_bus::typed as bus;

use crate::dao::AppKeyDao;
use actix_rt::Arbiter;
use std::cell::{Ref, RefCell};
use std::collections::HashMap;
use std::rc::Rc;

#[derive(Default)]
struct Subscription {
Expand Down Expand Up @@ -84,23 +87,49 @@ pub async fn activate(db: &DbExecutor) -> anyhow::Result<()> {
});

let dbx = db.clone();
let preconfigured_appkey = crate::autoconf::preconfigured_appkey()?;
let preconfigured_node_id = crate::autoconf::preconfigured_node_id()?;
let start_datetime = Utc::now().naive_utc();
// Retrieve an application key entry based on the key itself
let _ = bus::bind(&model::BUS_ID, move |get: model::Get| {
let db = dbx.clone();
let preconfigured_appkey = preconfigured_appkey.clone();
async move {
let (appkey, role) = db
.as_dao::<AppKeyDao>()
.get(get.key)
.await
.map_err(|e| model::Error::internal(e.to_string()))?;

Ok(model::AppKey {
name: appkey.name,
key: appkey.key,
role: role.name,
identity: appkey.identity_id,
created_date: appkey.created_date,
})
if preconfigured_appkey.as_ref() == Some(&get.key) {
let node_id = match preconfigured_node_id {
Some(node_id) => node_id,
None => {
let default_identity = bus::service(idm::BUS_ID)
.send(idm::Get::ByDefault)
.await
.map_err(model::Error::internal)?
.map_err(model::Error::internal)?
.ok_or_else(|| model::Error::internal("appkey not found"))?;
default_identity.node_id
}
};
Ok(model::AppKey {
name: "autoconfigured".to_string(),
key: get.key.clone(),
role: model::DEFAULT_ROLE.to_string(),
identity: node_id,
created_date: start_datetime.clone(),
})
} else {
let (appkey, role) = db
.as_dao::<AppKeyDao>()
.get(get.key)
.await
.map_err(|e| model::Error::internal(e.to_string()))?;

Ok(model::AppKey {
name: appkey.name,
key: appkey.key,
role: role.name,
identity: appkey.identity_id,
created_date: appkey.created_date,
})
}
}
});

Expand Down
71 changes: 43 additions & 28 deletions core/identity/src/service/identity.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
use chrono::Utc;
use ethsign::{KeyFile, Protected};
use futures::lock::Mutex;
use std::cell::{Ref, RefCell};
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use std::rc::Rc;
use std::sync::Arc;

use actix_rt::Arbiter;
use chrono::Utc;
use ethsign::{KeyFile, Protected};
use futures::lock::Mutex;
use futures::prelude::*;
use ya_client_model::NodeId;
use ya_service_bus::typed as bus;

use ya_core_model::identity as model;
use ya_persistence::executor::DbExecutor;
use ya_service_bus::typed as bus;

use crate::dao::identity::Identity;
use crate::dao::{Error as DaoError, IdentityDao};
use crate::id_key::{generate_new, IdentityKey};
use actix_rt::Arbiter;
use futures::prelude::*;
use std::cell::{Ref, RefCell};
use std::rc::Rc;
use crate::id_key::{default_password, generate_new, IdentityKey};

#[derive(Default)]
struct Subscription {
Expand Down Expand Up @@ -83,25 +84,39 @@ impl IdentityService {
})
}

let default_key = db
.as_dao::<IdentityDao>()
.init_default_key(|| {
log::info!("generating new default identity");
let key: IdentityKey = generate_new(None, "".into()).into();
let new_identity = Identity {
identity_id: key.id(),
key_file_json: key.to_key_file().map_err(|e| DaoError::internal(e))?,
is_default: true,
is_deleted: false,
alias: None,
note: None,
created_date: Utc::now().naive_utc(),
};

Ok(new_identity)
})
.await?
.identity_id;
let default_key =
if let Some(key) = crate::autoconf::preconfigured_identity(default_password())? {
db.as_dao::<IdentityDao>()
.init_preconfigured(Identity {
identity_id: key.id(),
key_file_json: key.to_key_file()?,
is_default: true,
is_deleted: false,
alias: None,
note: None,
created_date: Utc::now().naive_utc(),
})
.await?
.identity_id
} else {
db.as_dao::<IdentityDao>()
.init_default_key(|| {
log::info!("generating new default identity");
let key: IdentityKey = generate_new(None, "".into()).into();

Ok(Identity {
identity_id: key.id(),
key_file_json: key.to_key_file().map_err(|e| DaoError::internal(e))?,
is_default: true,
is_deleted: false,
alias: None,
note: None,
created_date: Utc::now().naive_utc(),
})
})
.await?
.identity_id
};

log::info!("using default identity: {:?}", default_key);

Expand Down

0 comments on commit 54c2683

Please sign in to comment.