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, SecretsError, 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))
.expect("the tenant store opens");
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))
.expect("the tenant store opens");
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))
.expect("the tenant store opens");
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))
.expect("the tenant store opens");
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))
.expect("the tenant store opens");
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))
.expect("the tenant store opens");
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))
.expect("the tenant store opens");
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"
);
}
#[pollster::test]
async fn rotating_one_store_never_rewrites_the_other_stores_row_of_the_same_name() {
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))
.expect("the tenant store opens");
global
.put(
"stripe/api_key",
&SecretBytes::from("global value"),
&actor(),
)
.await
.expect("global put");
tenant
.put(
"stripe/api_key",
&SecretBytes::from("tenant value"),
&actor(),
)
.await
.expect("tenant put");
let before = stored_row(db.as_ref(), "tenant-a").await;
let report = global
.rotate_dek(&actor(), false)
.await
.expect("rotate global");
assert_eq!(
report.reencrypted, 1,
"only the rotating store's own row is live to it"
);
let after = stored_row(db.as_ref(), "tenant-a").await;
assert_eq!(
after, before,
"rotating one store must not touch the other store's row of the same name"
);
assert_eq!(
tenant
.get("stripe/api_key", &actor())
.await
.expect("read")
.expect("present")
.expose(),
b"tenant value"
);
assert_eq!(
global
.get("stripe/api_key", &actor())
.await
.expect("read")
.expect("present")
.expose(),
b"global value"
);
}
#[pollster::test]
async fn rotating_one_store_never_moves_another_stores_key_rows_through_their_states() {
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))
.expect("the tenant store opens");
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 tenant_before = key_rows(db.as_ref(), "tenant-a").await;
assert_eq!(tenant_before.len(), 1, "the tenant owns one key row");
assert_eq!(tenant_before[0].1, "active");
global
.rotate_dek(&actor(), false)
.await
.expect("rotate global");
let tenant_after = key_rows(db.as_ref(), "tenant-a").await;
assert_eq!(
tenant_after, tenant_before,
"the other store's key rows keep their state and their count"
);
assert_eq!(
tenant_after[0].1, "active",
"the other store's key is neither retiring nor retired"
);
assert_eq!(
tenant
.get("venture/x", &actor())
.await
.expect("read")
.expect("present")
.expose(),
b"t"
);
tenant
.put("venture/y", &SecretBytes::from("u"), &actor())
.await
.expect("a put still resolves the other store's active key");
}
#[pollster::test]
async fn a_store_cannot_unwrap_another_stores_key_row() {
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))
.expect("the tenant store opens");
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 tenant_key_id: String = db
.query(&cratefield_core::Statement::new(
"SELECT key_id FROM harness_secret_keys WHERE store = 'tenant-a'",
))
.await
.expect("read the tenant's key")
.rows
.first()
.and_then(|row| row.get("key_id"))
.expect("the tenant's key id");
db.execute(&cratefield_core::Statement::with_values(
"UPDATE harness_secrets SET key_id = ? WHERE store = 'global'",
vec![sea_query_text(&tenant_key_id)],
))
.await
.expect("repoint the row");
let err = global
.get("platform/demo-key", &actor())
.await
.expect_err("a row naming another store's key cannot be opened");
assert!(
matches!(err, SecretsError::NoKey(ref store) if store == "global"),
"the key lookup is scoped to the asking store: {err}"
);
assert_eq!(
tenant
.get("venture/x", &actor())
.await
.expect("read")
.expect("present")
.expose(),
b"t"
);
}
#[pollster::test]
async fn tenant_refuses_the_control_planes_store_name() {
let db = shared_db();
let secrets = Secrets::new(kms());
let global = secrets.control_plane_global(Arc::clone(&db));
global
.put("platform/demo-key", &SecretBytes::from("g"), &actor())
.await
.expect("global put");
let err = secrets
.tenant("global", Arc::clone(&db))
.err()
.expect("`global` is reserved for the control plane's store");
assert!(
matches!(err, SecretsError::Invalid(_)),
"the refusal is Invalid: {err}"
);
let stores: Vec<String> = db
.query(&cratefield_core::Statement::new(
"SELECT DISTINCT store FROM harness_secrets",
))
.await
.expect("read the stores stamped")
.rows
.iter()
.filter_map(|row| row.get("store"))
.collect();
assert_eq!(stores, vec!["global"], "no row was stamped `global` twice");
assert_eq!(
global
.get("platform/demo-key", &actor())
.await
.expect("read")
.expect("present")
.expose(),
b"g"
);
}
#[pollster::test]
async fn tenant_refuses_a_blank_id() {
let db = shared_db();
let secrets = Secrets::new(kms());
for id in ["", " "] {
let err = secrets
.tenant(id, Arc::clone(&db))
.err()
.expect("a blank id is nobody's store");
assert!(
matches!(err, SecretsError::Invalid(_)),
"the refusal of `{id}` is Invalid: {err}"
);
}
}
#[pollster::test]
async fn an_ordinary_tenant_id_still_opens() {
let db = shared_db();
let secrets = Secrets::new(kms());
let tenant = secrets
.tenant("acme", Arc::clone(&db))
.expect("an ordinary tenant id");
tenant
.put("venture/x", &SecretBytes::from("t"), &actor())
.await
.expect("tenant put");
assert_eq!(
tenant
.get("venture/x", &actor())
.await
.expect("read")
.expect("present")
.expose(),
b"t"
);
}
fn sea_query_text(value: &str) -> sea_query::Value {
sea_query::Value::String(Some(Box::new(value.to_owned())))
}
async fn stored_row(db: &dyn Database, store: &str) -> (String, Vec<u8>, Vec<u8>) {
let rows = db
.query(&cratefield_core::Statement::with_values(
"SELECT key_id, nonce, ciphertext FROM harness_secrets WHERE store = ?",
vec![sea_query_text(store)],
))
.await
.expect("read the store's secret rows");
let row = rows.rows.first().expect("the store has a secret row");
(
row.get::<String>("key_id").expect("key_id"),
row.get::<Vec<u8>>("nonce").expect("nonce"),
row.get::<Vec<u8>>("ciphertext").expect("ciphertext"),
)
}
async fn key_rows(db: &dyn Database, store: &str) -> Vec<(String, String)> {
db.query(&cratefield_core::Statement::with_values(
"SELECT key_id, state FROM harness_secret_keys WHERE store = ? ORDER BY key_id",
vec![sea_query_text(store)],
))
.await
.expect("read the store's key rows")
.rows
.iter()
.map(|row| {
(
row.get::<String>("key_id").expect("key_id"),
row.get::<String>("state").expect("state"),
)
})
.collect()
}