encrypt index
This commit is contained in:
parent
94495b040f
commit
57b7ddef8b
8 changed files with 263 additions and 81 deletions
94
Cargo.lock
generated
94
Cargo.lock
generated
|
@ -2,6 +2,15 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
|
@ -40,7 +49,9 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"blake",
|
||||
"cargo-husky",
|
||||
"chacha20poly1305",
|
||||
"criterion",
|
||||
"fail",
|
||||
"femme",
|
||||
|
@ -87,6 +98,16 @@ version = "1.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "blake"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f76cff23583935d01f1d259e546bf988450648a644da066de24717fdd015aa81"
|
||||
dependencies = [
|
||||
"gcc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
|
@ -153,6 +174,40 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chacha20poly1305"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b84ed6d1d5f7aa9bdde921a5090e0ca4d934d250ea3b402a5fab3a994e28a2a"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"chacha20",
|
||||
"cipher",
|
||||
"poly1305",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.33.3"
|
||||
|
@ -339,6 +394,12 @@ version = "1.0.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "gcc"
|
||||
version = "0.3.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.4"
|
||||
|
@ -534,6 +595,17 @@ dependencies = [
|
|||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "poly1305"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede"
|
||||
dependencies = [
|
||||
"cpufeatures",
|
||||
"opaque-debug",
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.14"
|
||||
|
@ -864,6 +936,12 @@ dependencies = [
|
|||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.80"
|
||||
|
@ -938,6 +1016,16 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.2"
|
||||
|
@ -1091,3 +1179,9 @@ name = "winapi-x86_64-pc-windows-gnu"
|
|||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf68b08513768deaa790264a7fac27a58cbf2705cfcdc9448362229217d7e970"
|
||||
|
|
|
@ -10,6 +10,8 @@ description = "modern and simple, yet efficient backup solution"
|
|||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
base64 = "0.13"
|
||||
blake = "2"
|
||||
chacha20poly1305 = "0.9"
|
||||
fail = "0.4"
|
||||
femme = "2"
|
||||
hex = "0.4"
|
||||
|
|
|
@ -2,9 +2,13 @@ use std::{
|
|||
collections::HashMap,
|
||||
fs::{self, File},
|
||||
io::Read,
|
||||
os::unix::prelude::OsStrExt,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use chacha20poly1305::aead::{Aead, NewAead};
|
||||
use chacha20poly1305::{Key, XChaCha20Poly1305, XNonce}; // Or `XChaCha20Poly1305`
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::index::item::IndexItem;
|
||||
|
@ -18,14 +22,14 @@ use nix::unistd::getpid;
|
|||
use std::{cmp::max, io::Write};
|
||||
|
||||
impl Index {
|
||||
pub fn load(repository_path: &Path) -> Result<Self> {
|
||||
pub fn load(repository_path: &Path, secret: &[u8]) -> Result<Self> {
|
||||
if !repository_path.exists() {
|
||||
let mut index = Index::new()?;
|
||||
index.save(repository_path)?;
|
||||
index.save(repository_path, secret)?;
|
||||
}
|
||||
let lock = Lock::lock(repository_path)?;
|
||||
let index_file_path = &Index::index_file_path_for_repository_path(repository_path)?;
|
||||
let index = Index::load_from_file(index_file_path)?;
|
||||
let index = Index::load_from_file(index_file_path, secret)?;
|
||||
lock.release()?;
|
||||
log::debug!(
|
||||
"[{}] loaded index from {}, version: {}; {} items",
|
||||
|
@ -37,19 +41,19 @@ impl Index {
|
|||
Ok(index)
|
||||
}
|
||||
|
||||
pub fn save(&mut self, repository_path: &Path) -> Result<()> {
|
||||
pub fn save(&mut self, repository_path: &Path, secret: &[u8]) -> Result<()> {
|
||||
let lock_id = Uuid::new_v4();
|
||||
let lock = Lock::lock(repository_path)?;
|
||||
|
||||
let index_file_path = &Index::index_file_path_for_repository_path(repository_path)?;
|
||||
if index_file_path.exists() {
|
||||
let index = Index::load_from_file(&Index::index_file_path_for_repository_path(repository_path)?)?;
|
||||
let index = Index::load_from_file(&Index::index_file_path_for_repository_path(repository_path)?, secret)?;
|
||||
self.merge_items_by_file_id(index.items_by_file_id);
|
||||
self.merge_newest_items(index.newest_items_by_source_path);
|
||||
self.version = max(self.version, index.version);
|
||||
}
|
||||
self.version = self.version.next();
|
||||
self.write_index_to_file(index_file_path)?;
|
||||
self.write_index_to_file(index_file_path, secret)?;
|
||||
lock.release()?;
|
||||
log::debug!(
|
||||
"[{}] saved index version {} with lock id {} to {}; {} items",
|
||||
|
@ -62,7 +66,7 @@ impl Index {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn write_index_to_file(&mut self, index_file_path: &Path) -> Result<()> {
|
||||
fn write_index_to_file(&mut self, index_file_path: &Path, secret: &[u8]) -> Result<()> {
|
||||
let parent = index_file_path.parent();
|
||||
match parent {
|
||||
None => Err(anyhow!(format!(
|
||||
|
@ -75,7 +79,17 @@ impl Index {
|
|||
let serialised = serde_json::to_string_pretty(&self)?;
|
||||
|
||||
let bytes = serialised.as_bytes();
|
||||
let encoded = error_correcting_encoder::encode(bytes)?;
|
||||
|
||||
let mut hash = [0; 32];
|
||||
blake::hash(256, secret, &mut hash)?;
|
||||
let key = Key::from_slice(&hash);
|
||||
let cipher = XChaCha20Poly1305::new(key);
|
||||
|
||||
blake::hash(256, index_file_path.as_os_str().as_bytes(), &mut hash)?;
|
||||
let nonce = XNonce::from_slice(&hash[0..(192 / 8)]);
|
||||
|
||||
let encrypted = cipher.encrypt(nonce, bytes.as_ref()).map_err(|e| anyhow!("{}", e))?;
|
||||
let encoded = error_correcting_encoder::encode(&encrypted)?;
|
||||
|
||||
{
|
||||
let mut file = File::create(index_file_path)?;
|
||||
|
@ -97,13 +111,22 @@ impl Index {
|
|||
}
|
||||
}
|
||||
|
||||
fn load_from_file(index_file_path: &Path) -> Result<Self> {
|
||||
fn load_from_file(index_file_path: &Path, secret: &[u8]) -> Result<Self> {
|
||||
let mut file = File::open(index_file_path)?;
|
||||
let mut encoded = vec![];
|
||||
file.read_to_end(&mut encoded)?;
|
||||
|
||||
let decoded = error_correcting_encoder::decode(&encoded)?;
|
||||
let index_text = String::from_utf8(decoded)?;
|
||||
|
||||
let mut hash = [0; 32];
|
||||
blake::hash(256, secret, &mut hash)?;
|
||||
let key = Key::from_slice(&hash);
|
||||
let cipher = XChaCha20Poly1305::new(key);
|
||||
blake::hash(256, index_file_path.as_os_str().as_bytes(), &mut hash)?;
|
||||
let nonce = XNonce::from_slice(&hash[0..(192 / 8)]);
|
||||
|
||||
let decrypted = cipher.decrypt(nonce, decoded.as_ref()).map_err(|e| anyhow!("{}", e))?;
|
||||
let index_text = String::from_utf8(decrypted)?;
|
||||
|
||||
let index: Index = serde_json::from_str(&index_text)
|
||||
.context(format!("cannot read index from: {}", index_file_path.to_string_lossy()))?;
|
||||
|
@ -144,7 +167,8 @@ mod must {
|
|||
let mut index = Index::new()?;
|
||||
let old_version = index.version;
|
||||
|
||||
index.save(temp_dir.path())?;
|
||||
let secret = b"some secret";
|
||||
index.save(temp_dir.path(), secret)?;
|
||||
|
||||
let new_version = index.version;
|
||||
|
||||
|
@ -155,11 +179,13 @@ mod must {
|
|||
|
||||
#[test]
|
||||
fn be_same_when_loaded_from_disk() -> Result<()> {
|
||||
femme::with_level(log::LevelFilter::Debug);
|
||||
let repository_path = tempdir()?;
|
||||
let mut original = Index::new()?;
|
||||
|
||||
original.save(repository_path.path())?;
|
||||
let loaded = Index::load(repository_path.path())?;
|
||||
let secret = b"some secret";
|
||||
original.save(repository_path.path(), secret)?;
|
||||
let loaded = Index::load(repository_path.path(), secret)?;
|
||||
|
||||
assert_eq!(original, loaded);
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ pub struct Repository {
|
|||
/// path to where the repository is stored on disk
|
||||
path: PathBuf,
|
||||
index: Index,
|
||||
secret: String,
|
||||
}
|
||||
|
||||
const DATA_DIR_NAME: &str = "data";
|
||||
|
@ -97,20 +98,21 @@ impl Debug for ItemId {
|
|||
}
|
||||
|
||||
impl<'a> Repository {
|
||||
pub fn init(path: &Path) -> Result<Repository> {
|
||||
pub fn init(path: &Path, secret: &str) -> Result<Repository> {
|
||||
fs::create_dir_all(path)?;
|
||||
let mut index = Index::new()?;
|
||||
index.save(path)?;
|
||||
let repository = Repository::open(path)?;
|
||||
index.save(path, secret.as_bytes())?;
|
||||
let repository = Repository::open(path, secret)?;
|
||||
fs::create_dir_all(repository.data_dir()?)?;
|
||||
Ok(repository)
|
||||
}
|
||||
|
||||
pub fn open(path: &Path) -> Result<Repository> {
|
||||
let index = Index::load(path)?;
|
||||
pub fn open(path: &Path, secret: &str) -> Result<Repository> {
|
||||
let index = Index::load(path, secret.as_bytes())?;
|
||||
let repository = Repository {
|
||||
path: path.to_path_buf(),
|
||||
index,
|
||||
secret: secret.to_owned(),
|
||||
};
|
||||
|
||||
Ok(repository)
|
||||
|
@ -121,7 +123,7 @@ impl<'a> Repository {
|
|||
}
|
||||
|
||||
pub fn save_index(&mut self) -> Result<()> {
|
||||
self.index.save(&self.path)
|
||||
self.index.save(&self.path, self.secret.as_bytes())
|
||||
}
|
||||
|
||||
pub fn store(&mut self, source_path: &Path) -> Result<()> {
|
||||
|
@ -232,9 +234,10 @@ mod must {
|
|||
let file_size2 = 27;
|
||||
let source = TestSource::new()?;
|
||||
let repository_path = tempdir()?;
|
||||
Repository::init(repository_path.path())?;
|
||||
let secret = "some secret";
|
||||
Repository::init(repository_path.path(), secret)?;
|
||||
|
||||
let mut backup_repository = Repository::open(repository_path.path())?;
|
||||
let mut backup_repository = Repository::open(repository_path.path(), secret)?;
|
||||
source.write_random_bytes_to_file("file1", file_size1)?;
|
||||
backup_repository.store(&source.file_path("file1")?)?;
|
||||
|
||||
|
|
|
@ -19,15 +19,15 @@ pub mod in_memory {
|
|||
pub fn assert_same_after_restore(source_path: &Path) -> Result<()> {
|
||||
let repository_path = tempdir()?;
|
||||
let restore_target = tempdir()?;
|
||||
|
||||
Repository::init(repository_path.path())?;
|
||||
let secret = "some secret";
|
||||
Repository::init(repository_path.path(), secret)?;
|
||||
{
|
||||
let mut backup_repository = Repository::open(repository_path.path())?;
|
||||
let mut backup_repository = Repository::open(repository_path.path(), secret)?;
|
||||
let mut backup_engine = backup::Engine::new(source_path, &mut backup_repository)?;
|
||||
backup_engine.backup()?;
|
||||
}
|
||||
{
|
||||
let mut restore_repository = Repository::open(repository_path.path())?;
|
||||
let mut restore_repository = Repository::open(repository_path.path(), secret)?;
|
||||
|
||||
let mut restore_engine = restore::Engine::new(&mut restore_repository, restore_target.path())?;
|
||||
restore_engine.restore_all()?;
|
||||
|
@ -37,8 +37,13 @@ pub mod in_memory {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn assert_restored_file_contents(repository_path: &Path, source_file_full_path: &Path, contents: &[u8]) -> Result<()> {
|
||||
let mut restore_repository = Repository::open(repository_path)?;
|
||||
pub fn assert_restored_file_contents(
|
||||
repository_path: &Path,
|
||||
secret: &str,
|
||||
source_file_full_path: &Path,
|
||||
contents: &[u8],
|
||||
) -> Result<()> {
|
||||
let mut restore_repository = Repository::open(repository_path, secret)?;
|
||||
let item = restore_repository.newest_item_by_source_path(source_file_full_path)?;
|
||||
let restore_target = tempdir()?;
|
||||
let restore_engine = restore::Engine::new(&mut restore_repository, restore_target.path())?;
|
||||
|
@ -51,11 +56,12 @@ pub mod in_memory {
|
|||
|
||||
pub fn assert_restored_from_version_has_contents(
|
||||
repository_path: &Path,
|
||||
secret: &str,
|
||||
source_file_full_path: &Path,
|
||||
old_contents: &[u8],
|
||||
old_id: &ItemId,
|
||||
) -> Result<()> {
|
||||
let mut restore_repository = Repository::open(repository_path)?;
|
||||
let mut restore_repository = Repository::open(repository_path, secret)?;
|
||||
let old_item = restore_repository.item_by_id(old_id)?;
|
||||
let restore_target = tempdir()?;
|
||||
let restore_engine = restore::Engine::new(&mut restore_repository, restore_target.path())?;
|
||||
|
@ -65,9 +71,9 @@ pub mod in_memory {
|
|||
assert_target_file_contents(&restored_file_path, old_contents)
|
||||
}
|
||||
|
||||
pub fn newest_item(repository_path: &Path, source_file_full_path: &Path) -> Result<RepositoryItem> {
|
||||
pub fn newest_item(repository_path: &Path, secret: &str, source_file_full_path: &Path) -> Result<RepositoryItem> {
|
||||
let item = {
|
||||
let reading_repository = Repository::open(repository_path)?;
|
||||
let reading_repository = Repository::open(repository_path, secret)?;
|
||||
let item = reading_repository.newest_item_by_source_path(source_file_full_path)?;
|
||||
assert!(item.is_some());
|
||||
item.unwrap()
|
||||
|
@ -75,9 +81,9 @@ pub mod in_memory {
|
|||
Ok(item)
|
||||
}
|
||||
|
||||
pub fn restore_all_from_reloaded_repository(repository_path: &Path, restore_target: &Path) -> Result<()> {
|
||||
pub fn restore_all_from_reloaded_repository(repository_path: &Path, secret: &str, restore_target: &Path) -> Result<()> {
|
||||
{
|
||||
let mut restore_repository = Repository::open(repository_path)?;
|
||||
let mut restore_repository = Repository::open(repository_path, secret)?;
|
||||
let mut restore_engine = restore::Engine::new(&mut restore_repository, restore_target)?;
|
||||
restore_engine.restore_all()?;
|
||||
Ok(())
|
||||
|
@ -87,22 +93,30 @@ pub mod in_memory {
|
|||
pub fn backup_file_with_text_contents(
|
||||
source: &TestSource,
|
||||
repository_path: &Path,
|
||||
secret: &str,
|
||||
source_file_relative_path: &str,
|
||||
contents: &str,
|
||||
) -> Result<()> {
|
||||
{
|
||||
backup_file_with_byte_contents(source, repository_path, source_file_relative_path, contents.as_bytes())
|
||||
backup_file_with_byte_contents(
|
||||
source,
|
||||
repository_path,
|
||||
secret,
|
||||
source_file_relative_path,
|
||||
contents.as_bytes(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn backup_file_with_byte_contents(
|
||||
source: &TestSource,
|
||||
repository_path: &Path,
|
||||
secret: &str,
|
||||
source_file_relative_path: &str,
|
||||
contents: &[u8],
|
||||
) -> Result<()> {
|
||||
{
|
||||
let mut backup_repository = Repository::open(repository_path)?;
|
||||
let mut backup_repository = Repository::open(repository_path, secret)?;
|
||||
|
||||
let mut backup_engine = backup::Engine::new(source.path(), &mut backup_repository)?;
|
||||
source.write_bytes_to_file(source_file_relative_path, contents).unwrap();
|
||||
|
@ -111,9 +125,9 @@ pub mod in_memory {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn data_weight(repository_path: &Path) -> Result<u64> {
|
||||
pub fn data_weight(repository_path: &Path, secret: &str) -> Result<u64> {
|
||||
{
|
||||
let repository = Repository::open(repository_path)?;
|
||||
let repository = Repository::open(repository_path, secret)?;
|
||||
repository.data_weight()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,18 +23,20 @@ mod must {
|
|||
let dir = tempdir()?;
|
||||
let repository_path = dir.path();
|
||||
let repository_path = repository_path.join(&format!("repository-{}", getpid()));
|
||||
Repository::init(&repository_path)?;
|
||||
let secret = "some secret";
|
||||
Repository::init(&repository_path, secret)?;
|
||||
|
||||
let parallel_backups_number = 16;
|
||||
let files_per_backup_number = 16;
|
||||
let total_number_of_files = parallel_backups_number * files_per_backup_number;
|
||||
|
||||
let finished_backup_runs = backup_in_parallel(&repository_path, parallel_backups_number, files_per_backup_number)?;
|
||||
let finished_backup_runs =
|
||||
backup_in_parallel(&repository_path, secret, parallel_backups_number, files_per_backup_number)?;
|
||||
assert_eq!(finished_backup_runs.len(), parallel_backups_number);
|
||||
assert!(data_weight(&repository_path)? > 0);
|
||||
assert!(data_weight(&repository_path, secret)? > 0);
|
||||
|
||||
let target_path = tempdir()?;
|
||||
let all_restored_files = restore_all(&repository_path, target_path.path())?;
|
||||
let all_restored_files = restore_all(&repository_path, secret, target_path.path())?;
|
||||
assert_eq!(all_restored_files.len(), total_number_of_files);
|
||||
|
||||
assert_all_files_in_place(parallel_backups_number, files_per_backup_number, &all_restored_files)?;
|
||||
|
@ -62,6 +64,7 @@ mod must {
|
|||
|
||||
fn backup_in_parallel(
|
||||
repository_path: &Path,
|
||||
secret: &str,
|
||||
parallel_backups_number: usize,
|
||||
files_per_backup_number: usize,
|
||||
) -> Result<Vec<usize>> {
|
||||
|
@ -73,7 +76,7 @@ mod must {
|
|||
child_pids.push(child);
|
||||
}
|
||||
Ok(ForkResult::Child) => {
|
||||
backup_process(*task_number, repository_path, files_per_backup_number)?;
|
||||
backup_process(*task_number, repository_path, secret, files_per_backup_number)?;
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
|
@ -93,8 +96,8 @@ mod must {
|
|||
Ok(task_numbers)
|
||||
}
|
||||
|
||||
fn backup_process(task_number: usize, repository_path: &Path, files_per_backup_number: usize) -> Result<()> {
|
||||
let mut repository = Repository::open(repository_path)?;
|
||||
fn backup_process(task_number: usize, repository_path: &Path, secret: &str, files_per_backup_number: usize) -> Result<()> {
|
||||
let mut repository = Repository::open(repository_path, secret)?;
|
||||
let source = TestSource::new().unwrap();
|
||||
let mut backup_engine = backup::Engine::new(source.path(), &mut repository)?;
|
||||
for i in 0..files_per_backup_number {
|
||||
|
@ -105,8 +108,8 @@ mod must {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn restore_all(repository_path: &Path, restore_target: &Path) -> Result<Vec<PathBuf>> {
|
||||
let mut restore_repository = Repository::open(repository_path)?;
|
||||
fn restore_all(repository_path: &Path, secret: &str, restore_target: &Path) -> Result<Vec<PathBuf>> {
|
||||
let mut restore_repository = Repository::open(repository_path, secret)?;
|
||||
let mut restore_engine = restore::Engine::new(&mut restore_repository, restore_target)?;
|
||||
restore_engine.restore_all()?;
|
||||
get_sorted_files_recursively(restore_target)
|
||||
|
|
|
@ -11,19 +11,20 @@ mod must {
|
|||
let source = TestSource::new().unwrap();
|
||||
let dir = tempdir().unwrap();
|
||||
let repository_path = dir.path();
|
||||
Repository::init(repository_path).unwrap();
|
||||
assert_eq!(data_weight(repository_path).unwrap(), 0);
|
||||
let secret = "some secret";
|
||||
Repository::init(repository_path, secret).unwrap();
|
||||
assert_eq!(data_weight(repository_path, secret).unwrap(), 0);
|
||||
|
||||
backup_file_with_byte_contents(&source, repository_path, "1", &contents).unwrap();
|
||||
let first_weight = data_weight(repository_path).unwrap();
|
||||
backup_file_with_byte_contents(&source, repository_path, secret, "1", &contents).unwrap();
|
||||
let first_weight = data_weight(repository_path, secret).unwrap();
|
||||
assert!(first_weight > 0);
|
||||
|
||||
backup_file_with_byte_contents(&source, repository_path, "2", &contents).unwrap();
|
||||
let second_weight = data_weight(repository_path).unwrap();
|
||||
backup_file_with_byte_contents(&source, repository_path, secret, "2", &contents).unwrap();
|
||||
let second_weight = data_weight(repository_path, secret).unwrap();
|
||||
assert_eq!(first_weight, second_weight);
|
||||
|
||||
assert_restored_file_contents(repository_path, &source.file_path("1").unwrap(), &contents).unwrap();
|
||||
assert_restored_file_contents(repository_path, &source.file_path("2").unwrap(), &contents).unwrap();
|
||||
assert_restored_file_contents(repository_path, secret, &source.file_path("1").unwrap(), &contents).unwrap();
|
||||
assert_restored_file_contents(repository_path, secret, &source.file_path("2").unwrap(), &contents).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#[cfg(test)]
|
||||
mod must {
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
use bakare::backup;
|
||||
use bakare::test::assertions::in_memory::*;
|
||||
use bakare::{repository::Repository, test::source::TestSource};
|
||||
|
@ -26,18 +29,18 @@ mod must {
|
|||
let dir = tempdir()?;
|
||||
let repository_path = dir.path();
|
||||
let restore_target = tempdir()?;
|
||||
|
||||
Repository::init(repository_path)?;
|
||||
let secret = "some secret";
|
||||
Repository::init(repository_path, secret)?;
|
||||
|
||||
let source_file_relative_path = "some file path";
|
||||
let original_contents = "some old contents";
|
||||
|
||||
backup_file_with_text_contents(&source, repository_path, source_file_relative_path, original_contents)?;
|
||||
backup_file_with_text_contents(&source, repository_path, secret, source_file_relative_path, original_contents)?;
|
||||
|
||||
restore_all_from_reloaded_repository(repository_path, restore_target.path())?;
|
||||
restore_all_from_reloaded_repository(repository_path, secret, restore_target.path())?;
|
||||
|
||||
let source_file_full_path = &source.file_path(source_file_relative_path)?;
|
||||
assert_restored_file_contents(repository_path, source_file_full_path, original_contents.as_bytes())
|
||||
assert_restored_file_contents(repository_path, secret, source_file_full_path, original_contents.as_bytes())
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -45,21 +48,28 @@ mod must {
|
|||
let source = TestSource::new().unwrap();
|
||||
let dir = tempdir()?;
|
||||
let repository_path = dir.path();
|
||||
Repository::init(repository_path)?;
|
||||
let secret = "some secret";
|
||||
Repository::init(repository_path, secret)?;
|
||||
|
||||
let source_file_relative_path = "some path";
|
||||
let source_file_full_path = source.file_path(source_file_relative_path)?;
|
||||
let old_contents = "some old contents";
|
||||
|
||||
backup_file_with_text_contents(&source, repository_path, source_file_relative_path, old_contents)?;
|
||||
backup_file_with_text_contents(&source, repository_path, secret, source_file_relative_path, old_contents)?;
|
||||
|
||||
let old_item = newest_item(repository_path, &source_file_full_path)?;
|
||||
let old_item = newest_item(repository_path, secret, &source_file_full_path)?;
|
||||
let old_id = old_item.id();
|
||||
|
||||
let new_contents = "totally new contents";
|
||||
backup_file_with_text_contents(&source, repository_path, source_file_relative_path, new_contents)?;
|
||||
backup_file_with_text_contents(&source, repository_path, secret, source_file_relative_path, new_contents)?;
|
||||
|
||||
assert_restored_from_version_has_contents(repository_path, &source_file_full_path, old_contents.as_bytes(), old_id)
|
||||
assert_restored_from_version_has_contents(
|
||||
repository_path,
|
||||
secret,
|
||||
&source_file_full_path,
|
||||
old_contents.as_bytes(),
|
||||
old_id,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -67,19 +77,20 @@ mod must {
|
|||
let source = TestSource::new().unwrap();
|
||||
let dir = tempdir()?;
|
||||
let repository_path = dir.path();
|
||||
Repository::init(repository_path)?;
|
||||
let secret = "some secret";
|
||||
Repository::init(repository_path, secret)?;
|
||||
|
||||
let source_file_relative_path = "some path";
|
||||
let source_file_full_path = source.file_path(source_file_relative_path)?;
|
||||
|
||||
backup_file_with_text_contents(&source, repository_path, source_file_relative_path, "old")?;
|
||||
backup_file_with_text_contents(&source, repository_path, secret, source_file_relative_path, "old")?;
|
||||
|
||||
let old_item = newest_item(repository_path, &source_file_full_path)?;
|
||||
let old_item = newest_item(repository_path, secret, &source_file_full_path)?;
|
||||
let old_version = old_item.version();
|
||||
|
||||
backup_file_with_text_contents(&source, repository_path, source_file_relative_path, "new")?;
|
||||
backup_file_with_text_contents(&source, repository_path, secret, source_file_relative_path, "new")?;
|
||||
|
||||
let new_item = newest_item(repository_path, &source_file_full_path)?;
|
||||
let new_item = newest_item(repository_path, secret, &source_file_full_path)?;
|
||||
let new_version = new_item.version();
|
||||
|
||||
assert!(new_version > old_version);
|
||||
|
@ -92,23 +103,25 @@ mod must {
|
|||
let source = TestSource::new().unwrap();
|
||||
let dir = tempdir()?;
|
||||
let repository_path = dir.path();
|
||||
Repository::init(repository_path)?;
|
||||
let secret = "some secret";
|
||||
Repository::init(repository_path, secret)?;
|
||||
|
||||
let source_file_relative_path = "some path";
|
||||
backup_file_with_text_contents(&source, repository_path, source_file_relative_path, "old contents")?;
|
||||
backup_file_with_text_contents(&source, repository_path, source_file_relative_path, "newer contents")?;
|
||||
backup_file_with_text_contents(&source, repository_path, source_file_relative_path, "newest contents")?;
|
||||
backup_file_with_text_contents(&source, repository_path, secret, source_file_relative_path, "old contents")?;
|
||||
backup_file_with_text_contents(&source, repository_path, secret, source_file_relative_path, "newer contents")?;
|
||||
backup_file_with_text_contents(&source, repository_path, secret, source_file_relative_path, "newest contents")?;
|
||||
|
||||
let source_file_full_path = &source.file_path(source_file_relative_path)?;
|
||||
assert_restored_file_contents(repository_path, source_file_full_path, b"newest contents")
|
||||
assert_restored_file_contents(repository_path, secret, source_file_full_path, b"newest contents")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forbid_backup_of_paths_within_repository() -> Result<()> {
|
||||
let dir = tempdir()?;
|
||||
let repository_path = dir.path();
|
||||
Repository::init(repository_path)?;
|
||||
let mut repository = Repository::open(repository_path)?;
|
||||
let secret = "some secret";
|
||||
Repository::init(repository_path, secret)?;
|
||||
let mut repository = Repository::open(repository_path, secret)?;
|
||||
|
||||
let error = backup::Engine::new(repository_path, &mut repository);
|
||||
assert!(error.is_err());
|
||||
|
@ -121,24 +134,26 @@ mod must {
|
|||
let source = TestSource::new().unwrap();
|
||||
let dir = tempdir()?;
|
||||
let repository_path = dir.path();
|
||||
Repository::init(repository_path).unwrap();
|
||||
let secret = "some secret";
|
||||
Repository::init(repository_path, secret).unwrap();
|
||||
|
||||
backup_file_with_text_contents(&source, repository_path, &filename, "some contents").unwrap();
|
||||
backup_file_with_text_contents(&source, repository_path, secret, &filename, "some contents").unwrap();
|
||||
|
||||
let repository = Repository::open(repository_path).unwrap();
|
||||
let repository = Repository::open(repository_path, secret).unwrap();
|
||||
|
||||
let second_file = repository.find_latest_by_path_fragment(&filename).unwrap().unwrap();
|
||||
assert_eq!(second_file.original_source_path(), source.file_path(&filename).unwrap().as_os_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_leak_file_names(filename in "[a-zA-Z]{4,}"){
|
||||
fn not_leak_file_names_via_file_names(filename in "[a-zA-Z]{4,}"){
|
||||
let source = TestSource::new().unwrap();
|
||||
let dir = tempdir()?;
|
||||
let repository_path = dir.path();
|
||||
Repository::init(repository_path).unwrap();
|
||||
let secret = "some secret";
|
||||
Repository::init(repository_path, secret).unwrap();
|
||||
|
||||
backup_file_with_text_contents(&source, repository_path, &filename, "some contents").unwrap();
|
||||
backup_file_with_text_contents(&source, repository_path, secret, &filename, "some contents").unwrap();
|
||||
|
||||
let walker = WalkDir::new(repository_path);
|
||||
let matching_paths = walker
|
||||
|
@ -148,6 +163,30 @@ mod must {
|
|||
|
||||
assert_eq!(matching_paths.count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_leak_file_names_via_file_contents(test_filename in "[a-zA-Z]{8,}"){
|
||||
let source = TestSource::new().unwrap();
|
||||
let dir = tempdir()?;
|
||||
let repository_path = dir.path();
|
||||
let secret = "some secret";
|
||||
Repository::init(repository_path, secret).unwrap();
|
||||
|
||||
backup_file_with_text_contents(&source, repository_path, secret, &test_filename, "some contents").unwrap();
|
||||
|
||||
let all_repo_files = get_sorted_files_recursively(repository_path).unwrap();
|
||||
assert!(all_repo_files.len() > 0);
|
||||
|
||||
for filepath in all_repo_files {
|
||||
let mut file = File::open(&filepath).unwrap();
|
||||
let filename = &filepath.as_os_str().to_string_lossy();
|
||||
let mut contents = vec![];
|
||||
file.read_to_end(&mut contents).unwrap();
|
||||
let test_filename_bytes = test_filename.as_bytes();
|
||||
let contains = contents.windows(test_filename_bytes.len()).any(move |sub_slice| sub_slice == test_filename_bytes);
|
||||
assert!(!contains, "file {} in the repository directory contains plain text file name '{}' that was previously backed up", filename, test_filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: encryption
|
||||
// TODO: resume from sleep while backup in progress
|
||||
|
|
Loading…
Reference in a new issue