use std::sync::Arc;
use cratefield_adapter_sqlite::SqliteDatabase;
use cratefield_core::Database;
use cratefield_kms::{Dek, Kms, LocalFileKms};
use cratefield_secrets::{Actor, SecretBytes, Secrets, StoreId, verify};
fn kms() -> Arc<dyn Kms> {
let kek = Dek::generate().expect("rng");
Arc::new(LocalFileKms::from_key(kek, "test-kek", "test").expect("not production"))
}
fn shared_db() -> Arc<dyn Database> {
let db = SqliteDatabase::in_memory().expect("in-memory db");
db.apply_migrations("secrets", cratefield_secrets::migrations().sqlite)
.expect("schema applies");
Arc::new(db)
}
fn actor() -> Actor {
Actor::new("test-suite").expect("named")
}
#[pollster::test]
async fn each_store_provisions_its_own_key_and_keeps_it() {
let db = shared_db();
let secrets = Secrets::new(kms());
let global = secrets.control_plane_global(Arc::clone(&db));
let tenant = secrets.tenant("tenant-a", Arc::clone(&db));
global
.put("platform/demo-key", &SecretBytes::from("g"), &actor())
.await
.expect("global put");
tenant
.put("venture/x", &SecretBytes::from("t"), &actor())
.await
.expect("tenant put");
let rows = db
.query(&cratefield_core::Statement::new(
"SELECT store, COUNT(*) AS n FROM harness_secret_keys GROUP BY store",
))
.await
.expect("keys by store");
let by_store: Vec<(String, i64)> = rows
.rows
.iter()
.map(|row| {
(
row.get::<String>("store").unwrap_or_default(),
row.get::<i64>("n").unwrap_or_default(),
)
})
.collect();
assert_eq!(
by_store,
vec![("global".to_owned(), 1), ("tenant-a".to_owned(), 1),],
"each store provisions its own data key, stamped with its store"
);
}
#[pollster::test]
async fn a_shared_database_serves_each_stores_reads_independently() {
let db = shared_db();
let secrets = Secrets::new(kms());
let global = secrets.control_plane_global(Arc::clone(&db));
let tenant = secrets.tenant("tenant-a", Arc::clone(&db));
global
.put("shared-name", &SecretBytes::from("global value"), &actor())
.await
.expect("global put");
tenant
.put("shared-name", &SecretBytes::from("tenant value"), &actor())
.await
.expect("tenant put");
assert_eq!(
global
.get("shared-name", &actor())
.await
.expect("read")
.expect("present")
.expose(),
b"global value"
);
assert_eq!(
tenant
.get("shared-name", &actor())
.await
.expect("read")
.expect("present")
.expose(),
b"tenant value"
);
assert_eq!(
global.list(&actor()).await.expect("list").len(),
1,
"a store's list holds only its own rows"
);
}
#[pollster::test]
async fn deleting_in_one_store_leaves_the_other_stores_rows_alone() {
let db = shared_db();
let secrets = Secrets::new(kms());
let global = secrets.control_plane_global(Arc::clone(&db));
let tenant = secrets.tenant("tenant-a", Arc::clone(&db));
for store in [&global, &tenant] {
store
.put("shared-name", &SecretBytes::from("v"), &actor())
.await
.expect("put");
}
global
.delete("shared-name", &actor())
.await
.expect("delete in global");
assert!(
global
.get("shared-name", &actor())
.await
.expect("read")
.is_none(),
"global's own row is soft-deleted"
);
assert_eq!(
tenant
.get("shared-name", &actor())
.await
.expect("read")
.expect("present")
.expose(),
b"v",
"the other store's row of the same name survives"
);
}
#[pollster::test]
async fn rotating_one_store_never_touches_another_stores_rows() {
let db = shared_db();
let secrets = Secrets::new(kms());
let global = secrets.control_plane_global(Arc::clone(&db));
let tenant = secrets.tenant("tenant-a", Arc::clone(&db));
global
.put(
"platform/demo-key",
&SecretBytes::from("global value"),
&actor(),
)
.await
.expect("global put");
tenant
.put("venture/x", &SecretBytes::from("tenant value"), &actor())
.await
.expect("tenant put");
let report = global
.rotate_dek(&actor(), false)
.await
.expect("rotate global");
assert_eq!(report.reencrypted, 1, "only global's own row");
assert!(
report.to_key.is_some(),
"the run completed and named the new key"
);
assert_eq!(
global
.get("platform/demo-key", &actor())
.await
.expect("read")
.expect("present")
.expose(),
b"global value"
);
assert_eq!(
tenant
.get("venture/x", &actor())
.await
.expect("read")
.expect("present")
.expose(),
b"tenant value"
);
let tenant_report = tenant
.rotate_dek(&actor(), false)
.await
.expect("rotate tenant");
assert_eq!(tenant_report.reencrypted, 1);
}
#[pollster::test]
async fn a_rewrap_touches_only_its_own_stores_keys() {
let db = shared_db();
let secrets = Secrets::new(kms());
let global = secrets.control_plane_global(Arc::clone(&db));
let tenant = secrets.tenant("tenant-a", Arc::clone(&db));
global
.put("platform/demo-key", &SecretBytes::from("g"), &actor())
.await
.expect("global put");
tenant
.put("venture/x", &SecretBytes::from("t"), &actor())
.await
.expect("tenant put");
let report = global.rewrap(&actor(), false).await.expect("rewrap global");
assert_eq!(report.keys, 1, "one store, one key to re-wrap");
let tenant_key: Vec<u8> = db
.query(&cratefield_core::Statement::new(
"SELECT wrapped_dek FROM harness_secret_keys WHERE store = 'tenant-a'",
))
.await
.expect("query")
.first()
.and_then(|row| row.get("wrapped_dek"))
.expect("the tenant's key");
let tenant_again: Vec<u8> = db
.query(&cratefield_core::Statement::new(
"SELECT wrapped_dek FROM harness_secret_keys WHERE store = 'tenant-a'",
))
.await
.expect("query")
.first()
.and_then(|row| row.get("wrapped_dek"))
.expect("the tenant's key");
assert_eq!(tenant_key, tenant_again);
}
#[pollster::test]
async fn both_stores_chains_verify_on_the_shared_ledger() {
let db = shared_db();
let secrets = Secrets::new(kms());
let global = secrets.control_plane_global(Arc::clone(&db));
let tenant = secrets.tenant("tenant-a", Arc::clone(&db));
global
.put("platform/demo-key", &SecretBytes::from("g"), &actor())
.await
.expect("global put");
tenant
.put("venture/x", &SecretBytes::from("t"), &actor())
.await
.expect("tenant put");
verify(&StoreId::Global, db.as_ref())
.await
.expect("the global store's chain verifies");
verify(&StoreId::Tenant("tenant-a".to_owned()), db.as_ref())
.await
.expect("the tenant store's chain verifies");
}
#[pollster::test]
async fn an_unstamped_row_belongs_to_no_store() {
let db = shared_db();
let secrets = Secrets::new(kms());
let global = secrets.control_plane_global(Arc::clone(&db));
let tenant = secrets.tenant("tenant-a", Arc::clone(&db));
let actor = actor();
global
.put("platform/key", &SecretBytes::from("legacy value"), &actor)
.await
.expect("put");
db.execute(&cratefield_core::Statement::with_values(
"UPDATE harness_secrets SET store = '' WHERE name = ?",
vec![sea_query_text("platform/key")],
))
.await
.expect("un-stamp the row");
assert!(
global
.get("platform/key", &actor)
.await
.expect("read")
.is_none(),
"an unstamped row is not the global store's to read"
);
tenant
.delete("platform/key", &actor)
.await
.expect("the delete runs, matching nothing");
let rows = db
.query(&cratefield_core::Statement::with_values(
"SELECT deleted_at FROM harness_secrets WHERE name = ?",
vec![sea_query_text("platform/key")],
))
.await
.expect("the row is still there");
assert_eq!(rows.len(), 1, "the row itself is never removed");
assert!(
rows.first()
.expect("one row")
.get::<String>("deleted_at")
.is_none(),
"no store may soft-delete a row it cannot account for"
);
global
.put("platform/owned", &SecretBytes::from("owned"), &actor)
.await
.expect("put");
assert_eq!(
global
.get("platform/owned", &actor)
.await
.expect("read")
.expect("present")
.expose(),
b"owned"
);
let listed: Vec<String> = global
.list(&actor)
.await
.expect("list")
.into_iter()
.map(|meta| meta.name)
.collect();
assert_eq!(
listed,
vec!["platform/owned".to_owned()],
"the unstamped row is in nobody's list, the owned one is in its own"
);
}
fn sea_query_text(value: &str) -> sea_query::Value {
sea_query::Value::String(Some(Box::new(value.to_owned())))
}