summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2021-05-29 11:38:06 +0300
committerLars Wirzenius <liw@liw.fi>2021-05-31 16:40:32 +0300
commit7a88987b3c10c81845fce749b7b86b62770aecca (patch)
tree2d2919a327f5b088f5efcb21e7edd728b287fce2
parent4e95e1003c4f2c89a807977b34f287d2f200f5bc (diff)
downloadobnam2-7a88987b3c10c81845fce749b7b86b62770aecca.tar.gz
refactor: only send/receive encrypted chunks with server
Also, refactor the client code to split out the HTTP bits more clearly. The refactor should've been separately, but got entangled with other changes during frantic debugging.
-rw-r--r--src/client.rs130
1 files changed, 65 insertions, 65 deletions
diff --git a/src/client.rs b/src/client.rs
index 00f5bd7..b1f9976 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -1,17 +1,18 @@
-use crate::checksummer::sha256;
use crate::chunk::DataChunk;
use crate::chunk::{GenerationChunk, GenerationChunkError};
use crate::chunker::{Chunker, ChunkerError};
use crate::chunkid::ChunkId;
use crate::chunkmeta::ChunkMeta;
-use crate::config::ClientConfig;
+use crate::cipher::{CipherEngine, CipherError};
+use crate::config::{ClientConfig, ClientConfigError};
use crate::fsentry::{FilesystemEntry, FilesystemKind};
use crate::generation::{FinishedGeneration, LocalGeneration, LocalGenerationError};
use crate::genlist::GenerationList;
use chrono::{DateTime, Local};
-use log::{debug, error, info, trace};
+use log::{debug, error, info};
use reqwest::blocking::Client;
+use reqwest::header::HeaderMap;
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
@@ -22,6 +23,9 @@ pub enum ClientError {
#[error("Server response claimed it had created a chunk, but lacked chunk id")]
NoCreatedChunkId,
+ #[error("Server does not have {0}")]
+ NotFound(String),
+
#[error("Server does not have chunk {0}")]
ChunkNotFound(String),
@@ -35,6 +39,12 @@ pub enum ClientError {
WrongChecksum(ChunkId, String, String),
#[error(transparent)]
+ ClientConfigError(#[from] ClientConfigError),
+
+ #[error(transparent)]
+ CipherError(#[from] CipherError),
+
+ #[error(transparent)]
GenerationChunkError(#[from] GenerationChunkError),
#[error(transparent)]
@@ -184,11 +194,13 @@ impl BackupClient {
pub struct ChunkClient {
client: Client,
base_url: String,
+ cipher: CipherEngine,
}
impl ChunkClient {
pub fn new(config: &ClientConfig) -> ClientResult<Self> {
- let config = config.config();
+ let pass = config.passwords()?;
+
let client = Client::builder()
.danger_accept_invalid_certs(!config.verify_tls_cert)
.build()
@@ -196,6 +208,7 @@ impl ChunkClient {
Ok(Self {
client,
base_url: config.server_url.to_string(),
+ cipher: CipherEngine::new(&pass),
})
}
@@ -208,44 +221,30 @@ impl ChunkClient {
}
pub fn has_chunk(&self, meta: &ChunkMeta) -> ClientResult<Option<ChunkId>> {
- trace!("has_chunk: url={:?}", self.base_url());
- let req = self
- .client
- .get(&self.chunks_url())
- .query(&[("sha256", meta.sha256())])
- .build()
- .map_err(ClientError::ReqwestError)?;
+ let body = match self.get("", &[("sha256", meta.sha256())]) {
+ Ok((_, body)) => body,
+ Err(err) => return Err(err),
+ };
- let res = self.client.execute(req).map_err(ClientError::ChunkExists)?;
- debug!("has_chunk: status={}", res.status());
- let has = if res.status() != 200 {
- debug!("has_chunk: error from server");
- None
+ let hits: HashMap<String, ChunkMeta> =
+ serde_json::from_slice(&body).map_err(ClientError::JsonParse)?;
+ let mut iter = hits.iter();
+ let has = if let Some((chunk_id, _)) = iter.next() {
+ Some(chunk_id.into())
} else {
- let text = res.text().map_err(ClientError::ReqwestError)?;
- debug!("has_chunk: text={:?}", text);
- let hits: HashMap<String, ChunkMeta> =
- serde_json::from_str(&text).map_err(ClientError::JsonParse)?;
- debug!("has_chunk: hits={:?}", hits);
- let mut iter = hits.iter();
- if let Some((chunk_id, _)) = iter.next() {
- debug!("has_chunk: chunk_id={:?}", chunk_id);
- Some(chunk_id.into())
- } else {
- None
- }
+ None
};
- info!("has_chunk result: {:?}", has);
Ok(has)
}
pub fn upload_chunk(&self, chunk: DataChunk) -> ClientResult<ChunkId> {
+ let enc = self.cipher.encrypt_chunk(&chunk)?;
let res = self
.client
.post(&self.chunks_url())
.header("chunk-meta", chunk.meta().to_json())
- .body(chunk.data().to_vec())
+ .body(enc.ciphertext().to_vec())
.send()
.map_err(ClientError::ReqwestError)?;
debug!("upload_chunk: res={:?}", res);
@@ -261,20 +260,8 @@ impl ChunkClient {
}
pub fn list_generations(&self) -> ClientResult<GenerationList> {
- let url = format!("{}?generation=true", &self.chunks_url());
- trace!("list_generations: url={:?}", url);
- let req = self
- .client
- .get(&url)
- .build()
- .map_err(ClientError::ReqwestError)?;
- let res = self
- .client
- .execute(req)
- .map_err(ClientError::ReqwestError)?;
- debug!("list_generations: status={}", res.status());
- let body = res.bytes().map_err(ClientError::ReqwestError)?;
- debug!("list_generations: body={:?}", body);
+ let (_, body) = self.get("", &[("generation", "true")])?;
+
let map: HashMap<String, ChunkMeta> =
serde_yaml::from_slice(&body).map_err(ClientError::YamlParse)?;
debug!("list_generations: map={:?}", map);
@@ -286,52 +273,65 @@ impl ChunkClient {
}
pub fn fetch_chunk(&self, chunk_id: &ChunkId) -> ClientResult<DataChunk> {
- info!("fetch chunk {}", chunk_id);
+ let (headers, body) = self.get(&format!("/{}", chunk_id), &[])?;
+ let meta = self.get_chunk_meta_header(chunk_id, &headers)?;
+
+ let meta_bytes = meta.to_json_vec();
+ let chunk = self.cipher.decrypt_chunk(&body, &meta_bytes)?;
- let url = format!("{}/{}", &self.chunks_url(), chunk_id);
+ Ok(chunk)
+ }
+
+ fn get(&self, path: &str, query: &[(&str, &str)]) -> ClientResult<(HeaderMap, Vec<u8>)> {
+ let url = format!("{}{}", &self.chunks_url(), path);
+ info!("GET {}", url);
+
+ // Build HTTP request structure.
let req = self
.client
.get(&url)
+ .query(query)
.build()
.map_err(ClientError::ReqwestError)?;
+
+ // Make HTTP request.
let res = self
.client
.execute(req)
.map_err(ClientError::ReqwestError)?;
+
+ // Did it work?
if res.status() != 200 {
- let err = ClientError::ChunkNotFound(chunk_id.to_string());
- error!("fetching chunk {} failed: {}", chunk_id, err);
- return Err(err);
+ return Err(ClientError::NotFound(path.to_string()));
}
- let headers = res.headers();
+ // Return headers and body.
+ let headers = res.headers().clone();
+ let body = res.bytes().map_err(ClientError::ReqwestError)?;
+ let body = body.to_vec();
+ Ok((headers, body))
+ }
+
+ fn get_chunk_meta_header(
+ &self,
+ chunk_id: &ChunkId,
+ headers: &HeaderMap,
+ ) -> ClientResult<ChunkMeta> {
let meta = headers.get("chunk-meta");
+
if meta.is_none() {
let err = ClientError::NoChunkMeta(chunk_id.clone());
error!("fetching chunk {} failed: {}", chunk_id, err);
return Err(err);
}
+
let meta = meta
.unwrap()
.to_str()
.map_err(ClientError::MetaHeaderToString)?;
- debug!("fetching chunk {}: meta={:?}", chunk_id, meta);
let meta: ChunkMeta = serde_json::from_str(meta).map_err(ClientError::JsonParse)?;
- debug!("fetching chunk {}: meta={:?}", chunk_id, meta);
-
- let body = res.bytes().map_err(ClientError::ReqwestError)?;
- let body = body.to_vec();
- let actual = sha256(&body);
- if actual != meta.sha256() {
- let err =
- ClientError::WrongChecksum(chunk_id.clone(), actual, meta.sha256().to_string());
- error!("fetching chunk {} failed: {}", chunk_id, err);
- return Err(err);
- }
-
- let chunk: DataChunk = DataChunk::new(body, meta);
- Ok(chunk)
+ Ok(meta)
}
}