SSE-C encryption #730
7 changed files with 53 additions and 46 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1495,6 +1495,7 @@ dependencies = [
|
||||||
"garage_table",
|
"garage_table",
|
||||||
"garage_util",
|
"garage_util",
|
||||||
"hex",
|
"hex",
|
||||||
|
"http 1.0.0",
|
||||||
"opentelemetry",
|
"opentelemetry",
|
||||||
"rand",
|
"rand",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
@ -34,7 +34,7 @@ args@{
|
||||||
ignoreLockHash,
|
ignoreLockHash,
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
nixifiedLockHash = "170b83bf5f94d624b1caf773805f52b36970c99f4db21088c4ac794dad02c53b";
|
nixifiedLockHash = "c3296a54f1c6f385e0d4a4a937734f1fe0fee4405b44d7462249d72675f7ac40";
|
||||||
workspaceSrc = if args.workspaceSrc == null then ./. else args.workspaceSrc;
|
workspaceSrc = if args.workspaceSrc == null then ./. else args.workspaceSrc;
|
||||||
currentLockHash = builtins.hashFile "sha256" (workspaceSrc + /Cargo.lock);
|
currentLockHash = builtins.hashFile "sha256" (workspaceSrc + /Cargo.lock);
|
||||||
lockHashIgnored = if ignoreLockHash
|
lockHashIgnored = if ignoreLockHash
|
||||||
|
@ -2171,6 +2171,7 @@ in
|
||||||
garage_table = (rustPackages."unknown".garage_table."0.10.0" { inherit profileName; }).out;
|
garage_table = (rustPackages."unknown".garage_table."0.10.0" { inherit profileName; }).out;
|
||||||
garage_util = (rustPackages."unknown".garage_util."0.10.0" { inherit profileName; }).out;
|
garage_util = (rustPackages."unknown".garage_util."0.10.0" { inherit profileName; }).out;
|
||||||
hex = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }).out;
|
hex = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }).out;
|
||||||
|
http = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."1.0.0" { inherit profileName; }).out;
|
||||||
opentelemetry = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }).out;
|
opentelemetry = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }).out;
|
||||||
rand = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }).out;
|
rand = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }).out;
|
||||||
serde = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.196" { inherit profileName; }).out;
|
serde = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.196" { inherit profileName; }).out;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//! Function related to GET and HEAD requests
|
//! Function related to GET and HEAD requests
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, UNIX_EPOCH};
|
use std::time::{Duration, UNIX_EPOCH};
|
||||||
|
@ -53,7 +54,6 @@ fn object_headers(
|
||||||
let date_str = httpdate::fmt_http_date(date);
|
let date_str = httpdate::fmt_http_date(date);
|
||||||
|
|
||||||
let mut resp = Response::builder()
|
let mut resp = Response::builder()
|
||||||
.header(CONTENT_TYPE, headers.content_type.to_string())
|
|
||||||
.header(LAST_MODIFIED, date_str)
|
.header(LAST_MODIFIED, date_str)
|
||||||
.header(ACCEPT_RANGES, "bytes".to_string());
|
.header(ACCEPT_RANGES, "bytes".to_string());
|
||||||
|
|
||||||
|
@ -61,8 +61,23 @@ fn object_headers(
|
||||||
resp = resp.header(ETAG, format!("\"{}\"", version_meta.etag));
|
resp = resp.header(ETAG, format!("\"{}\"", version_meta.etag));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (k, v) in headers.other.iter() {
|
// When metadata is retrieved through the REST API, Amazon S3 combines headers that
|
||||||
resp = resp.header(k, v.to_string());
|
// have the same name (ignoring case) into a comma-delimited list.
|
||||||
|
// See: https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingMetadata.html
|
||||||
|
let mut headers_by_name = BTreeMap::new();
|
||||||
|
for (name, value) in headers.0.iter() {
|
||||||
|
match headers_by_name.get_mut(name) {
|
||||||
|
None => {
|
||||||
|
headers_by_name.insert(name, vec![value.as_str()]);
|
||||||
|
}
|
||||||
|
Some(headers) => {
|
||||||
|
headers.push(value.as_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (name, values) in headers_by_name {
|
||||||
|
resp = resp.header(name, values.join(","));
|
||||||
}
|
}
|
||||||
|
|
||||||
encryption.add_response_headers(&mut resp);
|
encryption.add_response_headers(&mut resp);
|
||||||
|
|
|
@ -945,10 +945,7 @@ mod tests {
|
||||||
state: ObjectVersionState::Uploading {
|
state: ObjectVersionState::Uploading {
|
||||||
multipart: true,
|
multipart: true,
|
||||||
encryption: ObjectVersionEncryption::Plaintext {
|
encryption: ObjectVersionEncryption::Plaintext {
|
||||||
headers: ObjectVersionHeaders {
|
headers: ObjectVersionHeaders(vec![]),
|
||||||
content_type: "text/plain".to_string(),
|
|
||||||
other: BTreeMap::<String, String>::new(),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use base64::prelude::*;
|
use base64::prelude::*;
|
||||||
|
@ -609,57 +609,35 @@ impl Drop for InterruptedCleanup {
|
||||||
|
|
||||||
// ============ helpers ============
|
// ============ helpers ============
|
||||||
|
|
||||||
pub(crate) fn get_mime_type(headers: &HeaderMap<HeaderValue>) -> Result<String, Error> {
|
|
||||||
Ok(headers
|
|
||||||
.get(hyper::header::CONTENT_TYPE)
|
|
||||||
.map(|x| x.to_str())
|
|
||||||
.unwrap_or(Ok("blob"))?
|
|
||||||
.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_headers(headers: &HeaderMap<HeaderValue>) -> Result<ObjectVersionHeaders, Error> {
|
pub(crate) fn get_headers(headers: &HeaderMap<HeaderValue>) -> Result<ObjectVersionHeaders, Error> {
|
||||||
let content_type = get_mime_type(headers)?;
|
let mut ret = Vec::new();
|
||||||
let mut other = BTreeMap::new();
|
|
||||||
|
|
||||||
// Preserve standard headers
|
// Preserve standard headers
|
||||||
let standard_header = vec![
|
let standard_header = vec![
|
||||||
|
hyper::header::CONTENT_TYPE,
|
||||||
hyper::header::CACHE_CONTROL,
|
hyper::header::CACHE_CONTROL,
|
||||||
hyper::header::CONTENT_DISPOSITION,
|
hyper::header::CONTENT_DISPOSITION,
|
||||||
hyper::header::CONTENT_ENCODING,
|
hyper::header::CONTENT_ENCODING,
|
||||||
hyper::header::CONTENT_LANGUAGE,
|
hyper::header::CONTENT_LANGUAGE,
|
||||||
hyper::header::EXPIRES,
|
hyper::header::EXPIRES,
|
||||||
];
|
];
|
||||||
for h in standard_header.iter() {
|
for name in standard_header.iter() {
|
||||||
if let Some(v) = headers.get(h) {
|
if let Some(value) = headers.get(name) {
|
||||||
match v.to_str() {
|
ret.push((name.to_string(), value.to_str()?.to_string()));
|
||||||
Ok(v_str) => {
|
|
||||||
other.insert(h.to_string(), v_str.to_string());
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Discarding header {}, error in .to_str(): {}", h, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preserve x-amz-meta- headers
|
// Preserve x-amz-meta- headers
|
||||||
for (k, v) in headers.iter() {
|
for (name, value) in headers.iter() {
|
||||||
if k.as_str().starts_with("x-amz-meta-") {
|
if name.as_str().starts_with("x-amz-meta-") {
|
||||||
match std::str::from_utf8(v.as_bytes()) {
|
ret.push((
|
||||||
Ok(v_str) => {
|
name.to_string(),
|
||||||
other.insert(k.to_string(), v_str.to_string());
|
std::str::from_utf8(value.as_bytes())?.to_string(),
|
||||||
}
|
));
|
||||||
Err(e) => {
|
|
||||||
warn!("Discarding header {}, error in .to_str(): {}", k, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ObjectVersionHeaders {
|
Ok(ObjectVersionHeaders(ret))
|
||||||
content_type,
|
|
||||||
other,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn next_timestamp(existing_object: Option<&Object>) -> u64 {
|
pub(crate) fn next_timestamp(existing_object: Option<&Object>) -> u64 {
|
||||||
|
|
|
@ -27,6 +27,7 @@ blake2.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
err-derive.workspace = true
|
err-derive.workspace = true
|
||||||
hex.workspace = true
|
hex.workspace = true
|
||||||
|
http.workspace = true
|
||||||
base64.workspace = true
|
base64.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
|
|
|
@ -216,8 +216,6 @@ mod v010 {
|
||||||
|
|
||||||
use super::v09;
|
use super::v09;
|
||||||
|
|
||||||
pub use v09::ObjectVersionHeaders;
|
|
||||||
|
|
||||||
/// An object
|
/// An object
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct Object {
|
pub struct Object {
|
||||||
|
@ -303,6 +301,10 @@ mod v010 {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Vector of headers, as tuples of the format (header name, header value)
|
||||||
|
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct ObjectVersionHeaders(pub Vec<(String, String)>);
|
||||||
|
|
||||||
impl garage_util::migrate::Migrate for Object {
|
impl garage_util::migrate::Migrate for Object {
|
||||||
const VERSION_MARKER: &'static [u8] = b"G010s3ob";
|
const VERSION_MARKER: &'static [u8] = b"G010s3ob";
|
||||||
|
|
||||||
|
@ -357,7 +359,19 @@ mod v010 {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn migrate_headers(old: v09::ObjectVersionHeaders) -> ObjectVersionEncryption {
|
fn migrate_headers(old: v09::ObjectVersionHeaders) -> ObjectVersionEncryption {
|
||||||
ObjectVersionEncryption::Plaintext { headers: old }
|
use http::header::CONTENT_TYPE;
|
||||||
|
|
||||||
|
let mut new_headers = Vec::with_capacity(old.other.len() + 1);
|
||||||
|
if old.content_type != "blob" {
|
||||||
|
new_headers.push((CONTENT_TYPE.as_str().to_string(), old.content_type));
|
||||||
|
}
|
||||||
|
for (name, value) in old.other.into_iter() {
|
||||||
|
new_headers.push((name, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectVersionEncryption::Plaintext {
|
||||||
|
headers: ObjectVersionHeaders(new_headers),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since ObjectVersionHeaders can now be serialized independently, for the
|
// Since ObjectVersionHeaders can now be serialized independently, for the
|
||||||
|
|
Loading…
Reference in a new issue