feat: v1.0.6, support list
This commit is contained in:
47
src/db.rs
47
src/db.rs
@@ -9,6 +9,11 @@ pub struct Key {
|
||||
pub comment: Option<String>,
|
||||
}
|
||||
|
||||
pub struct Keys {
|
||||
pub count: usize,
|
||||
pub keys: Vec<Key>,
|
||||
}
|
||||
|
||||
pub fn make_value_key_name(name: &str) -> String {
|
||||
format!("value:{}", name)
|
||||
}
|
||||
@@ -123,6 +128,48 @@ pub fn update_key(conn: &Connection, key: &Key) -> XResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn list_keys(conn: &Connection, ty: &str, search: &str, limit: usize) -> XResult<Keys> {
|
||||
let name = format!("{}:%{}%", ty, search);
|
||||
let mut count_stmt = conn.prepare("SELECT count(*) FROM keys WHERE name like ?1")?;
|
||||
let mut count_iter = count_stmt.query_map(params![name], |row| {
|
||||
let count: usize = row.get(0)?;
|
||||
Ok(count)
|
||||
})?;
|
||||
let count = match count_iter.next() {
|
||||
None => 0,
|
||||
Some(Ok(count)) => count,
|
||||
Some(Err(e)) => return simple_error!("List keys failed: {}", e),
|
||||
};
|
||||
log::debug!("found {} keys via: {}, limit: {}", count, name, limit);
|
||||
let mut keys = vec![];
|
||||
if count > 0 {
|
||||
let mut list_stmt =
|
||||
conn.prepare("SELECT id, name, value, comment FROM keys WHERE name like ?1 LIMIT ?2")?;
|
||||
let mut list_iter = list_stmt.query_map(params![name, limit], |row| {
|
||||
Ok(Key {
|
||||
name: row.get(1)?,
|
||||
encrypted_key: row.get(2)?,
|
||||
comment: row.get(3)?,
|
||||
})
|
||||
})?;
|
||||
|
||||
loop {
|
||||
match list_iter.next() {
|
||||
None => {
|
||||
break;
|
||||
}
|
||||
Some(Ok(r)) => {
|
||||
log::debug!("found key name={}", r.name);
|
||||
keys.push(r);
|
||||
}
|
||||
Some(Err(e)) => return simple_error!("List keys failed: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Keys { count, keys })
|
||||
}
|
||||
|
||||
pub fn find_key(conn: &Connection, name: &str) -> XResult<Option<Key>> {
|
||||
let mut stmt = conn.prepare("SELECT id, name, value, comment FROM keys WHERE name = ?1")?;
|
||||
let mut key_iter = stmt.query_map(params![name], |row| {
|
||||
|
||||
173
src/serve.rs
173
src/serve.rs
@@ -28,16 +28,55 @@ lazy_static::lazy_static! {
|
||||
pub struct CommandImpl;
|
||||
|
||||
impl Command for CommandImpl {
|
||||
fn name(&self) -> &str { "serve" }
|
||||
fn name(&self) -> &str {
|
||||
"serve"
|
||||
}
|
||||
|
||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||
SubCommand::with_name(self.name()).about("Local mini KMS serve")
|
||||
.arg(Arg::with_name("listen").long("listen").short("L").takes_value(true).default_value("127.0.0.1:5567").help("Listen"))
|
||||
.arg(Arg::with_name("local-db").long("local-db").short("d").takes_value(true).default_value("local-mini-kms.db").help("Local db file"))
|
||||
.arg(Arg::with_name("yubikey-challenge").long("yubikey-challenge").short("c").takes_value(true).help("Yubikey challenge"))
|
||||
.arg(Arg::with_name("init-encrypted-master-key").long("init-encrypted-master-key").short("k").takes_value(true).help("Init encrypted mater key"))
|
||||
.arg(Arg::with_name("log-level").long("log-level").takes_value(true).help("Log level: trace, debug, info, warn or error"))
|
||||
.arg(Arg::with_name("log-file").long("log-file").takes_value(true).help("Log file #DEFAULT or config file"))
|
||||
SubCommand::with_name(self.name())
|
||||
.about("Local mini KMS serve")
|
||||
.arg(
|
||||
Arg::with_name("listen")
|
||||
.long("listen")
|
||||
.short("L")
|
||||
.takes_value(true)
|
||||
.default_value("127.0.0.1:5567")
|
||||
.help("Listen"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("local-db")
|
||||
.long("local-db")
|
||||
.short("d")
|
||||
.takes_value(true)
|
||||
.default_value("local-mini-kms.db")
|
||||
.help("Local db file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("yubikey-challenge")
|
||||
.long("yubikey-challenge")
|
||||
.short("c")
|
||||
.takes_value(true)
|
||||
.help("Yubikey challenge"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("init-encrypted-master-key")
|
||||
.long("init-encrypted-master-key")
|
||||
.short("k")
|
||||
.takes_value(true)
|
||||
.help("Init encrypted mater key"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("log-level")
|
||||
.long("log-level")
|
||||
.takes_value(true)
|
||||
.help("Log level: trace, debug, info, warn or error"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("log-file")
|
||||
.long("log-file")
|
||||
.takes_value(true)
|
||||
.help("Log file #DEFAULT or config file"),
|
||||
)
|
||||
}
|
||||
|
||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||
@@ -48,7 +87,9 @@ impl Command for CommandImpl {
|
||||
println!("[ERROR] Init logger failed: {}", e);
|
||||
}
|
||||
|
||||
let local_mini_kms_db = sub_arg_matches.value_of("local-db").expect("Get local mini kms db error");
|
||||
let local_mini_kms_db = sub_arg_matches
|
||||
.value_of("local-db")
|
||||
.expect("Get local mini kms db error");
|
||||
match init_instance(local_mini_kms_db) {
|
||||
Ok(true) => success!("Init server success"),
|
||||
Ok(false) => failure_and_exit!("SHOULD NOT HAPPEN, server already init"),
|
||||
@@ -59,9 +100,13 @@ impl Command for CommandImpl {
|
||||
#[cfg(feature = "yubikey")]
|
||||
init_with_yubikey_challenge(&rt, sub_arg_matches);
|
||||
|
||||
let listen = sub_arg_matches.value_of("listen").expect("Get argument listen error");
|
||||
let listen = sub_arg_matches
|
||||
.value_of("listen")
|
||||
.expect("Get argument listen error");
|
||||
rt.block_on(async {
|
||||
let addr = listen.parse().unwrap_or_else(|_| panic!("Parse listen error: {}", listen));
|
||||
let addr = listen
|
||||
.parse()
|
||||
.unwrap_or_else(|_| panic!("Parse listen error: {}", listen));
|
||||
let client = Client::new();
|
||||
let new_service = make_service_fn(move |conn: &AddrStream| {
|
||||
let remote_addr = conn.remote_addr();
|
||||
@@ -94,7 +139,12 @@ async fn response_requests(
|
||||
let request_idx = GLOBAL_REQUEST_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
let process = proc::get_process(remote_addr.port());
|
||||
match process {
|
||||
None => log::info!("[{:06}] Receive request: {}, from: {}", request_idx, req.uri(), remote_addr ),
|
||||
None => log::info!(
|
||||
"[{:06}] Receive request: {}, from: {}",
|
||||
request_idx,
|
||||
req.uri(),
|
||||
remote_addr
|
||||
),
|
||||
Some(process) => log::info!(
|
||||
"[{:06}] Receive request: {}, from: {}, process: {} {} {:?}",
|
||||
request_idx,
|
||||
@@ -110,17 +160,22 @@ async fn response_requests(
|
||||
(&Method::POST, "/update") => update().await,
|
||||
(&Method::POST, "/decrypt") => serve_encrypt_decrypt::decrypt(req).await,
|
||||
(&Method::POST, "/encrypt") => serve_encrypt_decrypt::encrypt(req).await,
|
||||
(&Method::POST, "/list") => serve_read_write::list(req).await,
|
||||
(&Method::POST, "/read") => serve_read_write::read(req).await,
|
||||
(&Method::POST, "/write") => serve_read_write::write(req).await,
|
||||
(&Method::POST, "/datakey") => serve_datakey::generate(req).await,
|
||||
(&Method::GET, "/status") => serve_status::status().await,
|
||||
(&Method::GET, "/version") => get_version().await,
|
||||
(&Method::GET, "/") => get_root().await,
|
||||
_ => Ok(Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(format!("{}\n", serde_json::to_string_pretty(&json!({
|
||||
"error": "not_found",
|
||||
}))?).into())?),
|
||||
_ => Ok(Response::builder().status(StatusCode::NOT_FOUND).body(
|
||||
format!(
|
||||
"{}\n",
|
||||
serde_json::to_string_pretty(&json!({
|
||||
"error": "not_found",
|
||||
}))?
|
||||
)
|
||||
.into(),
|
||||
)?),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +185,9 @@ fn init_instance(db: &str) -> XResult<bool> {
|
||||
let conn = db::open_db(db)?;
|
||||
db::init_db(&conn)?;
|
||||
|
||||
let mut startup_rw_lock = serve_common::STATUP_RW_LOCK.lock().expect("Lock write startup rw lock error");
|
||||
let mut startup_rw_lock = serve_common::STATUP_RW_LOCK
|
||||
.lock()
|
||||
.expect("Lock write startup rw lock error");
|
||||
match &*startup_rw_lock {
|
||||
Some(_) => Ok(false),
|
||||
None => {
|
||||
@@ -146,7 +203,9 @@ fn init_instance(db: &str) -> XResult<bool> {
|
||||
}
|
||||
|
||||
fn update_instance_rsa_key_pair() -> XResult<bool> {
|
||||
let mut startup_rw_lock = serve_common::STATUP_RW_LOCK.lock().expect("Lock write startup rw lock error");
|
||||
let mut startup_rw_lock = serve_common::STATUP_RW_LOCK
|
||||
.lock()
|
||||
.expect("Lock write startup rw lock error");
|
||||
match &mut *startup_rw_lock {
|
||||
Some(k) => {
|
||||
k.instance_rsa_key_pair = jose::generate_rsa_key(4096)?;
|
||||
@@ -162,21 +221,30 @@ async fn update() -> Result<Response<Body>> {
|
||||
|
||||
async fn inner_update() -> XResult<(StatusCode, Value)> {
|
||||
let update = update_instance_rsa_key_pair()?;
|
||||
Ok((StatusCode::OK, json!({
|
||||
"update": update,
|
||||
})))
|
||||
Ok((
|
||||
StatusCode::OK,
|
||||
json!({
|
||||
"update": update,
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
async fn get_version() -> Result<Response<Body>> {
|
||||
Ok(Response::builder().body(format!(
|
||||
"{} - {}\n", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")
|
||||
).into())?)
|
||||
Ok(Response::builder().body(
|
||||
format!(
|
||||
"{} - {}\n",
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("CARGO_PKG_VERSION")
|
||||
)
|
||||
.into(),
|
||||
)?)
|
||||
}
|
||||
|
||||
async fn get_root() -> Result<Response<Body>> {
|
||||
if std::env::var("LOCAL_MINI_KMS_HELP").is_ok() {
|
||||
Ok(Response::builder().body(format!(
|
||||
r##"{} - {}
|
||||
Ok(Response::builder().body(
|
||||
format!(
|
||||
r##"{} - {}
|
||||
Supports commands:
|
||||
- GET /version
|
||||
- GET /status
|
||||
@@ -186,8 +254,12 @@ Supports commands:
|
||||
- POST /decrypt
|
||||
- POST /read
|
||||
- POST /write
|
||||
"##, env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")
|
||||
).into())?)
|
||||
"##,
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("CARGO_PKG_VERSION")
|
||||
)
|
||||
.into(),
|
||||
)?)
|
||||
} else {
|
||||
Ok(Response::builder().body("Root Not Found\n".into())?)
|
||||
}
|
||||
@@ -195,29 +267,37 @@ Supports commands:
|
||||
|
||||
#[cfg(feature = "yubikey")]
|
||||
fn init_with_yubikey_challenge(rt: &Runtime, sub_arg_matches: &ArgMatches) {
|
||||
let mut yubikey_challenge = sub_arg_matches.value_of("yubikey-challenge").map(ToString::to_string);
|
||||
let mut yubikey_challenge = sub_arg_matches
|
||||
.value_of("yubikey-challenge")
|
||||
.map(ToString::to_string);
|
||||
let init_encrypted_master_key = sub_arg_matches.value_of("init-encrypted-master-key");
|
||||
|
||||
if init_encrypted_master_key.is_some() && yubikey_challenge.is_none() {
|
||||
yubikey_challenge = rpassword::prompt_password("Yubikey challenge: ").ok();
|
||||
yubikey_challenge =
|
||||
pinentry_util::read_pin(Some("Input yubikey challenge"), Some("Challenge: "))
|
||||
.ok()
|
||||
.map(|p| p.get_pin().to_string());
|
||||
}
|
||||
|
||||
let (challenge_key, init_encrypted_master_key) = match (yubikey_challenge, init_encrypted_master_key) {
|
||||
(Some(yubikey_challenge), Some(init_encrypted_master_key)) => {
|
||||
match yubikey_hmac::yubikey_challenge_as_32_bytes(yubikey_challenge.as_bytes()) {
|
||||
Err(e) => {
|
||||
warning!("Yubikey challenge failed: {}", e);
|
||||
return;
|
||||
let (challenge_key, init_encrypted_master_key) =
|
||||
match (yubikey_challenge, init_encrypted_master_key) {
|
||||
(Some(yubikey_challenge), Some(init_encrypted_master_key)) => {
|
||||
match yubikey_hmac::yubikey_challenge_as_32_bytes(yubikey_challenge.as_bytes()) {
|
||||
Err(e) => {
|
||||
warning!("Yubikey challenge failed: {}", e);
|
||||
return;
|
||||
}
|
||||
Ok(challenge_key) => (challenge_key, init_encrypted_master_key),
|
||||
}
|
||||
Ok(challenge_key) => (challenge_key, init_encrypted_master_key),
|
||||
}
|
||||
}
|
||||
(Some(_), None) | (None, Some(_)) => {
|
||||
warning!("Arguments yubikey-challenge and init-encrypted-master-key should both assigned.");
|
||||
return;
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
(Some(_), None) | (None, Some(_)) => {
|
||||
warning!(
|
||||
"Arguments yubikey-challenge and init-encrypted-master-key should both assigned."
|
||||
);
|
||||
return;
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
match jose::deserialize_jwe_aes(init_encrypted_master_key, &challenge_key) {
|
||||
Err(e) => warning!("Yubikey seal master key failed: {}", e),
|
||||
@@ -229,7 +309,8 @@ fn init_with_yubikey_challenge(rt: &Runtime, sub_arg_matches: &ArgMatches) {
|
||||
clear_master_key_hex: Some(hex::encode(&key)),
|
||||
clear_master_key_base64: None,
|
||||
encrypted_master_key: None,
|
||||
}).await
|
||||
})
|
||||
.await
|
||||
});
|
||||
match init_master_key_result {
|
||||
Err(e) => warning!("Init master key failed: {}", e),
|
||||
|
||||
@@ -6,7 +6,14 @@ use hyper::body::Buf;
|
||||
use hyper::{Body, Request, Response, StatusCode};
|
||||
use rust_util::XResult;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct KeysQuery {
|
||||
r#type: Option<String>,
|
||||
name: Option<String>,
|
||||
limit: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Named {
|
||||
@@ -21,6 +28,34 @@ struct NamedValue {
|
||||
comment: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn list(req: Request<Body>) -> Result<Response<Body>> {
|
||||
do_response!(inner_list(req).await)
|
||||
}
|
||||
|
||||
async fn inner_list(req: Request<Body>) -> XResult<(StatusCode, Value)> {
|
||||
let whole_body = hyper::body::aggregate(req).await?;
|
||||
let keys_query: KeysQuery = serde_json::from_reader(whole_body.reader())?;
|
||||
|
||||
let conn = open_local_db()?;
|
||||
let keys = db::list_keys(
|
||||
&conn,
|
||||
keys_query.r#type.as_deref().unwrap_or("%"),
|
||||
keys_query.name.as_deref().unwrap_or(""),
|
||||
keys_query.limit.unwrap_or(10),
|
||||
)?;
|
||||
|
||||
let mut map = Map::new();
|
||||
map.insert("count".to_string(), keys.count.into());
|
||||
let keys = Value::Array(
|
||||
keys.keys
|
||||
.iter()
|
||||
.map(|k| k.name.to_string().into())
|
||||
.collect(),
|
||||
);
|
||||
map.insert("keys".to_string(), keys);
|
||||
serve_common::ok(Value::Object(map))
|
||||
}
|
||||
|
||||
pub async fn read(req: Request<Body>) -> Result<Response<Body>> {
|
||||
do_response!(inner_read(req).await)
|
||||
}
|
||||
|
||||
@@ -8,18 +8,46 @@ use rust_util::{failure_and_exit, opt_result, success};
|
||||
pub struct CommandImpl;
|
||||
|
||||
impl Command for CommandImpl {
|
||||
fn name(&self) -> &str { "yubikey-init-master-key" }
|
||||
fn name(&self) -> &str {
|
||||
"yubikey-init-master-key"
|
||||
}
|
||||
|
||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||
SubCommand::with_name(self.name()).about("Local mini KMS init by Yubikey(HMAC)")
|
||||
.arg(Arg::with_name("yubikey-challenge").long("yubikey-challenge").short("c").takes_value(true).help("Yubikey challenge"))
|
||||
.arg(Arg::with_name("key-hex").long("key-hex").short("x").takes_value(true).help("Key(hex), for encrypt"))
|
||||
.arg(Arg::with_name("key-base64").long("key-base64").short("b").takes_value(true).help("Key(base64), for encrypt"))
|
||||
.arg(Arg::with_name("generate-key").long("generate-key").short("K").help("Generate key"))
|
||||
SubCommand::with_name(self.name())
|
||||
.about("Local mini KMS init by Yubikey(HMAC)")
|
||||
.arg(
|
||||
Arg::with_name("yubikey-challenge")
|
||||
.long("yubikey-challenge")
|
||||
.short("c")
|
||||
.takes_value(true)
|
||||
.help("Yubikey challenge"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("key-hex")
|
||||
.long("key-hex")
|
||||
.short("x")
|
||||
.takes_value(true)
|
||||
.help("Key(hex), for encrypt"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("key-base64")
|
||||
.long("key-base64")
|
||||
.short("b")
|
||||
.takes_value(true)
|
||||
.help("Key(base64), for encrypt"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("generate-key")
|
||||
.long("generate-key")
|
||||
.short("K")
|
||||
.help("Generate key"),
|
||||
)
|
||||
}
|
||||
|
||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||
let yubikey_challenge_opt = sub_arg_matches.value_of("yubikey-challenge").map(ToString::to_string);
|
||||
let yubikey_challenge_opt = sub_arg_matches
|
||||
.value_of("yubikey-challenge")
|
||||
.map(ToString::to_string);
|
||||
|
||||
let hex_value_opt = sub_arg_matches.value_of("key-hex");
|
||||
let base64_value_opt = sub_arg_matches.value_of("key-base64");
|
||||
@@ -29,12 +57,18 @@ impl Command for CommandImpl {
|
||||
failure_and_exit!("--key-hex, --key-base64 or --generate-key must assign one");
|
||||
}
|
||||
let clear_master_key = if let Some(hex_value) = hex_value_opt {
|
||||
opt_result!( hex::decode(hex_value), "Decode key-hex failed: {}")
|
||||
opt_result!(hex::decode(hex_value), "Decode key-hex failed: {}")
|
||||
} else if let Some(base64_value) = base64_value_opt {
|
||||
opt_result!(STANDARD.decode(base64_value), "Decode key-base64 failed: {}")
|
||||
opt_result!(
|
||||
STANDARD.decode(base64_value),
|
||||
"Decode key-base64 failed: {}"
|
||||
)
|
||||
} else {
|
||||
let clear_master_key: [u8; 32] = random();
|
||||
success!("Clear master key generated: {}", hex::encode(clear_master_key));
|
||||
success!(
|
||||
"Clear master key generated: {}",
|
||||
hex::encode(clear_master_key)
|
||||
);
|
||||
clear_master_key.to_vec()
|
||||
};
|
||||
|
||||
@@ -42,19 +76,24 @@ impl Command for CommandImpl {
|
||||
failure_and_exit!("Master key must be 32 bytes");
|
||||
}
|
||||
|
||||
let yubikey_challenge = yubikey_challenge_opt.unwrap_or_else(
|
||||
|| match rpassword::prompt_password("Yubikey challenge: ") {
|
||||
Ok(yubikey_challenge) => yubikey_challenge,
|
||||
let yubikey_challenge = yubikey_challenge_opt.unwrap_or_else(|| {
|
||||
match pinentry_util::read_pin(Some("Input yubikey challenge"), Some("Challenge: ")) {
|
||||
Ok(yubikey_challenge) => yubikey_challenge.get_pin().to_string(),
|
||||
Err(e) => failure_and_exit!("Read yubikey challenge failed: {}", e),
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
let challenge_key = opt_result!(
|
||||
yubikey_hmac::yubikey_challenge_as_32_bytes(yubikey_challenge.as_bytes()), "Yubikey challenge failed: {}");
|
||||
yubikey_hmac::yubikey_challenge_as_32_bytes(yubikey_challenge.as_bytes()),
|
||||
"Yubikey challenge failed: {}"
|
||||
);
|
||||
|
||||
let encrypted_master_key = opt_result!(jose::serialize_jwe_aes(&clear_master_key, &challenge_key), "Encrypt master key failed: {}");
|
||||
let encrypted_master_key = opt_result!(
|
||||
jose::serialize_jwe_aes(&clear_master_key, &challenge_key),
|
||||
"Encrypt master key failed: {}"
|
||||
);
|
||||
success!("Encrypted master key: {}", encrypted_master_key);
|
||||
|
||||
Ok(Some(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user