feat: v1.0.6, support list

This commit is contained in:
2024-12-12 23:31:57 +08:00
parent 4796b53aae
commit 660a9e305d
7 changed files with 429 additions and 146 deletions

View File

@@ -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| {

View File

@@ -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),

View File

@@ -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)
}

View File

@@ -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))
}
}
}