Page MenuHomePhorge

D9035.1768528975.diff
No OneTemporary

Size
11 KB
Referenced Files
None
Subscribers
None

D9035.1768528975.diff

diff --git a/services/reports/Cargo.lock b/services/reports/Cargo.lock
--- a/services/reports/Cargo.lock
+++ b/services/reports/Cargo.lock
@@ -264,6 +264,42 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+[[package]]
+name = "aead"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
+dependencies = [
+ "bytes",
+ "crypto-common",
+ "generic-array",
+]
+
+[[package]]
+name = "aes"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "aes-gcm"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
[[package]]
name = "ahash"
version = "0.8.3"
@@ -846,6 +882,16 @@
"winapi",
]
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
[[package]]
name = "clap"
version = "4.4.0"
@@ -901,6 +947,8 @@
"actix-multipart",
"actix-web",
"actix-web-httpauth",
+ "aead",
+ "aes-gcm",
"anyhow",
"aws-config",
"aws-sdk-dynamodb",
@@ -979,9 +1027,19 @@
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
+ "rand_core",
"typenum",
]
+[[package]]
+name = "ctr"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
+dependencies = [
+ "cipher",
+]
+
[[package]]
name = "darling"
version = "0.20.3"
@@ -1220,6 +1278,16 @@
"wasi 0.11.0+wasi-snapshot-preview1",
]
+[[package]]
+name = "ghash"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
[[package]]
name = "gimli"
version = "0.28.0"
@@ -1413,6 +1481,15 @@
"hashbrown",
]
+[[package]]
+name = "inout"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
+dependencies = [
+ "generic-array",
+]
+
[[package]]
name = "instant"
version = "0.1.12"
@@ -1667,6 +1744,12 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
[[package]]
name = "openssl"
version = "0.10.56"
@@ -1802,6 +1885,18 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
+[[package]]
+name = "polyval"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
+]
+
[[package]]
name = "postmark"
version = "0.8.1"
@@ -2665,6 +2760,16 @@
"tinyvec",
]
+[[package]]
+name = "universal-hash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
+dependencies = [
+ "crypto-common",
+ "subtle",
+]
+
[[package]]
name = "untrusted"
version = "0.7.1"
diff --git a/services/reports/Cargo.toml b/services/reports/Cargo.toml
--- a/services/reports/Cargo.toml
+++ b/services/reports/Cargo.toml
@@ -16,6 +16,7 @@
comm-services-lib = { path = "../comm-services-lib", features = [
"blob-client",
"http",
+ "crypto",
] }
derive_more = "0.99"
hex = "0.4"
diff --git a/services/reports/src/config.rs b/services/reports/src/config.rs
--- a/services/reports/src/config.rs
+++ b/services/reports/src/config.rs
@@ -1,5 +1,5 @@
use anyhow::Result;
-use clap::Parser;
+use clap::{ArgAction, Parser};
use comm_services_lib::blob::client::Url;
use once_cell::sync::Lazy;
use tracing::{info, warn};
@@ -26,9 +26,16 @@
/// HTTP server listening port
#[arg(long, default_value_t = 50056)]
pub http_port: u16,
+
#[arg(env = ENV_BLOB_SERVICE_URL)]
#[arg(long, default_value = "http://localhost:50053")]
pub blob_service_url: Url,
+
+ /// Should reports be encrypted? Note that this flag disables encryption
+ /// which is enabled by default.
+ #[arg(long = "no-encrypt", action = ArgAction::SetFalse)]
+ pub encrypt_reports: bool,
+
/// AWS Localstack service URL
#[arg(env = ENV_LOCALSTACK_ENDPOINT)]
#[arg(long)]
@@ -72,6 +79,10 @@
}
}
+ if !cfg.encrypt_reports {
+ warn!("Encryption disabled. Reports will be stored in plaintext!");
+ }
+
Ok(cfg)
}
diff --git a/services/reports/src/database/item.rs b/services/reports/src/database/item.rs
--- a/services/reports/src/database/item.rs
+++ b/services/reports/src/database/item.rs
@@ -7,6 +7,7 @@
},
bytes::Bytes,
constants::DDB_ITEM_SIZE_LIMIT,
+ crypto::aes256::EncryptionKey,
database::{
self, AttributeExtractor, AttributeMap, DBItemError, TryFromAttribute,
},
@@ -35,7 +36,7 @@
#[serde(skip_serializing)]
pub content: ReportContent,
#[serde(skip_serializing)]
- pub encryption_key: Option<String>,
+ pub encryption_key: Option<EncryptionKey>,
}
/// contains some redundancy as not all keys are always present
@@ -73,7 +74,7 @@
attrs.insert(content_attr_name, content_attr);
if let Some(key) = self.encryption_key {
- attrs.insert(ATTR_ENCRYPTION_KEY.to_string(), AttributeValue::S(key));
+ attrs.insert(ATTR_ENCRYPTION_KEY.to_string(), key.into());
}
attrs
}
@@ -111,7 +112,7 @@
}
};
if let Some(key) = self.encryption_key.as_ref() {
- size += key.as_bytes().len();
+ size += key.as_ref().len();
}
size
}
@@ -130,7 +131,7 @@
let content = ReportContent::parse_from_attrs(&mut row)?;
let encryption_key = row
.remove(ATTR_ENCRYPTION_KEY)
- .map(|attr| String::try_from_attr(ATTR_ENCRYPTION_KEY, Some(attr)))
+ .map(|attr| EncryptionKey::try_from_attr(ATTR_ENCRYPTION_KEY, Some(attr)))
.transpose()?;
Ok(ReportItem {
diff --git a/services/reports/src/http/mod.rs b/services/reports/src/http/mod.rs
--- a/services/reports/src/http/mod.rs
+++ b/services/reports/src/http/mod.rs
@@ -86,7 +86,6 @@
ErrorInternalServerError("internal error")
}
},
- #[allow(unreachable_patterns)]
err => {
error!("Received an unexpected error: {0:?} - {0}", err);
ErrorInternalServerError("server error")
diff --git a/services/reports/src/service.rs b/services/reports/src/service.rs
--- a/services/reports/src/service.rs
+++ b/services/reports/src/service.rs
@@ -3,6 +3,7 @@
use comm_services_lib::{
auth::UserIdentity,
blob::client::{BlobServiceClient, BlobServiceError},
+ crypto::aes256,
database,
};
use derive_more::{Display, Error, From};
@@ -11,9 +12,10 @@
future::{ready, Ready},
sync::Arc,
};
-use tracing::error;
+use tracing::{error, trace};
use crate::{
+ config::CONFIG,
database::{
client::{DatabaseClient, ReportsPage},
item::{ReportContent, ReportItem},
@@ -38,6 +40,9 @@
/// Returned when trying to perform an operation on an incompatible report type
/// e.g. create a Redux Devtools import from a media mission report
UnsupportedReportType,
+ /// Error during encryption or decryption
+ #[display(fmt = "Encryption error")]
+ EncryptionError,
/// Unexpected error
Unexpected,
}
@@ -88,8 +93,7 @@
let blob_client = self.blob_client.clone();
let user_id = self.requesting_user_id.clone();
tasks.spawn(async move {
- let mut report = process_report(input, user_id)
- .map_err(ReportsServiceError::SerdeError)?;
+ let mut report = process_report(input, user_id)?;
report.db_item.ensure_size_constraints(&blob_client).await?;
Ok(report)
});
@@ -126,6 +130,7 @@
&self,
report_id: ReportID,
) -> ServiceResult<Option<ReportOutput>> {
+ use ReportsServiceError::{EncryptionError, SerdeError};
let Some(report_item) = self.db.get_report(&report_id).await? else {
return Ok(None);
};
@@ -135,12 +140,21 @@
platform,
creation_time,
content,
+ encryption_key,
..
} = report_item;
- let report_data = content.fetch_bytes(&self.blob_client).await?;
- let report_json = serde_json::from_slice(report_data.as_slice())
- .map_err(ReportsServiceError::SerdeError)?;
+ let mut report_data = content.fetch_bytes(&self.blob_client).await?;
+ if let Some(key) = encryption_key {
+ trace!("Encryption key present. Decrypting report data");
+ report_data = aes256::decrypt(&report_data, &key).map_err(|_| {
+ error!("Failed to decrypt report");
+ EncryptionError
+ })?;
+ }
+
+ let report_json =
+ serde_json::from_slice(report_data.as_slice()).map_err(SerdeError)?;
let output = ReportOutput {
id: report_id,
@@ -222,7 +236,9 @@
fn process_report(
input: ReportInput,
user_id: Option<String>,
-) -> Result<ProcessedReport, serde_json::Error> {
+) -> Result<ProcessedReport, ReportsServiceError> {
+ use ReportsServiceError::*;
+
let id = ReportID::default();
let email = crate::email::prepare_email(&input, &id, user_id.as_deref());
@@ -235,11 +251,26 @@
// Add "platformDetails" back to report content.
// It was deserialized into a separate field.
- let platform_details_value = serde_json::to_value(&platform_details)?;
+ let platform_details_value =
+ serde_json::to_value(&platform_details).map_err(SerdeError)?;
report_content.insert("platformDetails".to_string(), platform_details_value);
// serialize report JSON to bytes
- let content_bytes = serde_json::to_vec(&report_content)?;
+ let content_bytes =
+ serde_json::to_vec(&report_content).map_err(SerdeError)?;
+
+ // possibly encrypt report
+ let (content, encryption_key) = if CONFIG.encrypt_reports {
+ trace!(?id, "Encrypting report");
+ let key = aes256::EncryptionKey::new();
+ let data = aes256::encrypt(&content_bytes, &key).map_err(|_| {
+ error!("Failed to encrypt report");
+ EncryptionError
+ })?;
+ (data, Some(key))
+ } else {
+ (content_bytes, None)
+ };
let db_item = ReportItem {
id: id.clone(),
@@ -247,8 +278,8 @@
platform: platform_details.platform.clone(),
report_type,
creation_time: time.unwrap_or_else(Utc::now),
- encryption_key: None,
- content: ReportContent::Database(content_bytes),
+ encryption_key,
+ content: ReportContent::Database(content),
};
Ok(ProcessedReport { id, db_item, email })

File Metadata

Mime Type
text/plain
Expires
Fri, Jan 16, 2:02 AM (6 h, 25 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5941345
Default Alt Text
D9035.1768528975.diff (11 KB)

Event Timeline