v0.25.1
This commit is contained in:
1 parent
87e571c170
commit
fa7abc9e53
308 files changed
+49746
-50237
No files matched your search
+9
-108
@@ -59,10 +59,7 @@ fn read_key(path: &Path) -> Result<Zeroizing<[u8; KEY_BYTES]>> {
|
||||
metadata.is_file() && !metadata.file_type().is_symlink(),
|
||||
"secret store master key must be a regular file"
|
||||
);
|
||||
ensure!(
|
||||
metadata.len() == KEY_BYTES as u64,
|
||||
"invalid secret store master key size"
|
||||
);
|
||||
ensure!(metadata.len() == KEY_BYTES as u64, "invalid secret store master key size");
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
@@ -82,10 +79,7 @@ fn read_key(path: &Path) -> Result<Zeroizing<[u8; KEY_BYTES]>> {
|
||||
let mut file = options.open(path)?;
|
||||
file.read_exact(bytes.as_mut())?;
|
||||
let mut extra = [0u8; 1];
|
||||
ensure!(
|
||||
file.read(&mut extra)? == 0,
|
||||
"invalid secret store master key size"
|
||||
);
|
||||
ensure!(file.read(&mut extra)? == 0, "invalid secret store master key size");
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
@@ -164,10 +158,7 @@ pub async fn put(
|
||||
metadata: serde_json::Value,
|
||||
) -> Result<()> {
|
||||
validate_identity(secret_id, provider)?;
|
||||
ensure!(
|
||||
!plaintext.is_empty() && plaintext.len() <= MAX_SECRET_BYTES,
|
||||
"invalid secret size"
|
||||
);
|
||||
ensure!(!plaintext.is_empty() && plaintext.len() <= MAX_SECRET_BYTES, "invalid secret size");
|
||||
let metadata = serde_json::to_string(&metadata)?;
|
||||
ensure!(metadata.len() <= 4096, "secret metadata is too large");
|
||||
initialize(data_dir)?;
|
||||
@@ -221,16 +212,12 @@ pub async fn get(
|
||||
}
|
||||
validate_identity(secret_id, &provider)?;
|
||||
let version: i64 = row.try_get("encryption_version")?;
|
||||
ensure!(
|
||||
version == ENCRYPTION_VERSION,
|
||||
"unsupported secret encryption version"
|
||||
);
|
||||
ensure!(version == ENCRYPTION_VERSION, "unsupported secret encryption version");
|
||||
let nonce: Vec<u8> = row.try_get("nonce")?;
|
||||
ensure!(nonce.len() == NONCE_BYTES, "invalid secret nonce");
|
||||
let mut ciphertext = Zeroizing::new(row.try_get::<Vec<u8>, _>("ciphertext")?);
|
||||
ensure!(
|
||||
ciphertext.len() >= AES_256_GCM.tag_len()
|
||||
&& ciphertext.len() <= MAX_SECRET_BYTES + AES_256_GCM.tag_len(),
|
||||
ciphertext.len() >= AES_256_GCM.tag_len() && ciphertext.len() <= MAX_SECRET_BYTES + AES_256_GCM.tag_len(),
|
||||
"invalid encrypted secret size"
|
||||
);
|
||||
let mut nonce_bytes = [0u8; NONCE_BYTES];
|
||||
@@ -300,10 +287,7 @@ pub async fn migrate_acme_credentials(
|
||||
return Ok(0);
|
||||
}
|
||||
let root_meta = fs::symlink_metadata(&root)?;
|
||||
ensure!(
|
||||
root_meta.is_dir() && !root_meta.file_type().is_symlink(),
|
||||
"invalid ACME directory"
|
||||
);
|
||||
ensure!(root_meta.is_dir() && !root_meta.file_type().is_symlink(), "invalid ACME directory");
|
||||
let mut migrated = 0usize;
|
||||
for entry in fs::read_dir(&root)? {
|
||||
let entry = entry?;
|
||||
@@ -317,10 +301,7 @@ pub async fn migrate_acme_credentials(
|
||||
continue;
|
||||
}
|
||||
let profile: serde_json::Value = serde_json::from_slice(&fs::read(&profile_path)?)?;
|
||||
let provider = profile
|
||||
.get("provider")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.unwrap_or("");
|
||||
let provider = profile.get("provider").and_then(serde_json::Value::as_str).unwrap_or("");
|
||||
let certificate_id = profile
|
||||
.get("certificate_id")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
@@ -360,8 +341,7 @@ pub async fn migrate_acme_credentials(
|
||||
)
|
||||
.await?;
|
||||
plaintext.zeroize();
|
||||
fs::remove_file(&credential_path)
|
||||
.context("legacy ACME credential was encrypted but could not be removed")?;
|
||||
fs::remove_file(&credential_path).context("legacy ACME credential was encrypted but could not be removed")?;
|
||||
migrated += 1;
|
||||
}
|
||||
Ok(migrated)
|
||||
@@ -392,83 +372,4 @@ pub fn write_restored_master_key(data_dir: &Path, key: &[u8]) -> Result<()> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn secret_round_trip_is_encrypted_in_sqlite_and_survives_key_reload() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
crate::auth::prepare_directory(dir.path()).unwrap();
|
||||
let database = dir.path().join("config.sqlite3");
|
||||
crate::auth::prepare_database(&database).unwrap();
|
||||
let store = crate::db::Store::open(&database, &crate::config::Config::default())
|
||||
.await
|
||||
.unwrap();
|
||||
initialize(dir.path()).unwrap();
|
||||
let secret = b"cloudflare-example-token-0123456789";
|
||||
put(
|
||||
&store,
|
||||
dir.path(),
|
||||
"provider/cloudflare/demo/api_token",
|
||||
"cloudflare",
|
||||
secret,
|
||||
serde_json::json!({"id":"demo"}),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let row: Vec<u8> = sqlx::query_scalar(
|
||||
"SELECT ciphertext FROM secret_store WHERE secret_id='provider/cloudflare/demo/api_token'",
|
||||
)
|
||||
.fetch_one(&store.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(!row.windows(secret.len()).any(|window| window == secret));
|
||||
let value = get(
|
||||
&store,
|
||||
dir.path(),
|
||||
"provider/cloudflare/demo/api_token",
|
||||
Some("cloudflare"),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(value.as_slice(), secret);
|
||||
drop(value);
|
||||
assert_eq!(
|
||||
read_key(&master_key_path(dir.path())).unwrap().len(),
|
||||
KEY_BYTES
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn tampered_ciphertext_is_rejected() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
crate::auth::prepare_directory(dir.path()).unwrap();
|
||||
let database = dir.path().join("config.sqlite3");
|
||||
crate::auth::prepare_database(&database).unwrap();
|
||||
let store = crate::db::Store::open(&database, &crate::config::Config::default())
|
||||
.await
|
||||
.unwrap();
|
||||
put(
|
||||
&store,
|
||||
dir.path(),
|
||||
"acme/site/cloudflare/credential",
|
||||
"cloudflare",
|
||||
b"secret-value",
|
||||
serde_json::json!({}),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
sqlx::query("UPDATE secret_store SET ciphertext = zeroblob(length(ciphertext))")
|
||||
.execute(&store.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(get(
|
||||
&store,
|
||||
dir.path(),
|
||||
"acme/site/cloudflare/credential",
|
||||
Some("cloudflare")
|
||||
)
|
||||
.await
|
||||
.is_err());
|
||||
}
|
||||
}
|
||||
include!("secret_store/tests.rs");
|
||||
Reference in new issue
Block a user